Files
argocd-apps/apps/base/arrstack/radarr/deployment.yaml
T
unkin-agent d387301d6b arrstack: manage sonarr/radarr/prowlarr API keys via Vault (#369)
## Why

The sonarr/radarr/prowlarr images self-generate an API key into
/config/config.xml on first boot, so the key is unmanaged and differs per
volume reset. This makes Vault the source of truth for those keys (override
bootstrap, chosen by Ben): the key is minted in Vault and enforced into
config.xml before each app starts.

## Changes

- Add a `VaultAuth` `default` in the `arrstack` namespace (kubernetes auth,
  mount `k8s/au/syd1`, role `default`, SA `default`), mirroring jellyfin.
- Add a per-app `VaultStaticSecret` that syncs
  `kv/kubernetes/namespace/arrstack/default/<app>` (key `apitoken`) into the
  `<app>-apikey` Secret. The `default` k8s role's templated policy already
  grants read on that path for the `arrstack/default` SA, so no
  terraform-vault change is needed.
- Add an `apikey-init` initContainer to each of the three deployments that
  reads `API_KEY` from the VSO-created Secret, fails closed on a missing or
  non-hex value, and writes/updates only the `<ApiKey>` element in
  `/config/config.xml` (then fixes ownership 1000:1000, mode 600). Image is a
  pinned busybox via artifactapi to keep this PR atomic (no new image
  dependency).
- Wire the new manifests into the base and per-app kustomizations.

## Notes

- Keys already seeded in Vault at `kv/kubernetes/namespace/arrstack/default/<app>`.
- nzbget is out of scope: it has no config.xml `<ApiKey>` (uses ControlPassword),
  a separate follow-up.
- Downstream consumers (proxy, terraform) currently read
  `kv/service/media-apps/<app>`; the authoritative key now lives at the path
  above. Reconciliation is deferred.

---------

Co-authored-by: unkin-agent <unkin-agent@git.unkin.net>
Reviewed-on: #369
Co-authored-by: Unkin Agent <unkin-agent@unkin.net>
Co-committed-by: Unkin Agent <unkin-agent@unkin.net>
2026-08-15 16:43:02 +10:00

121 lines
3.6 KiB
YAML

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: radarr
namespace: arrstack
spec:
replicas: 1
strategy:
# RWO config PVC + single stateful SQLite DB: never run two pods at once.
type: Recreate
selector:
matchLabels:
app: radarr
template:
metadata:
labels:
app: radarr
spec:
securityContext:
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
# Enforce the Vault-sourced API key in /config/config.xml before the app
# starts. Vault is source of truth (override bootstrap): the key is minted
# in Vault, synced by VSO into the radarr-apikey Secret, and written here.
# Runs as root to fix ownership; touches only the <ApiKey> element.
- name: apikey-init
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/library/busybox:1.37.0
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 0
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: radarr-apikey
key: apitoken
command:
- sh
- -c
- |
set -eu
case "$API_KEY" in
"" | *[!0-9a-fA-F]*)
echo "apikey-init: API_KEY missing or not hex; refusing" >&2
exit 1
;;
esac
CFG=/config/config.xml
if [ ! -f "$CFG" ]; then
printf '<Config>\n <ApiKey>%s</ApiKey>\n</Config>\n' "$API_KEY" > "$CFG"
elif grep -q '<ApiKey>' "$CFG"; then
sed -i "s|<ApiKey>[^<]*</ApiKey>|<ApiKey>${API_KEY}</ApiKey>|" "$CFG"
else
sed -i "s|<Config>|<Config>\n <ApiKey>${API_KEY}</ApiKey>|" "$CFG"
fi
chown 1000:1000 "$CFG"
chmod 600 "$CFG"
echo "apikey-init: <ApiKey> enforced from Vault"
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 200m
memory: 64Mi
volumeMounts:
- name: config
mountPath: /config
containers:
- name: radarr
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/radarr:6.3.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 7878
protocol: TCP
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: Australia/Sydney
livenessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: config
mountPath: /config
- name: media-movies
mountPath: /media/movies
volumes:
- name: config
persistentVolumeClaim:
claimName: radarr-config
- name: media-movies
persistentVolumeClaim:
claimName: media-movies