34 Commits

Author SHA1 Message Date
enki 1049da9730 Update README: fix wrong UI paths, fix publisher config nesting, remove nonexistent CLI commands, add nginx note 2026-05-20 16:34:56 -07:00
enki 586b9cb51b Clean up deploy files for production: fix nginx config name, update example config 2026-05-20 16:29:20 -07:00
enki d6e1f43152 UI cleanup: plain language, fix relay status display, remove jargon 2026-05-19 23:56:49 -07:00
enki 66d8ff0203 Rewrite UI text to be more plain and human 2026-05-19 23:51:31 -07:00
enki 801779d84a Publishers: click-to-copy npub; settings descriptions; remove mute; fix qBit cookie
- Clicking a publisher row fills the override form with their npub and copies it to clipboard
- Settings page: section-level descriptions explaining what each setting does and how it affects kindexr
- Removed Mute button from publishers table (effectively same as Block; already-muted rows still show as blocked)
- qBittorrent: fix session cookie parsing for newer versions that use QBT_SID_<port>= instead of SID=
- qBittorrent: empty categories now fetches all completed torrents instead of returning nothing
- Update README with Deluge/qBit publishing docs and Settings UI table
2026-05-19 23:41:05 -07:00
enki e5451bbe83 Fix Deluge connection keep-alive error; remove stale restart banner
- Use pool_max_idle_per_host(0) on reqwest Client so each Deluge JSON-RPC
  call gets a fresh TCP connection; Twisted HTTP closes keep-alive conns
  aggressively causing "connection closed before message completed" errors
- Remove always-visible restart banner from Settings page; the old
  condition (settings_count > 0) was permanently true after any save
- Drop unused HashMap import in deluge.rs
- Update dev config: Deluge enabled with labels=[], dev password set
2026-05-19 21:55:28 -07:00
enki 6cb9428567 add Deluge and qBittorrent sections to Settings page
Both torrent clients now configurable from /ui/settings with enabled
toggle, URL, password, poll interval, and labels/categories fields.
Settings persist to DB and apply on next restart.
2026-05-19 21:24:41 -07:00
enki c6b9b676a4 add Deluge Web UI integration for publisher
Deluge uses a JSON-RPC HTTP API exposed by the Web UI plugin (port 8112),
not the daemon binary protocol (port 58846). The integration logs in,
fetches completed torrents, filters by configured labels, and enqueues
them the same way as the qBittorrent poller.

- DelugeConfig in config.rs (enabled, url, password, poll_interval_secs, labels)
- src/publisher/deluge.rs: DelugeClient with login/_session_id cookie
  handling, core.get_torrents_status, label + is_finished filtering
- Watcher: deluge_poll_loop alongside qb_poll_loop; each only starts
  if its respective client is configured/enabled
- Example config updated with deluge section and Web UI port note
2026-05-19 20:13:14 -07:00
enki bad4cdef77 add API key management to Settings page
- list_api_keys and delete_api_key DB functions
- API keys section at top of Settings: table of existing keys (masked),
  generate new key with label, revoke button per key
- New key displayed once in a green copy box after generation with a
  clipboard copy button; not shown again after leaving the page
2026-05-19 19:56:01 -07:00
enki 3d0b71c30b add Settings UI page with live config overrides stored in SQLite
- Migration 008: settings table (key/value with updated_at)
- DB: set_setting, get_all_settings, delete_setting, settings_count,
  apply_settings_overrides — merges DB settings over YAML config at startup
- Settings page at /ui/settings with sections for:
    Network/Proxy (mode picker: Direct/Tor/I2P/SOCKS5 + address field)
    Relays (add/remove with live list)
    Curation (operator pubkey, follow depth, WoT only, auto-block threshold)
    Ingest (backfill days)
    TMDB (enabled toggle + API key)
- Yellow restart-required banner when DB has saved settings
- Settings link added to nav
2026-05-19 16:03:32 -07:00
enki 3d1acfdc04 avoid re-ingesting already-indexed events on restart
Track the last seen event timestamp per relay in the DB. On startup,
subscribe to kind 2003 per relay using that timestamp as since instead
of always recomputing from backfill_days. Fresh relays with no stored
timestamp fall back to the full backfill window.
2026-05-18 22:43:14 -07:00
enki 619ab32421 improve publishers UI language and fix inflated publisher count
- Follow depth column: badges (you/direct/2nd/—) instead of raw 0/1/2
- Column tooltip explains hop distance in plain English
- Trust override form: "Local override only — nothing is broadcast to Nostr"
- publisher_count stat now counts only publishers with torrents_n > 0;
  previously counted every pubkey inserted by the WoT follow-graph
  builder which inflated the number with non-publishing follows
