diff --git a/CHANGELOG.md b/CHANGELOG.md index 6972fbaf..9265fedb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -## Unreleased (next: v0.344.0) — the line hold's three edges (R-1261) (2026-10-25 night) — committed, not released +## v0.344.0 (2026-10-26) — the line hold holds every seeder (R-1261); FileBrowser off the app network (R-1281); a set-aside restore is quiet (R-1288); the „later" media restart survives a restart (R-1282); a held app says why (R-1286); the install page offers a kept backup (R-1278) — attended release run + +**Upgrade note:** FileBrowser's rendered compose changes (R-1281), so the file manager restarts once at the update — a +short gap. **MinAgent: 0.158.1** (unchanged). The hold acts only on a box whose agent reports `waiting_for_quiet` (≥ 0.165.0). diff --git a/controller/README.md b/controller/README.md index d51cfe09..86b59a8a 100644 --- a/controller/README.md +++ b/controller/README.md @@ -1046,6 +1046,10 @@ Each app can define rich metadata in `.felhom.yml`: - **A reinstall never writes into an earlier install's backup (v0.343.0, D47):** each install has an `install_id`; a unit of another install is set aside whole (`backups/set-aside//`), listed as a kept copy and restorable; the operator gets one `backup_run_failures` line, leg `unit-set-aside`. `backup/unit_owner.go`. + - **A restore of a set-aside copy is quiet (v0.344.0, R-1288):** the replaced install's own unit is set aside AT the + restore, with no second alarm. **The install page offers a kept backup (v0.344.0, R-1278):** for an app not + installed whose earlier install's backup is kept (`backup.KeptBackupsFor`), an info notice says the new install + starts empty and links the restore page. - **An export that stops its app waits for a running restore (unreleased, R-1253):** it is refused (409, „Egy másik művelet … éppen leállítva tart egy alkalmazást…") while another operation holds the app-stop marker. - **A broken backup block never deletes a second-drive copy (unreleased, R-1252):** the night copy deletes dest @@ -1139,6 +1143,12 @@ cloudflared is only deployed when a tunnel token is configured. **Triggers**: a **FileBrowser admin password (v0.243.0, R-513).** FileBrowser used to accept `admin`/`admin` on every box. After `ensureFileBrowser`, every tick until decided, `Manager.EnsureFileBrowserAdminPassword` (`internal/stacks/filebrowser_password.go`) **v0.327.0 (R-1126, D11 = A): FileBrowser starts OFF `traefik-public`** — on its own network `felhom-filebrowser`, which the controller joins — and is put on `traefik-public` (so `files.` is served) only after the check below has passed for THIS run. Since v0.327.1 it carries no Traefik labels: `files.` is the file route `dynamic/filebrowser.yml` to `http://filebrowser:80` BY NAME (a label route kept the start-time address on the wrong network) (`stacks.ensureFileBrowserPublic`; also run right after `web.SyncFileBrowserMounts` re-creates it). FileBrowser's own admin-password setting cannot replace this: it re-applies at every start (1.5.6 `cmd/user.go:50-57`, measured), which would overwrite the household's own password. The check probes `http://filebrowser:80/api/auth/login` with admin/admin — **once per FileBrowser run since v0.326.0 (R-977)**: the probe repeats whenever FileBrowser's container StartedAt changes, so a re-created data volume that starts on the default again is reset at its first tick; a run on any other password changes nothing: **200** → a generated `password:16` is set through FileBrowser's API (never the `auth.adminPassword` config key — measured to overwrite a hand-set password on every start), verified both ways, and stored AES-encrypted in `settings.json` (`filebrowser_admin_state: generated`); **401** → `operator`, never touched. The FileBrowser app page shows user `admin` and the password behind the R-254 reveal (`POST /apps/filebrowser/initial-credentials/reveal`), or — `operator` — „Ezt a jelszót te állítottad be…" with „Új jelszó beállítása" (v0.320.0, the file browser's CLI with the server stopped; the `auth.adminPassword` key / `FILEBROWSER_ADMIN_PASSWORD` env re-apply on every start and are still never used). **v0.321.0 (R-1101):** in `generated`, the page first asks FileBrowser whether the stored password still logs in (`FileBrowserStoredPasswordRefused`; only a 401/403 counts); refused → „A Fájlkezelőben megváltoztattad a jelszót…" and the same press. The rendered config switches off FileBrowser's own update notice (`server.disableUpdateCheck`) and its „Install app" prompt (`frontend.disablePWAInstall`). +**FileBrowser is off the app network (v0.344.0, R-1281).** FileBrowser sits on its own `felhom-files` network with +traefik (and OnlyOffice while it is linked) and leaves `traefik-public`, so no app container reaches it or its sign-in +header. `ensureFileBrowserPublic` (`internal/stacks/filebrowser_gate.go`) creates the network, joins traefik and the +checked FileBrowser (by container id) and disconnects FileBrowser from `traefik-public`; R-1126's check-then-publish +gate is unchanged. The rendered compose changes, so FileBrowser is re-created once at the upgrade (a short gap). + **Controller routing + the wildcard cert anchor (`wireController` → `RenderControllerRoute`, v0.41.1 / v0.42.1).** filebrowser self-registers with traefik via Docker labels + `traefik-public` membership baked into its compose; the controller can't (it's started by the golden bootstrap *before* `traefik-public` exists, and the v2 `bootstrap.json` carries no domain — that comes from the hub pull). So `EnsureBaseStack` wires the controller **post-pull**: it `docker network connect traefik-public felhom-controller` and writes a traefik file-provider route `dynamic/controller.yml` (`Host(felhom.) → http://felhom-controller:8080`, write-if-changed). When DNS-01 ACME is configured, that route is **also the wildcard-cert anchor**: its router-level `tls.domains: *.` makes traefik **proactively obtain the wildcard `*.` + apex via Cloudflare DNS-01 at startup** (an entrypoint-level `http.tls.domains` does *not* trigger issuance in traefik v3 — only a router-level `tls.domains` does). Every other router then serves that one real wildcard cert by SNI — no per-app `certresolver` labels. This is what lets a LAN client reach the box directly at `*.` with the real cert (the `felhom-agent` split-horizon resolver depends on it). #### Missing Field Injection (`deploy.go`) @@ -2270,6 +2280,9 @@ that folder is never a dead end, and an install never runs into it silently (R-6 - **`generate: password:N:special` (v0.280.0)** — a lower, an upper, a digit and one of `-_.!@#%+=` (calibre-web's policy). - **R-704 (v0.278.0).** A new install (plain or "use my kept data") drops an update or crash-loop hold left by an EARLIER install of the app, and a removal clears both kinds; a restore hold (R-379) stays operator-cleared. +- **R-1286 (v0.344.0).** A held app says WHY: each hold reason (a failed restore, an unhealthy stop by the crash-loop + guard, …) has its own sentence in the start gate, the boot sweep and `--restore-holds` (an unhealthy stop used to + read as a failed restore). - **R-690 (fixed here).** The removed-app restore (R-487) never found a unit on a DATA drive — it asked `GetStackComposePath`, true for every catalog app — and restored with no env. It now asks `isStackDeployed`. - `.felhom.yml` **`after_load: {service, user, command: [...]}`** — one command run with `docker compose exec -T` @@ -3550,7 +3563,8 @@ no key into the household's account). Code: `internal/stacks/after_unclean_boot. Idempotent; keys never stored or logged. Old Sonarr/Radarr installs (`/media/`) are repaired once, no file moved. - **Drive events** (`internal/web/media_events.go`, `/api/storage/media/{status,restart,wire}`): added / removed / returned are recorded on the drive; the storage page and the dashboard show the notice with „Újraindítás most" - (stream check first) and „Később" (03:30), and per drive „restarted since". A returned drive recreates the file + (stream check first) and „Később" (03:30; the time is stored and resumed after a controller restart, v0.344.0, + R-1282), and per drive „restarted since". A returned drive recreates the file browser at once; a dead or removed drive is not bound into it. „Újraindítás most", „Később" and the safe removal recreate every media app EXCEPT one the household stopped (R-721; it reads the new binds at its next start — `MediaAppsToRecreate`, unreleased, next v0.343.0). **Safe removal** with media apps: pause that drive's @@ -3573,13 +3587,17 @@ best 10 s). The watch judges against the higher; the system settings page names upload limit of 50 % of the TESTED line only (D41; no test yet → no limit; never over a household's own limit), and its page says so in one line. State: settings.json `upwatch`. Spike: felhom.eu `audits/SPIKE-upload-watch-2026-10-23.md`. -**A box whose torrent app seeds all day** (unreleased, R-1250; `internal/upwatch/linehold.go`): the agent's test needs 10 +**A box whose torrent app seeds all day** (v0.343.0, R-1250; `internal/upwatch/linehold.go`): the agent's test needs 10 quiet minutes, which such a box never gives. When the agent reports `waiting_for_quiet` (agent ≥ 0.165.0) and qBittorrent or Transmission runs, the 15-minute tick writes `upwatch.line_test_hold` (the app's limit as the app stored it), holds the app at 1 KiB/s (Transmission 1 kB/s), calls `POST /host/line-test/run` (about 55 s) and puts the old limit back — the household's own included; a change made during the test stays. A killed controller puts it back at start (`RecoverHold`, before any rule); the 50 % rule skips while a record exists. Proven on demo-hp with a real seed (felhom.eu `audits/dooplex-alarms-2026-10-24/`). +**v0.344.0 (R-1261):** EVERY running seeder is held, one record per app (`upwatch.line_test_holds`); a record whose app +was removed while held is dropped (it used to stop the 50 % rule for good); qBittorrent's hold names its slot +(`up_limit` / `alt_up_limit` through `app/preferences` — `transfer/uploadLimit` follows the CURRENT speed mode), so a +speed-mode switch mid-test never leaves 1 KiB/s. Proven on a test VM with two seeders (felhom.eu `audits/night-2026-10-25/`). ### 8d. Storage that knows its physical disks — the internal place, partitions (v0.317.0 build A, v0.318.0 build B)