C1: bake shower wheel into image; wire borgmatic; refine NFS docs
Three follow-ups on the shower deployment branch:
1. containers/shower/default.nix now uses buildPythonPackage to install
the adelaide-baby-shower-app wheel + its deps at nix build time. The
wheel comes from the forge PyPI index with a pinned SRI hash. The
entrypoint no longer does pip-at-boot — it just runs migrations,
collectstatic, and execs gunicorn.
2. ansible/roles/borgmatic/defaults/main.yml:
- Adds shower to borgmatic_k8s_sqlite_dumps (context k3s-ringtail)
so /app/data/db.sqlite3 is dumped via kubectl exec on every run.
- Adds /Volumes/shower (sifaka SMB mount on indri) to
borgmatic_source_directories so prize-photo media gets archived.
3. NFS share docs corrected to match the real on-sifaka pattern:
exports allowlist 192.168.1.0/24 + 100.64.0.0/10 with all_squash to
admin (matching frigate/paperless/etc.), not "Squash=No mapping".
The pod's runAsUser doesn't need to match an on-disk uid because
all_squash rewrites every write to admin:users.
Also adds a missing service-versions entry for the tailscale container
introduced in PR #347 — pre-existing gap surfaced by the
container-version-check hook on this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6e37abda5d
commit
cb4f4085c2
6 changed files with 159 additions and 77 deletions
13
docs/changelog.d/shower-app-deploy.bugfix.md
Normal file
13
docs/changelog.d/shower-app-deploy.bugfix.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Shower app container now bakes the wheel + Python deps into the image
|
||||
at build time via `buildPythonPackage` instead of pip-installing on
|
||||
first boot. Boots are deterministic and don't depend on forge PyPI
|
||||
being reachable from the pod. The `wheelHash` in
|
||||
`containers/shower/default.nix` is the sha256 sourced from the
|
||||
[forge PyPI simple index](https://forge.eblu.me/api/packages/eblume/pypi/simple/adelaide-baby-shower-app/);
|
||||
bumping the version means bumping that hash too.
|
||||
|
||||
Borgmatic now covers the shower app: SQLite is dumped from the live
|
||||
pod via `kubectl exec` (mirroring the existing mealie entry, with
|
||||
`context: k3s-ringtail`), and the prize-photo media share is picked up
|
||||
through `/Volumes/shower` (sifaka SMB mount on indri, same pattern as
|
||||
`/Volumes/photos`).
|
||||
|
|
@ -64,26 +64,70 @@ django-axes has already locked them out.
|
|||
| `/app/media` | `shower-media` | NFS RWX on sifaka (`/volume1/shower`) | Prize photos survive pod rescheduling |
|
||||
| `/app/data` | `shower-data` | k3s `local-path` RWO | SQLite DB; NFS file locking can't be trusted for WAL/journal |
|
||||
|
||||
The container's entrypoint installs the wheel into `/app/data/.venv` on
|
||||
first boot, runs migrations, runs `collectstatic`, and `exec`s gunicorn.
|
||||
A `local_settings.py` shim overrides `DATABASES.NAME`, `MEDIA_ROOT`, and
|
||||
`STATIC_ROOT` to absolute paths under `/app/`, sidestepping the wheel's
|
||||
`BASE_DIR = parent.parent` of an in-site-packages settings module.
|
||||
The container has the app + its Python deps baked in at nix build time
|
||||
(`buildPythonPackage` against the wheel fetched from forge PyPI). The
|
||||
entrypoint runs migrations, runs `collectstatic`, and `exec`s gunicorn —
|
||||
no pip-at-boot. A `local_settings.py` shim overrides `DATABASES.NAME`,
|
||||
`MEDIA_ROOT`, and `STATIC_ROOT` to absolute paths under `/app/`,
|
||||
sidestepping the wheel's `BASE_DIR = parent.parent` of an
|
||||
in-site-packages settings module.
|
||||
|
||||
## Backups
|
||||
|
||||
[[borgmatic]] (running on indri) captures both halves of the persistent
|
||||
state on its daily 2 a.m. run:
|
||||
|
||||
- **`/app/data/db.sqlite3`** — dumped via `kubectl exec`'s
|
||||
`sqlite3.backup()` against the live pod (entry in
|
||||
`borgmatic_k8s_sqlite_dumps`, context `k3s-ringtail`). The dumped
|
||||
file lands in `borgmatic_k8s_dump_dir` on indri and is picked up by
|
||||
the main source-directory sweep.
|
||||
- **`/app/media`** — picked up via `/Volumes/shower`, the SMB mount of
|
||||
`sifaka:/volume1/shower` on indri. The same Synology share is exposed
|
||||
via SMB *and* NFS simultaneously; ringtail's pod uses the NFS export,
|
||||
while indri reads the SMB side for the borgmatic source.
|
||||
|
||||
Both archive to [[sifaka]] (`borg-backups`) and BorgBase offsite, with
|
||||
retention `keep_daily=7 / keep_monthly=12 / keep_yearly=1000`.
|
||||
|
||||
The SMB mount on indri is set up manually once via Finder (Cmd-K →
|
||||
`smb://sifaka/shower`, save credentials, "Always log in" so it
|
||||
reconnects after reboot). If `/Volumes/shower` is missing at backup
|
||||
time borgmatic will fail loudly — `source_directories_must_exist: true`
|
||||
applies to all entries.
|
||||
|
||||
## One-time setup steps
|
||||
|
||||
These steps are required the first time the service is deployed and are
|
||||
not encoded in the manifests.
|
||||
|
||||
### 1. NFS share on sifaka
|
||||
### 1. NFS + SMB share on sifaka
|
||||
|
||||
On the Synology:
|
||||
On the Synology DSM web UI:
|
||||
|
||||
1. Control Panel → Shared Folder → Create. Name: `shower`, Volume 1.
|
||||
2. Control Panel → File Services → NFS → NFS Rules. Add rule for
|
||||
`shower`: Hostname=`ringtail`, Privilege=Read/Write, Squash=No mapping.
|
||||
3. `chown -R 1000:1000 /volume1/shower` over SSH so the pod's uid 1000
|
||||
can write.
|
||||
1. **Control Panel → Shared Folder → Create**. Name: `shower`,
|
||||
Location: Volume 1. Leave the rest at default.
|
||||
2. **Control Panel → File Services → NFS → NFS Rules** (on the
|
||||
`shower` row's *Permissions* tab). Add a rule mirroring the other
|
||||
shares' pattern: Hostname/IP=`192.168.1.0/24` and again for
|
||||
`100.64.0.0/10`, Privilege=Read/Write, Squash=`Map all users to
|
||||
admin` (= `all_squash`), and tick *Allow connections from
|
||||
non-privileged ports*. (See [[sifaka#NFS Exports]] — the existing
|
||||
`frigate`, `paperless`, etc. shares use this exact pattern.)
|
||||
3. **Control Panel → File Services → SMB**: leave SMB enabled
|
||||
globally. No per-share rule required — the share inherits the
|
||||
default `eblume` access.
|
||||
4. The directory ownership at `/volume1/shower` will end up
|
||||
`root:root`, mode `0777` (DSM default) — which is fine because
|
||||
`all_squash` rewrites every NFS write to `admin:users`, and the
|
||||
`0777` lets pods read what other pods wrote. No `chown` needed.
|
||||
|
||||
After the share exists, mount it on indri for borgmatic:
|
||||
|
||||
- In Finder, **Cmd-K → `smb://sifaka/shower`**, sign in as `eblume`,
|
||||
and tick **Remember in Keychain** + **Always log in** so it
|
||||
reconnects on reboot. This produces `/Volumes/shower`, which the
|
||||
borgmatic source-directory list points at.
|
||||
|
||||
### 2. 1Password item
|
||||
|
||||
|
|
@ -106,7 +150,13 @@ freshly generated.
|
|||
### 3. Container image
|
||||
|
||||
Built by the `build-container` Forgejo Actions workflow on the
|
||||
`nix-container-builder` runner (ringtail, amd64). Trigger with:
|
||||
`nix-container-builder` runner (ringtail, amd64). The wheel is fetched
|
||||
from forge PyPI at nix build time and baked into the image — no
|
||||
pip-at-runtime. To bump the version, change `version` in
|
||||
`containers/shower/default.nix` and update `wheelHash` (or set it to
|
||||
`pkgs.lib.fakeHash` and let the next build print the correct one).
|
||||
|
||||
Trigger with:
|
||||
|
||||
```fish
|
||||
mise run container-build-and-release shower
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue