README: a short overview that points into docs/
It described the layout from before 0.4.0 (items across the top, a player below), and carried long sections on OPML, the log view and this server's own deploy steps, all of which docs/ and CLAUDE.md cover. It now says what ipx does, how to run it with Docker or from source, the first sign-in, the TLS caveat, where the docs are, and the tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0173mGu6rK18Ne7UGTwAaVJV
This commit is contained in:
@@ -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
|
||||
|
||||
144
README.md
144
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 '<password>' | 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.
|
||||
|
||||
Reference in New Issue
Block a user