The gateway compares the token audience with its allow-list of client ids. The scope
carried urn:zitadel:iam:org:project🆔<project>:aud, so every token said
aud = [396757796898471937] and the gateway answered
token validation failed: audience not allowed: [396757796898471937]
With plain openid the audience is the machine user, which is the entry that exists on
the list.
137 lines
5.5 KiB
YAML
137 lines
5.5 KiB
YAML
# Secret paperless-ai-config managed via SOPS (argocd/secrets/paperless-ai-secrets.enc.yaml)
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: paperless-ai
|
|
namespace: paperless
|
|
labels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
spec:
|
|
replicas: 1
|
|
# Recreate, not RollingUpdate: paperless-ai-data is a single RWO PVC. With
|
|
# RollingUpdate the new pod tries to mount the PVC while the old pod still
|
|
# holds it -> "Multi-Attach error" / stuck Init:0/1 forever, rollout wedged
|
|
# at "1 desired, 1 ready, 1 updated, 1 unavailable". Recreate terminates the
|
|
# old pod first so the PVC detaches before the new one starts. Same pattern
|
|
# as the Forgejo gitea Deployment.
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: paperless-ai
|
|
spec:
|
|
tolerations:
|
|
- key: node-role.kubernetes.io/control-plane
|
|
operator: Exists
|
|
effect: NoSchedule
|
|
volumes:
|
|
- name: paperless-ai-data
|
|
persistentVolumeClaim:
|
|
claimName: paperless-ai-data
|
|
- name: paperless-ai-env-config
|
|
configMap:
|
|
name: paperless-ai-env
|
|
- name: llm-key
|
|
secret:
|
|
secretName: paperless-llm-key
|
|
# 0440 with the pod's fsGroup. A Secret volume is owned by root, and the
|
|
# vendor container runs as 65532, so 0400 left the sidecar unable to read
|
|
# its own key: "permission denied" on every start.
|
|
defaultMode: 0440
|
|
# No init container. The token used to be fetched once at pod start with a
|
|
# client_credentials grant against a web app, which cannot work: a web app has
|
|
# no such grant, and even a token that was minted would be dead within the hour
|
|
# while this pod runs for weeks.
|
|
#
|
|
# llm-token-vendor runs beside the app instead. It signs a machine-key assertion,
|
|
# exchanges it for a token, and rewrites /app/data/llm_token.txt at 80% of the
|
|
# token lifetime. The vendor image keeps reading CUSTOM_API_KEY_FILE unchanged.
|
|
# The key is re-read on every mint, so a rotation lands without a restart.
|
|
securityContext:
|
|
fsGroup: 65532
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
containers:
|
|
- name: llm-token-vendor
|
|
image: forgejo.riotpiao.com/riotpiao-poimen/llm-token-vendor:0.1.0
|
|
imagePullPolicy: Always
|
|
args:
|
|
- --token-url=https://zitadel.riotpiao.com/oauth/v2/token
|
|
- --issuer=https://zitadel.riotpiao.com
|
|
- --user-id=397072668735570071
|
|
- --key-id=397072674272051351
|
|
- --key-file=/var/run/llm-key/key
|
|
# No project audience. ZITADEL puts the requested audience in `aud`, and
|
|
# the gateway compares `aud` with its allow-list of client ids. Asking for
|
|
# the project id made every token carry the project, so the gateway
|
|
# answered "audience not allowed: [396757796898471937]". With plain
|
|
# `openid` the token names its caller, which is the entry that exists.
|
|
- --scope=openid
|
|
- --out=/app/data/llm_token.txt
|
|
volumeMounts:
|
|
- {name: paperless-ai-data, mountPath: /app/data}
|
|
- {name: llm-key, mountPath: /var/run/llm-key, readOnly: true}
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
capabilities: {drop: ["ALL"]}
|
|
runAsNonRoot: true
|
|
runAsUser: 65532
|
|
seccompProfile: {type: RuntimeDefault}
|
|
resources:
|
|
requests: {cpu: 10m, memory: 32Mi}
|
|
limits: {cpu: 100m, memory: 64Mi}
|
|
- name: paperless-ai
|
|
image: clusterzx/paperless-ai:latest
|
|
env:
|
|
# Paperless-ngx connection
|
|
- name: PAPERLESS_API_URL
|
|
value: "http://paperless.paperless.svc.cluster.local:8000"
|
|
- name: PAPERLESS_API_TOKEN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: paperless-ai-config
|
|
key: PAPERLESS_API_TOKEN
|
|
- name: PAPERLESS_USERNAME
|
|
value: "admin"
|
|
# LLM API — via public gateway with JWT auth
|
|
- name: AI_PROVIDER
|
|
value: "custom"
|
|
- name: CUSTOM_BASE_URL
|
|
value: "https://api.riotpiao.com/v1"
|
|
# Token will be read from file at runtime by the application
|
|
# The init container fetches it and stores in /app/data/llm_token.txt
|
|
- name: CUSTOM_API_KEY_FILE
|
|
value: "/app/data/llm_token.txt"
|
|
- name: CUSTOM_MODEL
|
|
value: "qwen2.5:3b-instruct"
|
|
# Behavior - scan for new documents and tag them
|
|
- name: SCAN_INTERVAL
|
|
value: "60"
|
|
- name: PROCESS_PREDEFINED_DOCUMENTS
|
|
value: "no"
|
|
- name: ADD_AI_TAG
|
|
value: "yes"
|
|
- name: AI_TAG_NAME
|
|
value: "ai-processed"
|
|
- name: USE_PROMPT_TAGS
|
|
value: "yes"
|
|
- name: ADD_AI_DESCRIPTIONS
|
|
value: "yes"
|
|
volumeMounts:
|
|
- name: paperless-ai-data
|
|
mountPath: /app/data
|
|
- name: paperless-ai-env-config
|
|
mountPath: /app/data/.env
|
|
subPath: paperless-ai-config.env
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 512Mi
|
|
limits:
|
|
cpu: "1"
|
|
memory: 2Gi
|