diff --git a/.gitea/workflows/build-and-push.yml b/.gitea/workflows/build-and-push.yml new file mode 100644 index 0000000..bbe8e94 --- /dev/null +++ b/.gitea/workflows/build-and-push.yml @@ -0,0 +1,82 @@ +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". + test: + runs-on: ubuntu-latest + container: + image: python:3.13-slim + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install the package and the test tooling + run: | + pip install --no-cache-dir --upgrade pip + pip install --no-cache-dir -e . + pip install --no-cache-dir \ + pytest pytest-asyncio pytest-cov pytest-timeout httpx ruff + + - name: Lint + run: | + ruff check . + ruff format --check . + + # 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: Test + run: pytest -q --no-cov --ignore=tests/e2e + + - name: Smoke the image build + # Catches a broken Containerfile (a missing COPY source, a bad uv + # install) here rather than in the publish job below. + run: | + apt-get update -qq && apt-get install -y -qq --no-install-recommends podman + podman build -t mirror:ci . + podman 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 }} diff --git a/Containerfile b/Containerfile index 471cbbc..f632b55 100644 --- a/Containerfile +++ b/Containerfile @@ -6,6 +6,11 @@ # The image serves the app; it does not capture anything. Capture happens in the # viewer's and sharer's own browsers, so there is no display, D-Bus, or PipeWire # socket to pass in. +# +# Deliberately no HTTP client and no HEALTHCHECK: health checks belong to the +# deployment (see HealthCmd in the reeseapps/mirror quadlet), and shipping curl +# only to answer /healthz adds a package and a way for someone inside the +# container to make requests the image otherwise cannot make. FROM docker.io/library/python:3.13-slim AS builder @@ -23,12 +28,8 @@ FROM docker.io/library/python:3.13-slim LABEL org.opencontainers.image.title="mirror" \ org.opencontainers.image.description="Browser-only screen mirroring for the local network" \ - org.opencontainers.image.source="https://example.invalid/mirror" - -# curl is for the HEALTHCHECK only; nothing else in the runtime image reaches out. -RUN apt-get update \ - && apt-get install -y --no-install-recommends curl \ - && rm -rf /var/lib/apt/lists/* + org.opencontainers.image.source="https://gitea.reeseapps.com/experimental/mirror" \ + org.opencontainers.image.licenses="MIT" COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:${PATH}" \ @@ -43,8 +44,6 @@ USER mirror WORKDIR /home/mirror EXPOSE 8055 -HEALTHCHECK --interval=20s --timeout=3s --start-period=5s --retries=3 \ - CMD curl -fsS http://127.0.0.1:8055/healthz || exit 1 ENTRYPOINT ["mirror"] CMD ["serve"]