2026-05-18 21:59:01 -07:00
enki bac30ecfdf add SOCKS5/Tor/I2P proxy support and improve network UI clarity
- NetworkConfig/ProxyConfig in config.rs: mode=direct|tor|i2p|socks5
  with socks5_addr() helper that maps tor→9050, i2p→4446, socks5→url
- build_http_client() routes reqwest through SOCKS5 when configured
- build_nostr_opts() routes nostr-sdk WebSocket connections through
  SOCKS5 using Connection::new().proxy(addr)
- Shared http_client passed to Enricher and BlossomClient so proxy
  applies to TMDB and Blossom blob fetches as well
- nostr_opts passed to Reader, WotBuilder, ProfileFetcher
- Dashboard: network status card showing proxy mode, listen address,
  IPv4/IPv6; relay section now shows X connected / Y configured
- Publishers: rename vouch form to "Override publisher trust", add
  clear note that this is local-only (not broadcast to Nostr), explain
  WoT comes from NIP-02 kind 3; rename column headers for clarity
- Example and dev configs updated with network.proxy section
2026-05-18 21:46:09 -07:00
enki b88e329074 fix: sync relays table to config on startup — removes stale relay rows 2026-05-18 00:03:52 -07:00
enki cb1f2df8ee fix: drop .torrent extension from route — axum disallows mixed literal+param in path segment 2026-05-17 23:41:20 -07:00
enki 18326cbbfd fix: track last_event per relay on the dashboard
relay_url was being destructured as .. and thrown away, so
update_relay_last_event was called with an empty string and
matched no rows. Capture relay_url from the notification and
pass it through so each relay row gets its last_event updated.
2026-05-17 23:02:21 -07:00
enki 1c8088c138 fix: NIP compliance audit
NIP-35:
- Accept 64-char SHA256 info hashes (BitTorrent v2) in addition to 40-char SHA1 (v1)
- Parse `size` tag as explicit total size; file-size sum is now a fallback only
- Parse URL hint from third element of `x` tag as blossom_url fallback
- Parse `magnet` tag on ingest and extract trackers from it
- Emit `magnet` tag in published NIP-35 events

NIP-56:
- Handle `e` tags in kind 1984 reports: look up the torrent event's
  publisher and penalise them the same as a direct `p` tag report
2026-05-17 20:25:52 -07:00
enki a3db453942 feat: Blossom binary bridge (BUD-01)
- BlossomClient: unauthenticated fetch, kind 24242 signed upload
- Parser: capture `url` tag from NIP-35 events as blossom_url
- Reader: spawn background fetch + cache of blob when url tag present
- Writer: include `url` tag in published NIP-35 events when blob uploaded
- Watcher: upload .torrent file to Blossom before publishing, attach URL
- Migration 007: adds blossom_url + torrent_blob columns to torrents
- Route GET /torrent/<info_hash>.torrent serves cached blobs
- Config: blossom.enabled + blossom.server (disabled by default)
2026-05-17 19:09:42 -07:00
enki b15c399566 docs: drop completed phase checklist from README 2026-05-17 18:52:07 -07:00
enki b94a169714 docs: update README to reflect current feature state through Phase 4 + web UI 2026-05-17 18:51:08 -07:00
enki 184d42907b fix: upsert publisher row on torrent ingest, backfill migration
insert_torrent now upserts into publishers so torrent authors appear
in the publishers table immediately, making the name/nip05 join work
on the indexed and dashboard pages. Migration 006 backfills existing
torrent pubkeys on fresh DB opens.
2026-05-17 18:48:07 -07:00
enki fa7e6052fa feat: publisher profile enrichment
Fetch kind 0 metadata events for known publishers in background
batches (150 at a time, hourly). Store name, nip05, picture, about
in new columns (migration 005). UI now shows avatar + display name
instead of raw pubkeys across indexed, dashboard, and publishers views.
2026-05-17 18:18:08 -07:00
enki 93a75a9464 chore: remove Go archive 2026-05-17 16:08:09 -07:00
enki 4d23f89494 chore: expand .gitignore — exclude .claude/, db-wal, env files, secret configs 2026-05-17 15:51:22 -07:00
enki f595b8067f chore: update spec to reflect phases 0-4 done; fix axum 0.8 path syntax; add dev config 2026-05-17 15:35:39 -07:00
enki e6fb4f48c6 feat: web UI — dashboard, indexed browser, publisher manager, publish queue 2026-05-17 15:05:50 -07:00
enki f98e6f8dfa feat: Phase 4 — publisher, qBittorrent watcher, identity CLI
Adds the full writer/publisher stack: NIP-35 event signing and relay
delivery, qBittorrent polling with publish-delay queue, lava_torrent
.torrent file parsing, TMDB inline lookup before publish, and
kindexr-cli identity/publish subcommands.
2026-05-17 12:43:21 -07:00
enki b6705d5b85 phase 3: WoT graph, curation sets, reports, publisher CLI
- Migration 003: add wot_level/muted/report_count to publishers;
  create curation_items and reports tables
