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
1 change: 1 addition & 0 deletions website/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export default withMermaid({
items: [
{ text: 'Management UI', link: '/guide/management-ui' },
{ text: 'API keys & peering', link: '/guide/peering' },
{ text: 'Quick links', link: '/guide/quick-links' },
{ text: 'Running without Docker', link: '/guide/running-without-docker' },
{ text: 'Troubleshooting', link: '/guide/troubleshooting' },
],
Expand Down
17 changes: 17 additions & 0 deletions website/guide/management-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ operate jack day to day:
[`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
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.

Expand Down Expand Up @@ -50,6 +52,21 @@ One **Save** covers the form and only lights up once something changed;
you save — **every other key here is read at startup, so restart jack** to apply
it.

## 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:

- **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.

Treat a generated link as a credential — see
[Quick links](/guide/quick-links) for the full workflow and how to revoke one.

## Access control

The UI supports two auth modes, depending on where the management key comes
Expand Down
13 changes: 10 additions & 3 deletions website/guide/peering.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ 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) — 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.
(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.
- **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
Expand Down Expand Up @@ -46,6 +47,12 @@ address your own Radarr/Sonarr use to reach jack.

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.
:::

::: 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
Expand Down
129 changes: 129 additions & 0 deletions website/guide/quick-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
description: Share a jack instance with a friend by generating a single quick link that carries the peer URL, a freshly issued API key, and any proxy headers needed to reach you.
---

# Quick links

A quick link is one string carrying everything a friend needs to add you as a
peer: your peer URL, an API key issued for them, and any headers your proxy
requires.

```
jack-link:v1:eyJ2IjoxLCJ0eXBlIjoicGVlciIsIm5hbWUiOiJSb3oncyBKYWNrIiwidXJsIjoi...
```

They paste it into their management UI, review the decoded fields, and save the
peer.

::: danger A quick link is a credential
The link **contains a working API key in plain text**. Anyone who gets it can
search and download from your libraries until you revoke the key. Send it over
a channel you trust and never paste one into a public issue, gist, or forum.
:::

## Set up quick linking

Generating links requires a `jack.external` profile, editable under
***Settings -> Quick linking*** in the [management UI](/guide/management-ui):

- **Instance name**: the peer name suggested to whoever imports your link. They
can change it before saving.
- **External URL**: the URL a peer uses to reach you, e.g.
`https://jack.example.com`. HTTP and HTTPS only, with no `user:password@`
credentials embedded.
- **External headers**: optional headers a peer must send to get through your
proxy, such as Cloudflare Access service tokens. Each value is a
[`ConfigSecret`](/reference/configuration#configsecret), so prefer an `env` or
`file` reference over a literal.

Every link you generate reuses the saved profile. Full schema:
[`jack.external`](/reference/configuration#jack-external).

::: tip Use HTTPS
The API key in a quick link, and in every request the peer later makes, travels
in a header. An `http://` external URL exposes it to anything on the path, and
jack warns at startup about peers configured over plain HTTP.
:::

::: warning Internal and external URLs are not the same
[`jack.internalUrl`](/reference/configuration#jack-internalurl) is the address
your own Radarr/Sonarr use to reach jack. Putting it in the external URL hands
peers an address that only resolves on your side.
:::

## Generate a link

***Settings -> Quick linking -> Generate quick link*** asks for:

- **Suggested peer name**: prefilled from your instance name, this is how you
show up in your friend's peer list.
- **Key name** and an optional **description**: how you identify this credential
later when you need to revoke it.

On submit, jack resolves the external profile, **issues a new peer API key**,
and returns the encoded link.

The plaintext link appears **once**, in that dialog. Closing it discards the key
from the UI, the same one-time reveal as any other
[peer API key](/guide/peering). Copy it before closing; if you lose it, generate
a fresh link.

::: tip One link per friend
Every generated link carries its own key, so revoking one friend's access leaves
everyone else connected. Never reuse one link for two people.
:::

## Import a link

***Settings -> Quick linking -> Add via quick link*** takes the pasted link and
opens the normal ***Add peer*** form with the name, URL, key, and headers
filled in.
Nothing is saved until you submit, so you can rename the peer or check where
the URL points first.

A link that fails any of the [format checks](#link-format) is rejected outright,
instead of half-filling the form.

## Revoking access

Revoke the key under ***Settings -> API keys*** and the link stops working
immediately. Find it by the name you gave it at generation time.

Removing the `jack.external` block (***Remove configuration*** in the
***Quick linking*** section) only stops you from generating *new* links. Links already out
there keep working until their keys are revoked.

## Link format

The prefix `jack-link:v1:` followed by a base64url-encoded JSON object:

```json
{
"v": 1,
"type": "peer",
"name": "Roz's Jack",
"url": "https://jack.example.com",
"apiKey": "<the freshly issued peer API key>",
"headers": { "CF-Access-Client-Id": "..." }
}
```

There is no encryption. Anyone holding the link can read the key out of it.

Decoding is strict:

- `v` and `type` must match the values above.
- `url` must be `http`/`https`, with no embedded credentials.
- `headers` are validated against the same rules the config uses:
- reserved names are rejected: `X-Api-Key`, `Host`, `Content-Length`,
`Connection`, and `Transfer-Encoding`.
- names must be valid HTTP header tokens.
- no name may repeat.
- values must be non-empty.
- values may not contain line breaks.

To generate a link outside the UI, call
[`POST /quick-links`](/reference/management-api/postQuickLinks) on the
management API. A missing external profile, or one
with a secret reference that cannot be resolved, fails the request before any
key is issued, so a broken profile leaves no orphaned credential.
60 changes: 59 additions & 1 deletion website/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ it alone.)
| --- | --- |
| [`jack.internalUrl`](#jack-internalurl) | URL your *arr apps use to reach jack |
| [`jack.tmdbApiKey`](#jack-tmdbapikey) | TMDB metadata for the catalog |
| [`jack.external.instanceName`](#jack-external-instancename) | Peer name suggested in your quick links |
| [`jack.external.url`](#jack-external-url) | URL peers use to reach you |
| [`jack.external.headers`](#jack-external-headers) | Headers peers must send to reach you |
| [`downloads.completedPath`](#downloads-completedpath) | Where finished downloads go |
| [`downloads.maxConcurrentDownloads`](#downloads-maxconcurrentdownloads) | Simultaneous transfer cap |
| [`downloads.maxDownloadAttempts`](#downloads-maxdownloadattempts) | Retries before a download fails |
Expand Down Expand Up @@ -67,6 +70,53 @@ they use whatever you hand them (see [API keys & peering](/guide/peering)).
TMDB v3 API key, used by the management UI's catalog to enrich peer libraries
with artwork and metadata.

### `jack.external`

**Type:** `object`

How *another* jack reaches this instance — 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.

### `jack.external.instanceName`

**Type:** `string`\
**Format:** 1–100 characters

The peer name suggested to whoever imports one of your quick links. They can
change it before saving the peer. The management UI won't generate a link until
this is set; calling the API directly, the suggested name comes from the request
body instead.

### `jack.external.url`

**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`
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.

### `jack.external.headers`

**Type:** `object`\
**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).

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.

## `downloads`

Needed to **consume** (download) from peers — jack only registers itself as a
Expand Down Expand Up @@ -341,7 +391,15 @@ reports the problem and refuses to load that config.
{
"jack": {
"internalUrl": "http://jack:5225",
"tmdbApiKey": { "env": "TMDB_API_KEY" }
"tmdbApiKey": { "env": "TMDB_API_KEY" },
"external": {
"instanceName": "Roz's Jack",
"url": "https://jack.example.com",
"headers": {
"CF-Access-Client-Id": { "env": "MY_CF_CLIENT_ID" },
"CF-Access-Client-Secret": { "env": "MY_CF_CLIENT_SECRET" }
}
}
},
"downloads": {
"completedPath": "/data/torrents/completed",
Expand Down