name: Build and Push Containers on: push: branches: - main release: types: - published jobs: # Gate: the image is only published if the suite is green. A failing job here # leaves :main on the last good build, which is what `podman auto-update` # keeps serving on reeseapps-mirror — so red means "nothing shipped", not # "production is broken". # # The suite runs *inside* a pinned python:3.13-slim container rather than via a # `container:` job key. That is not stylistic: actions/checkout is a JavaScript # action and a python image has no Node runtime, so run #1 died in Checkout # before running a single command. The runner itself has both (it is where # build-push-action builds images), so the job stays on the runner and shells # into the container it needs. # # 3.13 is deliberate: it is the floor declared in pyproject's requires-python, # so CI verifies the floor while development happens on 3.14. test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Locate the container runtime id: runtime run: | set -euo pipefail runtime="$(command -v podman || command -v docker || true)" if [ -z "$runtime" ]; then echo "Neither podman nor docker is on this runner, so this job cannot" echo "run the suite in a pinned interpreter. Fix here rather than" echo "falling back to whatever python the runner image happens to ship." exit 1 fi echo "path=$runtime" >> "$GITHUB_OUTPUT" echo "using $runtime" # Unit + API only. tests/e2e drives a real Chromium through capture -> # encode -> SRTP -> decode and needs a browser install and, for the # realdisplay rung, an X server; both stay local gates (see AGENTS.md). # --ignore keeps them out of collection entirely: -m filtering happens # after import, and tests/e2e/conftest.py imports playwright. - name: Lint and test run: | set -euo pipefail "${{ steps.runtime.outputs.path }}" run --rm \ -v "$GITHUB_WORKSPACE:/src" -w /src \ docker.io/library/python:3.13-slim \ /bin/bash -euo pipefail -c ' pip install --no-cache-dir -q . pip install --no-cache-dir -q \ pytest pytest-asyncio pytest-cov pytest-timeout httpx ruff ruff check . ruff format --check . pytest -q --no-cov --ignore=tests/e2e ' - name: Smoke the image build # Catches a broken Containerfile (a missing COPY source, a failing uv # install step) here rather than in the publish job below. run: | set -euo pipefail # -f is not optional: without it docker looks for "Dockerfile", finds # none, and reports "failed to read dockerfile" with an empty context — # which is exactly how run #2 failed. The publish job gets away without # naming it because docker/build-push-action passes `file:` for us. "${{ steps.runtime.outputs.path }}" build -f Containerfile -t mirror:ci . "${{ steps.runtime.outputs.path }}" run --rm mirror:ci --help >/dev/null build-and-push-app: needs: test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Login to Gitea Container Registry uses: docker/login-action@v3 with: registry: gitea.reeseapps.com username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: gitea.reeseapps.com/experimental/mirror tags: | type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }} type=ref,event=tag - name: Build and push app image uses: docker/build-push-action@v6 with: context: . file: Containerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}