feat(nas): auto-download Audible purchases with headless Libation #362
No reviewers
Labels
No labels
bug
enhancement
in-progress
needs-info
needs-triage
p0
ready-for-agent
ready-for-human
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/nixos!362
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/360"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Runs Libation headlessly on
nasso newly purchased Audible books land in the audiobookshelf library at/var/lib/multimedia/audiobooks/Libation/<author>/<series>/<title>.Closes #360
What changed
hosts/nas/modules/libation.nix(new) —libation-synconeshot (scanthenliberate) on a 30-min timer, alibationsystem user in thejellyfingroup, tmpfiles for the books and in-progress dirs, and a root-onlylibation-cliwrapper that does the user/env dance for one-off ops.hosts/nas/modules/auto-shutdown.nix— check 5 blocks poweroff while a sync is in flight but, unlike checks 1–4, does not reset the idle streak. A 30-min timer resetting a 60-min streak would keep the box from ever powering off.hosts/nas/configuration.nix— imports the module and persists/var/lib/libation(tokens +LibationContext.db; losing them means a full re-download and a fresh browser login).Settings.jsonis regenerated from a Nix template on every run. That is not belt-and-braces:LibationClireads settings ephemerally and never writes them back, so re-seeding is what makes the config declarative.One-time bootstrap (after this deploys)
systemctl start libation-sync. It creates/var/lib/libation+Settings.jsonand exits 0 withno Audible account yet.--response-url \"<url>\"instead, or it hard-fails with exit 3.systemctl start libation-sync && journalctl -u libation-sync -f./var/lib/multimedia/audiobooks/Libationas a library folder — nothing in this PR configures ABS.Caveat worth knowing:
login-externalwrites the account row before it can fail, so a half-finished login leaves a token-less account behind andscanwill then report accounts, process 0 books, and exit 0. If nothing downloads, re-run the login rather than trusting the exit code.Corrections to the issue's research
Verified against libation 13.3.6 as actually packaged in nixos-26.05 (built it, ran it, read the tagged upstream source):
nullfalls back. The tag spellings in the issue are correct and were confirmed to round-trip viaget-setting; the risk of a typo is just worse than described, hence the comment pinning them.meta.mainProgramislibation, the GUI —lib.getExe pkgs.libationwould silently wire up the Avalonia app. The module uses the explicitlibationclipath.AutoScanis GUI-only. Nothing inLibationClireads it. Dropped rather than shipped as dead config; the systemd timer is what drives scanning.Settings.jsonabsent, every verb exits 255, before the "No accounts" exit-3 path is ever reached. That is why the sync writesSettings.jsonfirst and checks for the account second — the reverse order would never get past the first deploy.liberateexits 0 regardless — per-book failures are logged and swallowed, and it exits 0 even withBooksunset. Its exit code is useless for monitoring.Deliberate deviations from the issue
1.
InProgresssits on the media volume, notCacheDirectory=/var/cache/libation.This host's
/is an 8 GiB tmpfs, so/var/cacheis RAM. Libation holds both the encrypted download and the decrypted output underInProgress(~2× book size), and filling the root filesystem wedges the whole box rather than just failing the download./var/lib/multimedia/.libation-inprogressis on the same ext4 as the books dir, so the finished file is renamed into place instead of copied across filesystems — ABS never sees a partial file. Reverting is a one-line change to theletbinding plusCacheDirectory = \"libation\".Two consequences:
DownloadsInProgress(partials are deliberately kept as resumable), so moving it off tmpfs turns a boot-cleared scratch area into an unbounded one. The tmpfiles rule carries a30dage to bound it.DecryptInProgressis self-limiting — Libation clears it on every invocation./var/lib/multimedia, so auto-shutdown check 2 also covers them and does reset the clock — correct, an active download is real activity, exactly like pyload. Check 5 still covers the scan and move phases, where no such fd is held.2. A
Serilogblock was added toSettings.json(not in the issue).Without one, Libation builds a sink-less logger and discards its own error events entirely. Combined with
liberatealways exiting 0, a failing download would be completely invisible. The block routes to the bundled Console sink so journald — and therefore alloy → Loki — receives it, withstandardErrorFromLevelsplitting diagnostics onto stderr.MinimumLevelisWarningboth to keep stderr meaningful and to suppress a duplication quirk (Libation wrapsConsole.Outin a Serilog writer, so atInformationevery plain line is emitted twice).Worth knowing before editing that value: an invalid
MinimumLevelis fatal — Serilog throws insideConfigureLogging()and libationcli dies with exit 134 before doing any work.\"Info\"would brick the unit. The accepted set is in a comment at the line.Verification
scripts/test-configuration nasvia the pre-commit hook: OK.libationcli get-settingon the real 13.3.6 binary;Serilog.Sinks.Console.dllconfirmed present in the nixpkgs build.bash -n; generatedSettings.jsoninspected as JSON (</>survivebuiltins.toJSONunescaped).Not verified