Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .beads/interactions.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@
{"id":"int-e502a2517628e89f5d584691cab7e640","kind":"field_change","created_at":"2026-08-16T18:37:16.575687995Z","actor":"soeren","issue_id":"versiontracker-ylt","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Enabled the approved master ruleset with pull-request-only updates, five required GitHub Actions checks, and an admin emergency bypass."}}
{"id":"int-11fe797c40aae0c72e5c3c835e4e716a","kind":"field_change","created_at":"2026-08-16T19:00:22.088661938Z","actor":"soeren","issue_id":"versiontracker-17l","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Versioned Docker self-hosting, installer, release automation, and CI checks implemented"}}
{"id":"int-6f561cd3894c5d25e159a71c02adb186","kind":"field_change","created_at":"2026-08-16T19:20:47.925237745Z","actor":"soeren","issue_id":"versiontracker-2cw","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Hardened Docker installer, self-hosting docs, and container/release validation"}}
{"id":"int-0d151ef4ec7821968bfab2e1e18e8a66","kind":"field_change","created_at":"2026-08-16T19:50:45.14562513Z","actor":"soeren","issue_id":"versiontracker-0z1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Validated v0.1.1 installation release with Docker, browser DevTools, backup, and proxy/Caddy compose configuration."}}
{"id":"int-45ff5b83c06f318fa99a39159f8fea58","kind":"field_change","created_at":"2026-08-16T20:09:15.43629742Z","actor":"soeren","issue_id":"versiontracker-58y","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented and locally validated release acceptance, deployment bundle, operational hardening, CI security, and PHP 8.4/PHPUnit 13 upgrade."}}
{"id":"int-dc8ba7de281af8232cadf095dccff699","kind":"field_change","created_at":"2026-08-16T20:09:15.653609064Z","actor":"soeren","issue_id":"versiontracker-rzz","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented and locally validated release acceptance, deployment bundle, operational hardening, CI security, and PHP 8.4/PHPUnit 13 upgrade."}}
{"id":"int-1f901c67824319fd2c927e54a9d71d0e","kind":"field_change","created_at":"2026-08-16T20:09:15.863678001Z","actor":"soeren","issue_id":"versiontracker-9hd","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented and locally validated release acceptance, deployment bundle, operational hardening, CI security, and PHP 8.4/PHPUnit 13 upgrade."}}
{"id":"int-04ba3562d9d50496714fd5d68afe3422","kind":"field_change","created_at":"2026-08-16T20:09:16.095500058Z","actor":"soeren","issue_id":"versiontracker-ez6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented and locally validated release acceptance, deployment bundle, operational hardening, CI security, and PHP 8.4/PHPUnit 13 upgrade."}}
3 changes: 3 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
VERSION=
IMAGE_REPOSITORY=ghcr.io/soerennb/version-tracker
MARIADB_IMAGE=mariadb:11.8.8
CADDY_IMAGE=caddy:2.11.4
DEPLOYMENT_MODE=
APP_NAME=VersionTracker
APP_ENV=production
Expand Down
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,12 @@ updates:
- dependency-name: "*"
update-types:
- version-update:semver-major

- package-ecosystem: docker
directory: /
schedule:
interval: weekly
day: monday
time: "06:00"
timezone: Europe/Berlin
open-pull-requests-limit: 5
71 changes: 62 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
php-version: "8.4"
tools: composer
extensions: mbstring, pdo_sqlite, sqlite3
extensions: intl, mbstring, pdo_sqlite, sqlite3

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: ~/.cache/composer/files
key: ${{ runner.os }}-php-8.3-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-php-8.3-
key: ${{ runner.os }}-php-8.4-${{ hashFiles('composer.lock') }}
restore-keys: ${{ runner.os }}-php-8.4-

- name: Install PHP dependencies
run: composer install --no-interaction --no-progress --prefer-dist
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
php-version: "8.4"
tools: composer

- name: Install PHP dependencies
Expand Down Expand Up @@ -120,9 +120,9 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
php-version: "8.4"
tools: composer
extensions: mbstring, pdo_mysql
extensions: intl, mbstring, pdo_mysql

- name: Install PHP dependencies
run: composer install --no-interaction --no-progress --prefer-dist
Expand Down Expand Up @@ -163,10 +163,21 @@ jobs:
cache-from: type=gha,scope=container
cache-to: type=gha,mode=max,scope=container

- name: Scan application image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
with:
image-ref: ghcr.io/soerennb/version-tracker:test
format: table
exit-code: "1"
ignore-unfixed: true
severity: CRITICAL,HIGH
vuln-type: os,library

- name: Prepare Compose environment
run: |
cp .env.docker.example .env.docker
sed -i 's/^VERSION=$/VERSION=test/' .env.docker
sed -i 's/^DEPLOYMENT_MODE=$/DEPLOYMENT_MODE=proxy/' .env.docker
sed -i 's/^APP_KEY=$/APP_KEY=base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=/' .env.docker
sed -i 's/^DB_PASSWORD=$/DB_PASSWORD=versiontracker/' .env.docker
sed -i 's/^DB_ROOT_PASSWORD=$/DB_ROOT_PASSWORD=root/' .env.docker
Expand All @@ -189,9 +200,46 @@ jobs:
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml logs
exit 1

