M2c-pi: Pi bundle (infra/pi), cert-sync, Drive backup upload, Gitea workflow

The owner copies infra/pi/ to the Kvitt Pi and runs it there (D-100); nothing
on the Mac connects to the Pis. compose stack of SPEC §10.2 with images
${KVITT_REGISTRY}/kvitt-{server,cert-sync,backup}, Caddyfile §10.5.2 verbatim,
.env.example with the OQ-3 values as CHANGE_ME placeholders, install.sh,
deploy.sh (backup before every deploy), bootstrap.sh, backup-now.sh,
install-apk.sh. cert-sync image and sync.sh (§10.5.1, tar stream for both
authorized_keys variants); backup.sh uploads with rclone copy --immutable and
deletes local files once both are confirmed on Drive (D-002); both sidecars
schedule with supercronic because busybox crond runs no jobs as non-root;
backup runs as uid 10001. .gitea/workflows/build-images.yaml from the owner's
example (not yet run); infra/build-images.sh and infra/publish-apk.sh (R8
mapping kept, version per §10.6) on the Mac; README checklist and RESTORE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-18 21:46:44 +02:00
parent bfc3acbbe0
commit 2476667510
21 changed files with 1409 additions and 24 deletions
+104
View File
@@ -0,0 +1,104 @@
# 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 <version>.
#
# 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"