SPEC v0.37: image builds on the Mac, v1.0.3 deployed, web client verified (D-211)
infra/build-images.sh on the Mac with Podman is the normal image build, the Gitea workflow on the Pi 3 runner the fallback (revises D-156 point 5); tag rule, registry port 80 from the Mac (the Pi's .env keeps gitea.lan:3001), Podman insecure-registry files, the script fixesf15fa23and8fa9198. v1.0.3 on the Kvitt Pi; the owner verified the web client from the laptop browser and S-11's Insights (SPEC §10.3, §10.10, §11.2 to §11.4, §15; infra/README.md; HANDOFF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+52
-19
@@ -22,10 +22,10 @@ from the checklist in "Pi: what the owner does".
|
||||
| `backup/Dockerfile`, `backup/backup.sh`, `backup/healthcheck.sh` | Backup image and job of SPEC §10.9 (shared with the Pi stack); Drive upload and D-002 deletion |
|
||||
| `pi/` | **The Pi project** (copied to `~/kvitt` on the Kvitt Pi): `compose.yaml`, `.env.example`; git-ignored `pi/.env`, `pi/secrets/`, `pi/out/` |
|
||||
| `npm/kvitt.conf` | The complete NPM Custom Nginx Configuration block of step 4, as a file to open and paste (placeholder `KVITT_APP_KEY`) |
|
||||
| `build-images.sh` | Mac fallback for the image build: linux/arm64, push to the Gitea registry |
|
||||
| `build-images.sh` | The image build (the normal path, D-211): on the Mac with Podman, linux/arm64, push to the Gitea registry |
|
||||
| `publish-apk.sh` | Mac: release APK, mapping and `latest.json` into `pi/out/apk/` for the owner to copy to the Pi |
|
||||
| `RESTORE.md` | Restore from a Drive backup, step by step (SPEC §10.9) |
|
||||
| `../.gitea/workflows/build-images.yaml` | Gitea Actions image build (the normal path) |
|
||||
| `../.gitea/workflows/build-images.yaml` | Gitea Actions image build on the Pi 3 runner (the fallback since D-211) |
|
||||
| `secrets/` | Git-ignored: `fcm-service-account.json`, `google-services.json`, `rclone.conf`, optional `registry.env` (and `ha_ssh_key(.pub)`, unused since D-120). Never committed |
|
||||
|
||||
All Mac commands below run from the repository root of the main checkout (`/Users/jhag/projects/kvitt`).
|
||||
@@ -163,8 +163,8 @@ The key goes to the Pi as `~/kvitt/secrets/fcm-service-account.json` ("Pi: what
|
||||
|
||||
| Where | What runs | Who runs it |
|
||||
|---|---|---|
|
||||
| Gitea Actions (runner label `host`, a Raspberry Pi) | `.gitea/workflows/build-images.yaml`: builds `kvitt-server` and `kvitt-backup` for linux/arm64, pushes them to `gitea.lan/jacobh2/...` tagged `vMAJOR.MINOR.PATCH` and `latest`, pushes the git tag | Gitea, on a push to `server/**`, `infra/backup/**`, `.gitea/workflows/**`, or by hand (Actions, "Run workflow") |
|
||||
| Owner's Mac | `infra/build-images.sh [tag]` (fallback image build), `infra/publish-apk.sh` (release APK into `infra/pi/out/apk/`) | The owner (or an agent, for the APK build only) |
|
||||
| Gitea Actions (runner label `host`, a Raspberry Pi; the fallback since D-211) | `.gitea/workflows/build-images.yaml`: builds `kvitt-server` and `kvitt-backup` for linux/arm64, pushes them to `gitea.lan/jacobh2/...` tagged `vMAJOR.MINOR.PATCH` and `latest`, pushes the git tag | Gitea, on a push to `server/**`, `infra/backup/**`, `.gitea/workflows/**`, or by hand (Actions, "Run workflow") |
|
||||
| Owner's Mac | `infra/build-images.sh v1.0.N` (the image build, the normal path, D-211), `infra/publish-apk.sh` (release APK into `infra/pi/out/apk/`) | The owner (or an agent, for the APK build only) |
|
||||
| Kvitt Pi, `/home/jacobhagstedt/kvitt/` | `compose.yaml`, `.env`, `secrets/`, and the bind-mounted `data/` (SQLite, pictures, `apk/`), `backups/`, `import/`; nothing outside this folder (D-125) | The owner, in their own SSH session, with plain `docker compose` commands |
|
||||
| Home Assistant Pi | Nginx Proxy Manager (NPM): a proxy host for `<KVITT_PUBLIC_HOST>` with its own Let's Encrypt certificate, TLS on 443, plain HTTP to `<kvitt-pi-address>:8080` over the LAN (D-120); the app gate closes every request from outside the LAN without the app key (D-126) | The owner, in NPM's web UI (steps 4 and 4a) |
|
||||
| Router (ASUS) | No new rule: the existing external TCP 443 forward to the Home Assistant Pi (NPM, for Home Assistant) serves Kvitt too, NPM picks the proxy host by name; external 80 already reaches NPM for Let's Encrypt; nothing is forwarded to the Kvitt Pi (D-124) | The owner checks it (step 5) |
|
||||
@@ -265,7 +265,11 @@ confirmation by name and size, local deletion only after both files were confirm
|
||||
`last-success` unchanged when the upload failed; the leftovers uploaded by the next good run; healthcheck green on a
|
||||
fresh start and after a run, red at 26 h. (The Caddy and cert-sync rehearsals of that day concern the removed stack.)
|
||||
|
||||
### Gitea Actions workflow (`.gitea/workflows/build-images.yaml`)
|
||||
### Gitea Actions workflow (`.gitea/workflows/build-images.yaml`), the fallback
|
||||
|
||||
Since 2026-10-24 (D-211) images are built on the Mac (next section); this workflow on the Pi 3 runner stays as the
|
||||
fallback (about three hours per run, and the runner's disk fills). A push that touches its paths queues a run; the
|
||||
owner cancels it in Gitea (the repository, Actions) when the Mac build is used.
|
||||
|
||||
Same setup as the owner's working example: `runs-on: host`, `sh` steps, `apk add git docker-cli docker-cli-buildx`,
|
||||
`docker login gitea.lan` with `kvitt_cicd` (a user-level secret of the repository owner, created by the owner;
|
||||
@@ -298,14 +302,41 @@ keeps the cargo registry and target directory in BuildKit cache mounts, so only
|
||||
takes 48 s on the M4 Mac); later runs reuse the builder's cargo cache. If a build fails for memory, add swap on the
|
||||
runner Pi, or build on the Mac with `infra/build-images.sh`.
|
||||
|
||||
### Mac fallback: `infra/build-images.sh [tag]`
|
||||
### Image build on the Mac: `infra/build-images.sh v1.0.N` (the normal path, D-211)
|
||||
|
||||
Builds the same two images for linux/arm64 and pushes them when `infra/secrets/registry.env` exists
|
||||
(`KVITT_REGISTRY=gitea.lan/jacobh2`, optional `KVITT_REGISTRY_USER` and `KVITT_REGISTRY_TOKEN` with
|
||||
write:package); otherwise it builds locally and prints the exact push commands. Tag: the short git SHA by default,
|
||||
plus `latest`. Refuses a dirty tree and does not build while a Gradle build holds the gradle-serial lock. With
|
||||
Podman (this Mac) it pushes with `--tls-verify=false`; with Docker it creates a buildx builder that allows plain
|
||||
HTTP to the registry.
|
||||
Builds the two images for linux/arm64 and pushes them when `infra/secrets/registry.env` exists
|
||||
(`KVITT_REGISTRY=gitea.lan/jacobh2`, `KVITT_REGISTRY_USER` and `KVITT_REGISTRY_TOKEN` with write:package);
|
||||
otherwise it builds locally and prints the exact push commands. The server build gets
|
||||
`KVITT_VERSION_CODE=<git rev-list --count HEAD>` as in the workflow (`f15fa23`). Refuses a dirty tree and does not
|
||||
build while a Gradle build holds the gradle-serial lock. With Podman (this Mac) it pushes with `--tls-verify=false`;
|
||||
with Docker it creates a buildx builder that allows plain HTTP to the registry. The engine test captures
|
||||
`docker version` before matching it (`8fa9198`: a `grep -q` pipe under `pipefail` made it pick docker).
|
||||
|
||||
```bash
|
||||
cd /Users/jhag/projects/kvitt
|
||||
git fetch --tags
|
||||
git tag --sort=-v:refname | head -1 # highest tag on origin, for example v1.0.3
|
||||
infra/build-images.sh v1.0.4 # the next v1.0.N: kvitt-server and kvitt-backup, tagged v1.0.4 and latest
|
||||
git tag v1.0.4 && git push origin v1.0.4 # the same tag on the built commit, so the workflow's numbering continues
|
||||
```
|
||||
|
||||
Tag rule: the next `v1.0.N` after the highest `v1.0.*` tag on `origin` (`v1.0.3` = `8fa9198`, the first Mac
|
||||
build). The script ends with the deploy hint for the Pi (step 7). It prints no image digests; the digest comparison
|
||||
under Operations applies to workflow builds.
|
||||
|
||||
**Registry port.** From the Mac the registry is `gitea.lan` on port 80 (plain HTTP), so `registry.env` says
|
||||
`KVITT_REGISTRY=gitea.lan/jacobh2`. The Pi's own `.env` keeps `KVITT_REGISTRY=gitea.lan:3001/jacobh2`, the port it
|
||||
reaches the same registry on locally; both name the same images.
|
||||
|
||||
**Podman and the plain-HTTP registry** (owner's allowance 2026-10-24, outside the repository). Podman refuses
|
||||
`gitea.lan` until it is marked insecure, in both places (the Mac and the Podman machine, where builds and pushes
|
||||
run). Both files are in place since 2026-10-24; repeat this only on a new Mac or a recreated Podman machine:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/containers/registries.conf.d
|
||||
printf '[[registry]]\nlocation = "gitea.lan"\ninsecure = true\n' > ~/.config/containers/registries.conf.d/kvitt.conf
|
||||
podman machine ssh "sudo mkdir -p /etc/containers/registries.conf.d && printf '[[registry]]\nlocation = \"gitea.lan\"\ninsecure = true\n' | sudo tee /etc/containers/registries.conf.d/kvitt.conf"
|
||||
```
|
||||
|
||||
### Publishing an APK: `infra/publish-apk.sh`
|
||||
|
||||
@@ -364,8 +395,9 @@ folders `data/`, `backups/`, `import/`. Nothing outside that folder is installed
|
||||
the Kvitt Pi's LAN IPv4 address (static DHCP lease; ASUS router web UI, LAN, DHCP Server, client list), also NPM's
|
||||
Forward Hostname / IP (step 4).
|
||||
|
||||
Between two workflow builds, run `docker builder prune -af` on the runner Pi: its 15 GB disk reached 11 GB during
|
||||
builds (SPEC §10.3, D-156). On the Samsung phones, turn Auto Blocker off before tapping "Uppdatera" for an in-app
|
||||
Images are built on the Mac (`infra/build-images.sh v1.0.N`, "Image build on the Mac" above, D-211). If the
|
||||
fallback workflow runs instead, run `docker builder prune -af` on the runner Pi between two of its builds: its 15 GB
|
||||
disk reached 11 GB during builds (SPEC §10.3, D-156). On the Samsung phones, turn Auto Blocker off before tapping "Uppdatera" for an in-app
|
||||
update and back on afterwards; with it on, the install is refused (SPEC AC-16.3, D-156).
|
||||
|
||||
### 1. Prerequisites (already on the Pi)
|
||||
@@ -394,7 +426,7 @@ rm -r infra/pi/secrets # the originals stay in infra/secrets/
|
||||
|---|---|
|
||||
| `HA_HOST` | LAN IPv4 address of the Home Assistant Pi (NPM), for example `192.168.1.20`, not `homeassistant.local` (router client list, or Home Assistant, Settings, System, Network) |
|
||||
| `KVITT_UID`, `KVITT_GID` | `id -u` and `id -g` on the Kvitt Pi as `jacobhagstedt` (default 1000, 1000) |
|
||||
| `KVITT_TAG` | `v1.0.0`, or the newest `v1.0.N` the Gitea workflow pushed (Gitea, the repository, Packages, `kvitt-server`) |
|
||||
| `KVITT_TAG` | The newest `v1.0.N` pushed (by `infra/build-images.sh` or the workflow; Gitea, the repository, Packages, `kvitt-server`) |
|
||||
| `KVITT_PUBLIC_HOST`, `KVITT_REGISTRY`, `RCLONE_FOLDER_ID` | Already `kvitt.suorra.duckdns.org`, `gitea.lan/jacobh2`, `1jDp1dGE5R3H2DBcXCqIPN1iWzQnPkdlV` |
|
||||
|
||||
`rclone.conf` is generated from your `rclone authorize "drive"` (remote `[gdrive]`, `scope = drive`); the Drive
|
||||
@@ -670,7 +702,8 @@ itself opens the link anywhere. The partner's invite, once the admin is paired:
|
||||
|
||||
### 7. Deploying a new version
|
||||
|
||||
After the Gitea workflow pushed `v1.0.N` (or `infra/build-images.sh` printed a tag), on the Pi:
|
||||
After `infra/build-images.sh v1.0.N` on the Mac pushed the images (the normal path, D-211; or the fallback workflow
|
||||
pushed `v1.0.N`), on the Pi:
|
||||
|
||||
```bash
|
||||
cd ~/kvitt
|
||||
@@ -682,7 +715,7 @@ docker compose ps && curl -s localhost:8080/health
|
||||
|
||||
Continue only when the backup ended with `backup: ok`. The images come over plain HTTP from `gitea.lan` (D-001), so
|
||||
compare the pulled digests (Operations, "Image digests") with the workflow's "Print image digests" step for that
|
||||
run (D-117). Rollback: set the previous tag in `.env` the same way and run `docker compose pull && docker compose
|
||||
run (D-117; a Mac build prints none). Rollback: set the previous tag in `.env` the same way and run `docker compose pull && docker compose
|
||||
up -d`; if a migration had partially applied, restore the pre-deploy backup (`infra/RESTORE.md`).
|
||||
|
||||
### Fallback (SPEC §10.5.3): when NPM is down
|
||||
@@ -740,8 +773,8 @@ or a leaked key (rotate it, step 4a).
|
||||
* `mem_limit` (2026-10-24, D-156): the lines stay in `compose.yaml`; the Pi kernel without the memory cgroup ignores
|
||||
them with a warning, accepted.
|
||||
* Firewall rule limiting port 8080 to `HA_HOST` (2026-10-24, D-156): not now (deferred).
|
||||
* Image builds (2026-10-24, D-156): stay on the Pi runner for now; `infra/build-images.sh` on the Mac remains the
|
||||
fallback.
|
||||
* Image builds (2026-10-24, D-211, revises D-156): on the Mac with `infra/build-images.sh v1.0.N`; the workflow on
|
||||
the Pi runner is the fallback.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
||||
Reference in New Issue
Block a user