infra/stacks/paperless-ngx/main.tf
Viktor Barzin df5edd8542 wealthfolio, paperless-ngx: drop migration-leftover -proxmox PVCs
The 2026-04-13 encrypted-PVC migration replaced the wealthfolio and
paperless-ngx data volumes with -encrypted variants but never removed
the original -proxmox PVC blocks from TF — both were sitting orphaned
with no pod mounting them, occupying 1Gi each of LVM thin pool. The
autoresizer also logged repeated "failed to get volume stats" for them
(no kubelet stats without a mounted pod), masking real signal.

  * wealthfolio: removed kubernetes_persistent_volume_claim.data_proxmox
  * paperless-ngx: removed kubernetes_persistent_volume_claim.data_proxmox
  (the paperless PVC turned out to be out-of-TF-state, so deleted via
   kubectl after the TF block removal.)
2026-05-11 20:44:17 +00:00

271 lines
7.4 KiB
HCL

variable "tls_secret_name" {
type = string
sensitive = true
}
variable "nfs_server" { type = string }
variable "redis_host" { type = string }
variable "mysql_host" { type = string }
data "vault_kv_secret_v2" "secrets" {
mount = "secret"
name = "paperless-ngx"
}
locals {
homepage_credentials = jsondecode(data.vault_kv_secret_v2.secrets.data["homepage_credentials"])
}
resource "kubernetes_namespace" "paperless-ngx" {
metadata {
name = "paperless-ngx"
labels = {
tier = local.tiers.edge
}
# labels = {
# "istio-injection" : "enabled"
# }
}
lifecycle {
# KYVERNO_LIFECYCLE_V1: goldilocks-vpa-auto-mode ClusterPolicy stamps this label on every namespace
ignore_changes = [metadata[0].labels["goldilocks.fairwinds.com/vpa-update-mode"]]
}
}
resource "kubernetes_manifest" "external_secret" {
manifest = {
apiVersion = "external-secrets.io/v1beta1"
kind = "ExternalSecret"
metadata = {
name = "paperless-ngx-secrets"
namespace = "paperless-ngx"
}
spec = {
refreshInterval = "15m"
secretStoreRef = {
name = "vault-kv"
kind = "ClusterSecretStore"
}
target = {
name = "paperless-ngx-secrets"
}
dataFrom = [{
extract = {
key = "paperless-ngx"
}
}]
}
}
depends_on = [kubernetes_namespace.paperless-ngx]
}
module "tls_secret" {
source = "../../modules/kubernetes/setup_tls_secret"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
tls_secret_name = var.tls_secret_name
}
resource "kubernetes_persistent_volume_claim" "data_encrypted" {
wait_until_bound = false
metadata {
name = "paperless-ngx-data-encrypted"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
annotations = {
"resize.topolvm.io/threshold" = "10%"
"resize.topolvm.io/increase" = "100%"
"resize.topolvm.io/storage_limit" = "5Gi"
}
}
spec {
access_modes = ["ReadWriteOnce"]
storage_class_name = "proxmox-lvm-encrypted"
resources {
requests = {
storage = "1Gi"
}
}
}
lifecycle {
# The autoresizer expands requests.storage up to storage_limit and
# PVCs can't shrink. Without this, every TF apply tries to revert
# to the spec value, K8s rejects the shrink, and the PVC ends up
# in Terminating-but-in-use limbo.
ignore_changes = [spec[0].resources[0].requests]
}
}
resource "kubernetes_deployment" "paperless-ngx" {
metadata {
name = "paperless-ngx"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
labels = {
app = "paperless-ngx"
tier = local.tiers.edge
}
annotations = {
"reloader.stakater.com/search" = "true"
}
}
spec {
replicas = 1
strategy {
type = "Recreate"
}
selector {
match_labels = {
app = "paperless-ngx"
}
}
template {
metadata {
labels = {
app = "paperless-ngx"
}
annotations = {
"diun.enable" = "true"
"diun.include_tags" = "^\\d+(?:\\.\\d+)?(?:\\.\\d+)?$"
"dependency.kyverno.io/wait-for" = "mysql.dbaas:3306,redis-master.redis:6379"
}
}
spec {
container {
image = "ghcr.io/paperless-ngx/paperless-ngx:2.20.14"
name = "paperless-ngx"
env {
name = "PAPERLESS_REDIS"
// If redis gets stuck, try deleting the locks files in log dir
value = "redis://${var.redis_host}"
}
env {
name = "PAPERLESS_REDIS_PREFIX"
value = "paperless-ngx"
}
env {
name = "PAPERLESS_DBENGINE"
value = "mariadb"
}
env {
name = "PAPERLESS_DBHOST"
value = var.mysql_host
}
env {
name = "PAPERLESS_DBNAME"
value = "paperless-ngx"
}
env {
name = "PAPERLESS_DBUSER"
value = "paperless-ngx"
}
env {
name = "PAPERLESS_DBPASS"
value_from {
secret_key_ref {
name = "paperless-ngx-secrets"
key = "db_password"
}
}
}
env {
name = "PAPERLESS_CSRF_TRUSTED_ORIGINS"
value = "https://paperless-ngx.viktorbarzin.me,https://pdf.viktorbarzin.me"
}
env {
name = "PAPERLESS_DEBUG"
value = "false"
}
env {
name = "PAPERLESS_MEDIA_ROOT"
value = "../data"
}
env {
name = "PAPERLESS_OCR_USER_ARGS"
value = "{\"invalidate_digital_signatures\": true}"
}
volume_mount {
name = "data"
mount_path = "/usr/src/paperless/data"
}
resources {
requests = {
cpu = "50m"
memory = "2Gi"
}
limits = {
memory = "2Gi"
}
}
port {
container_port = 8000
}
}
volume {
name = "data"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.data_encrypted.metadata[0].name
}
}
}
}
}
lifecycle {
# KYVERNO_LIFECYCLE_V1: Kyverno admission webhook mutates dns_config with ndots=2
ignore_changes = [spec[0].template[0].spec[0].dns_config]
}
}
resource "kubernetes_service" "paperless-ngx" {
metadata {
name = "paperless-ngx"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
labels = {
"app" = "paperless-ngx"
}
}
spec {
selector = {
app = "paperless-ngx"
}
port {
name = "http"
target_port = 8000
port = 80
protocol = "TCP"
}
}
}
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# Paperless has a mobile app (`Paperless`) that uses /api/* with token
# auth. The app can't follow Authentik 302s. Paperless's own login
# gates the web UI.
# auth = "app": Paperless mobile app uses /api/* with token auth; Paperless enforces app-layer login for web UI; backend manages authentication.
auth = "app"
namespace = kubernetes_namespace.paperless-ngx.metadata[0].name
name = "paperless-ngx"
service_name = "paperless-ngx"
host = "pdf"
dns_type = "proxied"
tls_secret_name = var.tls_secret_name
port = 80
extra_annotations = {
"gethomepage.dev/enabled" = "true"
"gethomepage.dev/description" = "Document library"
"gethomepage.dev/group" = "Productivity"
"gethomepage.dev/icon" : "paperless-ngx.png"
"gethomepage.dev/name" = "Paperless-ngx"
"gethomepage.dev/widget.type" = "paperlessngx"
"gethomepage.dev/widget.url" = "http://paperless-ngx.paperless-ngx.svc.cluster.local"
# "gethomepage.dev/widget.token" = var.homepage_token
"gethomepage.dev/widget.username" = local.homepage_credentials["paperless-ngx"]["username"]
"gethomepage.dev/widget.password" = local.homepage_credentials["paperless-ngx"]["password"]
"gethomepage.dev/widget.fields" = "[\"total\"]"
"gethomepage.dev/pod-selector" = ""
# gethomepage.dev/weight: 10 # optional
# gethomepage.dev/instance: "public" # optional
}
}