- WoT builder (wot/follows.rs): fetches operator kind-3 contact list
  to build depth-1 and depth-2 trust graph; ingests kind-10000 mute
  list; runs at startup and refreshes every 24h
- Trust scoring (wot/trust.rs): recomputes trust from wot_level and
  report_count after each WoT build
- Reader: subscribes to kind-30004 (curation sets) from configured
  pubkeys; subscribes to kind-1984 (reports) from anyone; WoT/block/
  mute check gates kind-2003 ingest when wot_only=true; auto-block
  publishers that hit report threshold
- Search: curated events (referenced by kind-30004) sort first in
  both FTS and full-scan paths
- Publisher CLI: list, info, block, unblock, trust, mute subcommands
- Fix .gitignore: /bin/ not bin/ (was shadowing src/bin/)
2026-05-17 11:31:59 -07:00
enki c8005d514d phase 2: type-specific search, title parser, TMDB enrichment
- tvsearch/movie/music/book endpoints with ID filters (tvdbid, imdbid,
  tmdbid), season/ep, and type-specific default category restrictions
- Title parser (enrich/parser.rs) extracts season, episode, quality,
  source from scene-format names; fills DB fields not supplied by tags
- TMDB enricher (enrich/tmdb.rs) runs as background task after insert
  when no TMDB ID in event; uses reqwest against TMDB v3 API directly
- Adult content exclusion at ingest via curation.exclude_categories
- Simplified server.rs routing: t= param now part of SearchQuery
- Remove unused tmdb-api alpha crate; use reqwest directly
2026-05-17 02:38:28 -07:00
enki 57cda1281b rewrite phases 0+1 in Rust; archive Go implementation
Move entire Go tree to archive/go/ preserving history. Add Rust
implementation: axum HTTP server, nostr-sdk relay reader, sqlx/SQLite
storage, Torznab caps+search endpoints, figment config, clap CLI.
Update spec.md tech stack and repo layout to reflect Rust. Add
docs/FIPS.md with Mode A/B/C deployment walkthrough. Add Phase 6
(FIPS deployment) to phase plan.
2026-05-17 02:23:26 -07:00
enki 1933306392 cleanup: pre-phase-2 fixes
- fix two stale nzbstr comment refs
- migration 002: drop api_keys.visibility and curation_set
- remove Visibility from APIKey struct, GetAPIKey, CreateAPIKey, CLI
- omit torznab size/seeders/peers attrs when data is absent
- reset relay backoff on successful connection (>= 30s uptime)
- use last_event from relays table as since on reconnect
- fix TestSearchWithResults to actually query the test server DB
2026-05-16 23:21:22 -07:00
enki f47157e340 docs: replace spec with updated kindexr design 2026-05-16 23:09:11 -07:00
enki 2e491e20c1 feat: Phase 1 — Nostr reader + Torznab API
Reader (internal/nostr):
- Connects to all configured relays via WebSocket
- Subscribes to kind 2003 (NIP-35) events since (now - backfill_days)
- Parses x/title/file/tracker/i/t tags into DB rows
- Reconnects with exponential backoff (5s → 5min) on disconnect

Torznab API (internal/torznab):
- GET /api?t=caps  — XML capabilities doc (exact shape Sonarr expects)
- GET /api?t=search — FTS5 full-text search over title+description
- All other t= types (tvsearch, movie, etc.) route to the same search handler
- API key auth via ?apikey= query param; 401 XML error on missing/bad key
- Magnet links built from info_hash + event tracker tags

DB (internal/db/queries.go):
- InsertTorrent with upsert + related rows (trackers, tags, files)
- Search with FTS5 + optional category filter (parent cat expands to range)
- GetAPIKey, CreateAPIKey, UpsertRelay, UpdateRelaySync/LastEvent

CLI (cmd/kindexr-cli):
- apikey create --label <name> [--visibility all|wot|curated]

Health endpoint now reports live relays_connected count.
2026-05-16 19:19:49 -07:00
enki 1b7b70426c feat: Phase 0 bootstrap — kindexr boots, migrates, serves /health
- config: koanf-based loading (defaults → YAML → KINDEXR_ env vars)
- db: embedded SQLite migrations with BEGIN/END-aware statement splitter
- server: chi router, GET /health returns JSON stats
- cmd/kindexr: graceful SIGTERM shutdown
- cmd/kindexr-cli: stub
- deploy: systemd unit, example config, nginx snippet
- all packages covered by race-clean tests
2026-05-16 18:45:15 -07:00