name: Build and verify web migration on: push: branches: [main, 'codex/**'] pull_request: workflow_dispatch: permissions: contents: read jobs: verify: runs-on: ubuntu-latest services: mongo: image: mongo:8.0 ports: ['27017:27017'] options: >- --health-cmd "mongosh --quiet --eval 'db.adminCommand({ping:1}).ok'" --health-interval 10s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: { dotnet-version: '10.0.x' } - uses: actions/setup-node@v4 with: { node-version: '22', cache: npm, cache-dependency-path: web/package-lock.json } - name: Build services run: dotnet build src/GuestOps.Worker/GuestOps.Worker.csproj -c Release - name: Verify backup validation and failure recovery run: python3 -m unittest discover -s tests -p test_ops.py - name: Build interface working-directory: web run: npm ci && npm run build - name: Start isolated preview API run: | ASPNETCORE_ENVIRONMENT=Development Preview=true dotnet src/GuestOps.Api/bin/Release/net10.0/GuestOps.Api.dll --urls http://127.0.0.1:5180 > /tmp/guestops-api.log 2>&1 & for i in $(seq 1 30); do curl -fsS http://127.0.0.1:5180/health && exit 0; sleep 1; done cat /tmp/guestops-api.log exit 1 - name: Verify MongoDB and HTTP boundaries env: MONGO_TEST_URI: mongodb://127.0.0.1:27017 TEST_API_URL: http://127.0.0.1:5180 run: dotnet run --project tests/GuestOps.Tests/GuestOps.Tests.csproj -c Release - name: Build Linux images run: | docker build --target api -t guestops-api:${{ github.sha }} . docker build --target worker -t guestops-worker:${{ github.sha }} . - name: Package reviewed images if: github.event_name != 'pull_request' run: docker save guestops-api:${{ github.sha }} guestops-worker:${{ github.sha }} | gzip > guestops-images.tar.gz - name: Smoke test production containers and restart persistence env: GUESTOPS_API_IMAGE: guestops-api:${{ github.sha }} GUESTOPS_WORKER_IMAGE: guestops-worker:${{ github.sha }} BOOTSTRAP_EMAIL: ci-owner@example.invalid BOOTSTRAP_HOTEL: CI test hotel run: | export MONGO_ROOT_PASSWORD=$(openssl rand -hex 32) export MONGO_APP_PASSWORD=$(openssl rand -hex 32) export BOOTSTRAP_PASSWORD=$(openssl rand -hex 24) trap 'docker compose down --volumes' EXIT docker compose config --quiet docker compose up -d --no-build curl --retry 30 --retry-delay 2 --retry-all-errors --fail http://127.0.0.1:8080/health docker compose run --rm --no-deps -e BOOTSTRAP_EMAIL -e BOOTSTRAP_HOTEL -e BOOTSTRAP_PASSWORD api --bootstrap python3 tests/production_smoke.py docker compose restart api worker curl --retry 30 --retry-delay 2 --retry-all-errors --fail http://127.0.0.1:8080/health python3 tests/production_smoke.py --read install -m 600 /dev/null .env python3 deploy/ops.py preflight --offline mkdir -m 700 .guestops-test-keyring export GNUPGHOME="$PWD/.guestops-test-keyring" gpg --batch --pinentry-mode loopback --passphrase '' --quick-generate-key 'GuestOps CI ' rsa2048 encr 1d BACKUP_RECIPIENT=$(gpg --batch --with-colons --list-keys | awk -F: '$1=="fpr" {print $10; exit}') backup_dir=$(mktemp -d) python3 deploy/ops.py backup --recipient "$BACKUP_RECIPIENT" --output "$backup_dir/fixture.tar.gpg" --confirm-maintenance python3 deploy/ops.py restore-drill "$backup_dir/fixture.tar.gpg" --api-image "$GUESTOPS_API_IMAGE" curl --retry 30 --retry-delay 2 --retry-all-errors --fail http://127.0.0.1:8080/health/ready python3 tests/production_smoke.py --read - uses: actions/upload-artifact@v4 if: github.event_name != 'pull_request' with: name: guestops-linux-${{ github.run_number }} path: guestops-images.tar.gz retention-days: 7