chore: update spec to reflect phases 0-4 done; fix axum 0.8 path syntax; add dev config

This commit is contained in:
2026-05-17 15:35:39 -07:00
parent e6fb4f48c6
commit f595b8067f
4 changed files with 140 additions and 95 deletions
+35 -51
View File
@@ -1,81 +1,65 @@
# kindexr config
# Copy to /etc/kindexr/config.yaml and edit as needed.
# kindexr config — copy to /etc/kindexr/config.yaml and edit as needed.
server:
listen: "127.0.0.1:9117" # bind addr; sit behind nginx for TLS
base_url: "https://kindexr.example.com" # used in Torznab feed links
listen: "127.0.0.1:9117"
base_url: "https://kindexr.example.com" # used in Torznab feed magnet links
database:
path: "/var/lib/kindexr/kindexr.db"
logging:
level: "info" # debug|info|warn|error
format: "json" # json|text
level: "info" # debug | info | warn | error
format: "json" # json | text
# Relays to subscribe to for NIP-35 events.
# If empty on first run, defaults to a sane starter set.
# Nostr relays to subscribe to for NIP-35 (kind 2003) events.
relays:
- "wss://relay.damus.io"
- "wss://nos.lol"
- "wss://relay.primal.net"
- "wss://nostr.mom"
- "wss://relay.snort.social"
- "wss://sovbit.host" # eric's own relay
# add more
- "wss://sovbit.host"
# Initial backfill via NIP-77 negentropy. Set false to start from "now" only.
backfill_days: 365 # how far back to go on first connect per relay
negentropy_bootstrap: true
backfill_days: 365 # don't go further back than this
# Curation
# Curation / trust settings
curation:
# If true, only ingest events from pubkeys in your follow graph (within follow_depth hops).
wot_only: false
wot_only: false # if true, drop events from pubkeys outside your WoT graph
follow_depth: 2
# Always allow these pubkeys regardless of WoT
allowlist:
- "npub1..."
# Always block these
blocklist:
- "npub1..."
# Auto-subscribe to these curation sets (kind 30004 naddr)
curation_sets:
- "naddr1..."
operator_pubkey: "" # your npub or hex pubkey — defines WoT root
allowlist: []
blocklist: []
curation_sets: [] # kind 30004 naddr URIs to subscribe to
exclude_categories: [6000, 6010, 6020, 6030, 6040, 6050, 6060, 6070, 6080, 6090]
auto_block_threshold: 5 # auto-block a publisher after this many reports
# TMDB enrichment (optional; without it, only events with imdb/tmdb i-tags are searchable by ID)
# TMDB enrichment — title → TMDB ID lookup (optional)
tmdb:
enabled: true
api_key: "${TMDB_API_KEY}"
api_key: "${TMDB_API_KEY}" # or set KINDEXR_TMDB.API_KEY env var
cache_ttl: "168h"
# Health scraping (optional)
# Health scraping (off by default — can be rude to private trackers)
health:
enabled: false # off by default; rude to private trackers
method: "dht" # dht|tracker|both
enabled: false
method: "dht" # dht | tracker | both
refresh_interval: "30m"
# Writer side - publishing your own torrents to nostr
# Publisher — publish your own torrents as NIP-35 events
publisher:
enabled: false # off until you set it up explicitly
signer:
mode: "bunker" # local|ncryptsec|bunker
bunker_uri: "bunker://..." # for NIP-46
ncryptsec: "" # for ncryptsec mode
nsec: "" # for local mode (NOT RECOMMENDED)
outbox_relays:
enabled: false
outbox: # relays to publish your events to
- "wss://sovbit.host"
- "wss://relay.damus.io"
- "wss://nos.lol"
# Where to watch for completed downloads
client:
type: "qbittorrent" # qbittorrent|transmission|deluge|watch_dir
qbittorrent:
url: "http://127.0.0.1:8080"
username: "admin"
password: "${QBIT_PASSWORD}"
# Only publish torrents tagged with this category (so you don't accidentally publish everything)
category: "publish-nostr"
watch_dir:
path: "/var/lib/kindexr/watch"
# Auto-enrich title parsing -> TMDB lookup before publishing
enrich_before_publish: true
publish_delay_secs: 1800 # 30 min hold before publishing
identity:
nsec: "" # local signing key (bech32 nsec or hex); run: kindexr-cli identity init
bunker_url: "" # NIP-46 bunker URI (takes precedence over nsec when set)
qbittorrent:
url: "http://127.0.0.1:8080"
username: "admin"
password: "${QBIT_PASSWORD}"
poll_interval_secs: 60
categories:
- "publish-nostr" # only publish torrents with this category label
+52
View File
@@ -0,0 +1,52 @@
server:
listen: "127.0.0.1:9117"
base_url: "http://localhost:9117"
database:
path: "/tmp/kindexr-dev.db"
logging:
level: "info"
format: "text"
relays:
- "wss://relay.damus.io"
- "wss://nos.lol"
- "wss://sovbit.host"
backfill_days: 7
negentropy_bootstrap: false
curation:
wot_only: false
follow_depth: 2
operator_pubkey: ""
allowlist: []
blocklist: []
curation_sets: []
exclude_categories: []
auto_block_threshold: 5
tmdb:
enabled: false
api_key: ""
cache_ttl: "168h"
health:
enabled: false
method: "dht"
refresh_interval: "30m"
publisher:
enabled: false
outbox: []
publish_delay_secs: 1800
identity:
nsec: ""
bunker_url: ""
qbittorrent:
url: "http://127.0.0.1:8080"
username: "admin"
password: ""
poll_interval_secs: 60
categories: []
+50 -41
View File
@@ -4,14 +4,16 @@
## Build status
- **Phase 0** — bootstrap: 🔄 to redo in Rust
- **Phase 1** — reader, basic Torznab: 🔄 to redo in Rust
- **Phase 2** — full *arr compatibility: 🎯 next
- **Phase 3** — curation: planned
- **Phase 4** — writer / publisher: planned
- **Phase 0** — bootstrap: ✅ done
- **Phase 1** — reader, basic Torznab: ✅ done
- **Phase 2** — full *arr compatibility: ✅ done
- **Phase 3** — curation (WoT, reports, curation sets): ✅ done
- **Phase 4** — writer / publisher: ✅ done
- **Web UI** — dashboard, indexed browser, publisher manager, publish queue: ✅ done
- **Phase 5** — Blossom binary bridge: long horizon
- **Phase 6** — FIPS deployment: planned
When handing this spec to Claude Code, start at Phase 0 (Rust rewrite in progress). Phase 0 and 1 are being redone in Rust; the Go implementation is in archive/go/ for reference.
When handing this spec to Claude Code, phases 04 and the web UI are complete and the binary is running, indexing real NIP-35 events. Start at Phase 5 or 6 unless retrofitting earlier work.
## What this is
@@ -673,55 +675,62 @@ kindexr-cli wot status # current allowed pubkey cou
## Phase plan
### Phase 0 — bootstrap 🔄 to redo in Rust
### Phase 0 — bootstrap ✅ done
Scaffolding, config loading, DB migrations, health endpoint, systemd unit.
### Phase 1 — reader, basic Torznab 🔄 to redo in Rust
### Phase 1 — reader, basic Torznab ✅ done
Relay subscription, NIP-35 parsing, SQLite storage, FTS search, `t=caps`, `t=search`, valid Torznab RSS, Sonarr-add-as-indexer works.
### Phase 2 — full *arr compatibility 🎯 next
### Phase 2 — full *arr compatibility ✅ done
- [ ] `t=tvsearch` with tvdbid/imdbid/tmdbid + season/ep filters
- [ ] `t=movie` with imdbid/tmdbid
- [ ] `t=music`, `t=audio` with artist/album/year
- [ ] `t=book` with author/title
- [ ] Title parser integration (port or fork an existing one)
- [ ] TMDB enrichment with caching
- [ ] Newznab category map complete and applied at ingest
- [ ] Default-exclude XXX categories
- [ ] Sanity-check the caps `<categories>` block against a working Jackett response
- [x] `t=tvsearch` with tvdbid/imdbid/tmdbid + season/ep filters
- [x] `t=movie` with imdbid/tmdbid
- [x] `t=music`, `t=audio` with artist/album/year
- [x] `t=book` with author/title
- [x] Title parser (regex-based: season/ep, year, quality, source)
- [x] TMDB enrichment — direct reqwest to TMDB v3 API, async background update
- [x] Newznab category map complete and applied at ingest
- [x] XXX category exclusion configurable via `curation.exclude_categories`
- [x] Caps XML matches Torznab spec category tree
**Acceptance:** Sonarr automatically finds a known recent episode by tvdbid+season+ep with no manual intervention. Radarr finds a movie by imdbid. Lidarr finds an album. End-to-end: episode airs → Sonarr queries kindexr → results returned → Sonarr passes magnet to qBittorrent → download completes → Plex picks it up.
### Phase 3 — curation ✅ done
### Phase 3 — curation
- [ ] WoT graph builder from operator's kind 3 + follows-of-follows
- [ ] kind 10000 mute list ingestion
- [ ] kind 30004 curation set subscription
- [ ] kind 30382 trusted assertion ingestion
- [ ] kind 1985 label ingestion
- [ ] kind 1984 report aggregation with auto-block threshold
- [ ] Trust score computation per publisher
- [ ] CLI commands for publisher trust management
- [x] WoT graph builder from operator's kind 3 + follows-of-follows (level 0/1/2)
- [x] kind 10000 mute list ingestion
- [x] kind 30004 curation set subscription — curated events rank higher in search
- [x] kind 1984 report aggregation with configurable auto-block threshold
- [x] Trust score computation per publisher (WoT level + report penalty)
- [x] CLI commands: publisher list/info/block/unblock/mute/trust
- [ ] kind 30382 trusted assertion ingestion — deferred
- [ ] kind 1985 label ingestion — deferred
**Acceptance:** With `wot_only: true`, ingest from a known-spammy relay yields zero events that aren't in your WoT. Subscribing to a curation set surfaces those events at higher rank.
### Phase 4 — writer / publisher
### Phase 4 — writer / publisher ✅ done
- [ ] NIP-46 bunker client implementation
- [ ] kindexr identity init flow in CLI (`identity init`)
- [ ] qBittorrent webhook/poll integration
- [ ] Build kind 2003 from torrent metainfo
- [ ] Per-category outbox routing
- [ ] TMDB enrichment of own publishes
- [ ] Publish-delay buffer
- [ ] Insert published events into local DB
- [ ] CLI bulk publish (`publish --from-dir`)
- [ ] CLI identity profile publish + vouch flows
- [x] Local nsec signing (bech32 or hex) via `Signer`
- [x] `kindexr-cli identity init` — generate or import nsec, store in DB
- [x] qBittorrent poll integration — completed-in-category → enqueue → publish after delay
- [x] Build kind 2003 from lava_torrent metainfo (files, trackers, info_hash)
- [x] Publish-delay queue — configurable hold before signing and broadcasting
- [x] TMDB inline lookup before publish (10s timeout, best-effort)
- [x] Signed events published to outbox relays via nostr-sdk, inserted into local DB
- [x] `kindexr-cli publish --from <path>` — bulk enqueue .torrent files
- [ ] NIP-46 bunker signing — config field wired, implementation deferred
- [ ] Per-category outbox routing — all categories share one outbox for now
- [ ] CLI identity profile publish + vouch flows — deferred
**Acceptance:** Add a torrent to qBittorrent with category `publish-public`, wait for completion → after `publish_delay` the event appears on configured outbox relays *and* in local DB. Event is fetchable on dtan.xyz.
**Acceptance:** Add a torrent to qBittorrent with category matching `qbittorrent.categories`, wait for `publish_delay_secs` → event appears on outbox relays and in local DB and `/ui/published`.
### Web UI ✅ done
- [x] `/ui` — dashboard: indexed count, publisher count, queue stats, relay connection list, recent ingest feed
- [x] `/ui/indexed` — FTS-powered paginated torrent browser with search
- [x] `/ui/publishers` — publisher table with trust/WoT/block/mute status; vouch form accepts npub or hex pubkey; per-row block/unblock/mute actions
- [x] `/ui/published` — publish queue with pending / done tabs and error display
- Dark theme, server-side rendered HTML, no build step, no JS framework
### Phase 5 — Blossom binary bridge (long horizon)
+3 -3
View File
@@ -15,9 +15,9 @@ pub fn router() -> Router<AppState> {
.route("/ui/indexed", get(indexed::handler))
.route("/ui/publishers", get(publishers::list_handler))
.route("/ui/publishers/vouch", post(publishers::vouch_handler))
.route("/ui/publishers/:pubkey/block", post(publishers::block_handler))
.route("/ui/publishers/:pubkey/unblock", post(publishers::unblock_handler))
.route("/ui/publishers/:pubkey/mute", post(publishers::mute_handler))
.route("/ui/publishers/{pubkey}/block", post(publishers::block_handler))
.route("/ui/publishers/{pubkey}/unblock", post(publishers::unblock_handler))
.route("/ui/publishers/{pubkey}/mute", post(publishers::mute_handler))
.route("/ui/published", get(published::handler))
}