From eaa014d37ae0b78517434f1badd627fcab8a8c83 Mon Sep 17 00:00:00 2001 From: Claudio Date: Sun, 17 May 2026 21:52:31 +0200 Subject: [PATCH] Add LXC 127 mule-photos-new (PhotoPrism M0 test on @new branch) --- containers/127-mule-photos-new.md | 167 ++++++++++++++++++++++++++++++ containers/index.md | 1 + infrastructure/dns.md | 1 + 3 files changed, 169 insertions(+) create mode 100644 containers/127-mule-photos-new.md diff --git a/containers/127-mule-photos-new.md b/containers/127-mule-photos-new.md new file mode 100644 index 0000000..a7a032a --- /dev/null +++ b/containers/127-mule-photos-new.md @@ -0,0 +1,167 @@ +# 127 — `mule-photos-new` + +Side-by-side **PhotoPrism M0 test** of the `dtoro/mule-image` `new` branch +at `photos-new.hubris.network`. Production [LXC 120](120-mule-images.md) keeps +running on the legacy stack at `photos.hubris.network` until M5 cutover. + +## At a glance +- **Hostname:** `mule-photos-new` +- **IP:** `192.168.8.181` +- **Privilege:** unpriv +- **Resources:** 6 cores / 8 GiB RAM / 40 GiB rootfs / 1 GiB swap +- **Features:** `nesting=1,fuse=1,keyctl=1` +- **Mounts:** *(none — see scratch copy below)* +- **Public hostname:** [`photos-new.hubris.network`](../infrastructure/dns.md) → [caddy (121)](121-caddy.md) → split (PhotoPrism `:2342`, sidecar `:8000`, Vite `:5173`) + +## Stack (`/opt/mule-image`) + +`/opt/mule-image` is the working tree of `dtoro/mule-image` on branch +`new`. Compose lives at `docker-compose.photoprism.yml`; LXC-127-only +overrides at `docker-compose.photoprism.override.yml` (untracked — see +[Why an override exists](#why-an-override-exists)). Invoked with +`--env-file .env.photoprism`. + +| Service | Container | Port | Notes | +| ----------- | ------------- | ----------------- | -------------------------------------------------- | +| mariadb | `pp-mariadb` | `127.0.0.1:3306` | MariaDB 11; named volume `pp_mariadb_data`; init SQL provisions `mule_sidecar.marks` table | +| photoprism | `pp-app` | `:2342` | `docker.io/photoprism/photoprism:latest`, runs as uid 33 (PP_UID/GID env) | +| sidecar | `pp-sidecar` | `:8000` (overridden) | Go + Gin service for rename / folder mutations / heap convert / dup detect | +| vite (host) | systemd unit | `:5173` | SvelteKit dev server (`mule-vite.service`), `npm run dev` in `/opt/mule-image/web` | + +PhotoPrism is enterprise-tier (`tier: 1`) per the build tag (`-Plus`). TF +vision pipeline and EXIF backwrite are **disabled** in M0 — `PP_READONLY=true` +keeps the originals view read-only as the M0 safety net. + +## Library — writable rsync scratch copy (NOT the real admin Photos) + +Unlike LXC 120 (which mounts `/mnt/library` directly), this LXC has **no +bind-mount of the production library**. Instead a one-shot rsync of the +admin's Photos lives on the LXC's own rootfs: + +- **Host source (read-only reference):** `/mnt/library/homecloud/admin/files/Photos` (~4.9 GB) +- **LXC scratch (writable):** `/srv/photos-scratch` (owner `www-data:media`, mode 0775) + +This means **sidecar rename / folder mutation operations land in the +scratch copy, not the real admin library**. The scratch is not +auto-synced — it's a snapshot from `2026-05-17`. To refresh from +production: + +```bash +# on hubris (LXC 127 must be stopped to mount its rootfs) +pct stop 127 +pct mount 127 +rsync -aHAX --info=stats2 --chown=100033:110000 --no-perms \ + --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r \ + /mnt/library/homecloud/admin/files/Photos/ \ + /var/lib/lxc/127/rootfs/srv/photos-scratch/ +pct unmount 127 +pct start 127 +``` + +`--chown=100033:110000` accounts for the unprivileged-LXC ID shift +(host 100033 = LXC `www-data`, host 110000 = LXC `media`). Don't try a +bind-mount of `/mnt/library/...` — the admin Photos tree is 0750 and +unprivileged LXCs can't see through. + +## Auth — Authentik OIDC + +PhotoPrism's "Sign in with OIDC" button delegates to [Authentik (124)](124-authentik.md). + +- **Provider/Application slug:** `mule-photos-new` +- **Issuer:** `https://auth.hubris.network/application/o/mule-photos-new/` +- **Redirect URI:** `https://photos-new.hubris.network/api/v1/oidc/redirect` +- **Scopes:** `openid profile email` +- **Initiated by clicking the OIDC button** at `/library/login` → `GET /api/v1/oidc/login` → 302 to Authentik authorize. +- `OIDC_REGISTER=true` + `OIDC_ROLE=admin` so the first SSO login auto-creates a PhotoPrism admin account. + +Local PhotoPrism admin (username `admin`, password in +`/root/mule-photos-new-secrets.txt` on hubris) stays available as a +fallback. + +## Why an override exists + +`docker-compose.photoprism.override.yml` is **only on LXC 127** (not in +the git repo) and pins two M0-era inconsistencies from the upstream +compose file: + +1. **Sidecar bind address.** Upstream binds `sidecar` to `127.0.0.1:8000` + because the M4 design colocates Caddy with the sidecar. On this test + LXC Caddy lives on a different host (LXC 121), so the override binds + the sidecar port to `0.0.0.0:8000`. +2. **OIDC env-var names.** Upstream passes + `PHOTOPRISM_OIDC_ISSUER_URL` / `_CLIENT_ID` / `_CLIENT_SECRET` / + `_PROVIDER_NAME`, but PhotoPrism actually reads + `PHOTOPRISM_OIDC_URI` / `_CLIENT` / `_SECRET` / `_PROVIDER`. The + override re-maps. Confirmed live with `docker exec pp-app photoprism show config`. + +Both fixes should land upstream on the `new` branch; once they do the +override file becomes dead code and can be removed. + +## Auto-deploy + +Mirrors the LXC 120 pattern. + +- **Webhook listener:** `mule-deploy-webhook.service` → `python3 /opt/mule-deploy/webhook.py` on `0.0.0.0:9797`. +- **Branch filter:** `refs/heads/new` (LXC 120 still owns `main`). +- **HMAC secret:** `/etc/mule-deploy/secret` (mode 0600). +- **Deploy script:** `/opt/mule-deploy/deploy.sh` — `git fetch && git reset --hard origin/new`, fix `pp/{storage,import}` ownership to `33:10000` (PP container uid), `docker compose ... up -d --build --force-recreate` with both compose files, `systemctl restart mule-vite`. +- **Gitea webhook id 9** on `dtoro/mule-image` pointed at `http://192.168.8.181:9797/deploy`. + +Push to the `new` branch on [git.hubris.network/dtoro/mule-image](http://git.hubris.network/dtoro/mule-image) → webhook fires → rebuild. The legacy LXC 120 watches `main` and is unaffected. + +## Bootstrap secrets + +Saved on hubris at `/root/mule-photos-new-secrets.txt` (mode 0600): + +- `PP_ADMIN_PASSWORD` — initial PhotoPrism `admin` login +- `PP_DB_PASSWORD` — MariaDB `photoprism` user +- `PP_DB_ROOT_PASSWORD` — MariaDB root +- `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` — generated by `ak shell` against Authentik + +`SIDECAR_DB_PASSWORD` is still the literal placeholder +`replace-at-m4-bringup` because `mariadb/init/01-sidecar.sql` hardcodes +it; rotate before this stack ever goes public. + +## Health checks + +```bash +# from hubris +pct exec 127 -- curl -sf http://127.0.0.1:2342/api/v1/status # PP +pct exec 127 -- curl -sf http://127.0.0.1:8000/api/sidecar/healthz # sidecar +pct exec 127 -- curl -sf http://127.0.0.1:5173/ # Vite + +# through Caddy +curl -sk --resolve photos-new.hubris.network:443:192.168.8.175 \ + https://photos-new.hubris.network/api/v1/oidc/login -i | head -2 # 302 → auth.hubris.network +``` + +## Changelog + +### 2026-05-17 — Bring-up + +LXC 127 created from `debian-13-standard_13.1-2`, joined to vmbr0 with +static IP `192.168.8.181`. Docker engine + Node 20 installed. + +`dtoro/mule-image` cloned at branch `new`, compose stack +(`docker-compose.photoprism.yml`) brought up: MariaDB 11 + PhotoPrism +`:latest` (`-Plus` build) + Go sidecar (built locally). Vite dev server +running as `mule-deploy` via `mule-vite.service` on port 5173. + +Authentik OIDC application `mule-photos-new` provisioned via `ak shell` +(`OAuth2Provider` + `Application` + STRICT `RedirectURI`). PhotoPrism's +OIDC button delegates to Authentik; `OIDC_REGISTER=true` / +`OIDC_ROLE=admin` so the first SSO login becomes admin. + +Admin's Photos library rsynced (~4.9 GB, 1206 files) into +`/srv/photos-scratch` on the LXC rootfs (no bind-mount). Sidecar +mutations land in the scratch copy, not the real library. + +Caddy site `photos-new.hubris.network` added in `dtoro/caddy-conf`; +dnsmasq entry on LXC 124 → `192.168.8.175`. dnsmasq required a `restart` +(not `reload`) for the new `address=` line to take effect. + +`docker-compose.photoprism.override.yml` (LXC-only, untracked) pins two +upstream issues: sidecar bound to `127.0.0.1` (cross-host Caddy can't +reach), and OIDC env-var name mismatch +(`PHOTOPRISM_OIDC_ISSUER_URL` vs `PHOTOPRISM_OIDC_URI` and friends). +Should land upstream on `new` next iteration. diff --git a/containers/index.md b/containers/index.md index e54f9e5..45e0179 100644 --- a/containers/index.md +++ b/containers/index.md @@ -17,6 +17,7 @@ All containers live on [`hubris`](../hosts/hubris.md). Each row links to the per | 123 | [claudio-bot](123-claudio-bot.md) | 192.168.8.230 | unpriv | 1 | 512 MiB | 8 GiB | — | — | running | | 124 | [authentik](124-authentik.md) | 192.168.8.180 | priv | 2 | 4 GiB | 20 GiB | — | `auth.hubris.network` | running | | 126 | [plato](126-plato.md) | 192.168.8.190 | priv | 2 | 2 GiB | 8 GiB | `/mnt/library/documents/plato` | `plato.hubris.network` | running | +| 127 | [mule-photos-new](127-mule-photos-new.md) | 192.168.8.181 | unpriv | 6 | 8 GiB | 40 GiB | — (rsync scratch on rootfs) | `photos-new.hubris.network` | running | ## Recently destroyed (kept for archaeology) diff --git a/infrastructure/dns.md b/infrastructure/dns.md index e9b6c2f..2153858 100644 --- a/infrastructure/dns.md +++ b/infrastructure/dns.md @@ -29,6 +29,7 @@ address=/qbit.hubris.network/192.168.8.175 address=/sab.hubris.network/192.168.8.175 address=/blog.hubris.network/192.168.8.175 address=/photos.hubris.network/192.168.8.175 +address=/photos-new.hubris.network/192.168.8.175 address=/artifacto.hubris.network/192.168.8.175 address=/plato.hubris.network/192.168.8.175 address=/zimaos.hubris.network/192.168.8.175