Files
ipx/docs/cli.md
rays cc3b8742bc The first admin gets a random password, not admin / ipodderx (#321)
A database with no accounts created admin with the hard-coded password
ipodderx, published in the README, the docs and the sample compose
file, and the log line announcing it printed the password. /api/login
is open to anyone who reaches the port, and nothing made anyone change
it, so a fresh install was an admin account anyone could sign into.

Now the password is IPX_ADMIN_PASSWORD when set, for scripted installs,
or 24 random hex characters written to admin-password beside
config.toml, created 0600 so it is never readable by others even for a
moment. The log says where it is, never what it is, as it does for the
web token (#40). Existing databases are untouched: seeding runs only
with no accounts at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:55:34 +00:00

4.3 KiB

Command line

ipx [--config PATH] [--local] <command>

Every command that has a wire form probes the control socket first: if a daemon is running, the daemon does the work and the CLI just renders the events it streams back. That is deliberate — two processes must never download the same thing. --local forces the work to happen in-process.

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)

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 its next change, undoing them. If it does not answer they say so: restart it.

Accounts

Passwords are read from stdin, so they miss the shell history and any ps listing.

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
ipx user list                                         # who exists, and how each signs in
ipx 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 at all gets admin on the next daemon start, with a random password in admin-password beside config.toml (or IPX_ADMIN_PASSWORD), announced in the log — change it.

To avoid even the command line, read it interactively:

read -s PW && echo -n "$PW" | ipx user passwd admin

Scanning

ipx fetch                      # everything due
ipx 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 the filters, then download up to the per-scan cap, newest first. A feed nothing has changed in answers 304 and costs one request.

Retention

ipx reap --dry-run             # what would go, oldest first
ipx reap                       # actually delete

Files are deleted to get back under max_total_gb, oldest first, and items past max_age_days with no file are pruned from the database. An item anyone kept keeps its file, and one only counts as read when everyone subscribed has read it. The enclosure row survives as reaped, which is what stops the next scan fetching it again.

The daemon

ipx daemon                     # scheduler + socket + HTTP API
ipx 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 cleanly on SIGTERM, including mid-download.

To kill it, match the binary exactly:

pkill -x ipx

pkill -f ipx matches the shell running the command too, and kills your own session. On a machine that also runs ipx in a container, pkill -x ipx stops that one as well, since the host sees a container's processes: stop the one you started by its PID instead (kill <pid>).

Talking to it directly

printf '{"cmd":"fetch","force":true}\n' | socat - UNIX-CONNECT:$XDG_RUNTIME_DIR/ipx.sock

See architecture.md for the protocol.