The page loaded artwork from each publisher's server, or through /api/art, fetched every time, for http-only hosts. Nothing was kept, every visit asked every publisher, and artwork went when a publisher's server did. - The page draws every image a feed or item names from /api/art. The first time, iPX fetches it (only an address a feed or item names, only an image, up to 5 MB, within the feed timeout) and keeps it in art/ beside the database, under a hash of its address with its type beside it (src/art.rs). Later it comes from disk, which marks it as used. - art_cache_mb, a server setting on the admin page, 500 by default, caps what is kept: the sweep before each scan drops the least recently shown until it fits. 0 keeps nothing, and artwork is fetched through iPX each time. Settings saved before it get the default. - Served from iPX's own address, someone else's image must stay an image: nosniff, and a CSP with sandbox, so an SVG opened on its own runs no script as iPX. - The fixture server sends .jpg as image/jpeg, which /api/art requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.4 KiB
Configuration
Two places. config.toml holds what ipx needs before it reaches its database, and what decides
who gets in: where things are (download_dir, socket, organize), [torrent] and [web].
The database holds the catalogue of feeds ([feeds.<id>] below) and the server settings the
admin page edits (schedule, max_total_gb, max_age_days, max_new_per_check,
media_types, art_cache_mb). Change those in the web UI, or with ipx add, ipx rm and ipx import; they
take effect without a restart.
The first time ipx meets a database that holds no catalogue, it takes the feeds and those
settings from config.toml, then rewrites config.toml without them, keeping the original beside it
as config.toml.pre-database. After that, feeds or those settings written into config.toml are
ignored, with a warning in the log saying so. The sections below describe them as they were
written in config.toml, which is still how a fresh install begins.
config.toml's default location is $XDG_CONFIG_HOME/ipx/config.toml
(~/.config/ipx/config.toml), overridden with --config or $IPX_CONFIG.
| What | Where | Override |
|---|---|---|
| Config | ~/.config/ipx/config.toml |
--config, $IPX_CONFIG |
| Database | ~/.local/share/ipx/state.db |
$IPX_DATA_DIR |
| Control socket | $XDG_RUNTIME_DIR/ipx.sock |
[general] socket |
| Downloads | [general] download_dir |
— |
~ is expanded in paths. The database is SQLite in WAL mode unless IPX_DATABASE_URL names a
Postgres database instead. Back SQLite up by copying state.db while the daemon is stopped, or
with sqlite3 state.db .backup; back Postgres up with pg_dump.
[general]
[general]
download_dir = "~/Podcasts"
socket = "/run/user/1000/ipx.sock"
schedule = "every 1h" # "every 30m", "every 4h", "2d", "90" (minutes)
organize = "feed" # "feed" | "date"
max_total_gb = 50 # 0 = unlimited
max_age_days = 30 # 0 = keep forever
max_new_per_check = 3 # per feed, per scan. 0 = unlimited
media_types = ["audio", "video"]
art_cache_mb = 500 # artwork kept on disk. 0 = none
schedule, max_total_gb, max_age_days, max_new_per_check, media_types and art_cache_mb move into the
database as described above; download_dir, socket and organize stay in config.toml.
schedule— how often feeds are re-checked. A feed's own<ttl>still wins when it asks to be polled less often, and a per-feedscheduleoverrides both. A feed that keeps failing waits as long as it has been failing before the next try, up to a day, and is back on schedule after its first success. Admin-only from the UI.organize—feedfiles downloads under the feed's folder;dateunderYYYY-MM-DD.max_total_gb— the reaper deletes to get back under this, oldest first, keeping a 50 MB pad. Kept items are never deleted, and a file only counts as read once every subscriber has read it.0disables it entirely.max_age_days— items older than this with no file on disk are pruned from the database. Kept ones stay.0disables it.art_cache_mb— how much show and episode artwork iPX keeps on disk, inart/beside the database, so the page loads it from iPX rather than from every publisher's server. Over this, what has gone longest unshown goes first.0keeps none: artwork still comes through iPX, fetched each time. 500 by default.max_new_per_check— how many of a feed's newest episodes are downloaded; older ones stay listed to download by hand. It stops a new subscription pulling a whole back catalogue.0means every episode, for an archive; set it on the feeds you want archived, since on the global default it applies to every feed: subscribing to an OPML of 80 feeds with no limit fetched 216 files and 22 GB in one scan.media_types— top-level MIME types taken automatically. Anything else is still listed and can be fetched by hand; blog feeds put each article's header image in an<enclosure>, and without this the disk fills with artwork. Empty takes everything.
[torrent]
[torrent]
enabled = true
seed_ratio = 1.0 # stop seeding at this ratio ...
seed_time_mins = 60 # ... or after this long, whichever comes first
port_range = "6881-6889"
stall_mins = 30 # give up on a torrent making no progress
A .torrent body is handed to the torrent path whatever MIME type it was advertised as. Torrents
run on their own tasks (two at a time) so a slow swarm never blocks a scan.
[web]
[web]
enabled = true
bind = "0.0.0.0:8099" # 127.0.0.1:8080 by default
token = "" # generated and saved on first run
trusted_header = "" # e.g. "Cf-Access-Authenticated-User-Email"
trusted_proxies = ["127.0.0.1", "::1"]
access_team = "" # e.g. "<team>.cloudflareaccess.com"
access_aud = "" # the Access application's AUD tag
auto_create_users = true
sign_out_url = "" # e.g. "/cdn-cgi/access/logout"
session_days = 30
token— the shared secret, which signs in as the admin.?token=…sets a cookie, so you paste it once per browser. It is what the Docker healthcheck and any scripts use.trusted_header— a header naming the signed-in user, set by whatever fronts ipx. Empty disables that path. See sso.md.trusted_proxies— addresses allowed to assert that header, and the entire security boundary for it. Name the proxy, never a subnet.access_team,access_aud— with both set, a request through the proxy also has to carry theCf-Access-Jwt-AssertionCloudflare Access signed for this application, and the name comes from that token instead of the header. See sso.md.auto_create_users— create an account the first time the proxy vouches for a new name.sign_out_url— where Sign out sends someone the proxy signed in: the proxy's own sign-out,/cdn-cgi/access/logoutbehind Cloudflare Access. Empty sends them to the sign-in page, where the proxy signs them straight back in.session_days— sign a session out after this long without a request.
It is plain HTTP. On a LAN bind everything crosses the network in the clear — and a feed URL can itself carry a credential. Put TLS in front of it if that matters.
[feeds.<id>]
Kept in the database once ipx has moved them in: a feed's settings are changed in the web UI, and
feeds come and go with ipx add, ipx rm and ipx import. The table key is the feed id: stable,
human-readable, and used in paths and the API. ipx add derives it from the feed title.
[feeds.atp]
url = "https://atp.fm/rss"
folder = "Accidental Tech Podcast" # default: the feed title
schedule = "every 6h" # overrides [general] for this feed
media_types = ["audio"] # overrides [general] for this feed
category = "Technology" # the Directory's, if the feed names none
listed = true # in the Directory with no subscribers
username = "ray" # HTTP basic auth
password_env = "IPX_ATP_PASS" # preferred over a literal `password`
With more than one account, keywords, auto_download, allow_explicit and
max_new_per_check live on each person's subscription in the database, not here — the values in
config.toml are the fallback for a feed nobody has claimed. The keys above describe the feed itself
and are the same for everyone. See users.md.
The Directory lists every feed in the catalogue, whether or not anyone subscribes to it yet.
To put one there for others to find, ipx add --list --category News <url>: it subscribes no one,
and a listed feed stays when its last subscriber leaves, where another is dropped. A feed nobody
subscribes to is checked once a day. One that is dead (failing for 30 days) or quiet (nothing
new in a year), with nobody subscribed and no file on disk, is removed from the catalogue.
Feeds derived from a subscribed OPML are not in the catalogue: the OPML is the source of truth and they are re-derived on every scan. Editing one in the UI promotes it to a catalogue entry.
Environment
| Variable | Effect |
|---|---|
IPX_CONFIG |
Config file path |
IPX_DATA_DIR |
Directory holding state.db |
IPX_DATABASE_URL |
A postgres://user:password@host:port/database URL: use that database instead of state.db |
IPX_TEST_DATABASE_URL |
For cargo test: run the database tests on this Postgres database too, each in a schema of its own |
IPX_LOG |
What reaches stderr (ipx=debug, ipx::scan=debug, …) |
IPX_LOG_FORMAT |
json for one JSON object a line, with each request's and event's fields as its own (for Loki and the like); text otherwise |
IPX_UI_LOG |
What the in-process log buffer captures for the UI's Log view |
OTEL_EXPORTER_OTLP_ENDPOINT |
An OTLP/HTTP collector, such as Tempo at http://host:4318: the daemon sends it traces of scans, downloads and web requests. The other OTEL_EXPORTER_OTLP_* variables apply too |
http_proxy / https_proxy |
Honoured for feed and enclosure fetches |