name: Build + push frontend image (Forgejo registry) # Builds the FRONTEND domain's image (frontend/Dockerfile, context frontend/) and # pushes it to Forgejo's built-in container registry, tagged by git SHA (every # push) and semver (version tags) -- build-once, deploy-everywhere. # # Monorepo port (reunification): the split repos each derived IMAGE_PATH from # github.repository, which now collides for every domain -- so each domain's # build-push names its image explicitly: this one is emi/thermograph/frontend # (backend-build-push.yml publishes emi/thermograph/backend). infra's # compose/stack files reference the two paths independently # (FRONTEND_IMAGE_PATH / BACKEND_IMAGE_PATH -- their defaults were renamed to # match in the same cutover). # # Path-filtered: only a push that touches frontend/** builds this image. A # backend-only push builds nothing here; deploy.sh's persisted # .image-tags.env keeps the frontend's live tag for the sibling's deploy. # EXCEPTION: version-tag pushes (v*.*.*) carry no paths context, so a semver # tag builds BOTH domain images -- intended, a release names the whole set. # # Runs on the `docker` label -- the LAN runner's job containers get the host's # docker.sock automounted in (forgejo-runner config: container.docker_host: # automount; see infra/deploy/forgejo/register-lan-runner.sh), # Docker-outside-of-Docker rather than DinD -- no privileged mode needed. # The job image itself (node:20-bookworm) has no docker CLI, hence the # "Install Docker CLI" step. # # The registry is deliberately mesh-only: git.thermograph.org's public DNS # resolves to beta's public IP, which Caddy's ACL rejects for /v2/ paths, so # any runner host that pushes/pulls needs a /etc/hosts entry pointing # git.thermograph.org at beta's WireGuard IP (10.10.0.2). The docker # build/push commands run against the automounted HOST daemon, so it's the # runner HOST's own resolver that has to route over the mesh, not anything # settable from inside the job container. # # REGISTRY is the vars.REGISTRY_HOST Actions variable (git.thermograph.org), # NOT github.server_url -- that context resolves to the bare mesh IP:port # (http://10.10.0.2:3080, no TLS), which docker CLI refuses ("server gave HTTP # response to HTTPS client"). git.thermograph.org gets real TLS via Caddy and # routes over the mesh once /etc/hosts is set, so it's the only correct value. # deploy.sh resolves the SHA tag at deploy time and `docker compose pull`s it. on: push: branches: [dev, main, release] paths: ['frontend/**'] tags: ['v*.*.*'] workflow_dispatch: {} # The runner has capacity: 1 (one physical LAN box) -- a burst of pushes to the # same ref would otherwise queue whole builds back to back even though only the # last one still matters. Keying on domain + github.ref means each domain's # dev/main/release/tag builds get their own group, so this only cancels a # superseded build of the SAME domain on the SAME ref -- and a backend build # never cancels a frontend one. concurrency: group: frontend-build-push-${{ github.ref }} cancel-in-progress: true jobs: build-push: runs-on: docker env: REGISTRY: https://${{ vars.REGISTRY_HOST }} IMAGE_PATH: ${{ github.repository }}/frontend steps: - uses: actions/checkout@v4 - name: Install Docker CLI run: | apt-get update -qq apt-get install -y -qq docker.io - name: Compute image ref + tags id: image run: | host="${REGISTRY#https://}"; host="${host#http://}" path="$(printf '%s' "$IMAGE_PATH" | tr '[:upper:]' '[:lower:]')" ref="$host/$path" sha_tag="$ref:sha-${GITHUB_SHA:0:12}" echo "host=$host" >> "$GITHUB_OUTPUT" echo "sha_tag=$sha_tag" >> "$GITHUB_OUTPUT" if [[ "$GITHUB_REF" == refs/tags/v* ]]; then echo "semver_tag=$ref:${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" fi - name: Log in to the Forgejo registry run: | # NOT secrets.GITHUB_TOKEN -- Forgejo's per-job auto-injected token # can never push to the container registry (a known Forgejo # limitation independent of any permission setting). Needs a # manually provisioned PAT with write:package scope, stored as the # REGISTRY_TOKEN secret. echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ steps.image.outputs.host }}" \ --username emi --password-stdin - name: Build run: | # One build, both tags -- the semver tag (tag pushes only) rides # along with the SHA tag instead of a second tag+push round trip. # Context is the DOMAIN dir, so frontend/Dockerfile sees the same # tree it saw as a repo root in the split era. tag_args=(-t "${{ steps.image.outputs.sha_tag }}") if [[ -n "${{ steps.image.outputs.semver_tag }}" ]]; then tag_args+=(-t "${{ steps.image.outputs.semver_tag }}") fi docker build "${tag_args[@]}" frontend/ - name: Push (SHA tag, every push) run: docker push "${{ steps.image.outputs.sha_tag }}" - name: Push (semver tag, tag pushes only) if: startsWith(github.ref, 'refs/tags/v') run: docker push "${{ steps.image.outputs.semver_tag }}"