feat(k8s/grocy): add grocy stack (deployment scaled to 0 for data migration)
This commit is contained in:
76
kubernetes/app/grocy/cronjob-backup.yaml
Normal file
76
kubernetes/app/grocy/cronjob-backup.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: grocy-config-backup
|
||||
namespace: grocy
|
||||
labels:
|
||||
app: grocy-backup
|
||||
spec:
|
||||
schedule: "0 2 * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: grocy-backup
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
initContainers:
|
||||
- name: sqlite-backup
|
||||
image: alpine:3.21
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
apk add --no-cache sqlite
|
||||
sqlite3 /db-local/grocy.db ".backup /backup/grocy.db"
|
||||
volumeMounts:
|
||||
- name: db
|
||||
mountPath: /db-local
|
||||
readOnly: true
|
||||
- name: backup-tmp
|
||||
mountPath: /backup
|
||||
containers:
|
||||
- name: resticprofile
|
||||
image: creativeprojects/resticprofile:0.32.0
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
resticprofile -c /secrets/profiles.yaml -n grocy-config backup
|
||||
resticprofile -c /secrets/profiles.yaml -n grocy-config copy
|
||||
env:
|
||||
- name: B2_ACCOUNT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grocy-backup-config
|
||||
key: B2_ACCOUNT_ID
|
||||
- name: B2_ACCOUNT_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grocy-backup-config
|
||||
key: B2_ACCOUNT_KEY
|
||||
volumeMounts:
|
||||
- name: secrets
|
||||
mountPath: /secrets
|
||||
readOnly: true
|
||||
- name: config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
- name: backup-tmp
|
||||
mountPath: /backup
|
||||
volumes:
|
||||
- name: secrets
|
||||
secret:
|
||||
secretName: grocy-backup-config
|
||||
- name: config
|
||||
persistentVolumeClaim:
|
||||
claimName: grocy-config
|
||||
- name: db
|
||||
persistentVolumeClaim:
|
||||
claimName: grocy-db
|
||||
- name: backup-tmp
|
||||
emptyDir: {}
|
||||
96
kubernetes/app/grocy/deployment.yaml
Normal file
96
kubernetes/app/grocy/deployment.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: grocy
|
||||
namespace: grocy
|
||||
labels:
|
||||
app: grocy
|
||||
spec:
|
||||
replicas: 0
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: grocy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: grocy
|
||||
spec:
|
||||
securityContext:
|
||||
# runAsNonRoot omitted — LinuxServer image requires root for s6-overlay init
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
- name: migrate-db-to-local
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
if [ ! -d /config/data ]; then
|
||||
echo "No /config/data yet — skipping"
|
||||
exit 0
|
||||
fi
|
||||
cd /config/data
|
||||
for db in *.db; do
|
||||
[ -f "$db" ] && [ ! -L "$db" ] || continue
|
||||
echo "Migrating $db to local storage..."
|
||||
cp "$db" "/db-local/$db"
|
||||
rm -f "$db"
|
||||
ln -sf "/db-local/$db" "$db"
|
||||
done
|
||||
echo "Done"
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: db
|
||||
mountPath: /db-local
|
||||
containers:
|
||||
- name: grocy
|
||||
image: linuxserver/grocy:4.6.0
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: PUID
|
||||
value: "1027"
|
||||
- name: PGID
|
||||
value: "100"
|
||||
- name: TZ
|
||||
value: Etc/UTC
|
||||
- name: UMASK
|
||||
value: "002"
|
||||
- name: GROCY_AUTH_CLASS
|
||||
value: "Grocy\\Middleware\\ReverseProxyAuthMiddleware"
|
||||
- name: GROCY_REVERSE_PROXY_AUTH_HEADER
|
||||
value: Remote-User
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: db
|
||||
mountPath: /db-local
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 80
|
||||
initialDelaySeconds: 90
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumes:
|
||||
- name: config
|
||||
persistentVolumeClaim:
|
||||
claimName: grocy-config
|
||||
- name: db
|
||||
persistentVolumeClaim:
|
||||
claimName: grocy-db
|
||||
24
kubernetes/app/grocy/ingress.yaml
Normal file
24
kubernetes/app/grocy/ingress.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: grocy
|
||||
namespace: grocy
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.middlewares: authelia-chain-authelia-authelia-auth@kubernetescrd
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- ${GROCY_HOST}
|
||||
secretName: grocy-tls
|
||||
rules:
|
||||
- host: ${GROCY_HOST}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: grocy
|
||||
port:
|
||||
number: 80
|
||||
4
kubernetes/app/grocy/namespace.yaml
Normal file
4
kubernetes/app/grocy/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: grocy
|
||||
50
kubernetes/app/grocy/networkpolicy.yaml
Normal file
50
kubernetes/app/grocy/networkpolicy.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny-ingress
|
||||
namespace: grocy
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-ingress-controller
|
||||
namespace: grocy
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: grocy
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: traefik
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-backup-egress
|
||||
namespace: grocy
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: grocy-backup
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
- ports:
|
||||
- port: 8888
|
||||
protocol: TCP
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
32
kubernetes/app/grocy/pv-config.yaml
Normal file
32
kubernetes/app/grocy/pv-config.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: grocy-config-nfs
|
||||
spec:
|
||||
capacity:
|
||||
storage: 5Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ""
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
mountOptions:
|
||||
- hard
|
||||
- timeo=30
|
||||
- retrans=3
|
||||
nfs:
|
||||
server: synology.storage.lviv
|
||||
path: ${GROCY_NFS_PATH}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: grocy-config
|
||||
namespace: grocy
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ""
|
||||
volumeName: grocy-config-nfs
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
12
kubernetes/app/grocy/pvc.yaml
Normal file
12
kubernetes/app/grocy/pvc.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: grocy-db
|
||||
namespace: grocy
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local-path
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
26
kubernetes/app/grocy/secret-backup.sops.yaml
Normal file
26
kubernetes/app/grocy/secret-backup.sops.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: grocy-backup-config
|
||||
namespace: grocy
|
||||
stringData:
|
||||
profiles.yaml: ENC[AES256_GCM,data:oCTBX2/3P6xmhgi4ObZ81gl8goRVbqn1kL0ZNbiyurzX+omqsk7RrpVpKXsBwAXbqDIvwAk6gWyFQwT3g8LQEzpLJTXLSJE4pNSlpK3V02xtWzQu+28rPptnpRKqYqLM7DGeTmm1AJwzTNaCG7X46RBCc1gWP8S6vXvjjwpdqpsJH3G2zQpZsx8fmlRC3Vc38DWq1EQatnOi+RFMd2e7WufL6Yari9r14E8tpp3s1CZ1qwQ9W9K5uHAjXI+kuPL/NVRqYzkqwuSvmD4QTtHhH1eyu3WFIkIkah1tDYAivy+wCZjMLSe+q6heHiXIMV5IaVj0tTg/L5wbjwwQZXB53nnbuadViPuC3kFBej7QHkx6hwIMmEgKsp2skG7mFFLkYm9AXDOUxUZlrVU3eN47PxR2nFNYiWoQi5YSYkltBA4eEFZ9d4EKFcxWqj1tr7Y4VDtTjz2ZEfff/eyu8oJ75XdFb74Or8PXBKfFUpLjtOnax7Dc7Ff/fv1oTCvmHSxyv4pgdCfrkwERnMd5DspkxEppCqPpxtxMVIp31cAWDkvJ21wmCR9esCVHfe1eTj4+76Ok7c3vEXy49M/K9GstwPIWcEwiDrvftiXMEgHn95YRcNBGdjh3nLJ76DTO+pTsuVoOW5Bogcn4W7hGrgYKFFFESMzXUWRlF15YPN0RHg/2TI0I9Ke1lIOhUZj1ljefH9UwnERhKmXZ7AlsXvDPqUpYPwPmW55bbkDDa/VDp58BLA==,iv:tfWTMYk99Jkje9GAHLEUu77LIIP3XQjWaR25HQYHOLg=,tag:chdgdkDNp57f3OCIEOcYTw==,type:str]
|
||||
restic-password-nas: ENC[AES256_GCM,data:bG0LFnoo84t/r+cOGwjBPALc4dzrUj3Y9sMIuF7W1bjEkC9XsvSTQkczK1vmevhKuWdaaPYBr72gFzHcga7hUQ==,iv:tD1zf/mWknbMWtn/k0o8zJPcNvBK2Xa12eNSPyarybI=,tag:b03st6HrnpgXgkjDn0Gp+Q==,type:str]
|
||||
restic-password-b2: ENC[AES256_GCM,data:nyDMggQFE5Z2aCz/+kTdIIYe2BXisTb/MlVRfiRMCKjPPZDQ49jyf3z986jEXp9CyxKK665ySjnZEXn3txi/PQ==,iv:xvdCBKhlFPMH4cTXSm2VtFlpJlgbiggvdvfyjB7b76I=,tag:HEoizYHWcnkVTAe3APWsMA==,type:str]
|
||||
B2_ACCOUNT_ID: ENC[AES256_GCM,data:iXPdIOHzrggnh2pjimgFpQPNmA2FZgj8Mg==,iv:uS91Eg9xOHdZH113zfzlldgmCnFTopuOx/mmlsQsUC8=,tag:QDtAIhKwUkpt3v/tRZBYFA==,type:str]
|
||||
B2_ACCOUNT_KEY: ENC[AES256_GCM,data:RLT5JBRs7JfcJzFv6MilqXUTf5MocOeB1Pp1kQaUcg==,iv:FqyfAgE0wM7KFdPTBM2s3eKrXvzeUCO7qLyUwZUCPqM=,tag:rM15UoD2CyZ4yRYAQJ6xoA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age1zffnskvuezntkk703a0pyxsd5m8vx2hm33dr47wdfy8mn4fdw4sqgw0jgc
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArR1BFRmFZU3dJblVEVnRr
|
||||
bksyUzVHU0NjUkY2bGlXMzh3N0VEall4TFNVCmlBOUM4UnBPeVRPOHp3SnI5djUr
|
||||
elpodVdqSWt2L0ovMFZCNUZTVkxIcVEKLS0tIDRvNzdSQ3hxN0o3R3ViSzJoQi9E
|
||||
eDZTN3Fzak81RDJPR2dVOHlKOHJURzAK8DUPG6iCb6s7CQ6/+TZrIcLDKbN8bmh6
|
||||
lUwBN0Ciu8OAX+tRW6P3R/iZebc9s5jH/VkzGsR9qGjKw5BsNB811Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-03-11T19:28:51Z"
|
||||
mac: ENC[AES256_GCM,data:2SpDKL7Mwu/h7KLQoBXnk9iB84XNaex8hVbaUqmq4myIHAzvntb/zLXOexhsKPbQ0Ea7QDOKZIpkAG2dBPtRMtZWTr9uPpMoHzQG2lDGnYKGvBJXW/7WmaCdXlDf1FukqnYJZ3YoY+5xpTpYpXbWv1fyygp1YWmjKzY5qfcjtvw=,iv:TNbbiRmRRvPuQKiBjan7CVylNrt+eTjXxwKTJbeOhLw=,tag:9bqc9L4LlSq/d+oT257rsQ==,type:str]
|
||||
encrypted_regex: ^(data|stringData|email)$
|
||||
version: 3.12.1
|
||||
12
kubernetes/app/grocy/service.yaml
Normal file
12
kubernetes/app/grocy/service.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: grocy
|
||||
namespace: grocy
|
||||
spec:
|
||||
selector:
|
||||
app: grocy
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
Reference in New Issue
Block a user