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
+94
View File
@@ -0,0 +1,94 @@
# Nightly: pg_dump the paperless DB + mirror the media PVC into the scoped
# `paperless` MinIO bucket (see minio-provision-paperless-job.yaml). This is a
# BACKUP target, not live storage - paperless-ngx has no native S3 backend, it
# only ever reads/writes the local media PVC directly.
#
# Pinned to talos-cp-3, same as deployment.yaml: media is a ReadWriteOnce
# Longhorn volume with a single replica physically on that node's disk -
# mounting it read-only here from a different node would conflict with the
# live webserver's attachment.
apiVersion: batch/v1
kind: CronJob
metadata:
name: paperless-backup
spec:
schedule: "0 3 * * *" # 03:00 daily, low-traffic window
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: talos-cp-3
initContainers:
- name: pg-dump
image: postgres:16-alpine
env:
- name: PGHOST
value: paperless-db-rw
- name: PGDATABASE
value: paperless
- name: PGUSER
valueFrom:
secretKeyRef:
name: paperless-db-app
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: paperless-db-app
key: password
command:
- sh
- -c
- pg_dump --format=custom --file=/backup/paperless-db.dump
volumeMounts:
- name: backup
mountPath: /backup
containers:
- name: mc-mirror
image: minio/mc:latest
env:
- name: ACCESS_KEY
valueFrom:
secretKeyRef:
name: paperless-minio-creds
key: ACCESS_KEY
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: paperless-minio-creds
key: SECRET_KEY
- name: BUCKET
valueFrom:
secretKeyRef:
name: paperless-minio-creds
key: BUCKET
- name: ENDPOINT
valueFrom:
secretKeyRef:
name: paperless-minio-creds
key: ENDPOINT
command:
- /bin/sh
- -c
- |
set -e
mc alias set b "$ENDPOINT" "$ACCESS_KEY" "$SECRET_KEY"
mc cp /backup/paperless-db.dump "b/$BUCKET/db/paperless-db-$(date +%Y%m%d).dump"
mc mirror --overwrite /media "b/$BUCKET/media"
echo "Backup done."
volumeMounts:
- name: backup
mountPath: /backup
- name: media
mountPath: /media
readOnly: true
volumes:
- name: backup
emptyDir: {}
- name: media
persistentVolumeClaim:
claimName: paperless-media
readOnly: true
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: paperless-config
data:
PAPERLESS_URL: "https://paperless.riotpiao.com"
PAPERLESS_TIME_ZONE: "America/Los_Angeles"
PAPERLESS_OCR_LANGUAGE: "eng"
PAPERLESS_DBHOST: "paperless-db-rw"
PAPERLESS_DBNAME: "paperless"
PAPERLESS_REDIS: "redis://paperless-redis:6379"
# django-allauth generic OIDC provider. The client_id/secret/server_url
# bundle itself lives in the paperless-oidc Secret
# (SOCIALACCOUNT_PROVIDERS_JSON key, composed by authentik-provision.py) -
# env vars can't be split across a ConfigMap + Secret for the same key, so
# this whole value is sourced from the Secret in deployment.yaml instead.
PAPERLESS_APPS: "allauth.socialaccount.providers.openid_connect"
+86
View File
@@ -0,0 +1,86 @@
# Single container runs webserver + consumer + scheduler (paperless-ngx's
# stock entrypoint does this internally) - no need to split into separate
# Deployments. replicas: 1 only: paperless-media is ReadWriteOnce, and the
# consumer polling the media dir doesn't benefit from horizontal scaling here.
#
# Pinned to talos-cp-3: paperless-media's disk physically lives there. Longhorn
# RWO volumes can only be attached from one node at a time, and the nightly
# backup-cronjob.yaml also mounts this same PVC (read-only) to mirror it into
# MinIO - pinning both to the same node avoids a cross-node attach conflict,
# and keeps the 3.5Ti read/write path off the network entirely.
apiVersion: apps/v1
kind: Deployment
metadata:
name: paperless
spec:
replicas: 1
strategy:
type: Recreate # ReadWriteOnce media PVC - avoid two pods fighting over it
selector:
matchLabels:
app: paperless
template:
metadata:
labels:
app: paperless
spec:
nodeSelector:
kubernetes.io/hostname: talos-cp-3
containers:
- name: paperless
image: ghcr.io/paperless-ngx/paperless-ngx:2.13
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: paperless-config
env:
- name: PAPERLESS_DBUSER
valueFrom:
secretKeyRef:
name: paperless-db-app
key: username
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef:
name: paperless-db-app
key: password
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef:
name: paperless-secrets
key: PAPERLESS_SECRET_KEY
- name: PAPERLESS_ADMIN_USER
valueFrom:
secretKeyRef:
name: paperless-secrets
key: PAPERLESS_ADMIN_USER
- name: PAPERLESS_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: paperless-secrets
key: PAPERLESS_ADMIN_PASSWORD
- name: PAPERLESS_SOCIALACCOUNT_PROVIDERS
valueFrom:
secretKeyRef:
name: paperless-oidc
key: SOCIALACCOUNT_PROVIDERS_JSON
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "2", memory: "4Gi" }
volumeMounts:
- name: media
mountPath: /usr/src/paperless/media
- name: data
mountPath: /usr/src/paperless/data
- name: consume
mountPath: /usr/src/paperless/consume
volumes:
- name: media
persistentVolumeClaim:
claimName: paperless-media
- name: data
persistentVolumeClaim:
claimName: paperless-data
- name: consume
emptyDir: {}
+24
View File
@@ -0,0 +1,24 @@
# Direct nginx ingress to the paperless Service - not routed via the Go
# api-gateway (api.riotpiao.com), which has no WebSocket upgrade support and
# paperless-ngx keeps a long-lived /ws/ connection open for live task status.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: paperless
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0" # large scanned PDF uploads
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
ingressClassName: nginx
rules:
- host: paperless.riotpiao.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: paperless
port:
number: 8000
+15
View File
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: paperless
resources:
- pvc.yaml
- configmap.yaml
- redis.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- backup-cronjob.yaml
# postgres: paperless-db CNPG Cluster, deployed by k8s/infra/databases (wave 2,
# before this app at wave 8) - not duplicated here. Same for the paperless-oidc
# and paperless-minio-creds Secrets, written by PostSync provisioning Jobs in
# k8s/infra/iam and k8s/infra/minio respectively.
+33
View File
@@ -0,0 +1,33 @@
# Two volumes, deliberately separate storage classes:
#
# - media: the actual documents (originals + OCR'd archive PDFs + thumbnails).
# Grows to multi-TB, lives on the cp-3 USB HDD, single replica (see
# k8s/infra/longhorn/longhorn-paperless-storageclass.yaml). Sized 3500Gi to
# leave headroom on the 4TB disk rather than claiming it to 100%.
# - data: the SQLite classification model + search index. Small (low GB),
# frequently rewritten, and disposable (rebuilds from the DB + media on
# next consume) - stays on the default 3-replica pool instead of the
# single-disk HDD.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: paperless-media
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn-paperless-media
resources:
requests:
storage: 3500Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: paperless-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 5Gi
+37
View File
@@ -0,0 +1,37 @@
# Task queue broker + websocket channel layer for paperless-ngx. No PVC:
# queued/scheduled task state is disposable - a lost queue on restart just
# means re-triggering consumption, not data loss (documents themselves live
# on paperless-media).
apiVersion: apps/v1
kind: Deployment
metadata:
name: paperless-redis
spec:
replicas: 1
selector:
matchLabels:
app: paperless-redis
template:
metadata:
labels:
app: paperless-redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests: { cpu: "50m", memory: "64Mi" }
limits: { cpu: "250m", memory: "256Mi" }
---
apiVersion: v1
kind: Service
metadata:
name: paperless-redis
spec:
selector:
app: paperless-redis
ports:
- port: 6379
targetPort: 6379
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: paperless
spec:
selector:
app: paperless
ports:
- port: 8000
targetPort: 8000