7.8 KiB
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 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→ caddy (121) → 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). 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(ownerwww-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:
# 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).
- 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=adminso 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:
- Sidecar bind address. Upstream binds
sidecarto127.0.0.1:8000because 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 to0.0.0.0:8000. - OIDC env-var names. Upstream passes
PHOTOPRISM_OIDC_ISSUER_URL/_CLIENT_ID/_CLIENT_SECRET/_PROVIDER_NAME, but PhotoPrism actually readsPHOTOPRISM_OIDC_URI/_CLIENT/_SECRET/_PROVIDER. The override re-maps. Confirmed live withdocker 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.pyon0.0.0.0:9797. - Branch filter:
refs/heads/new(LXC 120 still ownsmain). - HMAC secret:
/etc/mule-deploy/secret(mode 0600). - Deploy script:
/opt/mule-deploy/deploy.sh—git fetch && git reset --hard origin/new, fixpp/{storage,import}ownership to33:10000(PP container uid),docker compose ... up -d --build --force-recreatewith both compose files,systemctl restart mule-vite. - Gitea webhook id 9 on
dtoro/mule-imagepointed athttp://192.168.8.181:9797/deploy.
Push to the new branch on 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 PhotoPrismadminloginPP_DB_PASSWORD— MariaDBphotoprismuserPP_DB_ROOT_PASSWORD— MariaDB rootOIDC_CLIENT_ID/OIDC_CLIENT_SECRET— generated byak shellagainst 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
# 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.