Metadata-Version: 2.4
Name: nazgarr
Version: 0.7.10
Summary: Media library, seeding folders, hardlinks, torrent clients and tracker uploads in one place.
License-Expression: GPL-3.0-only
Project-URL: Homepage, https://github.com/lktorrentz/nazgarr
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi
Requires-Dist: uvicorn[standard]
Requires-Dist: sqlalchemy
Requires-Dist: apscheduler
Requires-Dist: httpx
Requires-Dist: qbittorrent-api
Requires-Dist: pymediainfo
Requires-Dist: guessit
Requires-Dist: torf
Requires-Dist: ffmpeg-python
Requires-Dist: pyimgbox
Requires-Dist: jinja2
Requires-Dist: python-multipart
Requires-Dist: pyyaml
Requires-Dist: cryptography
Requires-Dist: pyjwt
Requires-Dist: packaging>=24
Requires-Dist: typer
Dynamic: license-file

<p align="center">
  <img src="https://raw.githubusercontent.com/lktorrentz/nazgarr/main/frontend/public/ring.png" alt="Nazgarr" width="128" height="128">
</p>

<h1 align="center">Nazgarr</h1>

<p align="center">
  Your media library, your seeding folders and your trackers, in one place.
  <br>
  <a href="https://github.com/lktorrentz/nazgarr/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/lktorrentz/nazgarr?label=stable"></a>
  <a href="LICENSE"><img alt="License: GPL-3.0" src="https://img.shields.io/github/license/lktorrentz/nazgarr"></a>
</p>

Nazgarr is a self-hosted web app for anyone who seeds on private trackers from the same files their media library uses. It reads your disks and finds the hardlinks between library and seeding folders. It also knows what your torrent clients are really seeding, finds what could seed again, and publishes your own uploads to your trackers.

It isn't tied to Unraid or to the \*arr stack. Separate disks without FUSE or RAID work, and Radarr, Sonarr and the rest are optional integrations, never dependencies. The name only borrows the \*arr naming style, like Bazarr or Prowlarr.

**Nothing that touches your files or your torrent clients runs without your approval.** Every hardlink and every torrent added to a client goes through a review queue first. Running them automatically is an opt-in setting, off by default.

## What it does

**See what seeds and what doesn't**
- Every file of the library and of the seeding folders gets a state: seeding (a hardlink is seeding in a client), orphaned, or tracked by a client without a link in the library.
- Folder and poster views, filters by state, size and tracker, duplicates, and an "exclude" for the files that never count.
- A dashboard with the library health (how much of it seeds, by size), its trend over time, and file-by-file changes since the last scan.

**Reseed what you already have**
- Library files that seed nowhere are searched on your trackers by TMDB id. Candidates are confirmed by reading the torrent's piece hashes against your files, not just by names and sizes.
- Optionally, cross-seed: a file seeding on one tracker is also searched on the others.
- Matches wait in a review queue with their confidence and what they would do. After your approval, Nazgarr creates the hardlinks and adds the torrent with a real recheck.

**Triage your seeding folder**
- Torrents seeding without a hardlink in the library are listed with their reason: replaced by an upgrade, a copy, removed from the library, never imported, extras only.
- Each tracker can set its minimum seed time and ratio. Triage then marks with a green OK the torrents you can remove safely. Anything else to check (files shared with another torrent, a client error) sits in a popover next to it.

**Upload your releases**
- A guided flow: TMDB identification with a confidence explained factor by factor, then a dupe check on every tracker. When the tracker already has the same release, a full hash check proves it and you reseed instead of uploading.
- Release names per tracker from editable patterns: resolution, source, HDR and Dolby Vision profile, audio, languages, REMUX and HYBRID detection.
- MediaInfo, screenshots uploaded to an image host (with fallback across hosts), and descriptions from a sandboxed template.
- The new torrent seeds from hardlinks in an upload folder, with the file names you chose, so the library keeps its own names.
- A watched folder for releasers: whatever lands there starts an upload on its own up to the decision, then moves to the releases folder once it seeds.
- Packs from episodes picked by hand: episodes downloaded one at a time, even ones already seeding with their own torrent, become a season pack or a complete pack.

