diff --git a/CHANGELOG.md b/CHANGELOG.md index 62f594e..48d2c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ The long form, with what was wrong before and how it was found, is in ## [Unreleased] +### Changed + +- The README is a short overview of what ipx does and how to run it, and points into `docs/` for + the rest. It still described the layout from before 0.4.0. + ## [0.4.0] - 2026-09-11 ### Added diff --git a/README.md b/README.md index 079473a..33f6260 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,83 @@ # ipodderx-rs -A headless podcatcher: scans RSS/Atom feeds, downloads enclosures (HTTP and BitTorrent), files them -into per-feed folders, and reaps old files to stay under a disk quota. Runs as a one-shot CLI or as -a daemon with a web UI, serving any number of people from one copy of the data. +A self-hosted podcatcher for a household. It checks your feeds, downloads the episodes, and serves +a web UI modelled on the 2004 Mac app **iPodderX**, for any number of people sharing one copy of +the files. One Rust binary, `ipx`, is both the daemon and the command line. -## Lineage +It is a rewrite of [ipodderx-core](https://git.sdf1.net/rays/ipodderx-core), the Python engine +behind iPodderX (2004-2008, Ray Slakinski & August Trometer). -A modern Rust rewrite of [ipodderx-core](https://git.sdf1.net/rays/ipodderx-core), the Python 2 -engine behind **iPodderX** (2004-2008, Ray Slakinski & August Trometer), open-sourced under the MIT -License in 2010. +## What it does -What carries over: the feed scan and TTL handling, GUID/URL dedupe, per-feed and per-date download -folders, keyword filters, the explicit-content filter, torrent enclosures, and "SmartSpace" -- the -oldest-first disk quota reaper. +- **The web UI.** It has a toolbar, and a feed list that opens with Directory, Popular and All + Subscriptions. Items sit in a sortable table with a Files pane, and there is a player bar. It + comes in Dark, Light and Classic themes, and works on a phone. +- **Several people, one copy.** Each person has their own subscriptions and their own read, kept + and playback state. There is one file on disk per episode, however many people want it. People + sign in with a password or through a proxy (Cloudflare Zero Trust or Authentik), and admins + manage accounts and settings. +- **Scanning.** Feeds are checked on a schedule, globally or per feed, and a feed's own TTL is + honoured. Keyword, explicit-content and media-type filters decide what is downloaded, with a cap + on new downloads per scan. +- **Downloads.** Files come over HTTP or BitTorrent and are filed into a folder per feed. + Retention deletes the oldest files to stay under a disk quota or an age limit, and never touches + an item someone has kept. +- **OPML.** You can import and export your own subscriptions. You can also subscribe to an OPML + URL, which keeps a whole list in step as a folder. -What does not: iTunes and iPhoto export via AppleScript, text-to-speech enclosures, the Windows -WMP/COM paths, XML plists and Python pickles for state, the `directory.iPodderX.com` survey ping, -3DES-encrypted preferences, and the `printMSG` stdout protocol -- replaced by a JSON-lines socket. +## Run it -## Quick start +With Docker: + +```sh +docker build -t ipodderx . +docker compose up -d +``` + +`docker-compose.yml` is set up for the author's own server. Point its `image` and its three volumes +(`/config`, `/data` and `/downloads`) at yours first. The UI is on port 8099. BitTorrent uses 6881 +over TCP and UDP. Files are written as `PUID`/`PGID`, 99:100 by default. + +From source: ```sh cargo build --release -install -m755 target/release/ipx ~/.cargo/bin/ - -ipx add https://atp.fm/rss # subscribe -ipx fetch # scan and download -ipx daemon # scheduler, control socket and web UI +./target/release/ipx daemon ``` -On first start with `[web] enabled = true` the daemon mints a token, writes it to config.toml and -prints the URL to open. A database with no accounts starts with **admin / ipodderx** at `/login` -- -change it with `echo -n '' | ipx user passwd admin`. +The first start creates **admin / ipodderx**. Sign in at `/login`, then change it: + +```sh +echo -n 'a good password' | ipx user passwd admin +``` + +The UI is plain HTTP, so put TLS in front of it if it is reachable from outside your network. ## Documentation | | | |---|---| -| [docs/configuration.md](docs/configuration.md) | Every config key, paths, environment variables | +| [docs/configuration.md](docs/configuration.md) | Every config key, path and environment variable | | [docs/cli.md](docs/cli.md) | Every command, including `ipx user` | | [docs/users.md](docs/users.md) | Accounts, and what several people share | -| [docs/sso.md](docs/sso.md) | Cloudflare Zero Trust or Authentik in front | -| [docs/architecture.md](docs/architecture.md) | How it works: modules, schema, socket, HTTP API | +| [docs/sso.md](docs/sso.md) | Signing in through Cloudflare Zero Trust or Authentik | +| [docs/architecture.md](docs/architecture.md) | How it works: modules, schema, control socket, HTTP API | | [CHANGELOG.md](CHANGELOG.md) | What changed, by release | -| [docs/history.md](docs/history.md) | How it was built: the long form, with what was wrong and why | -| [CLAUDE.md](CLAUDE.md) | Notes for anyone (or anything) working on the code | - -## The web UI - -`ipx daemon` serves it in the same process, so it reads SQLite and the event bus directly. - -Feeds down the side; the selected feed's items across the top; the selected item's text and its -enclosures below, which is where you play, download or delete them. The divider drags and its -position is remembered. Playback serves Range requests, so seeking works. An OPML subscription is a -collapsible folder whose page lists the feeds inside it. - -An item may carry several enclosures; all of them appear below, and anything that is not audio or -video gets a View link rather than a player -- the publisher's copy until it is downloaded, the -local one after. Opening an item marks it read. Show notes are untrusted feed HTML, sanitized with -`ammonia` server-side before they reach the page. - -The **Log** button shows the running daemon live in four tabs: *Daemon I/O* is the control protocol -itself, every command in and event out; *Scans* is feed and download activity; *HTTP* is web -requests; *All* is everything, with level and text filters and a copy button. It reads a ring buffer -held in the process, not a file, so it works the same under Docker. - -It is plain HTTP. On a LAN bind the token and everything else cross the network in the clear, and a -feed URL can itself carry a credential. Put TLS in front of it if that matters. - -## OPML - -**Importing and exporting** a file copies subscriptions in or out once: `ipx import subs.opml`, -`ipx export subs.opml`, or Settings → Subscriptions in the UI. - -**Subscribing to an OPML URL** is a live subscription, as iPodderX had. Add the OPML's URL like any -other feed; every scan re-reads it and keeps your list in step. The feeds inside are not written to -config.toml -- the OPML is the source of truth, so they are re-derived each scan and held in the -database. They show as a folder, download into one nested folder, and inherit the subscription's -settings until you change one, which gives it its own entry. - -When a feed drops out of the OPML upstream, it is unsubscribed and removed -- unless it has -downloads, in which case it is kept and flagged in the UI as no longer listed. A downloaded file is -never left behind with nothing explaining where it came from. - -## Docker - -```sh -docker buildx build --tag 192.168.1.130:5000/ipodderx:latest . --push -docker compose pull ipodderx && docker compose up -d ipodderx -docker compose logs -f ipodderx # the first start prints the default admin password -``` - -`docker-compose.yml` runs the image from the registry above rather than building it, so build and -push first; change the tag in both places to use another registry. It mounts `/config` (config.toml), -`/data` (state.db) and `/downloads` from this install's host paths, which you will want to change for -yours. It publishes 8099 for the -UI and 6881 (TCP **and** UDP -- DHT needs the UDP side), and sets `PUID`/`PGID` to `99:100` so files -land owned the way Unraid shares expect. The healthcheck runs `ipx status` through the control -socket, so it catches a daemon that is alive but wedged rather than merely one that has died. - -## Running it as a service - -`contrib/` has a systemd user unit for the daemon, and a timer plus one-shot service if you would -rather run periodic scans with no daemon -- in which case there is no socket for a UI to attach to. +| [docs/history.md](docs/history.md) | How it was built, with what was wrong and why | +| [CLAUDE.md](CLAUDE.md) | Notes for working on the code, including how production is deployed | ## Tests ```sh cargo test # the engine: parsing, filters, retention, schedules, SQL, per-user state node tests/page-smoke.js # the page script loads without throwing -npx playwright test # a real browser against a real daemon +npx playwright test # a real browser against a real daemon on fixture feeds ``` -`npm install` gets the test runner; the browser comes from -`npx playwright install --with-deps chromium` (in `install.sh`). +`npm install` gets the test runner, and `npx playwright install --with-deps chromium` gets the +browser. ## License -MIT. See [LICENSE](LICENSE). - -The icons are [Font Awesome Free](https://fontawesome.com) 7.3.1 by @fontawesome, under -[CC BY 4.0](https://fontawesome.com/license/free), embedded as SVG in `web/index.html`. +MIT, see [LICENSE](LICENSE). The icons are [Font Awesome Free](https://fontawesome.com) 7.3.1 by +@fontawesome, under [CC BY 4.0](https://fontawesome.com/license/free), embedded as SVG.