Commit Graph
14 Commits
Author SHA1 Message Date
renovate-bot 68c0978660 chore(deps): update all non-major dependencies
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
2026-10-05 02:01:26 +00:00
claude-botandClaude Opus 5.5 2dde64d1b6 chore(audiobookshelf): drop the replicated books PVC
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Books live on audiobookshelf-books-bulk since #955; nothing mounts
audiobookshelf-books any more. The PVC has prune disabled, so removing
it from git does not delete it; the PVC, its Retain PV and the CephFS
subvolume are deleted by hand after merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:05:14 +02:00
claude-botandClaude Opus 5.5 77d1e050f0 feat(audiobookshelf): move books to the EC bulk pool
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Points the audiobookshelf Deployment and the libation CronJob at
audiobookshelf-books-bulk (ceph-filesystem-bulk, EC 2+1). The data was
copied with rsync -aH from the old PVC and verified: 574 files,
87315610958 bytes on both sides.

audiobookshelf-books stays in pvcs.yaml (prune disabled) until the
new volume is confirmed in use; its removal is a separate change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:48:11 +02:00
claude-botandClaude Opus 5.5 5bca64898b feat(libation): strip the Audible intro/outro from downloads
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Enables StripAudibleBrandAudio (Libation trims the branding using Audible's
own chapter offsets, lossless) and OverwriteExisting, so a re-liberated book
replaces its file instead of landing next to it as 'Title (1)'.

Settings.json on the PVC is seeded once and the entrypoint copies it into
ephemeral /config-internal each run, so the flags are applied there with jq
(already in the image; the entrypoint uses it for Books/InProgress).

Only affects books decrypted from now on; existing files need a re-liberate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:38:35 +02:00
claude-botandClaude Opus 5 92ac021931 feat(libation): seed the token master key onto the PVC
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
TokenStorageMethod is Encrypted and the container has no OS secret store,
so LibationCli falls back to minting a portable master key at
/config-internal/libation-master.key -- a path that is not a mounted
volume. The key is therefore new on every run and discarded at exit,
which is why the entrypoint logs LAST-RESORT on each pass.

