From bd3d55c02479fb03c6b93a14640a55b31e3c4853 Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 1 Aug 2026 02:46:48 +0200 Subject: [PATCH 1/2] docs: document the real-time management UI streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SSE switch in #78 replaced the UI's polling and refresh controls, and the docs never caught up: nothing described how pages stay current, and nothing warned that a buffering reverse proxy breaks it. - Management UI gains a "Live updates" section: the three snapshot streams, the 15s ping, automatic reconnect, and the Live/Reconnecting badge. - Troubleshooting gains an entry for a UI stuck on "Reconnecting…" — proxy buffering, short idle timeouts, unreachable management API. - Home page and page descriptions mention live updates. --- website/guide/management-ui.md | 39 +++++++++++++++++++++++++++++--- website/guide/troubleshooting.md | 24 +++++++++++++++++++- website/index.md | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/website/guide/management-ui.md b/website/guide/management-ui.md index 41d7a15..765e30b 100644 --- a/website/guide/management-ui.md +++ b/website/guide/management-ui.md @@ -1,5 +1,5 @@ --- -description: Manage jack servers, peers, API keys, downloads, catalog requests, and logs from the self-hosted web management console. +description: Manage jack servers, peers, API keys, downloads, catalog requests, and logs from the self-hosted web management console, with live updates over Server-Sent Events. --- # Management UI @@ -11,11 +11,11 @@ instance without hand-editing `config.jsonc`. With the operate jack day to day: - **Overview** — your configured servers and peers, and whether each one - initialized cleanly. + initialized cleanly, with transfer activity updating live. - **Catalog** — browse everything your peers share, and request titles into your library. - **Downloads** — inspect, cancel, retry, and delete in-flight or finished - grabs. + grabs, updating live as they progress. - **Peers** and **Servers** — add, edit, and remove your friends and your Radarr/Sonarr connectors. - **Settings → Downloads** — the whole @@ -60,6 +60,39 @@ separate listener the UI never touches. See [`apps/ui/README.md`](https://github.com/roziscoding/jack/blob/main/apps/ui/README.md) for the full UI configuration reference. +## Live updates + +The UI doesn't poll. Overview, Downloads, and Settings each hold a +[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) +stream open through the BFF, and jack pushes a fresh snapshot the moment +something changes — a transfer's progress, a connector's state, a peer or server +you just edited. There is no refresh interval to tune and no manual refresh +button. + +The streams are plain `GET` endpoints on the management API: + +| Stream | What it pushes | +| --- | --- | +| `/overview/stream` | Connector overview + download state | +| `/downloads/stream` | Every persisted download change | +| `/config/stream` | Peer and server config changes | + +Each sends its current snapshot immediately on connect, then a full snapshot per +change — no deltas to reassemble — plus a `ping` event every 15 seconds so idle +proxies don't drop the connection. The Logs page tails `/logs/stream`, which +works the same way except each event is a single log line rather than a +snapshot. + +The browser reconnects on its own if a stream drops; Overview and Downloads show +a **Live** badge that flips to **Reconnecting…** while it's down. + +**Behind a reverse proxy:** SSE needs response buffering **off** and an idle +timeout **longer than 15 seconds** on whatever sits in front of the UI. jack +sends `X-Accel-Buffering: no` (nginx and compatible proxies honour it) and the +BFF forwards that header, but proxies that buffer regardless — or cut idle +connections early — will leave the UI stuck on **Reconnecting…**. See +[Troubleshooting](/guide/troubleshooting#management-ui-stuck-on-reconnecting). + ## Management API The UI's backing API — the **management API** — is a separate listener on its diff --git a/website/guide/troubleshooting.md b/website/guide/troubleshooting.md index d46c5cd..2a1d1e6 100644 --- a/website/guide/troubleshooting.md +++ b/website/guide/troubleshooting.md @@ -1,5 +1,5 @@ --- -description: Diagnose jack connectivity, Radarr or Sonarr registration, qBittorrent client checks, downloads, imports, permissions, and peer errors. +description: Diagnose jack connectivity, Radarr or Sonarr registration, qBittorrent client checks, downloads, imports, permissions, management UI live updates, and peer errors. --- # Troubleshooting @@ -126,6 +126,28 @@ depends_on: (This needs `healthcheck` blocks on those services — the linuxserver.io images ship with them.) +## Management UI stuck on "Reconnecting…" + +The Overview and Downloads pages show a **Live** badge while their +[event stream](/guide/management-ui#live-updates) is connected. If it sits on +**Reconnecting…** — or flips to it every few seconds — the stream is being +closed or buffered before it reaches the browser. The pages still load; they +just stop updating on their own. + +Three usual causes: + +- **A reverse proxy is buffering the response.** SSE only works if the proxy + streams bytes through. jack sends `X-Accel-Buffering: no` and the UI's BFF + forwards it, which nginx honours; for others turn buffering off explicitly + (`proxy_buffering off;` on nginx, `flush_interval = -1` on Traefik, Caddy's + `reverse_proxy` streams by default). +- **The proxy's idle timeout is too short.** Streams are idle between changes, + with only a `ping` every 15 seconds. Give the UI route a read/idle timeout + well above that — 60 seconds or more. +- **The management API is down or unreachable.** The BFF answers `503` when it + can't reach jack. Check `docker compose ps` and the backend logs for + `Management API listening`. + ## Seeing what jack is doing Set `LOG_LEVEL=trace` to log every HTTP request (method, path, status, diff --git a/website/index.md b/website/index.md index 2ae197e..73d5cf1 100644 --- a/website/index.md +++ b/website/index.md @@ -34,5 +34,5 @@ features: details: No trackers, no swarm — files transfer over plain, authenticated HTTP. - icon: 🖥️ title: Web management console - details: Manage servers, peers, API keys, and downloads without hand-editing config files. + details: Manage servers, peers, API keys, and downloads without hand-editing config files — transfers and connector state update live. --- From 6934491a8332a245f66309c7bf88557588f1c3f0 Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 1 Aug 2026 02:49:24 +0200 Subject: [PATCH 2/2] docs: cover unlinkImportedFiles in the guide, not just the reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.3.0 added downloads.unlinkImportedFiles and put the whole downloads block in the UI, but the guide never said jack's copy of a download outlives the import. A user following Getting started ends up with a completed folder that grows forever and no idea there's a switch for it — the option existed only in the config reference, which you read after you already have the problem. - How it works: the download flow now ends where it actually ends, with an "After the import" section on the two on-disk outcomes (hardlink vs copy), what the switch does, and why it's narrow. - Getting started: surfaces the choice while you're writing the downloads block. - Troubleshooting: "The completed folder keeps growing". - Management UI: a Settings -> Downloads section — defaults as placeholders, clearing a field restores the default, restart semantics. --- website/guide/getting-started.md | 26 +++++++++++++++++++ website/guide/how-it-works.md | 43 +++++++++++++++++++++++++++++++- website/guide/management-ui.md | 22 ++++++++++++++++ website/guide/troubleshooting.md | 22 ++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) diff --git a/website/guide/getting-started.md b/website/guide/getting-started.md index e7aa1e6..c035443 100644 --- a/website/guide/getting-started.md +++ b/website/guide/getting-started.md @@ -95,6 +95,32 @@ step 4: } ``` +Worth deciding now: **jack's copy of a download outlives the import.** Radarr +and Sonarr read the finished file out of this folder and write your library +copy, but nothing cleans up jack's — so the folder grows with every grab. Add +`unlinkImportedFiles` to have jack drop its copy once the import is confirmed: + +```jsonc +{ + "downloads": { + "completedPath": "/data/torrents/completed", + "unlinkImportedFiles": true + } +} +``` + +It's off by default because it deletes files, and it's a plain `unlink` of that +one file — if your *arr hardlinked into the library, the library keeps the data; +if it copied, you get the space back. You can also flip it later from the +management UI without a restart. See +[After the import](/guide/how-it-works#after-the-import) for the full picture, +and [`unlinkImportedFiles`](/reference/configuration#downloads-unlinkimportedfiles) +for the exact guarantees. + +Every other key in this block is an optional tuning knob with a sensible +default — see [`downloads`](/reference/configuration#downloads). All of them are +editable later from the management UI, so you don't have to get them right here. + ### `servers` One entry per Radarr/Sonarr. Set each server's URL, and reference the API keys diff --git a/website/guide/how-it-works.md b/website/guide/how-it-works.md index 7959e7c..ab6d3ac 100644 --- a/website/guide/how-it-works.md +++ b/website/guide/how-it-works.md @@ -1,5 +1,5 @@ --- -description: Learn how jack connects Radarr and Sonarr through Torznab and qBittorrent-compatible APIs to search and transfer media between trusted peers. +description: Learn how jack connects Radarr and Sonarr through Torznab and qBittorrent-compatible APIs to search, transfer, and import media between trusted peers, and what happens to downloaded files afterwards. --- # How it works @@ -64,6 +64,8 @@ sequenceDiagram ARR->>JACK: GET /api/v2/torrents/info (poll progress) JACK-->>ARR: completed → content_path = finished file Note over ARR: scans completed folder, imports into library + JACK->>ARR: poll history — did the import land? + Note over JACK: confirmed → optionally unlink jack's copy ``` 1. You grab a release. Your *arr's download client is the **qBittorrent** @@ -78,6 +80,45 @@ sequenceDiagram 5. *arr polls jack's `/api/v2/torrents/info` for progress; once jack reports the torrent complete, *arr scans the completed folder and imports the file into your library, renamed and tracked. +6. jack watches that *arr's history until the import is confirmed, then — if + you've turned it on — removes its own copy from the completed folder. See + [After the import](#after-the-import) below. + +### After the import + +Importing doesn't consume the file in `completedPath`. Radarr and Sonarr read +it and write your library copy; jack's copy stays where it was, and jack has no +further use for it — a finished download is never re-served to peers or +re-imported. + +That leaves you with two on-disk outcomes, and which one you get is decided by +your *arr, not by jack: + +- **Your *arr hardlinked** (its default when the completed folder and the + library live on the same filesystem) — the library entry and jack's copy are + two names for the same bytes. Nothing is duplicated, but the completed folder + keeps filling with entries you'll never look at. +- **Your *arr copied or moved** (different filesystems, or hardlinks disabled) — + the library now holds its own bytes, and jack's copy is a genuine second copy + of every file you've ever grabbed. Left alone, `completedPath` grows without + bound. + +[`downloads.unlinkImportedFiles`](/reference/configuration#downloads-unlinkimportedfiles) +is the switch for this. Turn it on and jack unlinks its copy as soon as the +import is confirmed: in the hardlink case that just drops the redundant +directory entry and your library is untouched, and in the copy case it frees the +space. It's **off by default**, so an instance you set up and forget will +accumulate. + +The unlink is deliberately narrow. It runs only on an import jack has confirmed +— the destination *arr reports the download in its history, or the manual import +jack pushed reports `completed` — so a queued, in-progress, or failed import +keeps its file, and so does a file another download still needs. jack removes +that one file and nothing else; it never touches your library. + +Flip it from **Settings → Downloads** in the [management +UI](/guide/management-ui) or set it in `config.jsonc`. It's the one key in the +`downloads` block that applies without a restart. ## 3. Serving — being a peer to others diff --git a/website/guide/management-ui.md b/website/guide/management-ui.md index 765e30b..73fbcaa 100644 --- a/website/guide/management-ui.md +++ b/website/guide/management-ui.md @@ -28,6 +28,28 @@ operate jack day to day: Don't want it? Delete the `jack-ui` service and the backend's `JACK_MANAGEMENT_KEY` line to run jack headless. +## Settings → Downloads + +The whole [`downloads`](/reference/configuration#downloads) block is editable +here, so there's no reason to hand-edit `config.jsonc` for it: + +- **Completed folder**, **simultaneous transfers**, and the + **[drop imported files](/reference/configuration#downloads-unlinkimportedfiles)** + switch — the settings you actually choose — sit at the top. +- **Transfer retries** and **import watcher** tuning are collapsed below, each + labelled with how many of its values you've moved off jack's defaults. + +Every field's placeholder is jack's default, and **clearing a field means "use +the default"** — jack drops the key from the config file rather than storing a +blank. `completedPath` is the exception: it has no default, so it can't be +cleared. If your config has no `downloads` block at all, the page offers to set +one up. + +One **Save** covers the form and only lights up once something changed; +**Revert** discards your edits. `unlinkImportedFiles` takes effect the moment +you save — **every other key here is read at startup, so restart jack** to apply +it. + ## Access control The UI supports two auth modes, depending on where the management key comes diff --git a/website/guide/troubleshooting.md b/website/guide/troubleshooting.md index 2a1d1e6..6af143d 100644 --- a/website/guide/troubleshooting.md +++ b/website/guide/troubleshooting.md @@ -126,6 +126,28 @@ depends_on: (This needs `healthcheck` blocks on those services — the linuxserver.io images ship with them.) +## The completed folder keeps growing + +Everything imports fine, but `downloads.completedPath` never shrinks — every +title you've ever grabbed is still sitting there. + +That's the default behaviour, not a bug. Importing doesn't consume the file: +Radarr/Sonarr read it and write your library copy, and jack's copy stays put. +Whether that costs you real space depends on your *arr — a hardlinked import +shares bytes with the library, a copied one doesn't — but either way the folder +fills up. + +**Fix:** turn on +[`downloads.unlinkImportedFiles`](/reference/configuration#downloads-unlinkimportedfiles), +in `config.jsonc` or from **Settings → Downloads** in the management UI. It +applies immediately, with no restart. jack then unlinks its copy as each import +is confirmed — safely: only on a confirmed import, only that one file, never +your library. Details in [After the import](/guide/how-it-works#after-the-import). + +This only affects downloads imported **after** you enable it. Files already in +the folder are yours to clear out — deleting the download rows from the +management UI's Downloads page removes their files too. + ## Management UI stuck on "Reconnecting…" The Overview and Downloads pages show a **Live** badge while their