From 600a48da936154e25c7db2db691d81d82c82e308 Mon Sep 17 00:00:00 2001 From: Barely Removable Date: Wed, 26 Aug 2026 09:34:46 -0700 Subject: [PATCH] Persist settings.json across deploys via a data volume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit made Settings-saved API keys override .env, but the key still never survived: settings.json was written to the container filesystem, and every deploy runs `up -d --build`, which recreates the container and discards it. Saving a rotated SABnzbd key and restarting therefore still fell back to the stale .env value. SETTINGS_FILE is now configurable and pointed at /data/settings.json on a ./data bind mount, so runtime settings live on the host. The deploy rsync also excludes data/ — it runs --delete and would otherwise wipe the directory for not being in the repo, reintroducing the same bug. This affected everything the Settings page writes, not just API keys: timezone and a UI-changed login password were being reverted on every deploy too. Co-Authored-By: Claude Opus 5 --- .forgejo/workflows/deploy.yml | 6 ++++-- CLAUDE.md | 17 +++++++++++++++-- app.py | 7 ++++++- docker-compose.yml | 6 ++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 2f77155..8f6c78d 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -17,13 +17,15 @@ jobs: # at /mnt/user/appdata/greendale on the host, bind-mounted into this # container at the same path (see runner container.options) so # `docker compose`'s relative paths resolve against the real host - # directory rather than an ephemeral one. `.env` is server-only and - # must survive every deploy. + # directory rather than an ephemeral one. `.env` and `data/` are + # server-only state and must survive every deploy — without the + # excludes, --delete removes them because they aren't in the repo. - name: Sync source to deploy path run: | rsync -a --delete \ --exclude='.git' \ --exclude='.env' \ + --exclude='data' \ ./ /mnt/user/appdata/greendale/ - name: Rebuild and restart diff --git a/CLAUDE.md b/CLAUDE.md index 9783b39..6793b43 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,5 +89,18 @@ pipeline mechanics. Push to `main` on the `forgejo` remote and `LOGIN_PASSWORD` — this project was already doing this correctly before the Forgejo migration (compose file uses `${VAR}` substitution from `.env`, no hardcoded secrets), unlike `n64-tracker` which needed fixing. -`settings.json` (timezone, changed login password) also persists -server-side outside git. +Runtime settings written by the Settings page (API-key overrides, +timezone, changed login password) live in `settings.json` at +`SETTINGS_FILE`, which the compose file points at `/data/settings.json` +on the `./data` bind mount — i.e. `/mnt/user/appdata/greendale/data`. +That mount is load-bearing: without it the file sits on the container +filesystem, and every `up -d --build` recreates the container and +silently discards it. `data/` is likewise excluded from the deploy +rsync, which runs `--delete` and would otherwise remove it for not +being in the repo. + +A key saved through the Settings page overrides the corresponding +`.env` value at startup (see the block after the `*_API_KEY` reads in +`app.py`); `.env` remains the source of truth for anything never +overridden there. Both paths need a container restart to take effect, +since the keys are read once at import. diff --git a/app.py b/app.py index b2a8399..2f8f1ae 100644 --- a/app.py +++ b/app.py @@ -15,7 +15,11 @@ from flask import Flask, render_template, request, jsonify, redirect, url_for, s # Suppress SSL warnings for Unraid's self-signed certificate urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "settings.json") +# Must point at a path that survives `docker compose up --build` — the +# container filesystem does not. See the data volume in docker-compose.yml. +SETTINGS_FILE = os.environ.get( + "SETTINGS_FILE", os.path.join(os.path.dirname(__file__), "settings.json") +) def load_settings(): try: @@ -27,6 +31,7 @@ def load_settings(): def save_settings(data): existing = load_settings() existing.update(data) + os.makedirs(os.path.dirname(SETTINGS_FILE) or ".", exist_ok=True) with open(SETTINGS_FILE, "w") as f: json.dump(existing, f, indent=2) diff --git a/docker-compose.yml b/docker-compose.yml index 38a0e41..c7f7965 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,11 +4,17 @@ services: container_name: arr-summary ports: - "${HOST_PORT:-5055}:5000" + volumes: + # Runtime settings (API keys saved via the Settings page, timezone, + # changed login password) must outlive `up -d --build`, which + # recreates the container and discards its filesystem. + - ./data:/data environment: # ── Login / proxy ────────────────────────────────────── - LOGIN_PASSWORD=${LOGIN_PASSWORD} - SECRET_KEY=${SECRET_KEY} - SCRIPT_NAME=${SCRIPT_NAME:-} + - SETTINGS_FILE=/data/settings.json # ── Media services ───────────────────────────────────── - ARR_HOST=${ARR_HOST} - SONARR_API_KEY=${SONARR_API_KEY}