diff --git a/AGENTS.md b/AGENTS.md index b4d93c2..c085bd0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -114,3 +114,44 @@ bun --hot ./index.ts ``` For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. + +## Writing + +Applies to the docs site under `website/`, PR descriptions, and commit bodies. + +### Structure + +- Lead with the answer, the definition, or the change. No preamble, no restating the question, no announcing the shape of what follows. +- Stop when the point is made. No tie-backs, no per-section recaps, no closing summary. +- Don't count items before listing them ("three things", "for two reasons") unless the count is the point ("both calls fail", "all four migrations ran"). +- Cut what's obvious from context. "The link stops working" needs no "for whoever holds it". +- Make each point once, in the place it belongs. A clarification that dangles at the end of a block is misplaced, not merely wordy: move it onto the item it qualifies, or promote it to a callout. +- Don't defend a point nobody contested. +- Documentation is not a changelog. Describe the current behaviour. + +### Constructions to avoid + +- Plainest word that carries the meaning. Ornate vocabulary is a problem when it inflates something ordinary, not on sight: "JS ecosystem" is the right word, "a robust ecosystem of tooling" where "several tools" is meant is not. Same for analogies and figurative language, which are worth it only when they explain something a plain description can't. +- Adverbs used to lend weight (quietly, deeply, fundamentally, remarkably, arguably) rather than to say something true about degree. +- Negative parallelism: "it's not X, it's Y", "not just X but Y", "the question isn't A, it's B". +- Em dashes as dramatic pauses or pivots. A comma, a colon, or a second sentence usually reads better. +- The prose/list hybrid: a sentence that trails into an enumeration ("rejected whole: bad prefix, corrupt payload, a non-HTTP URL, or a bad header"). Write a real list or write prose. +- Cute vagueness standing in for the fact. "The endpoint does what the button does" says nothing; say what it does. +- Quotable one-liners that carry no information ("a quick link is only as live as the key inside it"). +- Fragment paragraphs for emphasis, and repeated sentence openings. +- Rule-of-three phrasing. Not one per page, not one per section: zero. It's the loudest AI tell in the text. +- Invented compound labels (`the supervision paradox`, `credential creep`) presented as established terms. +- Filler and teaching voice: "it's worth noting", "importantly", "let's unpack", "think of it as". +- "serves as" / "represents" where "is" works. "Despite these challenges, ..." dismissals. Vague attributions to experts or reports. +- Wh-word headings ("What's inside a link" -> "Link format"). Title case headings. + +### Markdown conventions + +- **Bold leads on definition bullets** are good, keep them. A bulleted field list beats a table unless the content is genuinely tabular and each cell is short. +- UI references (breadcrumbs, buttons, form and section names) get bold+italic: `***Settings -> Downloads***`, `***Add peer***`. Plain italic stays free for emphasis. +- One idea per list item. Split compound items rather than joining them with "and". +- When an item has its own rules, nest a sub-list instead of packing them into the sentence. +- Callout titles state the fact plainly: "Internal and external URLs are not the same", not something clever. +- Link an identifier to its canonical reference page whenever the docs generate one. Management API routes have a per-operation page at `/reference/management-api/`; linking the overview instead makes the reader hunt. +- ASCII punctuation in prose: `->`, straight quotes, `...`. Mermaid blocks are the exception and keep `→`, since `->` collides with mermaid's own arrow syntax. +- Wrap prose at 80 columns, and keep `jack` lowercase in running text. diff --git a/website/guide/getting-started.md b/website/guide/getting-started.md index c035443..0761b81 100644 --- a/website/guide/getting-started.md +++ b/website/guide/getting-started.md @@ -7,7 +7,7 @@ description: Install jack with Docker Compose, connect Radarr or Sonarr, configu This guide takes you from nothing to a running jack instance: configured against your Radarr/Sonarr, registered there as an indexer and download client, with the management UI up and ready to connect to friends. Docker -Compose is the recommended way to self-host jack — two images are published to +Compose is the recommended way to self-host jack. Two images are published to GitHub Container Registry on every push to `main`, so there's nothing to build. ## Before you start @@ -15,13 +15,13 @@ GitHub Container Registry on every push to `main`, so there's nothing to build. You'll need: - **Docker with Compose.** -- **Radarr and/or Sonarr already running**, and their API keys (Settings → - General → API Key). -- **To know your media paths** — the paths *inside* your Radarr/Sonarr +- **Radarr and/or Sonarr already running**, and their API keys + (***Settings -> General -> API Key***). +- **To know your media paths**: the paths *inside* your Radarr/Sonarr containers where media lives (e.g. `/movies`, `/tv`, or `/data/media`). jack streams files using those exact paths, so you'll mirror them. -If you haven't yet, skim [What is jack?](/guide/what-is-jack) — the rest of +If you haven't yet, skim [What is jack?](/guide/what-is-jack). The rest of this guide assumes you know what a *source*, *destination*, and *peer* are. ## 1. Create the project files @@ -37,9 +37,9 @@ jack/ └── config.jsonc ``` -Start from the examples below — they're working templates, and the -rest of this guide assumes them as the starting point: the next steps walk you -through adapting each part to your setup. +Start from the examples below. They're working templates, and the rest of this +guide assumes them as the starting point; the next steps walk you through +adapting each part to your setup. ::: code-group @@ -68,7 +68,7 @@ these are the parts that matter: ### `jack.internalUrl` -The address **your own Radarr/Sonarr** will use to reach jack — it's what jack +The address **your own Radarr/Sonarr** will use to reach jack, and what jack registers as the indexer and download-client URL. If your *arr apps run on the same Docker network as jack, the default `http://jack:5225` (the container name) is correct. If they run elsewhere, use the host's IP or domain: @@ -84,7 +84,7 @@ name) is correct. If they run elsewhere, use the host's IP or domain: ### `downloads` Where jack writes finished downloads (path inside the container). Keep the -default unless you have a reason not to — you'll mount a host folder here in +default unless you have a reason not to; you'll mount a host folder here in step 4: ```jsonc @@ -97,7 +97,7 @@ 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 +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 @@ -109,22 +109,23 @@ copy, but nothing cleans up jack's — so the folder grows with every grab. Add } ``` -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 +It's off by default because it deletes files. The operation is 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. +default, documented under [`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 -from the environment — you'll put the actual values in `.env` in the next step, +from the environment. You'll put the actual values in `.env` in the next step, and the compose file forwards them into the container: ```jsonc @@ -148,27 +149,27 @@ and the compose file forwards them into the container: By default each server is both a **source** (share its library with friends) and a **destination** (search your friends' libraries from its UI). Set -`"source": false` or `"destination": false` to opt out of either — see the -[configuration reference](/reference/configuration#servers) for these and the +`"source": false` or `"destination": false` to opt out of either. The +[configuration reference](/reference/configuration#servers) covers these and the other per-server options. ::: info You can also inline a key as a plain string (`"apiKey": "abc123..."`), but -it's not recommended — the config file then holds live secrets. See +it's not recommended, since the config file then holds live secrets. See [`ConfigSecret`](/reference/configuration#configsecret) for all the forms, including reading from a secret file. ::: ### `peers` -Leave it empty (or delete it) for now — you'll add friends through the +Leave it empty (or delete it) for now; you'll add friends through the management UI in step 6. ## 3. Fill in `.env` Open `.env` and fill in the values: -- **`JACK_MANAGEMENT_KEY`** — the secret gating the management API (and the +- **`JACK_MANAGEMENT_KEY`**: the secret gating the management API (and the UI's access to it). Both containers read this same variable: the backend requires it on every management request, and the UI injects it, so they always match. Generate one: @@ -177,25 +178,25 @@ Open `.env` and fill in the values: openssl rand -base64 32 ``` -- **`RADARR_API_KEY` / `SONARR_API_KEY`** — the *arr API keys your - `config.jsonc` references (Settings → General → API Key). +- **`RADARR_API_KEY` / `SONARR_API_KEY`**: the *arr API keys your + `config.jsonc` references (***Settings -> General -> API Key***). ## 4. Line up the mounts The compose file mounts three host paths into jack. Two of them **must mirror -your *arr containers** — this is the part people get wrong, so take a minute +your *arr containers**, which is the part people get wrong, so take a minute here: | Mount | Purpose | | --- | --- | -| `./config` → `/config` | Config, database, and logs | -| `${MEDIA_PATH:-./data/media}` → `/data/media` | Your media, so jack can stream it to peers | -| `${TORRENTS_PATH:-./data/torrents}` → `/data/torrents` | Download path | +| `./config` -> `/config` | Config, database, and logs | +| `${MEDIA_PATH:-./data/media}` -> `/data/media` | Your media, so jack can stream it to peers | +| `${TORRENTS_PATH:-./data/torrents}` -> `/data/torrents` | Download path | ### Media jack streams files to peers using the absolute path each *arr -stores for the file — the path *inside the Radarr/Sonarr container*. Mount +stores for the file, the path *inside the Radarr/Sonarr container*. Mount your media into jack at that **same path**. The `/data/media` target in the compose file is a placeholder: if your Radarr sees movies at `/movies` and Sonarr sees shows at `/tv`, replace it with one mount per path: @@ -208,10 +209,10 @@ volumes: ### Download path -jack's downloads live under this mount — in particular, finished files are -written to the literal `downloads.completedPath`, and your *arr imports them -by resolving that same path in *its own* filesystem. So mount the same host -folder into jack **and** into Radarr/Sonarr at the same container path: +jack's downloads live under this mount. Finished files are written to the +literal `downloads.completedPath`, and your *arr imports them by resolving that +same path in *its own* filesystem, so mount the same host folder into jack +**and** into Radarr/Sonarr at the same container path: ```yaml # jack (already in the compose file) @@ -225,15 +226,15 @@ volumes: - ./data/torrents:/data/torrents ``` -Use a dedicated folder — don't point it at a folder another download client -already writes to. jack runs as uid/gid 1000 (matching the linuxserver.io -defaults), so make sure the folder is writable by that user. +Use a dedicated folder, not one another download client already writes to. jack +runs as uid/gid 1000 (matching the linuxserver.io defaults), so make sure the +folder is writable by that user. ### Networking If Radarr/Sonarr run in their own compose network, uncomment -the `networks:` block at the bottom of the compose file so jack joins it — -otherwise container names like `http://radarr:7878` and `http://jack:5225` +the `networks:` block at the bottom of the compose file so jack joins it. +Without that, container names like `http://radarr:7878` and `http://jack:5225` won't resolve. Optionally also uncomment `depends_on` so jack starts after your *arr apps are healthy and registration succeeds on first boot. @@ -246,9 +247,9 @@ docker compose logs -f jack In the logs you should see: -- `Server listening` — jack is up. +- `Server listening`: jack is up. - `Registered Jack as Torznab indexer` and `Registered Jack as qBittorrent - download client` — once per destination server. + download client`: once per destination server. Verify from the outside: @@ -262,23 +263,23 @@ reports container health automatically.) Then open the management UI at **http://localhost:3000**. The Overview page shows your servers and whether each connector initialized cleanly. In -Radarr/Sonarr, you'll find a new **Jack** indexer under Settings → Indexers -and a **Jack** download client under Settings → Download Clients — both tests -should pass. +Radarr/Sonarr, you'll find a new **Jack** indexer under ***Settings -> +Indexers*** and a **Jack** download client under ***Settings -> Download +Clients***. Both tests should pass. If something's off, the [troubleshooting guide](/guide/troubleshooting) covers the common failures. ## 6. Connect with a friend -jack is useful once you're peered with someone (they run jack too — send them +jack is useful once you're peered with someone who also runs jack (send them this page). Peering is symmetric and takes one exchange in each direction: -1. In the management UI, go to **API keys** and issue a key named after your +1. In the management UI, go to ***API keys*** and issue a key named after your friend. Send them that key plus the URL where your peer API is reachable - from the internet (a reverse-proxied `https://` address — not - `jack.internalUrl`). -2. They do the same for you, and you add them under **Peers** in your UI with + from the internet: a reverse-proxied `https://` address, not + `jack.internalUrl`. +2. They do the same for you, and you add them under ***Peers*** in your UI with the URL and key they sent. See [API keys & peering](/guide/peering) for how the keys are scoped and why @@ -286,18 +287,18 @@ each peer gets their own. ## 7. Use it -That's it — from here everything happens in your normal *arr workflow. Search -for a movie or episode as you always would: releases your friends have show up -as **Jack** indexer results, and grabbing one pulls the file straight from -their server into your library. +From here everything happens in your normal *arr workflow. Search for a movie +or episode as you always would: releases your friends have show up as **Jack** +indexer results, and grabbing one pulls the file straight from their server +into your library. ## Next steps -- [How it works](/guide/how-it-works) — what actually happens on search and +- [How it works](/guide/how-it-works): what actually happens on search and grab. -- [Management UI](/guide/management-ui) — day-to-day operation and access +- [Management UI](/guide/management-ui): day-to-day operation and access control. -- [Configuration reference](/reference/configuration) — every key, including +- [Configuration reference](/reference/configuration): every key, including the download tuning knobs. -- [Running without Docker](/guide/running-without-docker) — for bare-metal +- [Running without Docker](/guide/running-without-docker): for bare-metal setups. diff --git a/website/guide/how-it-works.md b/website/guide/how-it-works.md index ab6d3ac..07aa0d3 100644 --- a/website/guide/how-it-works.md +++ b/website/guide/how-it-works.md @@ -5,8 +5,8 @@ description: Learn how jack connects Radarr and Sonarr through Torznab and qBitt # How it works There are two flows: **searching** for media (Torznab) and **downloading** it -(the qBittorrent API). The `.torrent` files involved are *not real torrents* — -they're tiny stubs jack hands to *arr, which sends them back to jack through the +(the qBittorrent API). The `.torrent` files involved are tiny stubs rather than +real torrents. jack hands one to *arr, which sends it back to jack through the qBittorrent download-client API. Nothing ever touches BitTorrent. ## 1. Search flow (Torznab) @@ -27,8 +27,8 @@ sequenceDiagram ``` The setup happens once, at startup: jack registers itself in each -`destination` Radarr/Sonarr as a **Torznab indexer** and — when `downloads` is -configured — as a **qBittorrent download client**. Both registrations point at +`destination` Radarr/Sonarr as a **Torznab indexer**, and (when `downloads` is +configured) as a **qBittorrent download client**. Both registrations point at `jack.internalUrl` and authenticate with an auto-issued **managed key**. If you'd rather register jack yourself, set that server's [`autoregister.enable`](/reference/configuration#servers-autoregister) to @@ -40,12 +40,12 @@ From then on, every search works like this: endpoint with that managed key. 2. jack **fans the query out to every `peer`** you've configured, calling each one's `/peer/search` with the API key that peer issued you. -3. Each peer searches **its own Radarr/Sonarr** library — movies and episodes - that have files — and returns matching releases, mirroring the *arr file +3. Each peer searches **its own Radarr/Sonarr** library (movies and episodes + that have files) and returns matching releases, mirroring the *arr file metadata. 4. jack turns each match into a Torznab "release" whose download link points back at itself: `/torznab/download/:.torrent`. -5. Radarr/Sonarr show these as grabbable releases — indistinguishable from a +5. Radarr/Sonarr show these as grabbable releases, indistinguishable from a normal indexer's results. ## 2. Download flow (qBittorrent API) @@ -69,36 +69,35 @@ sequenceDiagram ``` 1. You grab a release. Your *arr's download client is the **qBittorrent** - client jack registered on startup, pointed at jack's own qBittorrent API — - so *arr fetches the stub `.torrent` from jack and immediately POSTs it back - to jack at `/api/v2/torrents/add`. -2. That `.torrent` is a **stub** — bencoded data that just encodes the - `peerId` and `itemId`. No trackers, no pieces; it's never written to disk. + client jack registered on startup, pointed at jack's own qBittorrent API, so + *arr fetches the stub `.torrent` from jack and immediately POSTs it back to + jack at `/api/v2/torrents/add`. +2. That `.torrent` is a **stub**: bencoded data encoding the `peerId` and + `itemId`, with no trackers and no pieces. It's never written to disk. 3. jack parses the stub, finds the matching peer, and queues the download. 4. jack **downloads the real file over HTTP** from that peer's `/peer/items/:id/file` endpoint into `downloads.completedPath`. 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 +6. jack watches that *arr's history until the import is confirmed, then removes + its own copy from the completed folder if you've turned that on. 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 +it and write your library copy; jack's copy stays where it was. jack has no +further use for it, since 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: +Which on-disk outcome you get is decided by your *arr: - **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 + 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) — +- **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. @@ -110,26 +109,26 @@ 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. +The unlink is deliberately narrow. It runs only on an import jack has +confirmed, meaning the destination *arr reports the download in its history or +the manual import jack pushed reports `completed`. 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. +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 +## 3. Serving as a peer to others When a friend lists *you* as a peer, their jack calls your `/peer/*` endpoints, authenticated with the peer API key you issued them: -- `/peer/search` — search your Radarr/Sonarr library. -- `/peer/items/:id` — release metadata. -- `/peer/items/:id/file` — stream the actual file. +- `/peer/search`: search your Radarr/Sonarr library. +- `/peer/items/:id`: release metadata. +- `/peer/items/:id/file`: stream the actual file. jack streams files straight from disk using the paths your Radarr/Sonarr report, so **the jack process must be able to read your media files at those -same paths** — mount your media into the container the same way your *arr apps +same paths**. Mount your media into the container the same way your *arr apps see it. diff --git a/website/guide/management-ui.md b/website/guide/management-ui.md index 57b99df..630032c 100644 --- a/website/guide/management-ui.md +++ b/website/guide/management-ui.md @@ -4,82 +4,83 @@ description: Manage jack servers, peers, API keys, downloads, catalog requests, # Management UI -jack ships with a web console — the **management UI** — so you can run your +jack ships with a web console, the **management UI**, so you can run your instance without hand-editing `config.jsonc`. With the [Getting started](/guide/getting-started) compose file it runs as the `jack-ui` service at **http://localhost:3000**, and it's the easiest way to operate jack day to day: -- **Overview** — your configured servers and peers, and whether each one +- ***Overview***: your configured servers and peers, and whether each one initialized cleanly, with transfer activity updating live. -- **Catalog** — browse everything your peers share, and request titles into +- ***Catalog***: browse everything your peers share, and request titles into your library. -- **Downloads** — inspect, cancel, retry, and delete in-flight or finished +- ***Downloads***: inspect, cancel, retry, and delete in-flight or finished grabs, updating live as they progress. -- **Peers** and **Servers** — add, edit, and remove your friends and your +- ***Peers*** and ***Servers***: add, edit, and remove your friends and your Radarr/Sonarr connectors. -- **Settings → Downloads** — the whole - [`downloads`](/reference/configuration#downloads) block: where finished files - land, how many transfers run at once, whether jack drops its copy after an - import, and the retry and import-watcher tuning. -- **Settings → Quick linking** — how peers reach this instance, plus the +- ***Settings -> Downloads***: the whole + [`downloads`](/reference/configuration#downloads) block, covering where + finished files land, how many transfers run at once, whether jack drops its + copy after an import, and the retry and import-watcher tuning. +- ***Settings -> Quick linking***: how peers reach this instance, plus the buttons that generate and import [quick links](/guide/quick-links). -- **API keys** — issue and revoke the keys you hand to peers. -- **Logs** — live-tail the backend's logs. +- ***API keys***: issue and revoke the keys you hand to peers. +- ***Logs***: live-tail the backend's logs. Don't want it? Delete the `jack-ui` service and the backend's `JACK_MANAGEMENT_KEY` line to run jack headless. -## Settings → Downloads +## 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 +- The settings you actually choose sit at the top: ***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. + switch. +- ***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 +the default"**: jack drops the key from the config file rather than storing a +blank. `completedPath` is the exception, since it has no default and so 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 +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. -## Settings → Quick linking +## Settings -> Quick linking This section holds the [`jack.external`](/reference/configuration#jack-external) -profile — the instance name, external URL, and any proxy headers a peer needs to -reach you — and the two buttons that use it: +profile, meaning the instance name, external URL, and any proxy headers a peer +needs to reach you, along with the two buttons that use it: -- **Generate quick link** issues a fresh peer API key and encodes it, together +- ***Generate quick link*** issues a fresh peer API key and encodes it, together with the profile, into a single shareable string. It's enabled only once the profile is saved, and the link is revealed exactly once. -- **Add via quick link** decodes a link a friend sent you and opens the *Add - peer* form prefilled, for you to review before saving. +- ***Add via quick link*** decodes a link a friend sent you and opens the + ***Add peer*** form prefilled, for you to review before saving. -Treat a generated link as a credential — see -[Quick links](/guide/quick-links) for the full workflow and how to revoke one. +Treat a generated link as a credential. [Quick links](/guide/quick-links) covers +the full workflow and how to revoke one. ## Access control The UI supports two auth modes, depending on where the management key comes from: -1. **Injected — feels authless.** Set `JACK_MANAGEMENT_KEY` in the UI's env; - the compose file wires this for you. The UI adds the key on every request - and the browser is never prompted — put a proxy such as Cloudflare Access - or Traefik forward-auth in front of it to gate access. +1. **Injected**, which feels authless. Set `JACK_MANAGEMENT_KEY` in the UI's + env; the compose file wires this for you. The UI adds the key on every + request and the browser is never prompted, so put a proxy such as Cloudflare + Access or Traefik forward-auth in front of it to gate access. 2. **Cookie prompt.** Leave `JACK_MANAGEMENT_KEY` unset. The browser is prompted for the key once; it's validated against the management API and - stored in a sealed `HttpOnly` cookie. Set `JACK_SESSION_KEY` — at least 32 - characters — in production to seal that cookie. + stored in a sealed `HttpOnly` cookie. Set `JACK_SESSION_KEY` in production, + at least 32 characters, to seal that cookie. ## How it works @@ -89,7 +90,7 @@ browser app and proxies every call it makes. Nothing management-related is ever exposed to your browser directly. The browser only talks to the BFF; the BFF forwards each request to jack's [management API](#management-api) and attaches the `X-Management-Key` -credential server-side — either injected from its environment or unsealed from +credential server-side, either injected from its environment or unsealed from the session cookie, depending on the [access-control mode](#access-control). The browser never holds the key in plain text. @@ -104,9 +105,9 @@ for the full UI configuration reference. 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. +something changes: a transfer's progress, a connector's state, or 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: @@ -117,40 +118,40 @@ The streams are plain `GET` endpoints on the management API: | `/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 +change, with no deltas to reassemble. A `ping` event every 15 seconds keeps idle +proxies from dropping 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. +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 +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 +The UI's backing API, the **management API**, is a separate listener on its own port (`MANAGEMENT_PORT`, default `5226`), started only when `JACK_MANAGEMENT_KEY` is set. It can read and rewrite your whole config, including connector credentials, so treat it as an admin socket: -Anyone who can reach it with the key controls your jack — and can read the +Anyone who can reach it with the key controls your jack, and can read the config, redirect downloads, or point your *arr connectors elsewhere. A single bug in its auth check would hand that to anyone who can reach it at all. -That's why the default compose setup is the way it is — and why you shouldn't +That's why the default compose setup is the way it is, and why you shouldn't change it: it publishes no host port for `5226`, so the management API is -reachable only from inside the Docker network, which is exactly the set of -things that need it — the UI's BFF. If you want remote management, expose the -**UI** behind an authenticating proxy and let it do the talking. +reachable only from inside the Docker network, which holds exactly the one thing +that needs it, the UI's BFF. If you want remote management, expose the **UI** +behind an authenticating proxy and let it do the talking. If you truly need to expose the management API itself, don't let its key be -the only lock: put an independent auth layer in front — Cloudflare Access, -Authelia, Traefik forward-auth, or similar — so a bug in jack's auth isn't a -bug in *your* perimeter. No code is immune to bugs; defense in depth is the +the only lock. Put an independent auth layer in front, such as Cloudflare +Access, Authelia, or Traefik forward-auth, so a bug in jack's auth isn't a bug +in *your* perimeter. No code is immune to bugs; defense in depth is the point. diff --git a/website/guide/peering.md b/website/guide/peering.md index c09f21c..ca113ce 100644 --- a/website/guide/peering.md +++ b/website/guide/peering.md @@ -7,11 +7,11 @@ description: Connect jack instances securely, exchange scoped peer API keys, and jack authenticates each external surface by **key type**, so a credential only works where it's meant to (`/ping` is the only unauthenticated route): -- **Peer API keys.** Issued per peer from the management UI's *API keys* section - (or the management API, or implicitly when you generate a - [quick link](/guide/quick-links)) — named, revocable, and optionally expiring. - Scoped to the **peer API** (`/handshake`, `/peer/*`): a peer key **cannot** - query your Torznab indexer or act as your download client. +- **Peer API keys.** Issued per peer from the management UI's ***API keys*** + section, from the management API, or implicitly when you generate a + [quick link](/guide/quick-links). They are named, revocable, and optionally + expiring. Scoped to the **peer API** (`/handshake`, `/peer/*`): a peer key + **cannot** query your Torznab indexer or act as your download client. - **Managed keys.** jack mints these automatically and registers them in your Radarr/Sonarr when it auto-registers as their indexer + download client. Scoped to the ***arr surface** (`/torznab`, the qBittorrent API); you never @@ -26,13 +26,26 @@ So keys flow in two directions: ## Sharing with friends (peering) -Peering is symmetric — you each run jack and exchange two things: a URL where -the other instance can reach your peer API and a **peer API key**. This public -or LAN-reachable peer URL is independent of `jack.internalUrl`, which is the +Peering is symmetric. You each run jack and exchange a URL where the other +instance can reach your peer API, plus a **peer API key**. This public or +LAN-reachable peer URL is independent of `jack.internalUrl`, which is the address your own Radarr/Sonarr use to reach jack. +### With a quick link + +The easiest way to do that exchange is a [quick link](/guide/quick-links): +generate one in the management UI and send it to your friend, who pastes it into +theirs. The link carries your peer URL, a peer API key issued on the spot, and +any proxy headers needed to reach you, so neither of you types a credential by +hand. + +### By hand + +You can also exchange the two values yourself, which is what a quick link +automates: + - **You give a friend** your reachable peer URL plus a peer API key you issue - them (management UI → *API keys*). They add you under `peers` in *their* + them (management UI, ***API keys***). They add you under `peers` in *their* config: ```jsonc @@ -45,18 +58,13 @@ address your own Radarr/Sonarr use to reach jack. - **They give you** theirs, and you add them the same way in your config. -After that, each side's Radarr/Sonarr can find and pull media the other has. - -::: tip Skip the copy-paste -[Quick links](/guide/quick-links) package the URL, a freshly issued key, and any -proxy headers into one string your friend pastes into their management UI. Same -peering, fewer things to get wrong. -::: +Either way, each side's Radarr/Sonarr can then find and pull media the other +has. ::: tip One key per peer -Issue a **separate key per peer**. Each is scoped to the peer API — it can't -reach your indexer or download client — and can be revoked individually without -disrupting your other peers. +Issue a **separate key per peer**. Each one is scoped to the peer API, so it +can't reach your indexer or download client, and each can be revoked +individually without disrupting your other peers. ::: ::: warning Use HTTPS for peer URLs diff --git a/website/guide/sample-files.md b/website/guide/sample-files.md index e2c0782..1927682 100644 --- a/website/guide/sample-files.md +++ b/website/guide/sample-files.md @@ -26,8 +26,8 @@ The standard deployment: the jack backend plus the management UI. ## `config.jsonc` -The configuration template — see the -[configuration reference](/reference/configuration) for every key. +The configuration template. The +[configuration reference](/reference/configuration) documents every key. <<< ../../examples/config.jsonc{jsonc} diff --git a/website/guide/troubleshooting.md b/website/guide/troubleshooting.md index 6af143d..14c5c35 100644 --- a/website/guide/troubleshooting.md +++ b/website/guide/troubleshooting.md @@ -17,29 +17,29 @@ Unable to communicate with Jack. Failed to connect to qBittorrent, please check your settings. ``` -First, a decoding tip: when *arr mentions **Jack** and **qBittorrent** in the -same breath, it's talking about **jack's qBittorrent API**. *arr doesn't know -jack isn't a real qBittorrent instance — jack registered itself as one — so -this **does not** mean your own qBittorrent has a problem (if you even run -one). Both the indexer and the download client point at jack, so either way -the failure is *arr ↔ jack. +When *arr mentions **Jack** and **qBittorrent** in the same message, it's +talking about **jack's qBittorrent API**. jack registered itself as a +qBittorrent instance, and *arr can't tell the difference, so this **does not** +mean your own qBittorrent has a problem (if you even run one). Both the indexer +and the download client point at jack, so either way the failure is between +*arr and jack. -That leaves three usual causes: +The usual causes: - **jack isn't running.** Check `docker compose ps` / the container logs. - **jack is unreachable from *arr.** Same networking story as [below](#qbittorrent-download-client-test-fails-failed-to-register-download-client): `jack.internalUrl` must resolve from the Radarr/Sonarr side. -- **The managed key is no longer valid** — for example after wiping jack's +- **The managed key is no longer valid**, for example after wiping jack's database. **Restart jack**: auto-registration runs again on startup and updates the registration with a fresh, valid key. ## qBittorrent download client test fails / `Failed to register download client` -The qBittorrent client *arr registers (or the "Test" button in -Settings → Download Clients) connects to jack's qBittorrent API at the host/port -jack derives from `jack.internalUrl`. A failing test almost always means *arr -can't reach that address. +The qBittorrent client *arr registers (or the ***Test*** button in +***Settings -> Download Clients***) connects to jack's qBittorrent API at the +host/port jack derives from `jack.internalUrl`. A failing test almost always +means *arr can't reach that address. **Fix:** make sure `jack.internalUrl` is resolvable **from the Radarr/Sonarr side**. On a shared Docker network use the container name (`http://jack:5225`); @@ -51,7 +51,7 @@ jack to *arr's network (the `networks:` block in the example compose). The download completes in jack (you see it finish in the logs) but Radarr/Sonarr never pick the file up. jack writes finished files to the **literal** `downloads.completedPath`, and *arr imports them by resolving that path in **its -own** filesystem — so the completed folder must exist at the **same path** +own** filesystem, so the completed folder must exist at the **same path** inside the Radarr/Sonarr containers. **Fix:** mount the **same host folder** into Radarr **and** Sonarr at the **same @@ -79,10 +79,10 @@ Two gotchas: - **Permissions.** jack runs as **uid/gid 1000**, matching the `PUID/PGID` the linuxserver.io *arr images default to, so files jack writes are owned by the same user that imports them. Make sure the completed folder (and the `/config` - mount) are readable/writable by uid 1000 — `chown -R 1000:1000` them; if your + mount) are readable/writable by uid 1000, with `chown -R 1000:1000`. If your *arr uses a different `PUID`, set it to match. -## `No "downloads" config set` — no download client registered +## `No "downloads" config set`, no download client registered ``` No "downloads" config set; skipping download client auto-registration. Grabs will fail until a qBittorrent client is configured. @@ -96,7 +96,7 @@ have nowhere to go. **Fix:** add a `downloads` block with `completedPath` and restart jack. If registration fails with `Failed to register indexer`, check that the -destination server is reachable and its API key is correct — registration logs +destination server is reachable and its API key is correct. Registration logs the raw *arr response body at `error` level. ## `ConnectionRefused` on startup @@ -123,40 +123,40 @@ depends_on: sonarr: {condition: service_healthy} ``` -(This needs `healthcheck` blocks on those services — the linuxserver.io images +(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 +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. +Whether that costs you real space depends on your *arr, since a hardlinked +import shares bytes with the library and 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 +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). +is confirmed, and only then: one file per confirmed import, 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 +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…" +## Management UI stuck on "Reconnecting..." -The Overview and Downloads pages show a **Live** badge while their +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 +***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: +The 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 @@ -165,7 +165,7 @@ Three usual causes: `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. + 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`. @@ -174,5 +174,5 @@ Three usual causes: Set `LOG_LEVEL=trace` to log every HTTP request (method, path, status, duration). Registration failures always log the raw *arr response body at -`error` level, which carries the real validation message — read that body, it -usually tells you exactly what *arr is unhappy about. +`error` level. That body carries the real validation message and usually tells +you exactly what *arr is unhappy about. diff --git a/website/guide/what-is-jack.md b/website/guide/what-is-jack.md index e22d974..fc75483 100644 --- a/website/guide/what-is-jack.md +++ b/website/guide/what-is-jack.md @@ -7,8 +7,9 @@ description: Understand how jack privately shares Radarr and Sonarr media librar **jack** lets you and your friends share media libraries with each other through the *arr stack you already run. You point Radarr/Sonarr at jack, search like you would on any indexer, and when a friend has the movie or episode you want, jack -pulls it straight from their server into your library — no public trackers, no -BitTorrent swarm, just a private peer-to-peer bridge between your media servers. +pulls it straight from their server into your library. The transfer runs over a +private peer-to-peer bridge between your media servers, with no public tracker +and no BitTorrent swarm involved. Built with [Bun](https://bun.com) and [Hono](https://hono.dev). @@ -17,15 +18,15 @@ Built with [Bun](https://bun.com) and [Hono](https://hono.dev). jack sits between three kinds of servers. You only need to configure the ones relevant to what you want to do (share, consume, or both). -jack talks to **Radarr/Sonarr** for everything — there's no separate media +jack talks to **Radarr/Sonarr** for everything; there's no separate media server. Each server you configure is one entry in `servers`, with two role flags (it can be either, or both): -| Role | What it does | You need it to… | +| Role | What it does | Needed to | | --- | --- | --- | | **`source`** | jack reads your Radarr/Sonarr library and serves it to peers: it searches your movies/episodes that have files and streams those files. | **Share** your library with friends. | -| **`destination`** | jack registers itself in that Radarr/Sonarr as a Torznab indexer + qBittorrent download client and triggers imports of finished downloads. | **Consume** — drive everything from your existing *arr UI. | -| **Peer** | Another **jack** instance — a friend. You list their URL + API key under `peers`; jack queries them when your *arr searches. | **Consume** media your friends have. | +| **`destination`** | jack registers itself in that Radarr/Sonarr as a Torznab indexer + qBittorrent download client and triggers imports of finished downloads. | **Consume**, driving everything from your existing *arr UI. | +| **Peer** | Another **jack** instance, run by a friend. You list their URL + API key under `peers`; jack queries them when your *arr searches. | **Consume** media your friends have. | So a typical "both" setup has your Radarr/Sonarr as `source: true` **and** `destination: true` (share your library *and* search your friends'), plus some @@ -34,12 +35,12 @@ So a typical "both" setup has your Radarr/Sonarr as `source: true` **and** ::: tip What's Torznab? **Torznab** is the search API that indexers speak to Radarr/Sonarr (the same protocol Prowlarr/Jackett expose). jack pretends to be a Torznab indexer so -your *arr apps can search your friends' libraries with zero special setup — -to them, jack looks like any other indexer. +your *arr apps can search your friends' libraries without special setup. To +them, jack looks like any other indexer. ::: ## Next steps -- [Getting started](/guide/getting-started) — run jack with Docker Compose. -- [How it works](/guide/how-it-works) — the search and download flows in detail. -- [API keys & peering](/guide/peering) — exchange keys with friends and start sharing. +- [Getting started](/guide/getting-started): run jack with Docker Compose. +- [How it works](/guide/how-it-works): the search and download flows in detail. +- [API keys & peering](/guide/peering): exchange keys with friends and start sharing. diff --git a/website/index.md b/website/index.md index 73d5cf1..e07b190 100644 --- a/website/index.md +++ b/website/index.md @@ -6,7 +6,7 @@ description: Share Radarr and Sonarr libraries directly with friends through a p hero: name: jack text: Share media libraries through the *arr stack you already run - tagline: Point Radarr/Sonarr at jack, search like any indexer, and pull media straight from your friends' servers — no public trackers, no BitTorrent swarm. + tagline: Point Radarr/Sonarr at jack, search like any indexer, and pull media straight from your friends' servers, with no public trackers and no BitTorrent swarm. image: light: /logo-light.svg dark: /logo-dark.svg @@ -25,14 +25,14 @@ hero: features: - icon: 🔍 title: Looks like a normal indexer - details: jack registers itself in Radarr/Sonarr as an indexer and download client. Zero special setup. + details: jack registers itself in Radarr/Sonarr as an indexer and download client, with no special setup on their side. - icon: 🤝 title: Private peer-to-peer bridge details: Search your friends' libraries and pull files straight from their servers into yours. - icon: 🚫 title: No BitTorrent involved - details: No trackers, no swarm — files transfer over plain, authenticated HTTP. + details: Files transfer over plain, authenticated HTTP, with no tracker and no swarm. - icon: 🖥️ title: Web management console - details: Manage servers, peers, API keys, and downloads without hand-editing config files — transfers and connector state update live. + details: Manage servers, peers, API keys, and downloads without hand-editing config files. Transfers and connector state update live. --- diff --git a/website/reference/configuration.md b/website/reference/configuration.md index ab0a7e5..d16fcac 100644 --- a/website/reference/configuration.md +++ b/website/reference/configuration.md @@ -10,8 +10,8 @@ the file doesn't exist, jack writes a default one on first boot. Copy [`examples/config.jsonc`](https://github.com/roziscoding/jack/blob/main/examples/config.jsonc) as a starting point. -The `jack` block is required. `downloads`, `servers`, and `peers` are optional — -configure only what you need for what you're doing. (A top-level `version` +The `jack` block is required. `downloads`, `servers`, and `peers` are optional, +so configure only what you need for what you're doing. (A top-level `version` number also appears in the file; jack manages it for config migrations, so leave it alone.) @@ -57,11 +57,11 @@ This instance's identity. Required. **Type:** `string` · **Required**\ **Format:** URL -URL your own *arr apps use to reach jack — the address jack registers for its +URL your own *arr apps use to reach jack, and the address jack registers for its Torznab indexer and qBittorrent download client. Must be resolvable **from the Radarr/Sonarr side**: on a shared Docker network use the container name -(`http://jack:5225`); otherwise the host IP/domain. Peers never use this URL — -they use whatever you hand them (see [API keys & peering](/guide/peering)). +(`http://jack:5225`); otherwise the host IP/domain. Peers never use this URL. +They use whatever you hand them (see [API keys & peering](/guide/peering)). ### `jack.tmdbApiKey` @@ -74,12 +74,12 @@ with artwork and metadata. **Type:** `object` -How *another* jack reaches this instance — the profile jack encodes into the +How *another* jack reaches this instance, and the profile jack encodes into the [quick links](/guide/quick-links) you generate. Optional: without it, peering still works, you just hand out the URL and key by hand. -Edit it from the management UI (**Settings → Quick linking**), which writes this -block through its own endpoints so the rest of `jack` is left untouched. +Edit it from the management UI (***Settings -> Quick linking***), which writes +this block through its own endpoints so the rest of `jack` is left untouched. ### `jack.external.instanceName` @@ -96,7 +96,7 @@ body instead. **Type:** `string` · **Required**\ **Format:** `http`/`https` URL, no embedded `user:password@` credentials -The URL a peer should use to reach this instance — what ends up in the `url` +The URL a peer should use to reach this instance, and what ends up in the `url` field of every quick link you generate. Distinct from [`jack.internalUrl`](#jack-internalurl), which is for your own *arr apps. Use `https://`: the peer's API key travels in a request header. @@ -104,26 +104,28 @@ field of every quick link you generate. Distinct from ### `jack.external.headers` **Type:** `object`\ -**Content:** header name → [`ConfigSecret`](#configsecret) value\ +**Content:** header name -> [`ConfigSecret`](#configsecret) value\ **Default:** `{}` -Extra headers a peer must send to get through whatever sits in front of you — -Cloudflare Access service tokens and the like. They're copied into the quick -link so your friend doesn't have to configure them by hand, landing in their -config as [`peers[].headers`](#peers-headers). +Extra headers a peer must send to get through whatever sits in front of you, +such as Cloudflare Access service tokens. They're copied into the quick link so +your friend doesn't have to configure them by hand, landing in their config as +[`peers[].headers`](#peers-headers). Values are resolved when the profile is saved and when a link is generated, -while `env` and `file` references remain references in your config file. Reserved headers -(`X-Api-Key`, `Host`, `Content-Length`, `Connection`, `Transfer-Encoding`), -duplicate names, and values with line breaks are rejected; at most 100 headers. +while `env` and `file` references remain references in your config file. +Reserved headers (`X-Api-Key`, `Host`, `Content-Length`, `Connection`, +`Transfer-Encoding`), duplicate names, and values with line breaks are rejected; +at most 100 headers. ## `downloads` -Needed to **consume** (download) from peers — jack only registers itself as a -qBittorrent download client when this block is present. Everything except +Needed to **consume** (download) from peers, since jack only registers itself as +a qBittorrent download client when this block is present. Everything except `completedPath` is an optional tuning knob with a sensible default. -Every key here is also editable from the management UI (Settings → Downloads). +Every key here is also editable from the management UI +(***Settings -> Downloads***). Clearing a field there removes the key from the file, so the default below applies again. Only `unlinkImportedFiles` takes effect immediately; the rest are read at startup, so restart jack after changing them. @@ -135,8 +137,8 @@ read at startup, so restart jack after changing them. Where jack writes finished downloads. The path is inside jack's container; jack creates it if missing. It must also be mounted into your **Radarr and Sonarr** -containers at the **same path** — *arr resolves it in its own filesystem to -import finished files (see the callout in +containers at the **same path**, because *arr resolves it in its own filesystem +to import finished files (see the callout in [Getting started](/guide/getting-started)). ### `downloads.maxConcurrentDownloads` @@ -196,8 +198,8 @@ which finished downloads were imported. **Default:** `6` When a manual import trigger keeps failing (e.g. *arr returns 500 because the -title's library folder is missing), jack backs off between attempts and gives -up — marking the download failed — after this many attempts. +title's library folder is missing), jack backs off between attempts and marks +the download failed after this many attempts. ### `downloads.manualImportBackoffBaseMs` @@ -221,16 +223,16 @@ Upper bound for the manual-import retry backoff. The default is 30 minutes. **Default:** `false` Remove jack's copy of a download from `completedPath` once the *arr that grabbed -it confirms the import. jack has no use for the file after that — it is never -re-served or re-imported. +it confirms the import. jack has no use for the file after that, since it is +never re-served or re-imported. jack calls `unlink` on that one file and nothing else, so what actually happens depends on how your *arr imports: - **Hardlink** (Radarr/Sonarr's default when the download and library folders - share a filesystem) — the library's link keeps the data alive; only jack's + share a filesystem): the library's link keeps the data alive, and only jack's extra directory entry disappears. -- **Copy or move** — the library already has its own bytes, so removing jack's +- **Copy or move**: the library already has its own bytes, so removing jack's copy just frees the space. The unlink only ever runs on an import jack has confirmed: either the @@ -240,8 +242,8 @@ still importing, or failed keep their file, and a file that another download row still references is left alone. If the unlink fails, the download stays `imported` and the failure is logged. -Editable from the management UI (Settings → Downloads) and applies immediately — -unlike the other keys in this block, it does not need a restart. +Editable from the management UI (***Settings -> Downloads***) and applies +immediately. Unlike the other keys in this block, it does not need a restart. ## `servers` @@ -272,12 +274,12 @@ Base URL of the *arr server, reachable from jack. **Type:** [`ConfigSecret`](#configsecret) · **Required**\ **Format:** resolves to exactly 32 hexadecimal characters -The Radarr/Sonarr API key (Settings → General). +The Radarr/Sonarr API key (***Settings -> General***). ### `servers[].headers` **Type:** `object`\ -**Content:** header name → [`ConfigSecret`](#configsecret) value\ +**Content:** header name -> [`ConfigSecret`](#configsecret) value\ **Default:** `{}` Extra HTTP headers sent to this server, for reverse proxies or access layers @@ -307,8 +309,8 @@ into it. Controls the indexer/download-client registration jack performs in destination servers on startup. -- **`enable`** — set `false` to register jack in that *arr yourself. -- **`priority`** — indexer priority in *arr (lower = preferred). The +- **`enable`**: set `false` to register jack in that *arr yourself. +- **`priority`**: indexer priority in *arr (lower = preferred). The qBittorrent download client is always registered at *arr's lowest priority (50): *arr's general client pool only round-robins among the best-priority group, so torrents grabbed from your other indexers never get routed to @@ -318,7 +320,7 @@ servers on startup. ## `peers` -Other jack instances (friends) you consume from. Sources only — sharing back is +Other jack instances (friends) you consume from. Sources only; sharing back is configured on *their* side. ### `peers[].name` @@ -332,7 +334,7 @@ Display name, used in logs, health output, and search results. **Type:** `string` · **Required**\ **Format:** URL -The reachable peer URL your friend gave you. Use `https://` — the API key +The reachable peer URL your friend gave you. Use `https://`, since the API key travels in a request header, and jack logs a startup warning for `http://` peers. @@ -346,10 +348,10 @@ The peer API key that friend issued *you* (see ### `peers[].headers` **Type:** `object`\ -**Content:** header name → [`ConfigSecret`](#configsecret) value\ +**Content:** header name -> [`ConfigSecret`](#configsecret) value\ **Default:** `{}` -Extra HTTP headers sent to this peer — same semantics as +Extra HTTP headers sent to this peer, with the same semantics as [`servers[].headers`](#servers-headers), e.g. Cloudflare Access service tokens. ## `ConfigSecret` diff --git a/website/reference/environment-variables.md b/website/reference/environment-variables.md index 0ca89fc..3f49e38 100644 --- a/website/reference/environment-variables.md +++ b/website/reference/environment-variables.md @@ -4,7 +4,7 @@ description: Reference jack environment variables for ports, configuration paths # Environment variables -All variables are optional — jack boots with sensible defaults for a Docker +All variables are optional; jack boots with sensible defaults for a Docker setup. They configure the backend process; for the management UI's own variables, see [`apps/ui/README.md`](https://github.com/roziscoding/jack/blob/main/apps/ui/README.md). @@ -79,7 +79,7 @@ headless. **Type:** `integer`\ **Default:** `5226` -Port for the management API listener — separate from `PORT` so the peer-facing +Port for the management API listener, separate from `PORT` so the peer-facing port never exposes management. Only used when a management key is set. ## Logging @@ -89,8 +89,8 @@ port never exposes management. Only used when a management key is set. **Type:** `"trace" | "debug" | "info" | "warn" | "error" | "fatal"`\ **Default:** `"info"` -Minimum level to log. `trace` also logs every HTTP request — method, path, -response status, and duration — as it completes. +Minimum level to log. `trace` also logs every HTTP request as it completes, +with method, path, response status, and duration. ### `ENABLE_LOGS`