With no network the home-screen app showed nothing: no service worker, and the lists the page had fetched, full text included, lived only in its memory. A service worker (web/sw/sw.ts, TypeScript checked against a worker's globals by tsconfig.sw.json, built by swc to /sw.js) goes to the network first for the page and every API read and keeps the answers, so online nothing is an old copy and #388's version check works as before; offline the kept copy answers. Scripts named by hash come from what is kept. Reads, flags and places in an episode that cannot reach the server wait in an outbox, sent in order when the connection is back (and new ones queue behind them meanwhile, so none lands out of turn); a kept list served offline shows the reads waiting there. Pictures: a worker cannot keep another site's pictures sensibly, since browsers count each opaque answer as megabytes. ipx now remembers the picture addresses in item text it sends (bounded, in memory) and /api/art fetches those as it does artwork, through the guarded client; the worker keeps them, for unread items, under a 200 MB budget, and serves them offline in place of the publisher's. Online, pictures still come from their publisher. Sign-out, or another account signing in on the device, clears what was kept. The browser suite blocks the worker except in the offline tests, since it answers before page.route can. The fixture JPEGs are placeholders a browser cannot draw; the offline test uses a real PNG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ipx
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.
It is a rewrite of ipodderx-core, the Python engine behind iPodderX (2004-2008, Ray Slakinski & August Trometer).
What it does
- The web UI. It has a toolbar, and a feed list that opens with the Directory, Currently Listening 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, pinned 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 limit on how many of a feed's newest episodes are downloaded.
- 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 pinned.
- 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.
Run it
With Docker:
docker build -t ipx .
docker build -f Dockerfile.web -t ipx-web .
docker compose up -d
docker-compose.yml is a sample: ipx, with its database in SQLite and its three volumes
(/config, /data and /downloads) in folders beside the file, and ipx-web, the page, served by
Caddy in front of it. Change them to suit. 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:
cargo build --release
./target/release/ipx daemon
The first start creates admin with a random password, written to admin-password beside
config.toml (readable by ipx's user alone; the log says where, never what it is). Set
IPX_ADMIN_PASSWORD to choose it instead. Sign in at /login, then change it:
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 | Every config key, path and environment variable |
| docs/cli.md | Every command, including ipx user |
| docs/users.md | Accounts, and what several people share |
| docs/sso.md | Signing in through Cloudflare Zero Trust or Authentik |
| docs/architecture.md | How it works: modules, schema, control socket, HTTP API |
| crates/ipx/CHANGELOG.md, web/CHANGELOG.md, crates/directory/CHANGELOG.md | What changed, by release, for ipx, the page and the directory |
| CLAUDE.md | Notes for working on the code, including how production is deployed |
Tests
cargo test # the engine: parsing, filters, retention, schedules, SQL, per-user state
node tests/page-smoke.js # the page script loads without throwing
node tests/native-bridge.js # the page hands playback to a native shell
npx playwright test # a real browser against a real daemon on fixture feeds
npm install gets the test runner, and npx playwright install --with-deps chromium gets the
browser.
License
MIT, see LICENSE. The icons are Font Awesome Free 7.3.1 by @fontawesome, under CC BY 4.0, embedded as SVG.