From 3c7967e44507137e997fa9edf3c649954ef7807f Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 11 May 2026 20:08:03 -0700 Subject: [PATCH] C1: deploy shower v1.1.0 (phases + guest memories) (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Deploys `adelaide-baby-shower-app` **v1.1.0** to ringtail k3s. ### App changes (since v1.0.2) - **Four-phase `ShowerState`** replaces the boolean `locked` flag — `pre_event` → `party` → `prizes_locked` → `event_locked` — with a backfill migration that maps `locked=True → pre_event`, `locked=False → party`. - **Guest memories**: append-only photos + comments panel where guests can leave notes for the baby. Adds `GuestPhoto` + `GuestComment` models with file-extension validators and a max-size validator; new `shower.imaging` module for thumbnail generation. - **Admin + QR polish**: configurable host link, fixed "View Site" URL, guest-facing QR copy improvements, contest tweaks. Three Django migrations run automatically in the entrypoint against the SQLite PV: - `0009_shower_phase` - `0010_guest_memories` - `0011_book_description` No ConfigMap / env-var changes. The deploy uses `strategy: Recreate` with a single replica, so the old pod releases the data PVC before the new one mounts it and runs migrations. ### Container build changes The v1.1.0 tag exposed a latent issue with the Forgejo PyPI install path: - The recent commit [2d38418e](https://forge.eblu.me/eblume/blumeops/commit/2d38418e) closed the forge package leak at the Fly edge by blocking `/api/packages/*` publicly. - Forgejo's PyPI simple index returns absolute file URLs hardcoded to its public `ROOT_URL` (`forge.eblu.me`), so pip-installing from the tailnet index URL still tries to download from `forge.eblu.me` → 403. - Previous shower builds escaped this because their FOD outputs were already in the nix store; bumping to a new version forced a fresh pip run that hit the block. Fix mirrors what we already do for the sdist: both wheel and sdist are pulled via direct `fetchurl` against `forge.ops.eblu.me`, then the wheel is copied to TMPDIR under its clean filename (nix store path's hash prefix breaks pip's wheel-filename parser) and handed to pip as a local path. The forge `--extra-index-url` is no longer needed. FOD outputHash pinned to `sha256-kTNOswobtkgyQmmqbQM8XO4vvaGg57nCuuZGbNXb0NM=` from run 547. Image: `registry.ops.eblu.me/blumeops/shower:v1.1.0-444ff91-nix`. ### Adjacent finding (already handled) The ringtail `gitea-runner-nix_container_builder` systemd unit was left `inactive` after the recent `provision-ringtail` (matches the known `sshd-restart-hangs-mux` lesson — the rebuild changed the unit's PATH closure + config.yaml, systemd stopped it, then the playbook hung before the activation could restart it). Manually started; the existing memory `lesson_provision_ringtail_ssh_hang.md` was extended to mention the runner as the canary service to check after provisions. ## Test plan - [ ] `argocd app diff shower --revision shower-v1.1.0` — review the manifest change - [ ] `argocd app set shower --revision shower-v1.1.0 && argocd app sync shower` - [ ] `kubectl --context=k3s-ringtail logs -n shower deploy/shower` — confirm migrations 0009/0010/0011 applied, no errors - [ ] Hit `https://shower.ops.eblu.me/` (tailnet) — splash page renders, phase indicator visible - [ ] Hit `https://shower.ops.eblu.me/host/` — host console loads, phase dropdown shows the four states - [ ] Hit `https://shower.eblu.me/` (public via Fly) — splash page still served - [ ] After merge: `argocd app set shower --revision main && argocd app sync shower` Reviewed-on: https://forge.eblu.me/eblume/blumeops/pulls/354 --- argocd/manifests/shower/kustomization.yaml | 2 +- containers/shower/default.nix | 39 ++++++++++++++++------ docs/changelog.d/shower-v1.1.0.feature.md | 15 +++++++++ service-versions.yaml | 4 +-- 4 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 docs/changelog.d/shower-v1.1.0.feature.md diff --git a/argocd/manifests/shower/kustomization.yaml b/argocd/manifests/shower/kustomization.yaml index d2ce83c..6fe641f 100644 --- a/argocd/manifests/shower/kustomization.yaml +++ b/argocd/manifests/shower/kustomization.yaml @@ -14,4 +14,4 @@ resources: images: - name: registry.ops.eblu.me/blumeops/shower - newTag: v1.0.2-292d354-nix + newTag: v1.1.0-444ff91-nix diff --git a/containers/shower/default.nix b/containers/shower/default.nix index d9863e1..e2d369d 100644 --- a/containers/shower/default.nix +++ b/containers/shower/default.nix @@ -1,11 +1,15 @@ # Nix-built shower app container — Adelaide / Heidi / Addie baby shower. # # The app is published as a wheel to the Forgejo PyPI index at -# https://forge.eblu.me/api/packages/eblume/pypi/. The wheel + its -# transitive Python deps are baked in at build time via a fixed-output -# derivation that runs `pip install --target` against forge PyPI (proxied -# through pypi.ops.eblu.me for upstream packages). Build runs on the -# nix-container-builder runner (ringtail, amd64) so the image is native. +# https://forge.ops.eblu.me/api/packages/eblume/pypi/ (tailnet-only — the +# public forge.eblu.me /api/packages/* surface is blocked at the Fly edge). +# We can't point pip at Forgejo's simple index even from the tailnet, +# because Forgejo's index returns absolute file URLs hardcoded to its +# public ROOT_URL (forge.eblu.me), which then 403s. So both the wheel and +# the sdist are pulled by direct `fetchurl` against forge.ops.eblu.me, and +# the wheel is then handed to `pip install` as a local path; transitive +# deps come from pypi.ops.eblu.me. Build runs on the nix-container-builder +# runner (ringtail, amd64) so the image is native. # # Going through pip-install-target rather than nixpkgs Python packages # sidesteps two issues we hit going through `python.pkgs.buildPythonPackage`: @@ -21,7 +25,7 @@ { pkgs ? import { } }: let - version = "1.0.2"; + version = "1.1.0"; python = pkgs.python314; @@ -39,7 +43,17 @@ let showerSdist = pkgs.fetchurl { name = "adelaide_baby_shower_app-${version}.tar.gz"; url = "https://forge.ops.eblu.me/api/packages/eblume/pypi/files/adelaide-baby-shower-app/${version}/adelaide_baby_shower_app-${version}.tar.gz"; - hash = "sha256-nlCtlx9zuYaLoJZSckybLV5YPpA8vZamN96O3RXOstM="; + hash = "sha256-5dp+0u4metOIC6s6/nPlT4cdpFBCV6S3+Z/3RO0sX5U="; + }; + + # Wheel pulled from forge.ops.eblu.me (tailnet) for the same reason the + # sdist is: Forgejo's PyPI simple index would return forge.eblu.me URLs + # that the Fly edge 403s on /api/packages/*. We hand this path to pip + # below so it never touches the forge index at all. + showerWheel = pkgs.fetchurl { + name = "adelaide_baby_shower_app-${version}-py3-none-any.whl"; + url = "https://forge.ops.eblu.me/api/packages/eblume/pypi/files/adelaide-baby-shower-app/${version}/adelaide_baby_shower_app-${version}-py3-none-any.whl"; + hash = "sha256-7orFbycON9dQxEIb6q45Xx2rFlEZ8xXSrC2tnrO5uug="; }; staticAssets = pkgs.runCommand "shower-static-assets-${version}" { } '' @@ -68,11 +82,16 @@ let ${python}/bin/python -m venv "$TMPDIR/venv" "$TMPDIR/venv/bin/pip" install --upgrade pip + + # Nix store paths embed a 32-char hash prefix, which pip's wheel + # filename parser rejects ("Invalid wheel filename"). Copy to a + # clean filename in TMPDIR before installing. + cp ${showerWheel} "$TMPDIR/${showerWheel.name}" + "$TMPDIR/venv/bin/pip" install \ --no-cache-dir \ --index-url=https://pypi.ops.eblu.me/root/pypi/+simple/ \ - --extra-index-url=https://forge.ops.eblu.me/api/packages/eblume/pypi/simple/ \ - "adelaide-baby-shower-app==${version}" \ + "$TMPDIR/${showerWheel.name}" \ gunicorn runHook postBuild @@ -129,7 +148,7 @@ let outputHashAlgo = "sha256"; # Pinned dep closure — reproducible until version bumps. To recompute, # set to pkgs.lib.fakeHash and read the failure. - outputHash = "sha256-tSTH/HaDY7M0qxlauBTM+JekZAgF++K2lGP3PLvym/o="; + outputHash = "sha256-kTNOswobtkgyQmmqbQM8XO4vvaGg57nCuuZGbNXb0NM="; dontFixup = true; }; diff --git a/docs/changelog.d/shower-v1.1.0.feature.md b/docs/changelog.d/shower-v1.1.0.feature.md new file mode 100644 index 0000000..d2c3400 --- /dev/null +++ b/docs/changelog.d/shower-v1.1.0.feature.md @@ -0,0 +1,15 @@ +Deploy adelaide-baby-shower-app v1.1.0 to ringtail k3s. Replaces the +boolean lock with a four-phase `ShowerState` (`pre_event` → `party` → +`prizes_locked` → `event_locked`), adds an append-only "guest memories" +panel where guests can leave photos and comments for the baby, and +polishes the admin and QR views. Three Django migrations +(`0009_shower_phase`, `0010_guest_memories`, `0011_book_description`) +run automatically in the entrypoint against the SQLite PV. No config +or env-var changes. + +Container build also gains a Forgejo-PyPI workaround: Forgejo's simple +index returns absolute file URLs hardcoded to the public ROOT_URL +(`forge.eblu.me`), which the Fly edge 403s on `/api/packages/*`. The +wheel and sdist are now both pulled via direct `fetchurl` against +`forge.ops.eblu.me` (tailnet-only) and the wheel is handed to pip as +a local path. diff --git a/service-versions.yaml b/service-versions.yaml index 56000df..63bc5df 100644 --- a/service-versions.yaml +++ b/service-versions.yaml @@ -46,8 +46,8 @@ services: - name: shower type: argocd - last-reviewed: 2026-05-10 - current-version: "1.0.2" + last-reviewed: 2026-05-11 + current-version: "1.1.0" upstream-source: https://forge.eblu.me/eblume/adelaide-baby-shower-app notes: | Django app for Adelaide / Heidi / Addie's baby shower. Wheel