fix(gotify): CNPG Postgres backend, SOPS-encrypt secrets

- Replace SQLite + PVC with CNPG Postgres cluster (2 replicas, longhorn-cnpg)
- Connection string built at runtime via shell wrapper (env var interpolation)
- Remove gotify-data PVC (DB handles persistence)
- Encrypt secrets.yaml with SOPS
- Add db.yaml: gotify-db cluster in notifications namespace
This commit is contained in:
2026-09-10 10:34:44 +09:00
parent b0c17527f2
commit 3abe2a7184
5 changed files with 136 additions and 59 deletions
+35
View File
@@ -0,0 +1,35 @@
# CNPG Postgres for Gotify. Lightweight — 2 instances, 2Gi storage.
# CNPG generates secret `gotify-db-app` + service `gotify-db-rw` in ns notifications.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: gotify-db
namespace: notifications
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
instances: 2
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
bootstrap:
initdb:
database: gotify
owner: app
encoding: UTF8
localeCollate: C
localeCType: C
enableSuperuserAccess: false
resources:
requests: { memory: "256Mi", cpu: "100m" }
limits: { memory: "512Mi", cpu: "500m" }
storage:
size: 2Gi
storageClass: longhorn-cnpg
monitoring:
enablePodMonitor: true
affinity:
podAntiAffinityType: preferred
topologyKey: kubernetes.io/hostname
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
+17 -7
View File
@@ -24,6 +24,12 @@ spec:
# --- Gotify server --- # --- Gotify server ---
- name: gotify - name: gotify
image: ghcr.io/gotify/server:2.6.1 image: ghcr.io/gotify/server:2.6.1
command: ["/bin/sh", "-c"]
args:
- |
export GOTIFY_DATABASE_DIALECT=postgres
export GOTIFY_DATABASE_CONNECTION="host=gotify-db-rw.notifications port=5432 user=${DB_USER} password=${DB_PASS} dbname=gotify sslmode=disable"
exec /app/gotify-app
ports: ports:
- containerPort: 80 - containerPort: 80
protocol: TCP protocol: TCP
@@ -38,6 +44,16 @@ spec:
secretKeyRef: secretKeyRef:
name: gotify-admin name: gotify-admin
key: password key: password
- name: DB_USER
valueFrom:
secretKeyRef:
name: gotify-db-app
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: gotify-db-app
key: password
- name: GOTIFY_SERVER_PORT - name: GOTIFY_SERVER_PORT
value: "80" value: "80"
- name: GOTIFY_SERVER_KEEPALIVEPERIODSECONDS - name: GOTIFY_SERVER_KEEPALIVEPERIODSECONDS
@@ -51,9 +67,6 @@ spec:
limits: limits:
cpu: 200m cpu: 200m
memory: 128Mi memory: 128Mi
volumeMounts:
- mountPath: /app/data
name: data
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /health path: /health
@@ -188,7 +201,4 @@ spec:
limits: limits:
cpu: 100m cpu: 100m
memory: 64Mi memory: 64Mi
volumes: # No volumes — Postgres handles persistence
- name: data
persistentVolumeClaim:
claimName: gotify-data
+1 -1
View File
@@ -2,7 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- namespace.yaml - namespace.yaml
- pvc.yaml - db.yaml
- secrets.yaml - secrets.yaml
- deployment.yaml - deployment.yaml
- service.yaml - service.yaml
-12
View File
@@ -1,12 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gotify-data
namespace: notifications
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
+83 -39
View File
@@ -1,48 +1,92 @@
# Gotify secrets — SOPS-encrypt before committing! #ENC[AES256_GCM,data:zdor03HqWha5KnsrNNfM+NT96bZafKhVJ1vfARlqbXzd56QTMq6tScippCbJWCtqLhK5,iv:shItz1uc3RYNBo9Xmq4M+0/Yjw6PbpF4x1tfZZQOSUM=,tag:8Bcwz5XkgLtjuMrDxFXOZQ==,type:comment]
# sops -e -i k8s/apps/gotify/secrets.yaml #ENC[AES256_GCM,data:QuQeqD21UgcVOUjlJTzQOAXioYWmnc4b38UTPAClL3WIf7/MqDZ2SQ==,iv:PHw7QplRXB4XBQh3fEolZpdvMMxLucaZx/aY8K5LE20=,tag:Vp3BUb2JPOBffwZW0jPfOg==,type:comment]
# #
# After Gotify starts: #ENC[AES256_GCM,data:3JOwvqkN0IsF4ogdR6SK9xZKCs7I,iv:uB3qIR9r2AZ7pewPzV+5anulFHnnmUmlBhfhUdM8A2U=,tag:w4g3k9bBcqO6o0pGfCRjuA==,type:comment]
# 1. Login to gotify.riotpiao.com with admin creds below #ENC[AES256_GCM,data:mMQ0VWb11cOPA5QgdUqNGgeeLyzbFRKU7YOhFHmUx7Zb3NmePRXZ2gT6Y9vuieiMxCL/GeNXeg==,iv:URCI0VXAlcT5QI01L4yBB+P3+VT7lEfbmQiMQhVTUYk=,tag:CiyVNUzFZBmydrK9I861Og==,type:comment]
# 2. Create an Application (e.g., "forgejo") → copy the app token #ENC[AES256_GCM,data:AX/p8bnjaddZFwR3hkvZNsm8op3nx6b8jnWEj6mNvPryq/MGpoS9SkNvlyACQ5KYMIYG6QtltG9buv4n5CVJzyYX,iv:YKdhnl+YtvvOFjpNXp3Ap3mnDPaDa0YoWupviRwPjwE=,tag:etGle/aGv7RemlmUeKEkCg==,type:comment]
# 3. Create a Client → copy the client token #ENC[AES256_GCM,data:Rs33VKbpeUCDrUnblHfBXiVjZa7tlxFV+QWiMYMv/uLzmXBS7NCpU7fIgNKG,iv:ZrZug41B6AysyVylQ6T6Z+Txc2nBUvblX8pRP2r12iQ=,tag:SvrNa0N0mprMq/Wqf77tZg==,type:comment]
# 4. Update gotify-tokens secret with the client token #ENC[AES256_GCM,data:sBzMx0RT9FAdtE4pEyWUkNX/nKDgHoEHk4IITSKpAl0/3i2uWgWs9NO4Sp1USAQaTtIa+Os=,iv:Zqj2GAtfw3UsIDgrTZ8erug+KgjP2ckNcas7NaZei+E=,tag:6xRTvzspIVLVhQJMQinPOQ==,type:comment]
# 5. Configure Forgejo webhook (see README below) #ENC[AES256_GCM,data:bD+GzYKonJecM6Pym1powOYISWJDPIEVT8V1Hu8AM1U/wCdmq70TNq6DRu1FDSG+,iv:a/p1nM+rkie+mZ7E9A1Zg3qQloGTdtRWSIUJPiFNk24=,tag:ymyVypKvIUWmcZeHc5BQFQ==,type:comment]
--- apiVersion: ENC[AES256_GCM,data:4vs=,iv:JAbEScwusLVWdsz4AuuYsBQDuusynhsbstrEyihrZ/k=,tag:b1IY2bxT6TL7djcFCiyMlg==,type:str]
apiVersion: v1 kind: ENC[AES256_GCM,data:1+fCn5DR,iv:HvI/Id59qQC//Uh3Q0P3oJo8ZSWZnhTXv3LTox3BuEU=,tag:i9aPbsqch9YKekzbf5G4cg==,type:str]
kind: Secret
metadata: metadata:
name: gotify-admin name: ENC[AES256_GCM,data:4528BwgvzAtwiHyK,iv:/vz1jcRbLJmAoqDvgTmDbxvE5gYxpInSEGBP7IUFvlI=,tag:yZPEJNrHht9IVrlzsAUqFA==,type:str]
namespace: notifications namespace: ENC[AES256_GCM,data:FZMvrE3BV3iD3As59A==,iv:PfOFjLpZspP6JeTdNwa1IwjRl4JE1XKeAOab+8S8imk=,tag:yg3bWjkPngilwirv1CscDw==,type:str]
type: Opaque type: ENC[AES256_GCM,data:QnoC8qce,iv:8paBbJZtmEbKSKlkwhy+3lLiRLoy1I+lB0xMBPdcNMg=,tag:p4/aUaSZHBzeU1CRqJ5rLw==,type:str]
stringData: stringData:
username: admin username: ENC[AES256_GCM,data:ANiWzQM=,iv:I3LrmBV5HEtjO+aysXuZEOmbvhyOfuOJCqZHx1ykWyU=,tag:zEV4gu2125L0bHGUkgGdNQ==,type:str]
password: CHANGE_ME_BEFORE_DEPLOY password: ENC[AES256_GCM,data:E5OW/ydSmskcnR8UYZsi9SZM5kpbiAE=,iv:NTgtzAK8P0jeczb8Egrrkmlf1Ejulabxh2/rLWzwNTk=,tag:kF2luernkCvnNl8o3EEvfA==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3b3VUNi9oRFB3ZjFyeVQr
a2dTR3AvWnFDNi9YZ0QvaVNLU3dUSnlEMVdZCklhZjAzNURsZDBNWEI1MzZ1LzFl
cUNhUWJUcVhNSjZqSG43MWdudStaRUkKLS0tIEpuc2kwR2hpUzRiSXZyb1I0V3lE
YWJPcHFZU1lBOFFrQ3ErdzF4RzRqYzgK7q0N+ZcF/plIHR7HeRTF2qRmE4Za+eO+
mMCo01fxd3ybf8gXnC9lrYmIK4oCkS8cg/B8mfIIBqts7XDzHiCaGA==
-----END AGE ENCRYPTED FILE-----
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
lastmodified: "2026-09-10T01:34:10Z"
mac: ENC[AES256_GCM,data:SURFYfHkesBFpdzeoMSdASSduGefetZ9sPY+0KgeUP4lMRX70kH6vnZdt5UyXe4hMgRCDA38sF+UgITW1pxhngqp8tL8QK5GBj+wTX9/zGmbtYl0zPvjvvXLx1vXGvUtfcEyxHW90EfDWae3JxQHCNlUcEH61hNj1ccZX+ceufM=,iv:mCQp3110nD3tg1eaf6P4e9XiMPbO+bhchC0zZ5+vqX8=,tag:vWGl3iaEi3KEoW87JBl3/g==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2
--- ---
apiVersion: v1 apiVersion: ENC[AES256_GCM,data:too=,iv:DFFOApoqaZDGmJe7NcGBrvE69kp85YpLwJUsImKSxmk=,tag:sKWVbV1U3oI656Sb2+iWew==,type:str]
kind: Secret kind: ENC[AES256_GCM,data:Lp1+y5tL,iv:pHbNnGCX6qsAIMSjkdHNyuHZct63jcDatnaek6Z5rjg=,tag:k/0Y7noIi/nAAVLpCv9GTw==,type:str]
metadata: metadata:
name: gotify-tokens name: ENC[AES256_GCM,data:QHAhhTHIxeyBXCfE2g==,iv:Q/2HuRPlbEC5AMONhi6POcCDdCJv3vHWMvmm6ZXGrr0=,tag:B+3Jhut9I7NzfpGtF969dQ==,type:str]
namespace: notifications namespace: ENC[AES256_GCM,data:VnXAy9fR8ixXanheUw==,iv:Uvkcm7zqgLCWlzcx9fO2/ccytzM7MlD0Zl1X8WNaz7Q=,tag:JsWelai4T+aNg1UabZPMTg==,type:str]
type: Opaque type: ENC[AES256_GCM,data:I3UeAwER,iv:mtIMz/kuzbHx0HMSPVaqzHMLvhCGzvkdNLRfr7xNKBM=,tag:QCgh1anTe7oN2j3kkUM2/g==,type:str]
stringData: stringData:
# Create after first login: #ENC[AES256_GCM,data:1XpJNypHreS+gUaRgEZe+CaJykvp3JJgI6Q=,iv:g/loGxxl6hvVvODmqwtip6om92df0tWVVg/msZvDOrg=,tag:ATCy0nG4PpGNoPQDRkXRcA==,type:comment]
# - App token: Gotify UI → Applications → Create → copy token #ENC[AES256_GCM,data:EmitK30ZlOZz7MruQ/ItDim2LjU3uUZlOQhfr2KO65t2lcAw3xcGZAdaQpQfyZ0RDUQucUgnl+g8ckhH6rZ7+/N9,iv:4655+CO98ak8cG/dZI0H2RCykLS29AWAZ4LjrH3Tg2M=,tag:eqhcnuY6rbyy98ABZrtPrw==,type:comment]
# - Client token: Gotify UI → Clients → Create → copy token #ENC[AES256_GCM,data:iBIgf2Ru/cwE8D1L7uTdCmcii3lJxny1JE84jntuhROkCK1bqZU2cyf8eC0rq1zONiWwPuTokqFv18v+pdmbzQ==,iv:cnOCtS1qGy/w7ZkG2zRjqpgSeLU0FgvuV4tuBJ2SQ5U=,tag:3uxrBDiH/zMpLvFxZVfn1Q==,type:comment]
app-token: CHANGE_AFTER_FIRST_LOGIN app-token: ENC[AES256_GCM,data:NNpIl+DOZ9hjP5TY0bmHWRfSC88GTy1O,iv:NDnz4i/rYbbUxpxUSHFyXc31iI4PQSasjEcHV3u9E70=,tag:8mBEOwhXxVPmoX85fAVKdg==,type:str]
client-token: CHANGE_AFTER_FIRST_LOGIN client-token: ENC[AES256_GCM,data:nMSuoalWILpFijzVcB7sfA+d4EDb7HzL,iv:cgqkBnBUDqYMBXpQVCMzIPvpVvMu3BKl1CDqtY04xrk=,tag:Eql+ZAqPdvzm2M19HOoOHA==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3b3VUNi9oRFB3ZjFyeVQr
a2dTR3AvWnFDNi9YZ0QvaVNLU3dUSnlEMVdZCklhZjAzNURsZDBNWEI1MzZ1LzFl
cUNhUWJUcVhNSjZqSG43MWdudStaRUkKLS0tIEpuc2kwR2hpUzRiSXZyb1I0V3lE
YWJPcHFZU1lBOFFrQ3ErdzF4RzRqYzgK7q0N+ZcF/plIHR7HeRTF2qRmE4Za+eO+
mMCo01fxd3ybf8gXnC9lrYmIK4oCkS8cg/B8mfIIBqts7XDzHiCaGA==
-----END AGE ENCRYPTED FILE-----
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
lastmodified: "2026-09-10T01:34:10Z"
mac: ENC[AES256_GCM,data:SURFYfHkesBFpdzeoMSdASSduGefetZ9sPY+0KgeUP4lMRX70kH6vnZdt5UyXe4hMgRCDA38sF+UgITW1pxhngqp8tL8QK5GBj+wTX9/zGmbtYl0zPvjvvXLx1vXGvUtfcEyxHW90EfDWae3JxQHCNlUcEH61hNj1ccZX+ceufM=,iv:mCQp3110nD3tg1eaf6P4e9XiMPbO+bhchC0zZ5+vqX8=,tag:vWGl3iaEi3KEoW87JBl3/g==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2
--- ---
apiVersion: v1 apiVersion: ENC[AES256_GCM,data:Ub4=,iv:t0/4neL1XNR9sc2a0rZ7c4fOnZjRw0ACOY2Pp2A61IY=,tag:X8W/L0wfQLw4JF5aQ1dteg==,type:str]
kind: Secret kind: ENC[AES256_GCM,data:54r8Je+L,iv:feX1TVQNZ7VrYIFiYKvopB0hYEIl9iw0ly2A6u/j+1Y=,tag:gD7iCoOCxIUT5uj1bGfikQ==,type:str]
metadata: metadata:
name: gotify-smtp name: ENC[AES256_GCM,data:ub9FC+uwioKPMXg=,iv:1YbPJQle7t60Onnh77R8ffc8M87DNU1cmBQbVA72B28=,tag:3DsCpXMHcTIYN3uBh6Ztug==,type:str]
namespace: notifications namespace: ENC[AES256_GCM,data:dLIZlOoIo9YF2VyHew==,iv:YxQFWoMhVMG3jUJqmAtAUwzRr7MkSRjiWlq92rnymIE=,tag:iw5AI7kKlp+wKcgg2GGMxw==,type:str]
type: Opaque type: ENC[AES256_GCM,data:4+CN/8f0,iv:Gmnh4TP0oCAiJ2VTrBT/Q1PWkPZks6XnzWu3io32hvQ=,tag:m5SNbovIU7pjwGACc4Q50g==,type:str]
stringData: stringData:
# SMTP config for email forwarding #ENC[AES256_GCM,data:bGrL1ia7o4ADA4J7soyOLCMVq3Y7kUU0BHhdz9ID45/q,iv:fwMHTJ68cYgBrzeAQ/CNdpfH14bAv7Exu7H6/QUk1ok=,tag:Lwc+FnbCd48oErLp5qEmcw==,type:comment]
# Gmail example (use App Password, not account password): #ENC[AES256_GCM,data:bQdj5OgHL60gffBB5DZPOL6+RFWxrrBxa8lPkXbYR7viOf0v0xb9iBqjS/o5kIOjjcdeJibgGhQ=,iv:DJQBppj4w3KqFOC1MTEYwoc1LT0Qz17fKb611hgH+AE=,tag:D+l7BE+FEgxZFTIDLpKsiA==,type:comment]
host: smtp.gmail.com host: ENC[AES256_GCM,data:kaPQN225/tgQLLmejLQ=,iv:Xn69DoAa7Zop6EjZwIRgO/zVPdxvARgKkoacIE4Fcwc=,tag:hlCapggLwTsXr4DeejAYkQ==,type:str]
port: "587" port: ENC[AES256_GCM,data:vkBH,iv:uCA8fhnMZDg/qiSQYRYmhuc7dnteoavcuJxfSCE8GU0=,tag:ujosmW6GjrEa30mBCEFXgw==,type:str]
from: [email protected] from: ENC[AES256_GCM,data:kiWkZ7zmIT6gOHCH6hHI8fiJcQ==,iv:tqpYbLohLYbQ6kotOC5vDw6cZEFvNK1UEiKIa+nFzuQ=,tag:POZAxMpiGItr1lRY1yhW6w==,type:str]
user: CHANGE_ME user: ENC[AES256_GCM,data:Vdew4lqPsOD2,iv:1zJE/hEDbchie4crugcjCfvACklXoeSzqopXbPulD/E=,tag:QdJvWQRi9Tc7pgMBigKsBg==,type:str]
password: CHANGE_ME password: ENC[AES256_GCM,data:JcjnnXFkAXmI,iv:VgpYDrTn6o3l7tAk0lGMB2rpu+kB1IuAFu2VZMyJkX4=,tag:QR+En9nv/30yyt09GTcyqg==,type:str]
notify-email: [email protected] notify-email: ENC[AES256_GCM,data:zMvA9KXy6rK2HFRrjAmZd5c/ay8=,iv:BtCXUsHneVc1QzvPKRwBjmbYc39I3Xy/zFoMg4l22Ks=,tag:NTZKDoPaLARJoTIX9u6oGQ==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3b3VUNi9oRFB3ZjFyeVQr
a2dTR3AvWnFDNi9YZ0QvaVNLU3dUSnlEMVdZCklhZjAzNURsZDBNWEI1MzZ1LzFl
cUNhUWJUcVhNSjZqSG43MWdudStaRUkKLS0tIEpuc2kwR2hpUzRiSXZyb1I0V3lE
YWJPcHFZU1lBOFFrQ3ErdzF4RzRqYzgK7q0N+ZcF/plIHR7HeRTF2qRmE4Za+eO+
mMCo01fxd3ybf8gXnC9lrYmIK4oCkS8cg/B8mfIIBqts7XDzHiCaGA==
-----END AGE ENCRYPTED FILE-----
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
lastmodified: "2026-09-10T01:34:10Z"
mac: ENC[AES256_GCM,data:SURFYfHkesBFpdzeoMSdASSduGefetZ9sPY+0KgeUP4lMRX70kH6vnZdt5UyXe4hMgRCDA38sF+UgITW1pxhngqp8tL8QK5GBj+wTX9/zGmbtYl0zPvjvvXLx1vXGvUtfcEyxHW90EfDWae3JxQHCNlUcEH61hNj1ccZX+ceufM=,iv:mCQp3110nD3tg1eaf6P4e9XiMPbO+bhchC0zZ5+vqX8=,tag:vWGl3iaEi3KEoW87JBl3/g==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2