Files
homelab/kubernetes/apps/libation/cronjob.yaml
renovate-bot 68c0978660
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
chore(deps): update all non-major dependencies
2026-10-05 02:01:26 +00:00

146 lines
6.5 KiB
YAML

# Libation runs headless via the official rmcrackan/libation image.
# Each invocation = `LibationCli scan` then `LibationCli liberate`,
# driven by the image's entrypoint (liberate.sh). SLEEP_TIME=-1 makes
# the entrypoint exit after one pass instead of looping internally —
# the CronJob handles scheduling.
#
# Books land in /audiobooks (the audiobookshelf-books-bulk PVC, shared
# with the audiobookshelf Deployment). Config + DB live on
# libation-config (RWO ceph-block, seeded once from
# libation-bootstrap secret).
#
# fsGroup 1000 matches audiobookshelf's pod uid so files written here
# are readable by ABS without extra chown.
apiVersion: batch/v1
kind: CronJob
metadata:
name: libation
namespace: audiobookshelf
labels:
app.kubernetes.io/name: libation
spec:
schedule: "0 4 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 3
startingDeadlineSeconds: 3600
jobTemplate:
spec:
backoffLimit: 1
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app.kubernetes.io/name: libation
spec:
restartPolicy: OnFailure
enableServiceLinks: false
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
initContainers:
# Seed the libation-config PVC from the bootstrap secret on
# first run only — sentinel file gates re-runs so DB updates
# from subsequent liberate passes are preserved. Refreshed
# Audible tokens are preserved by the container args, not by
# this sentinel: the entrypoint copies AccountsSettings.json
# into ephemeral /config-internal rather than symlinking it.
- name: seed-config
image: busybox:1.38.0@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e
command: ["/bin/sh", "-eu", "-c"]
args:
- |
# `install -m 660` sets read+write for owner and group on
# each file. The bootstrap Secret mount is read-only
# (defaultMode: 0400), so a plain `cp` would propagate
# 0400 and LibationCli's SQLite migrations would fail
# with "cannot write database". fsGroup 1000 on the pod
# gives every container gid 1000, so 0660 + gid 1000 is
# enough for both the init copy and the main libation
# process to read and write the files.
if [ -f /libation-state/.bootstrapped ]; then
echo "libation-config already bootstrapped; skipping seed"
exit 0
fi
echo "seeding libation-config from bootstrap secret"
install -m 660 /bootstrap/AccountsSettings.json /libation-state/AccountsSettings.json
install -m 660 /bootstrap/Settings.json /libation-state/Settings.json
install -m 660 /bootstrap/LibationContext.db /libation-state/LibationContext.db
# Without this key the seeded tokens cannot be decrypted:
# TokenStorageMethod is Encrypted and the container has no
# OS secret store, so a missing key file makes the
# entrypoint mint a throwaway one on every run.
install -m 660 /bootstrap/libation-master.key /libation-state/libation-master.key
touch /libation-state/.bootstrapped
ls -la /libation-state
volumeMounts:
- name: libation-state
mountPath: /libation-state
- name: bootstrap
mountPath: /bootstrap
readOnly: true
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
containers:
- name: libation
# Pinned by digest: Spegel caches mutable tag resolutions, so
# :latest stayed on 13.7.11 despite imagePullPolicy Always.
image: docker.io/rmcrackan/libation:14.2.2@sha256:fe188ac3c4ce63cc305971c76e648748341427e16bcf884011da8a384fd3908c
# The entrypoint stages config into ephemeral /config-internal
# by copy (only the DB is symlinked to the PVC), so Audible
# tokens refreshed during a run are lost at container exit.
# Passing args makes the entrypoint set up the environment and
# then exec this instead of its own loop; the trap copies the
# refreshed file back to the PVC on every exit path.
args:
- /bin/sh
- -c
- |
trap 'cp /config-internal/AccountsSettings.json /libation-state/AccountsSettings.json' EXIT
set -e
# Strip the Audible intro/outro; re-liberated books replace their file instead of gaining a " (1)" copy.
jq '.StripAudibleBrandAudio = true | .OverwriteExisting = true' /config-internal/Settings.json > /config-internal/Settings.json.tmp
mv /config-internal/Settings.json.tmp /config-internal/Settings.json
/libation/LibationCli scan
/libation/LibationCli liberate
env:
- name: LIBATION_CONFIG_DIR
value: /libation-state
- name: LIBATION_BOOKS_DIR
value: /audiobooks
- name: SLEEP_TIME
value: "-1"
- name: LOG_LEVEL
value: info
volumeMounts:
- name: libation-state
mountPath: /libation-state
- name: books
mountPath: /audiobooks
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
volumes:
- name: libation-state
persistentVolumeClaim:
claimName: libation-config
- name: books
persistentVolumeClaim:
claimName: audiobookshelf-books-bulk
- name: bootstrap
secret:
secretName: libation-bootstrap
defaultMode: 0400