- name: Back up and restore application data
run: |
backup_directory="$(./install.sh backup)"
test -s "$backup_directory/database.sql"
test -s "$backup_directory/storage.tar.gz"
test "$(stat --format=%a "$backup_directory/environment.backup")" = 600

docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml down --volumes --remove-orphans
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml up --detach db

for attempt in {1..30}; do
if docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml exec --no-TTY db healthcheck.sh --connect --innodb_initialized; then
break
fi

sleep 2
done

docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml exec --no-TTY db sh -c 'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE"' < "$backup_directory/database.sql"
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml exec --no-TTY db sh -c 'mariadb -uroot -p"$MARIADB_ROOT_PASSWORD" "$MARIADB_DATABASE" --skip-column-names --execute="SELECT COUNT(*) FROM migrations"' | grep --extended-regexp '^[1-9][0-9]*$'
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml run --rm app sh -c 'tar -xzf - -C /var/www/html' < "$backup_directory/storage.tar.gz"
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml up --detach app

for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8080/up; then
exit 0
fi

sleep 2
done

docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml logs
exit 1

- name: Clean up containers
if: always()
run: docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml down --volumes --remove-orphans
run: |
if [[ -f .env.docker ]]; then
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml down --volumes --remove-orphans
fi

compose:
name: Compose configuration
Expand All @@ -207,6 +255,9 @@ jobs:
docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml config --quiet
docker compose --env-file .env.docker -f compose.yml -f compose.caddy.yml config --quiet

- name: Validate Caddyfile
run: docker run --rm --env CADDY_DOMAIN=example.invalid --env CADDY_EMAIL=ci@example.invalid --volume "$GITHUB_WORKSPACE/Caddyfile:/etc/caddy/Caddyfile:ro" caddy:2.11.4 caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile

shell:
name: Installer script
runs-on: ubuntu-latest
Expand All @@ -218,4 +269,6 @@ jobs:
run: sudo apt-get update && sudo apt-get install --yes shellcheck

- name: Lint installer
run: shellcheck install.sh
run: |
test -x install.sh
shellcheck install.sh
4 changes: 2 additions & 2 deletions .github/workflows/release-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
php-version: "8.4"
tools: composer
extensions: mbstring, pdo_sqlite, sqlite3
extensions: intl, mbstring, pdo_sqlite, sqlite3

- name: Set up Node.js
uses: actions/setup-node@v7
Expand Down
48 changes: 47 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,50 @@ jobs:
docker logs versiontracker
exit 1

- name: Scan published image
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
with:
image-ref: ghcr.io/${{ github.repository }}@${{ steps.image.outputs.digest }}
format: table
exit-code: "1"
ignore-unfixed: true
severity: CRITICAL,HIGH
vuln-type: os,library

- name: Validate published installer path
env:
VERSION: ${{ github.ref_name }}
COMPOSE_PROJECT_NAME: versiontracker-release-install
run: |
test -x install.sh
printf '%s\n' 'Release Test Password 2026' | ./install.sh install \
--version "$VERSION" \
--mode proxy \
--port 18080 \
--admin-name 'Release Test' \
--admin-email release-test@example.invalid \
--admin-password-stdin
./install.sh status
backup_directory="$(./install.sh backup)"
test -s "$backup_directory/database.sql"
test -s "$backup_directory/storage.tar.gz"

- name: Build deployment bundle
env:
VERSION: ${{ github.ref_name }}
run: |
bundle="versiontracker-deploy-${VERSION}.tar.gz"
tar --create --gzip --file "$bundle" \
--transform "s,^,versiontracker-deploy-${VERSION}/," \
install.sh compose.yml compose.proxy.yml compose.caddy.yml Caddyfile .env.docker.example docs/self-hosting.md
sha256sum "$bundle" > "${bundle}.sha256"

- name: Clean up release installation test
if: always()
env:
COMPOSE_PROJECT_NAME: versiontracker-release-install
run: docker compose --env-file .env.docker -f compose.yml -f compose.proxy.yml down --volumes --remove-orphans

- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -98,4 +142,6 @@ jobs:
gh release create "$GITHUB_REF_NAME" \
--generate-notes \
--title "$GITHUB_REF_NAME" \
--notes $'### Container image\n\n`'"$IMAGE@$DIGEST"$'\n\n### Upgrade notes\n\nSee the self-hosting guide for backup, update, and rollback instructions.'
--notes $'### Container image\n\n`'"$IMAGE@$DIGEST"$'\n\n### Deployment bundle\n\nDownload the `versiontracker-deploy-` archive and verify its SHA-256 checksum before installation.\n\n### Upgrade notes\n\nSee the self-hosting guide for backup, update, and rollback instructions.' \
"versiontracker-deploy-${GITHUB_REF_NAME}.tar.gz" \
"versiontracker-deploy-${GITHUB_REF_NAME}.tar.gz.sha256"
2 changes: 1 addition & 1 deletion .github/workflows/security-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
php-version: "8.4"
tools: composer
extensions: mbstring, sqlite, pdo_sqlite

