168 lines
7.8 KiB
Markdown
168 lines
7.8 KiB
Markdown
# 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.
|