# Publishing (Writer side) nzbstr's writer side (Phase 4+) watches a download client for completed downloads and publishes them as NIP-35 kind 2003 events to configured Nostr relays. ## Overview ``` qBittorrent / Transmission / watch_dir | v (poll completed downloads in tagged category) nzbstr publisher | v (build kind 2003 event) NIP-46 bunker signer OR local nsec | v (WebSocket publish) outbox_relays (e.g. wss://sovbit.host) | v (also stored locally) local SQLite DB ``` ## Configuration Enable the publisher in `config.yaml`: ```yaml publisher: enabled: true signer: mode: "bunker" bunker_uri: "bunker://..." outbox_relays: - "wss://sovbit.host" - "wss://relay.damus.io" client: type: "qbittorrent" qbittorrent: url: "http://127.0.0.1:8080" username: "admin" password: "${QBIT_PASSWORD}" category: "publish-nostr" enrich_before_publish: true ``` ## Signing modes ### NIP-46 bunker (recommended) Run a self-hosted NIP-46 bunker (e.g. [nsecbunker](https://github.com/kind-0/nsecbunker)) on a trusted machine. nzbstr connects as a client and requests signatures. Your nsec never touches the publishing machine. ```yaml signer: mode: "bunker" bunker_uri: "bunker://?relay=wss://relay.example.com&secret=" ``` ### ncryptsec (NIP-49) Encrypted private key stored on disk. nzbstr prompts for the passphrase at startup. ```yaml signer: mode: "ncryptsec" ncryptsec: "ncryptsec1..." ``` ### Local nsec (not recommended) Unencrypted private key. Only use on air-gapped or fully trusted machines. ```yaml signer: mode: "local" nsec: "nsec1..." ``` Set the `nsec` value via environment variable instead of writing it to the config file: ```sh NZBSTR_PUBLISHER_SIGNER_NSEC=nsec1... nzbstr --config /etc/nzbstr/config.yaml ``` ## Download clients ### qBittorrent nzbstr polls the qBittorrent Web API for torrents in the configured category (`publish-nostr` by default). Only torrents whose state is `pausedUP` (seeding completed) or `uploading` are considered. Tag your torrents in qBittorrent with the publish category to opt them in. This prevents accidentally publishing everything in your client. ### watch_dir (Phase 4+) Drop `.torrent` files into the configured watch directory. nzbstr picks them up, parses the metainfo, builds the event, and publishes. Useful for scripted workflows. ## Event construction For each completed download: 1. Parse `.torrent` metainfo: extract info-hash, title, file list, tracker list, total size. 2. If `enrich_before_publish: true`, run the title through the title parser and optionally TMDB lookup to backfill `imdb_id`, `tmdb_id`, `tvdb_id`, season, episode, quality, source. 3. Build a kind 2003 event with the appropriate `x`, `title`, `file`, `tracker`, `i`, and `t` tags. 4. Sign via the configured signer. 5. Publish to all `outbox_relays`. 6. Store in local SQLite as if ingested (so it appears in your own Torznab results immediately). ## Security notes - **NIP-46 bunker on a public-facing seedbox** is the right answer security-wise. Run a self-hosted bunker on a trusted internal machine with nzbstr as a client. Your nsec never leaves the bunker. - If you use `local` mode, use `ProtectSystem=strict` in the systemd unit (already set) and ensure the config file has mode 0640 and is owned by the `nzbstr` user. - The `category` filter in qBittorrent config is important — do not set it to a category that contains torrents you didn't deliberately choose to publish.