**Fits your setup**
- Several torrent clients at once ([qBittorrent](https://www.qbittorrent.org/), [qui](https://github.com/autobrr/qui)), [UNIT3D](https://github.com/HDInnovations/UNIT3D) trackers, optional [Radarr](https://radarr.video/) and [Sonarr](https://sonarr.tv/).
- Works without a media folder too, just for your torrents and uploads.
- Plugins, signed webhooks and API keys to extend it ([docs/SDK.md](docs/SDK.md)).
- English and Italian interface, and a guided tour that sets everything up at the first access.

## Who it is for

- **You seed from hardlinks of your library** and want to know, at a glance, what seeds and what doesn't.
- **You lost your torrents** (a reinstall, a moved disk, a client wiped) and want the library to seed again without downloading anything.
- **Your seeding folder keeps growing** and you want to know what can go, and whether it's safe to remove.
- **You release or re-upload** and want naming, dupe checks, screenshots and seeding handled in one flow, across several trackers.

## Install

### Docker Compose

```yaml
services:
  nazgarr:
    image: ghcr.io/lktorrentz/nazgarr:stable
    container_name: nazgarr
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - PUID=1000            # the user that owns your media and torrent folders
      - PGID=1000
      - TZ=Europe/Rome
      - APP_SECRET_KEY=change-me   # see below
    volumes:
      - ./config:/app/config # config.yaml, database, cache
      - /srv/data:/data      # torrents/ and media/, the same path your torrent client sees
```

Generate `APP_SECRET_KEY` once and keep it with your backups. It encrypts the stored credentials (tracker tokens, client passwords, API keys), and without it they can't be read:

```bash
openssl rand -base64 32 | tr '+/' '-_'
```

Then `docker compose up -d` and open `http://<host>:8080`. Building from source instead: clone the repository and run `docker compose up -d` with the [`docker-compose.yml`](docker-compose.yml) in it, which reads the key from a `.env` file.

### Unraid

A template is in [`unraid/nazgarr-template.xml`](unraid/nazgarr-template.xml). Save it to `/boot/config/plugins/dockerMan/templates-user/`, then pick it under Docker › Add Container › Template. It uses the `:stable` image, `99:100` as the user, and mounts `/mnt/user/data` as `/data`, like the TRaSH Guides layout.

### Python package (no Docker)

Every stable release is also on [PyPI](https://pypi.org/project/nazgarr/), with the web UI already built inside, so neither Docker nor Node is needed. You need Python 3.12 or newer, [pipx](https://pipx.pypa.io), `mediainfo` and `ffmpeg`:

```bash
sudo apt install pipx mediainfo ffmpeg      # Debian/Ubuntu (macOS: brew install pipx media-info ffmpeg)
pipx install nazgarr
nazgarr init --scan-root /mnt               # the folder your disks live under
nazgarr serve                               # or install it as a service, below
```

`init` writes `config.yaml` and a secret key (`secret.key`, readable only by you) to `~/.config/nazgarr` (macOS: `~/Library/Application Support/Nazgarr`). The database goes in `~/.local/share/nazgarr`. To start it at boot:

```bash
nazgarr install-service
systemctl --user daemon-reload && systemctl --user enable --now nazgarr        # Linux (systemd)
sudo loginctl enable-linger $USER                                              # Linux: start without logging in
launchctl load -w ~/Library/LaunchAgents/io.github.lktorrentz.nazgarr.plist    # macOS (launchd)
```

Run it as the user that owns your media and torrent folders, and as a single process (the upload worker and the scheduler live inside it). Update with `pipx upgrade nazgarr`. Windows is not tested yet.

### Release channels

| Tag | What it is |
| --- | --- |
| `:stable` (also `:latest`) | Releases promoted by hand after testing. Use this one. The Python package follows it. |
| `:nightly` | A test build for every push to `main`. It moves fast and may break. |
| `:X.Y.Z` | Every published version, pinned. |

Configuration › Application › Check for updates follows the channel you're on.

## Basic configuration

Nazgarr needs very little up front. Disks, folders, clients, trackers and thresholds are set from the web UI and saved in its database, with no restart.

**Port:** `8080`. Map it to whatever you like on the host (`"9000:8080"`). The Python package takes `nazgarr serve --host 0.0.0.0 --port 8080`.

**Environment**

| Variable | |
| --- | --- |
| `APP_SECRET_KEY` | **Required.** Encrypts the stored credentials. Keep it with your backups. |
| `PUID` / `PGID` | The user and group the app runs as. They need write access to your folders, because hardlinks are created there. Default `1000`. |
| `TZ` | Time zone for the schedule and the logs. |
| `NAZGARR_SETUP_CODE` | Optional: your own one-time code to create the account (otherwise one is generated and printed in the log). |

**`config/config.yaml`** is created on the first start with these defaults. It holds the only two settings that need a restart:

```yaml
disk_scan_root: /data      # where the disks are, inside the container
data_dir: /app/config/data # database and cache
```

**Disks and paths.** Each disk is a folder under `disk_scan_root` that holds the torrents and the media of one filesystem. Hardlinks only work inside one filesystem, so a disk's folders must all be on the same one. A disk can have several seeding folders and several media folders (say `movies/` and `tv/` side by side, or a separate cross-seed folder). With the TRaSH Guides layout there is a single disk:

```
/data
├── torrents/      seeding folder (what your torrent client downloads into)
│   └── uploads/   optional: where your uploads seed
├── media/
│   ├── movies/
│   └── tv/
└── releases/      optional: the watched folder for your own releases
```

With several separate disks, mount each one under a common parent and point `disk_scan_root` at it:

```yaml
    volumes:
      - ./config:/app/config
      - /mnt/disk1:/mnt/disk1
      - /mnt/disk2:/mnt/disk2
# config.yaml: disk_scan_root: /mnt
```

Every subfolder of `disk_scan_root` shows up in Configuration › Storage, ready to be added as a disk.

**Same paths as your torrent client.** Mount the data folder at the same path in Nazgarr and in your client (for example `/data` in both), and it just works. If the client sees a disk elsewhere (say `/downloads`), set that path for the disk in the client's settings.

## First access

1. **Create the account.** Nazgarr stays closed until its single account exists. The log prints a one-time setup code (`docker logs nazgarr`, or the service log). Open the web UI, enter the code, and choose a username and password.
2. **Follow the guided setup.** A short welcome asks whether you upload and whether you use Radarr/Sonarr. The tour then walks you through each screen:
   - disks and their folders;
   - torrent clients;
   - the TMDB API key ([free](https://www.themoviedb.org/settings/api));
   - your trackers, each with its API token;
   - for uploads only, image hosts and naming.

   A checklist on the dashboard follows what you have configured.
3. **Run the first scan.** It only reads: nothing is linked, added or moved. Then the tour shows you around the views. From there, scans run on the schedule you set.

## Command line

Besides running the server, the `nazgarr` command talks to a running instance: log in once with `nazgarr login --url http://HOST:8080`, then check the status, start scans, approve reviews and more from the terminal or a script (`--json`, `--yes`). Inside the container: `docker exec -it nazgarr nazgarr status`. Full guide: [docs/CLI.md](docs/CLI.md).

## API and automation

Everything the UI does goes through a JSON API under `/api`. The interactive reference is at `http://<host>:8080/docs`. Scripts authenticate with an API key created in Configuration › API keys. See [docs/SDK.md](docs/SDK.md) for plugins, webhooks and API keys, and [examples/nazgarr-ntfy](examples/nazgarr-ntfy) for a complete plugin.

## Development

```bash
python3 -m venv .venv
./.venv/bin/pip install -r requirements.txt -r requirements-dev.txt
cp config.example.yaml config.yaml          # point disk_scan_root/data_dir at local folders
export APP_SECRET_KEY=$(openssl rand -base64 32 | tr '+/' '-_')
./.venv/bin/uvicorn nazgarr.main:app --reload --port 8080

cd frontend && npm install && npm run dev   # http://localhost:5173, proxies /api to the backend
```

Checks, the same as CI: `ruff check .` and `pytest -q` for the backend, `npm run lint`, `npx tsc -b` and `npx vitest run` in `frontend/`. Python dependencies are locked with hashes: edit `requirements.in`, then run `uv pip compile requirements.in --python-version 3.12 --generate-hashes -o requirements.txt`.

Design decisions live in [`docs/SPEC.md`](docs/SPEC.md), the plan in [`docs/ROADMAP.md`](docs/ROADMAP.md), the schema in [`docs/schema.sql`](docs/schema.sql), and the frontend notes in [`frontend/README.md`](frontend/README.md). Issues and pull requests are welcome. If something in the spec looks wrong, open an issue before working around it.

## License

[GPL-3.0](LICENSE)
