From e7f096ac23cee32ccaff29d9ca4737da2456f1ce Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Sat, 18 Jul 2026 16:05:06 -0400 Subject: [PATCH] Support Immich URL/API key via docker-compose env vars docker-compose.yml is tracked in a repo meant for publishing, so it can't hold a real API key. Renamed it to docker-compose.yml.example (placeholder values, safe to commit) and gitignored the real docker-compose.yml -- deploying is now "cp the example, fill in real values, docker compose up", no .env file needed. config.load() now reads IMMICH_URL/IMMICH_API_KEY from the environment and applies them on top of whatever's in config.json, so setting them in the compose file's environment: block takes effect without ever touching the web UI. Env vars always win over the UI-saved values when both are present -- verified they survive a save() with different UI-entered values still in place. --- .gitignore | 3 ++ server/README.md | 28 +++++++++++++------ server/app/config.py | 17 +++++++++-- ...compose.yml => docker-compose.yml.example} | 2 ++ 4 files changed, 40 insertions(+), 10 deletions(-) rename server/{docker-compose.yml => docker-compose.yml.example} (72%) diff --git a/.gitignore b/.gitignore index c5c707f..37918ed 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ server/**/__pycache__/ server/.venv/ server/*.egg-info/ server/data/ +# Real deploy config, copied from docker-compose.yml.example -- holds the +# Immich API key, must never be committed. +server/docker-compose.yml .env # Editor / OS cruft diff --git a/server/README.md b/server/README.md index c67fca2..2e7f4b8 100644 --- a/server/README.md +++ b/server/README.md @@ -10,13 +10,22 @@ algorithm itself -- it just streams the response straight to the panel. 1. **Get an Immich API key**: in Immich, go to Account Settings -> API Keys -> New API Key. Read-only access to albums/assets is enough. -2. **Run the server**: +2. **Copy the compose file and fill in your Immich details**: + ``` + cp docker-compose.yml.example docker-compose.yml + ``` + Edit `docker-compose.yml` and set `IMMICH_URL`/`IMMICH_API_KEY` under + `environment:`. `docker-compose.yml` is gitignored (it'll hold your real + API key) -- `docker-compose.yml.example` is the one that's committed. +3. **Run the server**: ``` docker compose up -d ``` -3. Open `http://:8420/` in a browser, enter your Immich URL - and API key, click **Load Albums**, pick one, and **Save**. -4. On the ESP32's captive portal setup form, set the **Tools Server** field +4. Open `http://:8420/` in a browser, click **Load Albums**, + pick one, and **Save**. (The Immich URL/API key fields will already be + populated from the environment; changing them in the UI has no effect + as long as the env vars are set -- they win on every load.) +5. On the ESP32's captive portal setup form, set the **Tools Server** field to `:8420`. ## Endpoints @@ -31,8 +40,10 @@ algorithm itself -- it just streams the response straight to the panel. ## Notes -- Config (including the Immich API key) is stored in `./data/config.json` - on the host via the compose volume mount. +- Album/order/refresh-interval/etc. are stored in `./data/config.json` on + the host via the compose volume mount. Immich URL/API key are too if set + via the web UI, but `IMMICH_URL`/`IMMICH_API_KEY` env vars (see Setup + above) always take precedence when present. - `/frame/image` isn't authenticated yet. That's fine on a trusted home LAN for now, but worth revisiting once the ESP32 side is wired up to send a shared device token. @@ -47,8 +58,9 @@ Every push to `main` that touches `server/` triggers a Gitea Actions workflow (`.gitea/workflows/server-docker-build.yml`) that builds this image and pushes it to this repo's Gitea Container Registry at `git.thumeit.com/tfaour/espresso-frame-server`. `docker-compose.yml` -already points at that image, so a deploy host doesn't need this repo's -build context at all -- just the compose file: +(copied from `docker-compose.yml.example`, see Setup above) already +points at that image, so a deploy host doesn't need this repo's build +context at all -- just the compose file: ``` docker compose pull diff --git a/server/app/config.py b/server/app/config.py index 37bbe17..a1dfd04 100644 --- a/server/app/config.py +++ b/server/app/config.py @@ -28,8 +28,21 @@ class FrameConfig(BaseModel): def load() -> FrameConfig: with _lock: if not CONFIG_PATH.exists(): - return FrameConfig() - return FrameConfig(**json.loads(CONFIG_PATH.read_text())) + cfg = FrameConfig() + else: + cfg = FrameConfig(**json.loads(CONFIG_PATH.read_text())) + + # IMMICH_URL/IMMICH_API_KEY set in the environment (e.g. docker-compose.yml, + # see docker-compose.yml.example) take precedence over whatever's saved + # in CONFIG_PATH, so credentials never need to go through the web UI. + env_url = os.environ.get("IMMICH_URL") + env_key = os.environ.get("IMMICH_API_KEY") + if env_url: + cfg.immich_url = env_url + if env_key: + cfg.immich_api_key = env_key + + return cfg def save(cfg: FrameConfig) -> None: diff --git a/server/docker-compose.yml b/server/docker-compose.yml.example similarity index 72% rename from server/docker-compose.yml rename to server/docker-compose.yml.example index ff939a9..acfc03b 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml.example @@ -8,4 +8,6 @@ services: - ./data:/data environment: - CONFIG_PATH=/data/config.json + - IMMICH_URL=http://your-immich-host:2283 + - IMMICH_API_KEY=your-immich-api-key-here restart: unless-stopped