# Kvitt image build on Gitea Actions (SPEC ยง10.3, D-104). NOT YET RUN (written 2026-10-18 from the owner's working # example workflow and the Gitea Actions documentation; the first run is an owner step, infra/README.md). # # Runner: act_runner with the label `host` on the owner's Raspberry Pi (Alpine-based host, docker builds on the # Pi itself). Pushes three linux/arm64 images to the Gitea container registry of the same instance (gitea.lan, # plain HTTP), each tagged with the version vMAJOR.MINOR.PATCH and `latest`, then pushes the git tag of that version. # On the Kvitt Pi: /opt/kvitt/deploy.sh . # # Secret: `kvitt_cicd`, a repository secret holding a Gitea access token (scopes write:package, and # write:repository for the tag push) of the user that pushes (owner step, infra/README.md). # # RISK: the runner is a Raspberry Pi 3 with 1 GB RAM and the server image compiles about 232 Rust crates # (aws-lc-sys's C code included). The build passes CARGO_BUILD_JOBS=1 to keep memory down; server/Dockerfile must # declare `ARG CARGO_BUILD_JOBS` in its build stage for it to take effect (contract request in the M2c-pi report; # until then buildx warns that the argument is unused and cargo uses every core). The Dockerfile already keeps # the cargo registry and the target directory in BuildKit cache mounts, which persist in the reused builder # `local-http-builder`, so later builds are incremental. First-build duration on the Pi: unknown. # Fallback: infra/build-images.sh on the Mac. name: Build Kvitt images env: MAJOR: 1 MINOR: 0 defaults: run: shell: sh on: push: paths: - "server/**" - "infra/cert-sync/**" - "infra/backup/**" - ".gitea/workflows/**" workflow_dispatch: {} jobs: build: runs-on: host steps: - name: Install Git and Docker CLI run: apk add --no-cache git docker-cli docker-cli-buildx - name: Log in to Gitea Container Registry run: echo "${{ secrets.kvitt_cicd }}" | docker login gitea.lan -u ${{ github.actor }} --password-stdin - name: Create Custom Buildx Builder for HTTP run: | # 1. Write a configuration rule forcing plain HTTP for the local registry echo '[registry."gitea.lan"]' > buildkitd.toml echo ' http = true' >> buildkitd.toml # 2. Create the builder once and reuse it, so its BuildKit cache (cargo registry, target) survives runs if docker buildx inspect local-http-builder >/dev/null 2>&1; then docker buildx use local-http-builder else docker buildx create --use --name local-http-builder --config buildkitd.toml fi - name: Checkout uses: actions/checkout@v4 with: # Tells the action to use the local 'git' binary installed above set-safe-directory: 'true' fetch-depth: 0 - name: Determine Version run: | LATEST_TAG=$(git tag -l "v${{ env.MAJOR }}.${{ env.MINOR }}.*" | sort -V | tail -n 1) if [ -z "$LATEST_TAG" ]; then VERSION="v${{ env.MAJOR }}.${{ env.MINOR }}.0" else # Strip the 'v' and get the patch version PATCH=$(echo "$LATEST_TAG" | sed 's/v//' | cut -d'.' -f3) NEW_PATCH=$((PATCH + 1)) VERSION="v${{ env.MAJOR }}.${{ env.MINOR }}.$NEW_PATCH" fi echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Build cert-sync image run: | docker buildx build \ --platform linux/arm64 \ -t gitea.lan/jacobh2/kvitt-cert-sync:${{ env.VERSION }} \ -t gitea.lan/jacobh2/kvitt-cert-sync:latest \ ./infra/cert-sync \ --push - name: Build backup image run: | docker buildx build \ --platform linux/arm64 \ -t gitea.lan/jacobh2/kvitt-backup:${{ env.VERSION }} \ -t gitea.lan/jacobh2/kvitt-backup:latest \ ./infra/backup \ --push - name: Build server image run: | # Context server/: server/Dockerfile copies the Cargo workspace from its own directory. docker buildx build \ --platform linux/arm64 \ --build-arg CARGO_BUILD_JOBS=1 \ -f ./server/Dockerfile \ -t gitea.lan/jacobh2/kvitt-server:${{ env.VERSION }} \ -t gitea.lan/jacobh2/kvitt-server:latest \ ./server \ --push - name: Tag and Push Version run: | git tag "$VERSION" git push origin "$VERSION"