# mule-image Self-hosted photo management built on top of [PhotoPrism][pp]. A SvelteKit frontend ([`web/`](web/)) plus a small Go service ([`sidecar/`](sidecar/)) fill in the keyboard-driven UI and the file/folder/mark endpoints PhotoPrism's REST API does not expose. PhotoPrism itself handles indexing, originals, thumbnails, and the database; we never re-implement those. ## Architecture ```text ┌──────────────────┐ /api/v1/* ┌──────────────┐ │ SvelteKit web/ │ ───────────────▶ │ photoprism │ ──▶ mariadb │ (Vite : 5173) │ /api/sidecar/* │ :2342 │ │ │ ─────────┐ └──────────────┘ └──────────────────┘ ▼ ┌──────────────┐ │ sidecar │ ──▶ mariadb (mule_sidecar.*) │ :8000 │ ──▶ originals FS (rename / folders / dups) └──────────────┘ ``` Three compose services — `mariadb`, `photoprism`, `sidecar` — plus the SvelteKit `web/` app served separately. PhotoPrism's port `2342` is **bound to `127.0.0.1` only**; it isn't a user-facing surface. The SvelteKit app is. What the sidecar adds on top of PhotoPrism (full list in [`sidecar/README.md`](sidecar/README.md)): - Per-photo marks (rating + color) persisted to `mule_sidecar.marks` - File rename + folder create/rename/delete with PhotoPrism reindex - Heap (album) → folder conversion - Perceptual-hash duplicate scan + archive ## Quick start ```bash cp .env.example .env # edit .env: set PHOTO_DIRS to the host path holding your library # rotate PP_ADMIN_PASSWORD, PP_DB_PASSWORD, PP_DB_ROOT_PASSWORD # before any non-local deployment. podman-compose --env-file .env \ -f docker-compose.yml \ -f docker-compose.podman.yml \ up -d ``` Then serve the frontend. For local use the simplest path is the Vite dev server: ```bash cd web npm install npm run dev # open http://localhost:5173 ``` For a static deployment, `npm run build` produces a bundle under `web/build/` that any static file host (nginx, Caddy, GitHub Pages-style) can serve. Reverse-proxy `/api/v1/*` to `http://127.0.0.1:2342` and `/api/sidecar/*` to `http://127.0.0.1:8000`. PhotoPrism's own UI is still reachable from the host at `http://127.0.0.1:2342` if you need admin features (user management, settings) — set up an SSH tunnel from your laptop if the server is remote. ## Configuration All knobs live in [`.env.example`](.env.example). The required ones: | Variable | Notes | |----------------------|-----------------------------------------------------------------------------------------------| | `PHOTO_DIRS` | Host path mounted at `/photoprism/originals`. The library. | | `PP_ADMIN_PASSWORD` | First-boot admin password. Rotate. | | `PP_DB_PASSWORD` | MariaDB password for the `photoprism` user. Rotate. | | `PP_DB_ROOT_PASSWORD`| MariaDB root password. Rotate. | | `PP_UID` / `PP_GID` | Host UID/GID that owns `PHOTO_DIRS`. PhotoPrism + sidecar drop to this user inside. | | `PP_PORT` | Loopback host port for PhotoPrism (default `2342`). | | `PP_ORIGINALS_MODE` | `rw` (default) or `ro` — see [Read-only libraries](#read-only-libraries). | | `SIDECAR_PORT` | Loopback host port for the sidecar (default `8000`). | Sidecar-specific env (DB DSN, `USER_BASEPATHS`, etc.) is documented in [`sidecar/README.md`](sidecar/README.md). ## Read-only libraries The default originals mount is `:rw` because file operations (rename, folder mutations, duplicate archive, heap convert) need to mutate the filesystem. To run against a read-only archive, set `PP_ORIGINALS_MODE=ro` in `.env`. Browsing, marks, ratings, and color labels still work; the following sidecar endpoints return an OS error: - `POST /api/sidecar/files/:uid/rename` - `POST /api/sidecar/folders` / `:rel/rename` / `DELETE /:rel` - `POST /api/sidecar/albums/:uid/convert` - `POST /api/sidecar/duplicates/archive` PhotoPrism's `PHOTOPRISM_READONLY` is controlled separately by `PP_READONLY` and gates its own backwrite / import paths. ## Dev iteration loop For fast iteration on the sidecar without rebuilding its image on every change, run it as a host process — bring up just `mariadb` and `photoprism` from compose, then build and run the Go binary locally. Full instructions in [`sidecar/README.md`](sidecar/README.md#dev-iteration-loop-host-build). ## Layout ```text . ├── docker-compose.yml base stack: mariadb + photoprism + sidecar ├── docker-compose.podman.yml rootless-podman overlay (keep-id mapping) ├── .env.example required env vars (copy to .env) ├── mariadb/init/ first-boot SQL: creates mule_sidecar DB + user ├── pp/ PhotoPrism bind-mounted state (storage, import) ├── sidecar/ Go service — see sidecar/README.md └── web/ SvelteKit frontend ``` [pp]: https://photoprism.app/