Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<operationId>`; 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.
115 changes: 58 additions & 57 deletions website/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ 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

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
Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 fileif 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
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -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.

Expand All @@ -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:

Expand All @@ -262,42 +263,42 @@ 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
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.
Loading