feat: add paperless-ngx with OIDC, CNPG db, cp-3 HDD media, MinIO backup

Fixes controlplane.tftpl's install.wipe:true (should be false, live CPs already run false) and syncs coredns Corefile back to what's actually deployed (drops an unrolled-out, stale Kong-era rewrite).
This commit is contained in:
Story Crater Bot
2026-08-25 11:11:37 -07:00
parent 78e788abb3
commit e8e5acfb13
24 changed files with 660 additions and 11 deletions
+7 -1
View File
@@ -1,8 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: storage
# NOTE: no top-level `namespace:` transformer (see iam/kustomization.yaml for
# the same fix) - minio-provision-paperless-job.yaml's RoleBinding
# deliberately targets namespace paperless (least-privilege access for the
# provisioner ServiceAccount to write Secrets there); a namespace transformer
# would silently rewrite it back to storage, breaking the RBAC. Every resource
# here already sets its own explicit metadata.namespace.
resources:
- minio-tenant.yaml
- minio-provision-paperless-job.yaml
# The operator creates the minio S3/console/headless Services and the
# declarative bucket + user from the Tenant spec — no hand-rolled Service or
# Bucket/User CRs (those kinds don't exist in the operator CRD set).
@@ -0,0 +1,120 @@
# PostSync hook Job: creates a MinIO IAM user + policy scoped to only the
# `paperless` bucket (least-privilege — reuses root creds nowhere else in the
# cluster), then writes the generated access/secret key into a Secret in the
# `paperless` namespace for the nightly backup CronJob to consume.
#
# Idempotent: re-running never rotates existing credentials — if
# paperless-minio-creds already exists in ns paperless, the script reuses the
# access key it already wrote and just re-asserts the policy/user exist.
apiVersion: v1
kind: ServiceAccount
metadata:
name: minio-paperless-provisioner
namespace: storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: minio-paperless-provisioner
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: minio-paperless-provisioner
namespace: paperless
subjects:
- kind: ServiceAccount
name: minio-paperless-provisioner
namespace: storage
roleRef:
kind: ClusterRole
name: minio-paperless-provisioner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: Job
metadata:
name: minio-provision-paperless
namespace: storage
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
ttlSecondsAfterFinished: 600
backoffLimit: 3
template:
spec:
serviceAccountName: minio-paperless-provisioner
restartPolicy: Never
initContainers:
- name: kubectl-copy
image: bitnami/kubectl:latest
command: ["sh", "-c", "cp $(which kubectl) /shared/kubectl"]
volumeMounts:
- name: shared
mountPath: /shared
containers:
- name: provision
image: minio/mc:latest
volumeMounts:
- name: shared
mountPath: /shared
- name: minio-creds
mountPath: /minio-creds
readOnly: true
command:
- /bin/sh
- -c
- |
set -e
export PATH="/shared:$PATH"
# minio-creds ships as a shell-sourceable config.env blob
# (`export MINIO_ROOT_USER=... / MINIO_ROOT_PASSWORD=...`), not
# discrete keys — source it directly rather than re-parsing.
. /minio-creds/config.env
mc alias set m http://minio-cluster-hl.storage.svc.cluster.local:9000 \
"$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
echo "Checking for existing paperless-minio-creds secret..."
if kubectl -n paperless get secret paperless-minio-creds >/dev/null 2>&1; then
ACCESS_KEY=$(kubectl -n paperless get secret paperless-minio-creds -o jsonpath='{.data.ACCESS_KEY}' | base64 -d)
SECRET_KEY=$(kubectl -n paperless get secret paperless-minio-creds -o jsonpath='{.data.SECRET_KEY}' | base64 -d)
echo " reusing existing credentials"
else
ACCESS_KEY="paperless"
SECRET_KEY=$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 40)
echo " generated new credentials"
fi
echo "Ensuring MinIO user 'paperless' exists..."
if ! mc admin user info m "$ACCESS_KEY" >/dev/null 2>&1; then
mc admin user add m "$ACCESS_KEY" "$SECRET_KEY"
fi
echo "Writing scoped policy (paperless bucket only)..."
printf '%s' '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:ListBucket"],"Resource":["arn:aws:s3:::paperless"]},{"Effect":"Allow","Action":["s3:GetObject","s3:PutObject","s3:DeleteObject"],"Resource":["arn:aws:s3:::paperless/*"]}]}' > /tmp/paperless-rw-policy.json
mc admin policy create m paperless-rw /tmp/paperless-rw-policy.json || \
mc admin policy update m paperless-rw /tmp/paperless-rw-policy.json
mc admin policy attach m paperless-rw --user "$ACCESS_KEY"
echo "Writing paperless-minio-creds secret (ns paperless)..."
kubectl -n paperless create secret generic paperless-minio-creds \
--from-literal=ACCESS_KEY="$ACCESS_KEY" \
--from-literal=SECRET_KEY="$SECRET_KEY" \
--from-literal=BUCKET=paperless \
--from-literal=ENDPOINT=http://minio-cluster-hl.storage.svc.cluster.local:9000 \
--dry-run=client -o yaml | kubectl apply -f -
echo "Done."
volumes:
- name: shared
emptyDir: {}
- name: minio-creds
secret:
secretName: minio-creds
+1
View File
@@ -76,6 +76,7 @@ spec:
- name: loki-ruler
- name: loki-admin
- name: vault
- name: paperless
# Metrics are exposed at /minio/v2/metrics; scrape via a hand-rolled
# ServiceMonitor in the monitoring stack rather than operator auto-wiring