146 lines
6.5 KiB
YAML
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
|