Expand Down
5 changes: 4 additions & 1 deletion Caddyfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{$CADDY_DOMAIN} {
{
email {$CADDY_EMAIL}
}

{$CADDY_DOMAIN} {
reverse_proxy app:80
}
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ COPY public ./public
COPY vite.config.js ./
RUN npm run build

FROM php:8.3-apache-bookworm
FROM php:8.4-apache-bookworm

RUN apt-get update \
&& apt-get upgrade --yes \
&& apt-get install --yes --no-install-recommends curl libfreetype6-dev libicu-dev libjpeg62-turbo-dev libonig-dev libpng-dev libzip-dev unzip \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" gd intl mbstring pdo_mysql zip \
Expand Down
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,45 @@ VersionTracker is a Laravel 13 application that centralizes software versions, r

## Stack

- PHP 8.3 · Laravel 13 · Livewire 4 · Filament 5
- PHP 8.4 · Laravel 13 · Livewire 4 · Filament 5
- MariaDB/MySQL/PostgreSQL/SQLite (Default: SQLite)
- Node 22.18+ · Vite 8 · Vue 3 · Vue Router 5 · Vue I18n 11 · Tailwind CSS 4

## Requirements

- PHP >= 8.3 + Composer 2.x
- PHP >= 8.4.1 + Composer 2.x
- Node.js >= 22.18 + npm 10
- SQLite (default) or an alternative database

## Installation

1. **Clone Repository**
```bash
git clone https://github.com/<your-org>/versiontracker.git
cd versiontracker
```

```bash
git clone https://github.com/<your-org>/versiontracker.git
cd versiontracker
```

2. **Install the Application**
```bash
composer run setup
```
This creates the SQLite database, generates the application key, runs migrations, installs locked frontend dependencies, and builds the assets.

```bash
composer run setup
```

This creates the SQLite database, generates the application key, runs migrations, installs locked frontend dependencies, and builds the assets.

3. **Start Application**
```bash
composer run dev
```
App available at `http://localhost:8000`. The Filament panel is at `/admin`.
```bash
composer run dev
```
App available at `http://localhost:8000`. The Filament panel is at `/admin`.

For demo data, run `php artisan db:seed` after setup.

## Demo Accounts

| Environment | User | Password |
| ----------- | ----------------- | --------- |
| Environment | User | Password |
| -------------- | ------------------ | ---------- |
| Filament Admin | `demo@example.com` | `password` |

## Frontend Access
Expand All @@ -64,7 +67,22 @@ For demo data, run `php artisan db:seed` after setup.

## Self-Hosting with Docker

Each `v0.x.y` GitHub release publishes a container image at `ghcr.io/soerennb/version-tracker`. The installer requires a concrete release tag; production deployments never use `latest`.
Each `v0.x.y` GitHub release publishes a container image at `ghcr.io/soerennb/version-tracker` and a compact deployment bundle. The installer requires a concrete release tag; production deployments never use `latest`.

### Download a deployment bundle

Download the exact bundle and checksum from the GitHub Release page, then verify the archive before unpacking it:

```bash
VERSION=v0.1.2
curl -fsSLO "https://github.com/soerennb/version-tracker/releases/download/${VERSION}/versiontracker-deploy-${VERSION}.tar.gz"
curl -fsSLO "https://github.com/soerennb/version-tracker/releases/download/${VERSION}/versiontracker-deploy-${VERSION}.tar.gz.sha256"
sha256sum --check "versiontracker-deploy-${VERSION}.tar.gz.sha256"
tar -xzf "versiontracker-deploy-${VERSION}.tar.gz"
cd "versiontracker-deploy-${VERSION}"
```

Cloning the repository remains supported for contributors, but operators only need this deployment bundle.

### Existing reverse proxy or local network

Expand All @@ -74,7 +92,19 @@ cd version-tracker
./install.sh install
```

Choose `N` for Caddy, then enter the release tag (for example `v0.1.0`) and the exposed HTTP port. Point your existing reverse proxy at this port and configure `TRUSTED_PROXIES` in `.env.docker` with the proxy address.
Choose `N` for Caddy, then enter the release tag (for example `v0.1.2`) and the exposed HTTP port. Point your existing reverse proxy at this port and configure `TRUSTED_PROXIES` in `.env.docker` with the proxy address.

For unattended installations, provide the selected values explicitly and pipe only the administrator password through standard input:

```bash
printf '%s\n' 'choose-a-long-unique-password' | ./install.sh install \
--version v0.1.2 \
--mode proxy \
--port 8080 \
--admin-name 'Administrator' \
--admin-email admin@example.com \
--admin-password-stdin
```

### Public server with automatic HTTPS

Expand Down
Loading
Loading