A terminal reader, ipx; the server is ipxd (#437)

ipx-tui (crates/tui) is iPX's reader in ratatui: your feeds, a feed's
items, an item's text as plain text, read and flagged as on the page,
through the API with an API token. It installs as `ipx`, so the daemon's
binary is now `ipxd`: the Dockerfile, release script, tests and every
doc and message that names its commands follow.

Naming the binary ipxd alone renamed its crate, and with it every log
target (ipx::scan became ipxd::scan): the default filters are ipx=info,
so nearly every line would have been dropped and the dashboard's queries
on the targets gone blank. The daemon's code is now a library still
called ipx, under a five-line ipxd main.rs, and the targets are as they
were.

ratatui is taken without its default features: 32 new packages in place
of 78.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqkrVDqdiQEMWAkNGQ2tYA
This commit is contained in:
2026-11-09 11:52:10 +00:00
parent f844cf349a
commit 711b0395f1
40 changed files with 1128 additions and 238 deletions

View File

@@ -10,15 +10,15 @@ processes must never download the same thing. `--local` forces the work to happe
| Command | What it does |
|---|---|
| `ipx list` | Subscriptions and their state |
| `ipx status` | Counts: feeds, pending, downloaded |
| `ipx fetch [FEED] [--force]` | Scan everything, or one feed. `--force` ignores the TTL |
| `ipx add <url> [--folder X] [--keywords a,b] [--list] [--category C]` | Subscribe; the id comes from the feed title. `--list` subscribes no one but keeps it in the catalogue, and so in what this server shares with the directory, when its last subscriber leaves; `--category` is the category the directory files it under when the feed names none. Run for a feed already in the catalogue, these list it or set its category |
| `ipx rm <feed>` | Unsubscribe; downloads and history are kept |
| `ipx import <file.opml>` / `ipx export <file.opml>` | Move subscriptions in or out. Import subscribes the first admin, as the shared web token does; in the web UI it subscribes whoever is signed in |
| `ipx reap [--dry-run]` | Run retention now |
| `ipx user <add\|list\|passwd\|rm>` | Accounts for the web UI |
| `ipx daemon [--web ADDR]` | Scheduler, control socket and the HTTP API (the page is ipx-web's) |
| `ipxd list` | Subscriptions and their state |
| `ipxd status` | Counts: feeds, pending, downloaded |
| `ipxd fetch [FEED] [--force]` | Scan everything, or one feed. `--force` ignores the TTL |
| `ipxd add <url> [--folder X] [--keywords a,b] [--list] [--category C]` | Subscribe; the id comes from the feed title. `--list` subscribes no one but keeps it in the catalogue, and so in what this server shares with the directory, when its last subscriber leaves; `--category` is the category the directory files it under when the feed names none. Run for a feed already in the catalogue, these list it or set its category |
| `ipxd rm <feed>` | Unsubscribe; downloads and history are kept |
| `ipxd import <file.opml>` / `ipxd export <file.opml>` | Move subscriptions in or out. Import subscribes the first admin, as the shared web token does; in the web UI it subscribes whoever is signed in |
| `ipxd reap [--dry-run]` | Run retention now |
| `ipxd user <add\|list\|passwd\|rm>` | Accounts for the web UI |
| `ipxd daemon [--web ADDR]` | Scheduler, control socket and the HTTP API (the page is ipx-web's) |
`add`, `rm` and `import` change the catalogue in a process of their own. With a daemon running they
then tell it to read the catalogue again; without that it kept its own copy and wrote it back at
@@ -29,11 +29,11 @@ its next change, undoing them. If it does not answer they say so: restart it.
Passwords are read from **stdin**, so they miss the shell history and any `ps` listing.
```sh
echo -n 'a good password' | ipx user add ray # local account
ipx user add ray@example.com --no-password # signs in through the proxy only
echo -n 'a good password' | ipx user passwd admin # change a password; signs the account out everywhere
ipx user list # who exists, and how each signs in
ipx user rm sam # account, subscriptions and read state
echo -n 'a good password' | ipxd user add ray # local account
ipxd user add ray@example.com --no-password # signs in through the proxy only
echo -n 'a good password' | ipxd user passwd admin # change a password; signs the account out everywhere
ipxd user list # who exists, and how each signs in
ipxd user rm sam # account, subscriptions and read state
```
The first account created is an admin; later ones are ordinary users. A database with no accounts
@@ -43,14 +43,14 @@ config.toml (or `IPX_ADMIN_PASSWORD`), announced in the log — change it.
To avoid even the command line, read it interactively:
```sh
read -s PW && echo -n "$PW" | ipx user passwd admin
read -s PW && echo -n "$PW" | ipxd user passwd admin
```
## Scanning
```sh
ipx fetch # everything due
ipx fetch atp --force # one feed, ignoring its TTL and schedule
ipxd fetch # everything due
ipxd fetch atp --force # one feed, ignoring its TTL and schedule
```
A scan: conditional GET (`If-None-Match` / `If-Modified-Since`), parse, record new entries, apply
@@ -60,8 +60,8 @@ answers `304` and costs one request.
## Retention
```sh
ipx reap --dry-run # what would go, oldest first
ipx reap # actually delete
ipxd reap --dry-run # what would go, oldest first
ipxd reap # actually delete
```
Files are deleted to get back under `max_total_gb`, oldest first, and items past `max_age_days`
@@ -72,8 +72,8 @@ stops the next scan fetching it again.
## The daemon
```sh
ipx daemon # scheduler + socket + HTTP API
ipx daemon --web 0.0.0.0:8099 # override the configured bind for one run
ipxd daemon # scheduler + socket + HTTP API
ipxd daemon --web 0.0.0.0:8099 # override the configured bind for one run
```
One daemon per socket; a second refuses to start rather than fight over the database. It shuts down