The entrypoint looks for the key at ${LIBATION_CONFIG_DIR}/
libation-master.key, i.e. on the PVC, and copies it into place when
found. The key is now stored in OpenBao alongside the other bootstrap
files, so seed it like them: without it a rebuilt PVC would hold tokens
encrypted under a key that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:47:24 +02:00
claude-botandClaude Opus 5 5bbca6a162 fix(libation): persist refreshed Audible tokens back to the PVC
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
The image entrypoint stages config into /config-internal, which is not a
mounted volume, and it does so by copy:

    init_config_file() {
      ...
      cp ${FULLPATH} ${LIBATION_CONFIG_INTERNAL}/

Only LibationContext.db is symlinked to the PVC. AccountsSettings.json is
therefore read from the PVC at startup and written back nowhere, so every
token refresh Libation performs is discarded when the container exits. On
the live PVC the file still carried its 2026-05-14 seed timestamp, four
months after deployment, while the DB beside it was current.

Pass args instead of letting the entrypoint run its own loop. The
entrypoint completes its setup and then execs what it is given, so the
staged environment is intact; an EXIT trap copies the refreshed file back
to the PVC whether the run succeeds or fails.

This was found while recovering from Audible refusing content licenses
for this account. Re-registering the account fixed the refusals; without
this change the fresh registration would have been discarded on the next
run exactly like every refresh before it.

Also corrects the init container comment, which claimed the sentinel
preserved token refreshes. It preserves the DB; it never preserved the
tokens.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 18:26:49 +02:00
claude-botandClaude Opus 5 260f548e67 fix(libation): pin image to 14.2.1 by digest
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
The CronJob ran `rmcrackan/libation:latest` with imagePullPolicy Always
but was stuck on 13.7.11 (2026-08-24). Spegel answers containerd's tag
resolution from its peer cache -- a 200 from the mirror is a hit, so
upstream is never consulted and the tag never advances. Verified: the
pod's imageID was sha256:946177d3 (= tag 13.7.11) while Docker Hub's
:latest pointed at sha256:6f9d8ba1 (= 14.2.1), and querying a Spegel
pod's registry port directly returned the stale digest for that tag.

Audible changed device registration in early September; upstream shipped
an experimental fix in 14.1 and the real fix in 14.2, so the stale image
predates both. Since 2026-09-15 three newly purchased titles fail with
"Content license denied" and the library has not grown.

A digest reference is immune to the mirror's tag cache. Note that the
upstream maintainer's remedy also requires removing and re-adding the
Audible account with an interactive login, which this change does not
and cannot perform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 17:30:37 +02:00
claude-botandClaude Fable 5 a76b18a887 feat(secrets): migrate non-critical app secrets to OpenBao (apps batch)
evcc, otbr, libation, external-dns (apps), monitoring (4), woodpecker:
values in OpenBao KV (homelab/apps/*, loaded 2026-07-13), manifests are
now ExternalSecrets keeping the old Secret names. libation's payload is
a binary SQLite db — stored base64 in KV with decodingStrategy: Base64
on the ES. Decryption blocks dropped from apps/evcc/otbr/libation/
monitoring KS (paths sops-free) plus the dormant one on grafana-db.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRgBFn9m8a2ncFMLusikH6
2026-07-13 19:21:27 +02:00
k.rennerandClaude Opus 4.7 3c0720602c libation: drop the HA notification sidecar
Pulling the notifier back out — the channel itself worked (CNPs and
token were correct) but the notifier mounted libation-state read-only
and SQLite needs the directory writable for its WAL/journal even on a
SELECT. The pod failed at the sqlite3 open and the Job hit backoff
faster than logs could be retrieved.

Reverting all four notification-related additions (sidecar, HA token
secret in audiobookshelf ns, cross-namespace ingress allow on HA,
egress allow on libation). The libation CronJob goes back to its
plain "download books, exit" form; ABS's file watcher still picks up
each new folder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:44:56 +02:00
k.rennerandClaude Opus 4.7 074fcddb89 libation: add missing egress allow for HA notify path
The audiobookshelf default-deny-ingress CNP carries an egress allowlist
that doesn't include home-assistant, so libation's notifier SYN was
hanging unanswered. Adding a layered CNP that selects libation pods
and adds home-assistant:8123 to their permitted egress targets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:50:55 +02:00
k.rennerandClaude Opus 4.7 834169883e libation: HA notification sidecar for newly-downloaded books
After each scan+liberate pass, a notifier sidecar queries the libation
SQLite DB for books whose UserDefinedItem.BookStatus flipped to
Liberated during the run window and POSTs the titles to Home Assistant.
LastDownloaded is the truth column — only set on successful download,
untouched on license-denied attempts — so the query naturally skips
the 3 known-denied books that re-attempt every run.

Cross-namespace plumbing:
- ha-api-token.sops.yaml: clone of the existing home-assistant-api-token
  secret in the audiobookshelf namespace (k8s secrets are namespace-
  scoped, no reflector deployed).
- ha-ingress-policy.yaml: CiliumNetworkPolicy in the home-assistant
  namespace allowing tcp/8123 ingress from the libation pod. Owned by
  the libation Kustomization so decommissioning libation removes the
  allow rule as well.

HA_NOTIFY_SERVICE defaults to notify/persistent_notification (always
exists in HA core); switch to notify/mobile_app_<device> via kustomize
patch to push to a phone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:39:36 +02:00
k.rennerandClaude Opus 4.7 93db19a117 libation: refresh bootstrap secret with patched DB
After the first cluster scan, four library books (B07WC6QG79, B07JVHDWW8,
B0112N4LL2, B09KGX87QF) were updated in LibationContext.db with
BookStatus=1. Those books were never downloaded on the workstation
(so the workstation DB had them as NotLiberated) but already exist on
the audiobookshelf-books PVC from the S3 seed during the original
LXC→K8s migration. Leaving them NotLiberated caused liberate to
attempt re-downloads and would have produced duplicate folders in /audiobooks.

Refreshing the bootstrap secret with the post-patch DB so that if the
libation-config PVC is ever rebuilt, the same skip behavior applies on
re-seed. AccountsSettings.json now also reflects the refreshed token
state written by the first cluster run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:15:56 +02:00
k.rennerandClaude Opus 4.7 e7c02bbbed libation: use install -m 660 so SQLite migrations can write the DB
The bootstrap Secret mounts read-only (defaultMode: 0400), and the
init container's plain `cp` was propagating that mode into the
libation-config PVC. Result: LibationCli failed on the first scan
with "Libation cannot write its SQLite database (migrations need
write access)" against /config-internal/LibationContext.db.

`install -m 660` sets owner+group rw explicitly; fsGroup 1000 already
gives every container gid 1000, so 0660 is enough for the libation
container to update both AccountsSettings.json (token refresh) and
LibationContext.db (library state) across runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:05:00 +02:00
k.rennerandClaude Opus 4.7 3625f59a51 libation: cluster-native CronJob feeding the audiobookshelf-books PVC
Replaces the workstation-side Libation install with a daily CronJob that
runs the official rmcrackan/libation image headlessly. SLEEP_TIME=-1
makes the entrypoint do one scan+liberate pass and exit; the schedule
is handled by Kubernetes.

Lives in the audiobookshelf namespace because k8s PVCs are namespace-
scoped and we share /audiobooks with the audiobookshelf Deployment.
A small RWO ceph-block PVC (libation-config) holds AccountsSettings.json,
LibationContext.db, and the cover/search caches; an init container
seeds it once from the SOPS-encrypted bootstrap secret (sentinel-file
gated so token refreshes and DB updates persist across runs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:00:04 +02:00