Compare commits

...
144 Commits
Author SHA1 Message Date
azykov@mail.ru 454922b475 gitea port 2222 2026-09-05 22:31:49 +03:00
azykov@mail.ru 4623c7c9f7 gitea config path fix 2026-09-05 16:03:44 +03:00
azykov@mail.ru f2045463c4 gitea ready to start, replicas = 1 2026-09-05 15:52:51 +03:00
azykov@mail.ru 891727b487 gitea 2026-09-05 14:40:02 +03:00
azykov@mail.ru 67284c4d32 openwebui removed 2026-08-28 08:41:30 +03:00
azykov@mail.ru 852ca6c8f3 empty joplin 2026-08-27 20:05:27 +03:00
azykov@mail.ru 489439d330 joplin 2026-08-27 19:37:51 +03:00
azykov@mail.ru 5cabacee84 separate keys per app 2026-08-25 21:25:40 +03:00
azykov@mail.ru 07fd7110e9 fix for photo-stats remnants not cleaned up 2026-08-25 16:53:58 +03:00
azykov@mail.ru 2d78870b94 more generous liveness probe for metallb 2026-08-25 12:18:56 +03:00
azykov@mail.ru a6c72aa3bc cpu & memory limits for st-backup-proxy 2026-08-25 12:18:33 +03:00
azykov@mail.ru 3b833cc575 immich data-old folder deleted 2026-08-24 10:03:11 +03:00
azykov@mail.ru 6600fa660e immich app is back on after folder split 2026-08-24 09:51:28 +03:00
azykov@mail.ru f59b01e290 prepare immich for data volume split 2026-08-23 21:13:49 +03:00
azykov@mail.ru 1df90d3a6e md-vault-mcd longer startup probe 2026-08-23 17:52:22 +03:00
azykov@mail.ru 90014b22c1 fix for authelia and md-vault-mcp image versions 2026-08-23 17:45:35 +03:00
azykov@mail.ru cc7eabfedc fix for mosquitto and fava image versions 2026-08-23 17:39:19 +03:00
azykov@mail.ru cb86b45293 apps pinned to exact version numbers 2026-08-23 15:25:16 +03:00
azykov@mail.ru 9075f52f90 bump immich to 3.1.0 2026-08-23 12:02:56 +03:00
azykov@mail.ru 8bce8e2b63 immich moved to photo.aggtaa.com 2026-08-23 10:21:52 +03:00
azykov@mail.ru 1fdae9c55f immich temporarily moved to photo2.aggtaa.com. photo-stats removed 2026-08-23 10:08:33 +03:00
azykov@mail.ru c186e2b765 removed rsync & rsync-web from immich-server 2026-08-23 09:49:20 +03:00
azykov@mail.ru 82deffb39f pin immich ver to 2.7.5 2026-08-23 09:30:05 +03:00
azykov@mail.ru a26528a00c immich data pv bump to 200gb 2026-08-23 07:21:51 +03:00
azykov@mail.ru 805711e98e removed nfs-photo 2026-08-22 11:46:01 +03:00
azykov@mail.ru 25e74fc0cb immich mergerfs 2026-08-22 09:55:19 +03:00
azykov@mail.ru 87cc20df72 immich volume 100 to 800 gb size increase 2026-08-21 23:30:31 +03:00
azykov@mail.ru 09eefb20ae rsync from nas for immich 2026-08-21 21:39:14 +03:00
azykov@mail.ru b889c3a2f6 immich-server longer stratup probe 2026-08-21 15:42:39 +03:00
azykov@mail.ru 5f2b951480 immich-postgres fix 2026-08-21 15:37:25 +03:00
azykov@mail.ru 6a94f95c1c immich 2026-08-21 15:23:58 +03:00
azykov@mail.ru 85f758b76c category.search=newest for photo mergedfs 2026-08-18 08:18:51 +03:00
azykov@mail.ru e0bf2c08cf re-export of base and sub1 photo dirs via webdav 2026-08-18 02:09:34 +03:00
azykov@mail.ru ec6d48adb5 cluster ip for nfs-photo 2026-08-18 01:54:34 +03:00
azykov@mail.ru 6729a0bdd0 photo-stats now count to year ago exactly 2026-08-16 21:09:01 +03:00
azykov@mail.ru 42937adbac photo-stats 2026-08-16 20:48:51 +03:00
azykov@mail.ru 592f295e64 removed redundand namespace reference 2026-08-16 20:44:17 +03:00
azykov@mail.ru ce67fce3c2 fix for nfs-photo not exporting root dir 2026-08-16 20:42:10 +03:00
azykov@mail.ru 8600e786c6 fix for nfs-photo mergefs start before ganesha 2026-08-16 07:36:46 +03:00
azykov@mail.ru b87970e2d1 nfs-photo 2026-08-15 17:27:07 +03:00
azykov@mail.ru b7afe1f1ab webdav generous helm timeouts and retries 2026-08-15 09:45:48 +03:00
azykov@mail.ru 9b44c60723 bump longhorn to 1.12.1 2026-08-15 07:47:24 +03:00
azykov@mail.ru 22a9385dc8 dir changes to all apps that use ganesha shares 2026-08-13 20:45:07 +03:00
azykov@mail.ru 65c50d3bdd sync webdav with ganesha folder renamed 2026-08-13 20:26:08 +03:00
azykov@mail.ru cd647a619b ganesha share renamed 2026-08-13 20:20:26 +03:00
azykov@mail.ru 2ba8356bdb webdav shares renamed 2026-08-13 20:17:42 +03:00
azykov@mail.ru 0e4802c611 webdav folder shares restructured 2026-08-13 14:24:30 +03:00
azykov@mail.ru 5403c9d820 fix for NAS folder paths in webdav 2026-08-13 11:03:26 +03:00
azykov@mail.ru 2c8068dc42 NAS 10.0.64.80 exposed at dav.aggtaa.com 2026-08-13 10:52:05 +03:00
azykov@mail.ru ff17a67ea1 longhorn backup schedule 2026-08-11 22:29:15 +03:00
azykov@mail.ru f6077bbe44 longhorn default backup target 2026-08-11 22:24:41 +03:00
azykov@mail.ru 555a84fa28 longhorn backup target 2026-08-11 22:17:47 +03:00
azykov@mail.ru be53f040c7 rclone s3 proxy 2026-08-11 21:34:21 +03:00
azykov@mail.ru 3a0a00f994 synthing 2026-08-11 10:17:59 +03:00
azykov@mail.ru 56bf723968 radicale and agendav removed. infcloud at c.aggtaa.com 2026-08-10 11:06:34 +03:00
azykov@mail.ru c30a03cdc5 infcloud added. davical additional ingress at c2.aggtaa.com 2026-08-10 10:46:50 +03:00
azykov@mail.ru d4e0b37ce1 agendav switched to davical 2026-08-09 21:26:20 +03:00
azykov@mail.ru c8425bf0f2 fix for davical /server url path 2026-08-09 20:59:43 +03:00
azykov@mail.ru 6b966107d1 moved radicale to c.aggtaa.com/server2 and davical to c.aggtaa.com/server 2026-08-09 20:48:17 +03:00
azykov@mail.ru dc00fd2467 davical readiness probe 2026-08-09 08:20:36 +03:00
azykov@mail.ru 29984ca533 davical initcontainer 2026-08-09 08:00:28 +03:00
azykov@mail.ru 7e19f3cef7 davical 2026-08-08 19:39:02 +03:00
azykov@mail.ru 625d43b033 switch flux from main to prod 2026-08-08 19:28:34 +03:00
azykov@mail.ru fc14ffb02f bump openwebui to v0.11.0 2026-08-03 21:53:01 +03:00
azykov@mail.ru 4f0aa341e0 --poll-watcher for fava, removed actimeo from its pv 2026-08-01 23:12:01 +03:00
azykov@mail.ru 55e958673a noac to actimeo for fava 2026-08-01 22:41:46 +03:00
azykov@mail.ru 0724da6d35 fava nfs noac option 2026-08-01 22:24:10 +03:00
azykov@mail.ru 0c0927d63a driftDetection mode enabled for all apps 2026-08-01 20:04:51 +03:00
azykov@mail.ru 463a5608ff implicit consent for all internal oidc applications 2026-08-01 19:28:46 +03:00
azykov@mail.ru 765cb3a157 nfs-ganesha explicit chmod /export 2026-07-31 13:30:08 +03:00
azykov@mail.ru f34f9c9b50 fix for markdown-vault-mcp PVC 2026-07-31 12:58:33 +03:00
azykov@mail.ru 1070cd1be7 webdav depends on nfs, not mcp 2026-07-31 12:50:21 +03:00
azykov@mail.ru 04c88aabf1 fava files fix 2026-07-31 12:48:11 +03:00
azykov@mail.ru 7fed7d4d60 allow all file extensions in markdown-vault-mcp 2026-07-31 12:19:37 +03:00
azykov@mail.ru 4cb79c0e35 fava accounting viewer 2026-07-31 12:06:50 +03:00
azykov@mail.ru 299907cb6a webdav set to nfs root 2026-07-31 11:56:31 +03:00
azykov@mail.ru 0cd0dee6eb authelia config fix 2026-07-31 11:43:30 +03:00
azykov@mail.ru 095834ef77 longer authelia oidc keys 2026-07-31 11:38:38 +03:00
azykov@mail.ru 8b327ab538 markdown-vault adaptation to nfs-ganesha 2026-07-31 11:20:25 +03:00
azykov@mail.ru 4203b66402 webdav adaptation to nfs-ganesha 2026-07-31 11:13:43 +03:00
azykov@mail.ru b85207202a yet another metallb nfs pool fix 2026-07-31 09:03:45 +03:00
azykov@mail.ru 70401e78b5 another metallb nfs pool fix 2026-07-31 09:02:09 +03:00
azykov@mail.ru b85c86502b metallb nfs-pool fix 2026-07-31 08:55:57 +03:00
azykov@mail.ru 9e30326e9f nfs-ganesha 2026-07-31 08:51:16 +03:00
azykov@mail.ru 30f3696bd6 fix webdav manifest subpath field 2026-07-27 22:33:57 +03:00
azykov@mail.ru aeba7287de webdav 2026-07-27 14:11:26 +03:00
azykov@mail.ru 709da47a9f authelia audience for markdown-vault-mcp 2026-07-27 10:00:31 +03:00
azykov@mail.ru 67030be4b7 markdown-vault-mcp to authelia oidc 2026-07-27 09:44:07 +03:00
azykov@mail.ru f453de2842 authelia secret reencrypted due to MAC error 2026-07-26 22:38:40 +03:00
azykov@mail.ru 79f9c37c83 markdown-vault-mcp: point STATE_PATH at a file, not the PVC mount dir
Fixes IsADirectoryError on startup when the app tried to atomically
write its change-tracking state to /data/state itself.
2026-07-26 21:02:36 +03:00
azykov@mail.ru 50bbce8474 longhorn.aggtaa.com 2026-07-26 20:56:13 +03:00
azykov@mail.ru 4f75b37427 markdown-vault-mcp 2026-07-26 20:43:06 +03:00
azykov@mail.ru 61a99ffe88 openwebui:main 2026-07-26 20:05:11 +03:00
azykov@mail.ru 1205f71df5 openwebui:main-slim 2026-07-26 19:08:10 +03:00
azykov@mail.ru 5ec8726f67 openwebui 2026-07-26 18:33:24 +03:00
azykov@mail.ru f8eb00b1f3 give HA 5 minutes startup probe 2026-07-25 16:16:46 +03:00
azykov@mail.ru 1eab1d4b9d bump vw to 1.37.0 2026-07-25 16:03:41 +03:00
azykov@mail.ru 584327251e switched to another git 2026-07-25 15:49:38 +03:00
azykov@mail.ru 152ff0bc1f homeassistant 2026-07-25 08:46:46 +03:00
azykov@mail.ru fb12548ff6 zigbee2mqtt hidden behind authelia 2026-07-25 07:49:41 +03:00
azykov@mail.ru fd4f6e34a5 radicale-ui 2026-07-23 21:46:34 +03:00
azykov@mail.ru 572f42e278 radicale at /server web path 2026-07-23 17:20:43 +03:00
azykov@mail.ru 7739e3501a mosquitto 2026-07-21 22:13:15 +03:00
azykov@mail.ru cf2c016c0a faster flux: requeue-dependency=5s 2026-07-21 21:58:18 +03:00
azykov@mail.ru e46545f215 metallb 2026-07-21 21:49:41 +03:00
azykov@mail.ru 31e83a2e3f retryInterval 5s for everything 2026-07-21 21:40:16 +03:00
azykov@mail.ru 0b0f162bb9 typo fix 2026-07-21 20:47:40 +03:00
azykov@mail.ru 9e02835121 authelia 2026-07-21 20:44:04 +03:00
azykov@mail.ru 46afe4f522 vaultwarden meta.name changed 2026-07-21 20:32:46 +03:00
azykov@mail.ru 1ab5f07780 radicale meta.name changed 2026-07-21 20:30:04 +03:00
azykov@mail.ru 19fc6983ce radicale fix reverted. 2026-07-21 19:50:10 +03:00
azykov@mail.ru 080129db9c radicale manifest refactoring 2026-07-21 19:07:51 +03:00
azykov@mail.ru c197a63d13 radicale rwx pvc 2026-07-21 15:18:26 +03:00
azykov@mail.ru 35d36fb14b fixed directories and names 2026-07-21 13:10:40 +03:00
azykov@mail.ru c4529d9d90 typo fix 2026-07-21 13:04:14 +03:00
azykov@mail.ru d9a8f5d59d bump longhorn to 1.12.x 2026-07-21 13:02:13 +03:00
azykov@mail.ru 5c32ef2476 fix cert-manager depends on infra 2026-07-21 12:52:50 +03:00
azykov@mail.ru 1fddd776b5 typo fix 2026-07-21 12:51:26 +03:00
azykov@mail.ru 2a0a0acc71 folder structure. longhorn installation 2026-07-21 12:49:13 +03:00
azykov@mail.ru fe3e885b33 longhorn-retain -> longhorn, longhorn -> longhorn-no-retain 2026-07-21 12:04:43 +03:00
azykov@mail.ru d0a98c77a3 typo fix 2026-07-21 09:17:32 +03:00
azykov@mail.ru 417208ee97 local-path patch 2026-07-21 09:15:08 +03:00
azykov@mail.ru 6884505ef1 switch to new longhorn-retain storageclass 2026-07-20 22:49:10 +03:00
azykov@mail.ru d97c36f42f radicale namespace changed to pim 2026-07-20 22:19:48 +03:00
azykov@mail.ru 47e4ae6d82 retain: true for apps 2026-07-20 21:23:41 +03:00
azykov@mail.ru 8de86e0318 data-migate ns fix 2026-07-20 21:23:28 +03:00
azykov@mail.ru dea1346094 data-migrate utility 2026-07-20 20:57:55 +03:00
azykov@mail.ru 2989700647 kustomization per app 2026-07-20 19:17:08 +03:00
azykov@mail.ru 5b36590c04 radicale 2026-07-20 18:27:54 +03:00
azykov@mail.ru 75db76b185 zigbee2mqtt 2026-07-19 12:55:16 +03:00
azykov@mail.ru 57296b2d34 10m reconciliation interval for everything 2026-07-19 10:38:31 +03:00
azykov@mail.ru 7cf2f0b2e4 added decryption to all layers 2026-07-19 00:28:04 +03:00
azykov@mail.ru 6370c63629 prod letsencrypt certificates 2026-07-18 23:49:21 +03:00
azykov@mail.ru d30eb48264 cert-manager-issuers 2026-07-18 23:46:38 +03:00
azykov@mail.ru 5fdb19a553 vaultwarden 2026-07-18 22:17:58 +03:00
azykov@mail.ru ef3d2a8f05 fix vw.yaml ref path 2026-07-18 21:51:32 +03:00
azykov@mail.ru 08c9965d2f path fix 2026-07-18 21:47:51 +03:00
azykov@mail.ru 6b24b82749 vw app yaml fix 2026-07-18 21:42:00 +03:00
azykov@mail.ru 5a32cf5620 empty vaultwarden yaml 2026-07-18 21:37:50 +03:00
azykov@mail.ru c96e3a891f fix duplicate name 2026-07-18 21:35:31 +03:00
azykov@mail.ru fda2e9cebb cluster structure 2026-07-18 17:24:06 +03:00
azykov@mail.ru af587c6e45 sops age key 2026-07-18 15:49:08 +03:00
FluxandFlux bb558db08f Add Flux sync manifests 2026-07-18 15:26:20 +03:00
FluxandFlux ae22af0434 Add Flux v2.9.2 component manifests 2026-07-18 15:26:12 +03:00
146 changed files with 11373 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
migration-from-old-files/
migration-from-docker/
.decrypted~secret.enc.yaml
gitea-backup/
+37
View File
@@ -0,0 +1,37 @@
creation_rules:
- path_regex: authelia.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1ncw6uagcgncd5m2admqxt73ey0mcd99jsw9sa9lcdv3gu70mcerql8ya0k
- path_regex: davical.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1yx8mey02c0mu6te9hwxwdqp5n4enjz35e8thchsmvhqw7xsrzy0syg367g
- path_regex: immich.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1wmm8qrqnjzvd5nkraed6ylrsxlpn3ru7d7zettdudj7y9sg9dd2su0u3mr
- path_regex: markdown-vault-mcp.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1kct5g7d7eus2hvdnmx99ewsrtz70lhlasxa25euqwdctqhwqqpxqz0n7gg
- path_regex: mosquitto.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1u236u5mzkzj72y4mfxx68gfqw0u0j6tdgkudnm5pdfpe7apq4u6s89jr2h
- path_regex: vaultwarden.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age109zta6l0cwpkxn5wek82kvauxn6yqrq80rsz4mvu5atdnpcn9pusuvnqk5
- path_regex: webdav.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1y8t9v56yuvskznddqfsd4yddzzch0yf2dutvvt8rrx6rjh5f9g0s3qeays
- path_regex: zigbee2mqtt.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1tu4xgfefvgdda0vn2thaqndq4p9tuydq2ud6qa98tzzgnff6749qnlcx29
- path_regex: longhorn-system.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age18hhmdmupj90zjgjpxk3z5cf3lx36hcznnmqsp0fpyg50823pudrqjupydt
- path_regex: s3-backup-proxy.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age15vc3geq4lsec69rnxczluvwgcexeqae02zxzjpswsuatthaasu7qzath48
- path_regex: joplin.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1dertyl956fjsmh00cw468gdnl7lf3frasg4n4c82z0chm6qm2cks6w8gha
- path_regex: gitea.*\.enc\.yaml$
encrypted_regex: ^(data|stringData)$
age: age1ktlxm0hukw6jxm62txqy5x2nl8hpdnal8nv00kfhrf3pw33574aqa6j5cj
@@ -0,0 +1,70 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: authelia
namespace: auth
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: authelia
driftDetection:
mode: enabled
values:
controllers:
auth:
containers:
app:
image:
repository: authelia/authelia
tag: release-v4.39.21
probes:
liveness:
enabled: true
readiness:
enabled: true
service:
app:
controller: auth
ports:
http:
port: 9091
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: auth.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: auth-tls
hosts:
- auth.aggtaa.com
persistence:
config:
type: secret
name: authelia-secrets
globalMounts:
- path: /config/configuration.yml
subPath: configuration.yml
readOnly: true
- path: /config/users_database.yml
subPath: users_database.yml
readOnly: true
db:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 1Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /db
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: authelia
namespace: auth
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- helmrelease.yaml
- middleware.yaml
- secret.enc.yaml
@@ -0,0 +1,14 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authelia
namespace: auth
spec:
forwardAuth:
address: http://authelia.auth.svc.cluster.local:9091/api/verify?rd=https://auth.aggtaa.com
trustForwardHeader: true
authResponseHeaders:
- Remote-User
- Remote-Groups
- Remote-Name
- Remote-Email
File diff suppressed because one or more lines are too long
@@ -0,0 +1,118 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: davical
namespace: pim
spec:
interval: 10m
dependsOn:
- name: davical-postgres
chartRef:
kind: OCIRepository
name: davical
driftDetection:
mode: enabled
values:
controllers:
app:
initContainers:
seed-config:
image:
repository: tuxnvape/davical-standalone
tag: "82-1.1.12"
command:
- sh
- -c
- |
if [ ! -f /mnt/config-pvc/davical.php ]; then
cp -r /config/. /mnt/config-pvc/
fi
# Apache's SCRIPT_NAME (and DAViCal's base_url derived from it)
# only reflects the /server prefix if Apache itself resolves
# that prefix - Traefik forwards the path unstripped. Add an
# Alias so /server/* maps to the app root; idempotent so it
# self-heals if apache.conf predates this fix.
if ! grep -q '^\s*Alias /server ' /mnt/config-pvc/apache.conf; then
sed -i '/^\s*DocumentRoot \/usr\/share\/davical\/htdocs/a\ Alias /server /usr/share/davical/htdocs' /mnt/config-pvc/apache.conf
fi
containers:
app:
image:
repository: tuxnvape/davical-standalone
tag: "82-1.1.12"
env:
TZ: Europe/Moscow
HOST_NAME: c.aggtaa.com
DBHOST: davical-postgres
UPDATE_FROM_OLD: "false"
PGSQL_ROOT_PASS:
valueFrom:
secretKeyRef:
name: davical-secrets
key: PGSQL_ROOT_PASS
PASSDAVDB:
valueFrom:
secretKeyRef:
name: davical-secrets
key: PASSDAVDB
ADMINDAVICALPASS:
valueFrom:
secretKeyRef:
name: davical-secrets
key: ADMINDAVICALPASS
probes:
liveness:
enabled: true
readiness:
enabled: true
startup:
enabled: true
spec:
periodSeconds: 5
failureThreshold: 30
timeoutSeconds: 3
service:
app:
controller: app
ports:
http:
port: 80
ingress:
app:
className: traefik
# no cert-manager annotation here: infcloud's own Ingress already
# requests/owns the Certificate for c.aggtaa.com -> radicale-tls.
# A second Ingress with the annotation would create a competing
# Certificate for the same host/secret.
#
# No stripPrefix middleware either: Apache needs to see the /server
# prefix itself (via the Alias added in the seed-config initContainer)
# so that DAViCal's self-referential links come out correct.
hosts:
- host: c.aggtaa.com
paths:
- path: /server
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: radicale-tls
hosts:
- c.aggtaa.com
persistence:
config:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 256Mi
storageClass: longhorn
retain: true
advancedMounts:
app:
app:
- path: /config
seed-config:
- path: /mnt/config-pvc
@@ -0,0 +1,45 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: davical-postgres
namespace: pim
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: davical
driftDetection:
mode: enabled
values:
controllers:
postgres:
containers:
app:
image:
repository: postgres
tag: "16.14-alpine"
env:
TZ: Europe/Moscow
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: davical-secrets
key: PGSQL_ROOT_PASS
service:
app:
controller: postgres
ports:
postgres:
port: 5432
persistence:
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 2Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /var/lib/postgresql/data
subPath: data
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: davical
namespace: pim
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease-postgres.yaml
- helmrelease-davical.yaml
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: davical-secrets
namespace: pim
stringData:
PGSQL_ROOT_PASS: ENC[AES256_GCM,data:XEhP2zDIz0QCpAXXnZi5JtPsVIfns9zp4fvxcg9/bskHiLU5rR+EgU5n/SdOEDkJ,iv:wR3CGk3csHSCSBqFX+ukOUAmS/p+oZMmdNgvCFNHZlI=,tag:ttAe0GuLmwqA77cWLlne2Q==,type:str]
PASSDAVDB: ENC[AES256_GCM,data:d9RazQG7xeIH4g3ovti6HwyiIyLTzyaPjAcQvLmhsrwdgev7i0AJK7yQeRXxu/16,iv:9ZdfWTlOuyUNpV2ycykOgx8SY7Golz7/Lb+gfTa9fW0=,tag:eL1euF9uQJ+nDLsR1kxtvg==,type:str]
ADMINDAVICALPASS: ENC[AES256_GCM,data:1Zq7duPTIfJX2Stp6AEhKcLHkmQB6vWf4y1f0Iju/zI=,iv:PMOyD3O7UzVPRlFmHTOQSAys032iD4CF8JbI51agWcg=,tag:eDgMbg2lRMnl57NCf5iIkw==,type:str]
sops:
age:
- recipient: age1yx8mey02c0mu6te9hwxwdqp5n4enjz35e8thchsmvhqw7xsrzy0syg367g
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjNTVJZEFuRFhGazF6T1Yv
dnhhTHVUb1h4SEN2eEtDUGREUERkR2wrTUVVCktGSkVLZmlYR3BjekhjNmk4L3Q4
UEJsQXJjZXY1VlNDaFV2eER4NTdwRGcKLS0tIDJxb1AyYm01YlNUVk93Tm1Hb0xK
NVh0K25yOFUya0dYSGt2SGdJQURnQ2sKaKgwThB5ePeM091/zYgQXmiPeBeIhTdo
60PkAPNs659xyVqnlZw2YKEhxbEHS3ZMTBgC4tgng0yjClBizcTv5w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-08T16:37:01Z"
mac: ENC[AES256_GCM,data:OoE6fml/3DSH2EMafCoeYz/JHo89Fb9uwQ58k7hxQDU6YMazdO/eGM0jKX5ghjuMKJqA/zQs4FeZ4yASkIUT1CBbxRpycmE9qo6Zny121NLeqveBp3YMjmUZGt3tgw2dNqtkhpOnl/BmHQCtTTvX2brtSVmANx3LbFxjqbK2OGM=,iv:8Us7LJSZ2aHZQnYIjnsvkR7Av0KeHFEYEETZd7WZX4Q=,tag:7NbbEOq1k2c5IcsnqpvlOQ==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
+81
View File
@@ -0,0 +1,81 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: fava
namespace: finance
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: fava
driftDetection:
mode: enabled
values:
controllers:
app:
initContainers:
seed-ledger:
image:
repository: busybox
tag: "1.36"
command:
- sh
- -c
- |
if [ ! -f /documents/main.beancount ]; then
printf '1970-01-01 open Assets:Cash\n1970-01-01 open Equity:Opening-Balances\n' > /documents/main.beancount
fi
containers:
app:
image:
repository: yegle/fava
tag: "v1.30"
env:
TZ: Europe/Moscow
BEANCOUNT_FILE: /documents/main.beancount
args:
- "--poll-watcher"
probes:
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
service:
app:
controller: app
ports:
http:
port: 5000
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: auth-authelia@kubernetescrd
hosts:
- host: acc.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: fava-tls
hosts:
- acc.aggtaa.com
persistence:
documents:
type: persistentVolumeClaim
existingClaim: fava-documents
globalMounts:
- path: /documents
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: fava
namespace: finance
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- helmrelease.yaml
- pv-pvc-documents.yaml
@@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: fava-documents-nfs
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=4.1
nfs:
server: 10.0.64.5
path: /export/accounting
claimRef:
namespace: finance
name: fava-documents
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fava-documents
namespace: finance
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: fava-documents-nfs
resources:
requests:
storage: 30Gi
+104
View File
@@ -0,0 +1,104 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: gitea
namespace: git
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: gitea
driftDetection:
mode: enabled
values:
controllers:
app:
replicas: 1
containers:
app:
image:
repository: gitea/gitea
tag: "1.27.0"
env:
USER_UID: "1000"
USER_GID: "1000"
GITEA__server__DOMAIN: git.aggtaa.com
GITEA__server__ROOT_URL: https://git.aggtaa.com/
GITEA__server__SSH_DOMAIN: git.aggtaa.com
GITEA__server__SSH_PORT: "2222"
GITEA__server__LFS_START_SERVER: "true"
GITEA__security__INSTALL_LOCK: "true"
GITEA__security__PASSWORD_HASH_ALGO: pbkdf2
GITEA__security__INTERNAL_TOKEN:
valueFrom:
secretKeyRef:
name: gitea-secrets
key: INTERNAL_TOKEN
GITEA__oauth2__JWT_SECRET:
valueFrom:
secretKeyRef:
name: gitea-secrets
key: OAUTH2_JWT_SECRET
GITEA__server__LFS_JWT_SECRET:
valueFrom:
secretKeyRef:
name: gitea-secrets
key: LFS_JWT_SECRET
probes:
startup:
enabled: true
spec:
periodSeconds: 5
failureThreshold: 60
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 1Gi
service:
app:
controller: app
ports:
http:
port: 3000
ssh:
controller: app
type: LoadBalancer
labels:
loadBalancerPool: default
ports:
ssh:
port: 2222
targetPort: 22
protocol: TCP
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: git.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: gitea-tls
hosts:
- git.aggtaa.com
persistence:
data:
type: persistentVolumeClaim
existingClaim: gitea-data
globalMounts:
- path: /data
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: gitea
namespace: git
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- pvc.yaml
- helmrelease.yaml
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-data
namespace: git
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 3Gi
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: gitea-secrets
namespace: git
stringData:
INTERNAL_TOKEN: ENC[AES256_GCM,data:+eS1bjyL/NKvOpkXa1Kiyk9dBPemWvfRplJJCXG+cLXipbMlyHBDQLEMeFEnz9VdnwN/MFRWYL5ink3Lh6yNGf0vmVNR+OLMlvlaKDNaCA28HuUBxJbWLAqhITsRImH6rKcy/EfqEEcK,iv:ModTSwxLMj4Gx0e/bVKY8GVWzBjgNxATKnkWYfvKiIs=,tag:6gFkA/0toCBJ7QKL16654A==,type:str]
OAUTH2_JWT_SECRET: ENC[AES256_GCM,data:9626EzmbEmyAsTqfbRhASqLkzS+4xnJxpSEgJDSLzkjfuNfAq7uMfc/HsA==,iv:lMCRwHt4gT31hYZ5Ywt4GU/DJNj7Fya2GOUkyr/l6ro=,tag:aQ8UlVwpaVNDoOHju1oiMQ==,type:str]
LFS_JWT_SECRET: ENC[AES256_GCM,data:gZ37cb9uqSF1MkdoUFbVM3VhqBxEIyUv3IxiV4c3gbIVTk7FMHncZekM7Q==,iv:m7JxF7S1OnHy86s5s1tJHQ8kqhRJq5kFRoArLL/ab0o=,tag:GZuE7f9pNq15T+R+PlSZdA==,type:str]
sops:
age:
- recipient: age1ktlxm0hukw6jxm62txqy5x2nl8hpdnal8nv00kfhrf3pw33574aqa6j5cj
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkRVpZRTVLWU80RjEvZnRs
TU5YVko4V1M5Q0d5SXZGT1h2NStCYk5UZWpRCmNxVXVtR3VCZXJqNHYyaDUyZmdr
WXJKZHhJaFJhZll0VVBld0QrSGhtbncKLS0tIGZZREFXRUMvTi8rRHIyaXBUOE45
d2JBTG5oT2kvZzdLejMzdEpRbm03QmcKItsS4nKJRu3pRC8xLTf9kYpqFiD9S1tx
BFnJf3IsbGpl1RM8RmkN9O5QsZSq1hbw5pFhlx0XKeH5KgoJInbnog==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-09-05T11:29:22Z"
mac: ENC[AES256_GCM,data:906TL61C7WcxxTAfH3zpXLTLQn2WSIg2RPm0y/6LeVA6pvI6fMbFA32Usvg+EkpWVRGqcm8JPgHy63WQDhq1Ap3imJXpgVDgTgfo64TxJLmdUtCAdm0/CaGxtGeU2J3gm653wl75hOGoCyfBR3iOt0Vo1kUdUxS9B5h3u5L6P6o=,iv:C+CZe31F/AWk0ZqM6lgV248ys/L3mG6S8Tz44b5o628=,tag:C9LdYCtsiHWiHSZWaYCGWA==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,77 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: homeassistant
namespace: homeassistant
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: homeassistant
driftDetection:
mode: enabled
values:
controllers:
app:
strategy: Recreate
pod:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
app:
image:
repository: ghcr.io/home-assistant/home-assistant
tag: "2026.7.4"
env:
TZ: Europe/Moscow
probes:
liveness:
enabled: true
readiness:
enabled: true
startup:
enabled: true
spec:
periodSeconds: 10
failureThreshold: 30 # HA is sloooow to start
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
service:
app:
controller: app
ports:
http:
port: 8123
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: p.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: homeassistant-tls
hosts:
- p.aggtaa.com
persistence:
config:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 10Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /config
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: homeassistant
namespace: homeassistant
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- helmrelease.yaml
@@ -0,0 +1,54 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: immich-ml
namespace: photo
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: immich
driftDetection:
mode: enabled
values:
controllers:
ml:
containers:
app:
# CPU-only: hardware-accelerated tags (-openvino, -cuda, etc)
# need specific host devices (e.g. /dev/dri) that aren't set up
# on these nodes. Switch tags later if that changes.
image:
# pinned to match immich-server's version (see
# helmrelease-server.yaml) - server and ML communicate over
# an internal API contract that isn't guaranteed compatible
# across versions.
repository: ghcr.io/immich-app/immich-machine-learning
tag: v3.1.0
env:
TZ: Europe/Moscow
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
memory: 4Gi
service:
app:
controller: ml
ports:
http:
port: 3003
persistence:
# caches downloaded ML models so they survive pod restarts instead of
# re-downloading (a few hundred MB) every time.
cache:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 10Gi
storageClass: longhorn-single-replica
retain: true
globalMounts:
- path: /cache
@@ -0,0 +1,70 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: immich-postgres
namespace: photo
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: immich
driftDetection:
mode: enabled
values:
controllers:
postgres:
containers:
app:
# immich requires this specific image - it bundles the
# vectorchord/pgvecto.rs extensions immich's DB schema depends
# on; a plain postgres image will not work.
image:
repository: ghcr.io/immich-app/postgres
tag: 14-vectorchord0.4.3-pgvectors0.2.0
env:
TZ: Europe/Moscow
POSTGRES_USER: postgres
POSTGRES_DB: immich
POSTGRES_INITDB_ARGS: "--data-checksums"
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-secrets
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 2Gi
service:
app:
controller: postgres
ports:
postgres:
port: 5432
persistence:
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 10Gi
storageClass: longhorn
retain: true
globalMounts:
# subPath, not the raw mount root: a fresh Longhorn ext4 volume's
# root always contains a "lost+found" dir, and postgres's initdb
# refuses to initialize into a non-empty directory even when
# lost+found is the only thing in it.
- path: /var/lib/postgresql/data
subPath: data
# postgres' parallel query workers need real shared memory, not the
# default 64Mi container tmpfs - matches shm_size: 128mb in the
# original docker-compose.
dshm:
type: emptyDir
medium: Memory
sizeLimit: 128Mi
globalMounts:
- path: /dev/shm
@@ -0,0 +1,33 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: immich-redis
namespace: photo
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: immich
driftDetection:
mode: enabled
values:
controllers:
redis:
containers:
app:
image:
repository: docker.io/valkey/valkey
tag: "9.1.1"
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
memory: 256Mi
service:
app:
controller: redis
ports:
redis:
port: 6379
@@ -0,0 +1,235 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: immich-server
namespace: photo
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: immich
driftDetection:
mode: enabled
values:
controllers:
server:
strategy: Recreate
podDisruptionBudget:
maxUnavailable: "0"
initContainers:
fix-permissions:
image:
repository: hectorm/nfs-ganesha
tag: v10
# hectorm does not publish exact versions, v10.0 digest is 93df70b78...
digest: sha256:93df70b78aae56957f4a4f5c606c4250d84b729435f76132dca07b7290f5bfce
command:
- sh
- -c
- chmod -R a+rwX /disks/library-recent /disks/library-archive
# native sidecar (restartPolicy: Always): mounts the two library
# disks and exposes their union at /disks/merged-library via
# FUSE. It must be up and actually mounted *before* the "app"
# container is created, since HostToContainer propagation only
# relays mounts made *after* the receiving container's own mount
# exists - it won't retroactively pick up a FUSE mount that's
# already there. Running it as a restartPolicy:Always init
# container (started, gated on the startupProbe below, before
# regular containers) guarantees that ordering; a regular sidecar
# container racing "app" on every pod start does not.
mergerfs:
dependsOn:
- fix-permissions
restartPolicy: Always
image:
repository: ghcr.io/hotio/mergerfs
tag: release-v2.42.0
args:
- "-o"
# library-recent listed first: category.create=ff means new
# files created through the merged mount (i.e. new immich
# uploads going forward) land on library-recent by default,
# which is exactly what's wanted for freshly-uploaded photos.
- "allow_other,use_ino,func.create=ff,category.action=all,category.create=ff,category.search=newest"
- "/disks/library-recent:/disks/library-archive"
- "/disks/merged-library"
securityContext:
privileged: true
probes:
startup:
enabled: true
custom: true
spec:
exec:
command:
- sh
- -c
- grep -q fuse.mergerfs /proc/mounts
periodSeconds: 1
timeoutSeconds: 2
failureThreshold: 30
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 512Mi
containers:
app:
image:
repository: ghcr.io/immich-app/immich-server
# make sure immich-machine-learning version match this
tag: v3.1.0
env:
TZ: Europe/Moscow
DB_HOSTNAME: immich-postgres
DB_PORT: "5432"
DB_USERNAME: postgres
DB_DATABASE_NAME: immich
DB_PASSWORD:
valueFrom:
secretKeyRef:
name: immich-secrets
key: DB_PASSWORD
REDIS_HOSTNAME: immich-redis
REDIS_PORT: "6379"
IMMICH_MACHINE_LEARNING_URL: http://immich-ml:3003
probes:
# first boot runs DB migrations + vector index setup, which
# can take a while - without a startup probe the default
# liveness probe (periodSeconds=10, failureThreshold=3) kills
# the container ~30s in, well before it's finished binding
# the port, causing an endless restart loop.
startup:
enabled: true
spec:
periodSeconds: 5
failureThreshold: 60
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
memory: 4Gi
service:
app:
controller: server
ports:
http:
port: 2283
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: photo.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: immich-tls
hosts:
- photo.aggtaa.com
persistence:
# Immich's "library" folder, mergerfs-merged from two disks: recent
# files (per the rsync migration's cutoff) and everything else.
# New uploads going forward land on library-recent (see mergerfs
# category.create above). Not reusing the old single 800Gi library
# PVC/PV - this replaces it (that PVC becomes orphaned, retained,
# and unused; clean it up separately if no longer wanted).
library-recent:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 200Gi
storageClass: longhorn-single-replica
retain: true
advancedMounts:
server:
mergerfs:
- path: /disks/library-recent
fix-permissions:
- path: /disks/library-recent
library-archive:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 500Gi
storageClass: longhorn-single-replica
retain: true
advancedMounts:
server:
mergerfs:
- path: /disks/library-archive
fix-permissions:
- path: /disks/library-archive
merged-library:
type: emptyDir
advancedMounts:
server:
mergerfs:
- path: /disks/merged-library
mountPropagation: Bidirectional
app:
- path: /data/library
mountPropagation: HostToContainer
dev-fuse:
type: hostPath
hostPath: /dev/fuse
advancedMounts:
server:
mergerfs:
- path: /dev/fuse
# split out of the old combined "other" volume below: each of these
# purposes now gets its own Longhorn volume instead of sharing one
# 200Gi disk, so a single hot subtree (e.g. thumbs) doesn't force
# replica rebuilds/relocation to drag the whole 200Gi with it.
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 20Gi
storageClass: longhorn
retain: true
advancedMounts:
server:
app:
- path: /data
encoded-video:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 100Gi
storageClass: longhorn
retain: true
advancedMounts:
server:
app:
- path: /data/encoded-video
thumbs:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 100Gi
storageClass: longhorn
retain: true
advancedMounts:
server:
app:
- path: /data/thumbs
backups:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 30Gi
storageClass: longhorn
retain: true
advancedMounts:
server:
app:
- path: /data/backups
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: immich
namespace: photo
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease-postgres.yaml
- helmrelease-redis.yaml
- helmrelease-ml.yaml
- helmrelease-server.yaml
+22
View File
@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: immich-secrets
namespace: photo
stringData:
DB_PASSWORD: ENC[AES256_GCM,data:+bJ/+NEQCT3ZEqjnkzep4OsbLkJ9gyLZmGxCpoww2Wo=,iv:n1sLYgLl3zNZBdjMBhIrbSv/qHJFQ/nvZfBknFAUdug=,tag:DFnoc5K6XvHngVIrgJlM6Q==,type:str]
sops:
age:
- recipient: age1wmm8qrqnjzvd5nkraed6ylrsxlpn3ru7d7zettdudj7y9sg9dd2su0u3mr
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtd1hFZy82d0c0RXZMd28r
dWs1VWNlVG9tTE5WZDVLdGpFSXhuVXQ5anprCk5LWWMrWUVCd1N4Y1JENnRyWExC
d2VRNDJON2RzSHRyMGhRdmMva1NNY3cKLS0tIDNML2Q0YXFRMEs3V3prUkZYSS85
MWRGa3duNHgvbjNvVnZLOXpYMW1mbGcKaN7h3W0YKsRjnyk6P9+bziDy6If5xLQ8
kxFJf4CX9XU5jgYXqw2UNqRDusphclqfULUMRaEzMJkkPhd1cZ9PvQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-21T12:23:20Z"
mac: ENC[AES256_GCM,data:vGSUhl73AIQr1Ld1SHc+i4GpvOc0b0j8Vx9DmoRDjNs17TQSfmfulEZwIua12P5DHIz36P9yqggHfSyyrrEoMjfaUSTIDxCIGGvsfTl4vBt9Vis0e2gALaHtAgWZwxr5QMe+OP1tkXOZoX9E8VcoBq1ULvbIjqsPs2uxPVKsEWk=,iv:1exDUqurlFdBt1eZYWWMlkar75HbTLZ6RZelC0RsYcU=,tag:A0IhJHjVum2HBT5RwHo5qg==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: infcloud
namespace: pim
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: infcloud
driftDetection:
mode: enabled
values:
controllers:
infcloud:
containers:
app:
image:
repository: waja/infcloud
tag: "0.13.1-16"
probes:
liveness:
enabled: true
readiness:
enabled: true
service:
app:
controller: infcloud
ports:
http:
port: 80
ingress:
app:
className: traefik
# this ingress owns the cert-manager request for c.aggtaa.com
# (secret kept as "radicale-tls" post-migration so the existing,
# already-valid cert is reused instead of re-issued). davical's
# ingress just references the same secret without its own
# annotation - a second annotated Ingress would create a
# competing Certificate for the same host/secret.
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: c.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: radicale-tls
hosts:
- c.aggtaa.com
persistence:
config:
type: configMap
name: infcloud-config
globalMounts:
- path: /srv/infcloud/config/config.js
subPath: config.js
readOnly: true
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: infcloud
namespace: pim
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- configmap.yaml
- helmrelease-infcloud.yaml
@@ -0,0 +1,80 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: joplin
namespace: pim
spec:
interval: 10m
dependsOn:
- name: joplin-postgres
chartRef:
kind: OCIRepository
name: joplin
driftDetection:
mode: enabled
values:
controllers:
app:
containers:
app:
image:
repository: joplin/server
# verified via Docker Hub API (hub.docker.com/v2/repositories/
# joplin/server/tags): "3.7.1" and "latest" were pushed at the
# same timestamp, i.e. this is what "latest" (the original
# compose.yaml's tag) currently resolves to.
tag: "3.7.1"
env:
APP_PORT: "22300"
APP_BASE_URL: https://notes.aggtaa.com
DB_CLIENT: pg
POSTGRES_DATABASE: joplin
POSTGRES_USER: joplin
POSTGRES_HOST: joplin-postgres
POSTGRES_PORT: "5432"
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: joplin-secrets
key: POSTGRES_PASSWORD
probes:
startup:
enabled: true
spec:
periodSeconds: 5
failureThreshold: 60
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 1Gi
service:
app:
controller: app
ports:
http:
port: 22300
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: notes.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: joplin-tls
hosts:
- notes.aggtaa.com
@@ -0,0 +1,50 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: joplin-postgres
namespace: pim
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: joplin
driftDetection:
mode: enabled
values:
controllers:
postgres:
containers:
app:
image:
repository: postgres
# not alpine: joplin server needs a glibc-collation postgres,
# matching the official (Debian-based) image used everywhere
# else this pattern is followed in this repo.
tag: "16.15"
env:
TZ: Europe/Moscow
POSTGRES_USER: joplin
POSTGRES_DB: joplin
POSTGRES_PASSWORD:
valueFrom:
secretKeyRef:
name: joplin-secrets
key: POSTGRES_PASSWORD
service:
app:
controller: postgres
ports:
postgres:
port: 5432
persistence:
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 20Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /var/lib/postgresql/data
subPath: data
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: joplin
namespace: pim
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease-postgres.yaml
- helmrelease-joplin.yaml
+22
View File
@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: joplin-secrets
namespace: pim
stringData:
POSTGRES_PASSWORD: ENC[AES256_GCM,data:cI3zfdaJUbo2344RuLIG4DjPsgx4Afl6GKMJVHrOHOg=,iv:8QliTSQ44ScVUchDxh0DF/wU9bTLK9Jq4ojxSMXsNC8=,tag:VB2mf4uhLnRnafWg0mRRpg==,type:str]
sops:
age:
- recipient: age1dertyl956fjsmh00cw468gdnl7lf3frasg4n4c82z0chm6qm2cks6w8gha
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFQ2JKZWxseVAxUWU5NlhL
S0xWQUh1NFpBU2ZpQkJXbkg4TlZ2cTR5TXhjCjlMTGdGMEF4UzBEbS9wdndJb1VE
OXBOanpJS1lpTFVhWm52K2xhaGx2M00KLS0tIGF5b3FZOWRNVWNpenRmUDVOT3Vp
aHpCbXkvdXM3SjYxWTlsT0cxU2RDOFkKximxt9bmd6FZAtU/SBirzDH3NMitQKrZ
PfwicphmFEXVN+HtD9vwRbP4TMa1swOMbOVINGKuA8YZwEk1GyPe4w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-27T10:16:36Z"
mac: ENC[AES256_GCM,data:85dGV8WPFqd+4l6k7PLqDROm5+kwiAjO80ElNKfSJFOgeI9y7DyhyhDklcBJXMJljatbEfAmCFFqnoyRGmeIjc6FBfteUGQtpLgI6e8RTlRHPvaRzqDwA1OhnlQbAW9TTf5vha6tyYso/PprCWjJSMjBEfiu7ApiRMdZcLd1V4c=,iv:Dh9z/r7UbhdREwXT787HkmVM04X/BAtQmQteN3j7AKU=,tag:oUMYT5CL85PAYfItf5TIdw==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,107 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: markdown-vault-mcp
namespace: mcp
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: markdown-vault-mcp
driftDetection:
mode: enabled
values:
controllers:
app:
containers:
app:
image:
repository: ghcr.io/pvliesdonk/markdown-vault-mcp
tag: "v3.1.0"
env:
MARKDOWN_VAULT_MCP_SOURCE_DIR: /vault
MARKDOWN_VAULT_MCP_INDEX_PATH: /data/state/index.db
MARKDOWN_VAULT_MCP_EMBEDDINGS_PATH: /data/state/embeddings
MARKDOWN_VAULT_MCP_ATTACHMENT_EXTENSIONS: "*"
MARKDOWN_VAULT_MCP_STATE_PATH: /data/state/state.json
MARKDOWN_VAULT_MCP_READ_ONLY: "false"
MARKDOWN_VAULT_MCP_BASE_URL: "https://md-vault.mcp.aggtaa.com"
MARKDOWN_VAULT_MCP_OIDC_CONFIG_URL: "https://auth.aggtaa.com/.well-known/openid-configuration"
MARKDOWN_VAULT_MCP_OIDC_CLIENT_ID: "markdown-vault-mcp"
MARKDOWN_VAULT_MCP_OIDC_CLIENT_SECRET:
valueFrom:
secretKeyRef:
name: markdown-vault-mcp-secrets
key: OIDC_CLIENT_SECRET
MARKDOWN_VAULT_MCP_OIDC_JWT_SIGNING_KEY:
valueFrom:
secretKeyRef:
name: markdown-vault-mcp-secrets
key: OIDC_JWT_SIGNING_KEY
MARKDOWN_VAULT_MCP_BEARER_TOKEN:
valueFrom:
secretKeyRef:
name: markdown-vault-mcp-secrets
key: BEARER_TOKEN
probes:
# first boot downloads embedding models from HuggingFace Hub
# and builds the vector index/embeddings before binding the
# port - the default liveness probe budget (periodSeconds=10,
# failureThreshold=3 = 30s) isn't enough, so it gets killed
# mid-startup on every restart, in an endless loop.
startup:
enabled: true
spec:
periodSeconds: 5
failureThreshold: 36
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
service:
app:
controller: app
ports:
http:
port: 8000
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: md-vault.mcp.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: markdown-vault-mcp-tls
hosts:
- md-vault.mcp.aggtaa.com
persistence:
vault:
type: persistentVolumeClaim
existingClaim: markdown-vault-mcp-vault
globalMounts:
- path: /vault
state:
type: persistentVolumeClaim
suffix: state
accessMode: ReadWriteOnce
size: 2Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /data/state
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: markdown-vault-mcp
namespace: mcp
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- helmrelease.yaml
- secret.enc.yaml
- pv-pvc-vault.yaml
@@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: markdown-vault-mcp-vault-nfs
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=4.1
nfs:
server: 10.0.64.5
path: /export
claimRef:
namespace: mcp
name: markdown-vault-mcp-vault
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: markdown-vault-mcp-vault
namespace: mcp
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: markdown-vault-mcp-vault-nfs
resources:
requests:
storage: 30Gi
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: markdown-vault-mcp-secrets
namespace: mcp
stringData:
BEARER_TOKEN: ENC[AES256_GCM,data:vRsc/kmZ1qySDUrI1dMeLqSSn+jEgvPivw3dRYTLr7NRm6T7SYnmhhBb81CG6xQAl0Wz8RfOCescQpUJlbPl4w==,iv:Zha9o8uIdR6DPMk+KRL6ZnKdh3zPXuyjCCK9yKnuYFI=,tag:l+8oHY/hgZcKVQx9yiaEmg==,type:str]
OIDC_CLIENT_SECRET: ENC[AES256_GCM,data:2unvRtI/V0aTULgSacGvEoYYQN0uWBR3b2TKjl7hjvQTXesuglTtSNz9YsjDqc8ZHGNBndiz2Q6VZagS3ERprw==,iv:fjMhiKamjbIrNYAK9TU2PPn0smJR+Kr3qhRhEUbjm04=,tag:wuUZbccOBoFcdiTPqb8QCQ==,type:str]
OIDC_JWT_SIGNING_KEY: ENC[AES256_GCM,data:63S/0TsEis959MJRLd5atPXQrv5pNMC5iyMHJvT67luLLPkd2bdgfmX4Pu7R3Hr5YIGoujn1/xCuOdZH0RtIog==,iv:3Gi2ZukSzU9SIy7fPzmlvWbA4ykaNgta95DuviHf5zk=,tag:BPUXUPMIxW9kbyyQqoz0gQ==,type:str]
sops:
age:
- recipient: age1kct5g7d7eus2hvdnmx99ewsrtz70lhlasxa25euqwdctqhwqqpxqz0n7gg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPOVJCaXpjeUEzWkN4SEdz
RnFkZmtXa0IwaE9xdGtBdVRwV1B5MkdDbVMwCmVYeUJpQmQwZ29BbnRPK1AzWGJx
NHV0NnRxenRWV2M4MytrZ3llNmxaMzQKLS0tIHVOcDVyTDkrNHJRMkhRVzFPSGpW
V1RRNTdiUTBjSWZlMlEvRXF3OUowZ00KXf/+ucgZuDAczE1WlEW7D2/cR+sB66BQ
7JC42VJiyEf2djlODmvK26bDTeiJ/Uk9HsSuvamGUM8b8vAj2w/r/A==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-27T06:42:40Z"
mac: ENC[AES256_GCM,data:XRxbyHL4iJMoRMb4X6C2y1ChTNSkqou01bwtIb6zNfwcGCt7uMGbm1HxvmjbdeJI/zQIIVfgxbbTxhZw+sL2FF+PYAjPcaGvaJ86O97lr9fUB30NC0tMluW82pt2iiisK+6dEEPH9i/kGFrJHN6RlaYVkEfv6U8xjlpIcaNx0+A=,iv:YnqMRlHl1WVYNqqN9hRH88hgIALrfgv6WrJGyeNDstI=,tag:M1DYFr3G9ub/R2kNF8ZnNQ==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,6 @@
apiVersion: v2
name: mosquitto
description: Single-node Eclipse Mosquitto MQTT broker for homelab IoT
type: application
version: 0.1.0
appVersion: "2"
@@ -0,0 +1,14 @@
{{- define "mosquitto.fullname" -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "mosquitto.labels" -}}
app.kubernetes.io/name: mosquitto
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{- define "mosquitto.selectorLabels" -}}
app.kubernetes.io/name: mosquitto
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
@@ -0,0 +1,22 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mosquitto.fullname" . }}-config
labels:
{{- include "mosquitto.labels" . | nindent 4 }}
data:
mosquitto.conf: |
persistence true
persistence_location /mosquitto/data/
log_dest stdout
listener {{ .Values.service.mqttPort }}
allow_anonymous false
password_file /mosquitto/secrets/{{ .Values.auth.secretKey }}
{{- if .Values.websockets.enabled }}
listener {{ .Values.websockets.port }}
protocol websockets
allow_anonymous false
password_file /mosquitto/secrets/{{ .Values.auth.secretKey }}
{{- end }}
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mosquitto.fullname" . }}
labels:
{{- include "mosquitto.labels" . | nindent 4 }}
{{- if .Values.service.loadBalancerPool }}
loadBalancerPool: {{ .Values.service.loadBalancerPool }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if .Values.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
selector:
{{- include "mosquitto.selectorLabels" . | nindent 4 }}
ports:
- name: mqtt
port: {{ .Values.service.mqttPort }}
targetPort: mqtt
{{- if .Values.websockets.enabled }}
- name: ws
port: {{ .Values.websockets.port }}
targetPort: ws
{{- end }}
@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "mosquitto.fullname" . }}
labels:
{{- include "mosquitto.labels" . | nindent 4 }}
spec:
serviceName: {{ include "mosquitto.fullname" . }}
replicas: 1
selector:
matchLabels:
{{- include "mosquitto.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mosquitto.selectorLabels" . | nindent 8 }}
spec:
securityContext:
runAsUser: {{ .Values.securityContext.runAsUser }}
runAsGroup: {{ .Values.securityContext.runAsGroup }}
fsGroup: {{ .Values.securityContext.runAsGroup }}
containers:
- name: mosquitto
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: mqtt
containerPort: {{ .Values.service.mqttPort }}
{{- if .Values.websockets.enabled }}
- name: ws
containerPort: {{ .Values.websockets.port }}
{{- end }}
env:
- name: MOSQUITTO_UNSAFE_ALLOW_SYMLINKS
value: "1"
volumeMounts:
- name: config
mountPath: /mosquitto/config
- name: secrets
mountPath: /mosquitto/secrets
readOnly: true
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /mosquitto/data
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: config
configMap:
name: {{ include "mosquitto.fullname" . }}-config
- name: secrets
secret:
secretName: {{ .Values.auth.existingSecret }}
defaultMode: 0400
{{- if not .Values.persistence.enabled }}
- name: data
emptyDir: {}
{{- end }}
{{- if .Values.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: {{ .Values.persistence.storageClassName }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}
@@ -0,0 +1,38 @@
image:
repository: eclipse-mosquitto
tag: "2.1.2-alpine"
pullPolicy: IfNotPresent
# not really needed, as StatefulSet has hardcoded "replicas: 1"
replicaCount: 1
securityContext:
runAsUser: 1883
runAsGroup: 1883
persistence:
enabled: true
size: 1Gi
service:
type: LoadBalancer
mqttPort: 1883
loadBalancerPool: "mqtt"
# or loadBalancerIP: X.X.X.X
auth:
# Mount a Secret with a pre-generated mosquitto passwd file instead of
# putting credentials in values.yaml / git. See README for how to create it.
existingSecret: mosquitto-passwd
secretKey: passwd
websockets:
enabled: false
port: 9001
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 128Mi
@@ -0,0 +1,52 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: mosquitto
namespace: mqtt
spec:
interval: 30m
chart:
spec:
chart: ./clusters/base/apps/mosquitto/chart
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
reconcileStrategy: Revision
driftDetection:
mode: enabled
values:
image:
repository: eclipse-mosquitto
tag: "2.1.2-alpine"
pullPolicy: IfNotPresent
replicaCount: 1
securityContext:
runAsUser: 1883
runAsGroup: 1883
persistence:
enabled: true
size: 1Gi
service:
type: LoadBalancer
mqttPort: 1883
loadBalancerPool: "mqtt"
auth:
existingSecret: mosquitto-passwd
secretKey: passwd
websockets:
enabled: false
port: 9001
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 128Mi
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- secret.enc.yaml
- helmrelease.yaml
@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: mosquitto-passwd
namespace: mqtt
stringData:
passwd: ENC[AES256_GCM,data:YpTczjZ73h4Td+TmEZIgil9DN7WfVwBsi75F/t3D1oWGjGsifklnRsZUJqXiXyUwIxTxlFiWgfb01LjmILE0nB9rxoEbFdrASaLgtnCoYz/fvSZLqwCK+FSSub3+r0nJTNvxzD+bC/av8JEPNDW/8CSrHMrTrVCcLhAchOAvviRLgO18862XWcvPh1plO8luaZG38cuh+VbKKwidkwozc6B7Ghys5NfZuB1rP4vLZ6/f1PzcFRugtJ0fzzl/BA==,iv:9FKm4V/lYXott/6aVBKg4g8GtzVqgT/xndj0CKqMm+Y=,tag:41gCVbrK6Rp2IvVUt98Hpw==,type:str]
sops:
age:
- recipient: age1u236u5mzkzj72y4mfxx68gfqw0u0j6tdgkudnm5pdfpe7apq4u6s89jr2h
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBobk1BQW0yemEzV1FQM2ZJ
YkZJbjJPZlRiSDlyamx1WVdyNTJBNXo1YlQwCnhBT01SeE0rZDVOcFRkZm9KQ0t2
dUIyOHpzenVZc3lsa2hmMjF3LzRGMFUKLS0tIENmM05Ud2xGblNpdjZjWWFPeWg3
UEtuZWZ5enkwSGk0MXR1czBibzQ1M2sKfp+76C0X2DgsffWZVtBevI4lCs8RM09p
tzsrneX4iIX6WCn1kRLsWTZpN6Vg8Wsl6kUWnSA3WfHWI9W4CJdrag==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-21T19:09:49Z"
mac: ENC[AES256_GCM,data:qecTVUaC/3j/oP1DVnXEKV9Ub+k5gvGHfteol4W5I3gq8IB2YYZ+vfuinPVJJeSsN5HvK62ftnIgRNu0dtzDOrnuZENczEMwPrOz90RCUbX7WBeKh23KZ8gSSw2MBn6gsDsz1gXFWENIyjQTJFZ4qAJY6jfeH2IFC9RkpoU3j14=,iv:ksLG/aHHBAkl6aA52Xufs2vijZlpsaXccoxheYV3Gm4=,tag:4YiaPGMBWSWhVBO1tNF6vQ==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.13.2
@@ -0,0 +1,51 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nfs-ganesha-config
namespace: nfs
data:
ganesha.conf: |
NFS_Core_Param {
NFS_Port = 2049;
Protocols = NFSv4;
Fsid_Device = true;
}
NFSV4 {
RecoveryBackend = fs_ng;
RecoveryRoot = /recovery;
Lease_Lifetime = 40;
Grace_Period = 50;
}
VFS {
Link_Support = true;
Symlink_Support = true;
CanSetTime = true;
umask = 0000;
}
EXPORT {
Export_Id = 1;
Path = /export;
Pseudo = /export;
SecType = sys;
Protocols = NFSv4;
FSAL {
Name = VFS;
}
CLIENT {
Clients = *;
Access_Type = RW;
Squash = No_Root_Squash;
Protocols = NFSv4;
}
}
LOG {
Default_Log_Level = INFO;
}
@@ -0,0 +1,130 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: nfs-ganesha
namespace: nfs
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: nfs-ganesha
driftDetection:
mode: enabled
values:
controllers:
app:
strategy: Recreate
podDisruptionBudget:
maxUnavailable: "0"
initContainers:
mtab-setup:
image:
repository: hectorm/nfs-ganesha
tag: v10
# hectorm does not publish exact versions, v10.0 digest is 93df70b78...
digest: sha256:93df70b78aae56957f4a4f5c606c4250d84b729435f76132dca07b7290f5bfce
command:
- sh
- -c
- cat /proc/self/mounts > /mtab-vol/mtab
fix-permissions:
image:
repository: hectorm/nfs-ganesha
tag: v10
# hectorm does not publish exact versions, v10.0 digest is 93df70b78...
digest: sha256:93df70b78aae56957f4a4f5c606c4250d84b729435f76132dca07b7290f5bfce
command:
- sh
- -c
- chmod -R a+rwX /export
containers:
app:
image:
repository: hectorm/nfs-ganesha
tag: v10
# hectorm does not publish exact versions, v10.0 digest is 93df70b78...
digest: sha256:93df70b78aae56957f4a4f5c606c4250d84b729435f76132dca07b7290f5bfce
securityContext:
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- CHOWN
- DAC_OVERRIDE
- DAC_READ_SEARCH
- FOWNER
- FSETID
- SETGID
- SETUID
- SYS_RESOURCE
probes:
liveness:
enabled: true
spec:
initialDelaySeconds: 2
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readiness:
enabled: true
spec:
initialDelaySeconds: 1
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 2
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
service:
app:
controller: app
type: LoadBalancer
labels:
loadBalancerPool: nfs
ports:
nfs:
port: 2049
protocol: TCP
persistence:
config:
type: configMap
name: nfs-ganesha-config
globalMounts:
- path: /etc/ganesha/ganesha.conf
subPath: ganesha.conf
readOnly: true
export:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 30Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /export
run:
type: emptyDir
globalMounts:
- path: /run
tmp:
type: emptyDir
globalMounts:
- path: /tmp
recovery:
type: emptyDir
globalMounts:
- path: /recovery
etc-mtab:
type: emptyDir
advancedMounts:
app:
app:
- path: /etc/mtab
subPath: mtab
mtab-setup:
- path: /mtab-vol
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: nfs-ganesha
namespace: nfs
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- configmap.yaml
- helmrelease.yaml
@@ -0,0 +1,73 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: syncthing
namespace: syncthing
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: syncthing
driftDetection:
mode: enabled
values:
controllers:
app:
containers:
app:
image:
repository: syncthing/syncthing
tag: "2.1.3"
env:
TZ: Europe/Moscow
probes:
liveness:
enabled: true
readiness:
enabled: true
service:
app:
controller: app
ports:
http:
port: 8384
ingress:
app:
className: traefik
# the GUI has no auth of its own out of the box, so it's gated
# behind Authelia the same way fava/zigbee2mqtt are.
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: auth-authelia@kubernetescrd
hosts:
- host: sync.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: syncthing-tls
hosts:
- sync.aggtaa.com
persistence:
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 1Gi
storageClass: longhorn
retain: true
globalMounts:
- path: /var/syncthing
# same nfs-ganesha export webdav and markdown-vault-mcp use
# (10.0.64.5:/export); mounted at the same container path (/data)
# as webdav so the layout matches (/data/shared/documents/...).
shared:
type: persistentVolumeClaim
existingClaim: syncthing-shared
globalMounts:
- path: /data
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: syncthing
namespace: syncthing
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- pv-pvc-shared.yaml
- helmrelease.yaml
@@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: syncthing-shared-nfs
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=4.1
nfs:
server: 10.0.64.5
path: /export
claimRef:
namespace: syncthing
name: syncthing-shared
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: syncthing-shared
namespace: syncthing
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: syncthing-shared-nfs
resources:
requests:
storage: 30Gi
@@ -0,0 +1,69 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
interval: 30m
chartRef:
kind: OCIRepository
name: vaultwarden
driftDetection:
mode: enabled
values:
controllers:
vaultwarden:
containers:
app:
image:
repository: vaultwarden/server
tag: 1.37.0
env:
DOMAIN: "https://vw.aggtaa.com"
SIGNUPS_ALLOWED: "false"
WEBSOCKET_ENABLED: "true"
ADMIN_TOKEN:
valueFrom:
secretKeyRef:
name: vaultwarden-secrets
key: ADMIN_TOKEN
probes:
liveness:
enabled: true
readiness:
enabled: true
service:
app:
controller: vaultwarden
ports:
http:
port: 80
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: vw.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: vaultwarden-tls
hosts:
- vw.aggtaa.com
persistence:
data:
type: persistentVolumeClaim
storageClass: longhorn
accessMode: ReadWriteOnce
size: 2Gi
retain: true
globalMounts:
- path: /data
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease.yaml
@@ -0,0 +1,23 @@
apiVersion: v1
kind: Secret
metadata:
name: vaultwarden-secrets
namespace: vaultwarden
stringData:
#ENC[AES256_GCM,data:b8Yuhc+eN6k2X6xPhUUBAMyNmJhZl+cpszwhb9OUiZeDJUNC1CUFTSD4//XH81kud+iM3/6o854S,iv:0F4/f5sjeFBqp7zZfJhYd8tkiDRlfKJ6p5n337THiWo=,tag:ZNHbaR0tbHT6aWzyfqeqPw==,type:comment]
ADMIN_TOKEN: ENC[AES256_GCM,data:pYY8Nc2PevrFGU2pbZaFH7O+WNJzOHSEXMM3TAFYHO46/GGzQjrxYbc9IY/8PgY86N73kQAFLvw2bpVnjiliilLpnxm9Z0LyYDlAhui8545J67IqoQFDyZt5o3NHceWRP2eCxEL4Bf9gD6j4YfObRVYrp38x2Q==,iv:IvzPkKi/w/S8ueYl/m8kckWiddfl1oQKYiT8S77IVAA=,tag:rD5RsmPt4uihMHWSt4gGwA==,type:str]
sops:
age:
- recipient: age109zta6l0cwpkxn5wek82kvauxn6yqrq80rsz4mvu5atdnpcn9pusuvnqk5
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6RVkzM041NFZDMzlYRllw
a3BRS0JGZ1B0UGYyaTRLUkp2UTJ1S1pybDFnCnZhVWNYMFRlSXlFVTMvRXlpdlF3
Vk1xT1pKV1hzSUYwQXd4ZG92cnhDY0UKLS0tIEtLWXZiZUFDeGhFdytKbTJHSjRH
YkJhS2FJOTdLbHlRelBpUWRNUTdTMDQKhgb66tV4L0PKkPY8DvbypTpcBlavUvRH
0+Rz/UxfZpX8u8G9GeqF5glIpNG2x28VSOHchJxhvMKHIUq9ZUrHGw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-18T19:13:05Z"
mac: ENC[AES256_GCM,data:Ufdytxb9uH6JVXsWu39YhE1AfD/oG7bXhIwoTXYhYBE4IWvugrwZLCvBrgkfuazVXyKBuDIuEa5Vj/bzlTGDSlRNx7SDqabvilhmYAsBcuU5DAVU47pDz1YnXeHd9TuQI1dHAc1Q3uvkZwWNxh2YYXKZGwNMy3tOAkaOxwI1rMw=,iv:iUZenTP/9JhmjcR4p5+RM5K2YuXBLnRj0cd/RHseqFY=,tag:pesF8Pp8vBoQXcniwrep9g==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.13.2
+18
View File
@@ -0,0 +1,18 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: webdav-config
namespace: webdav
data:
config.yml: |
address: 0.0.0.0
port: 6065
directory: /webdav
permissions: R
users:
- username: "{env}WEBDAV_USERNAME"
password: "{env}WEBDAV_PASSWORD"
permissions: CRUD
+102
View File
@@ -0,0 +1,102 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: webdav
namespace: webdav
spec:
interval: 30m
timeout: 10m
chartRef:
kind: OCIRepository
name: webdav
driftDetection:
mode: enabled
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
controllers:
app:
containers:
app:
image:
repository: ghcr.io/hacdias/webdav
tag: v5.14.2
args:
- "-c"
- "/config.yml"
env:
TZ: Europe/Moscow
WEBDAV_USERNAME:
valueFrom:
secretKeyRef:
name: webdav-secrets
key: WEBDAV_USERNAME
WEBDAV_PASSWORD:
valueFrom:
secretKeyRef:
name: webdav-secrets
key: WEBDAV_PASSWORD
probes:
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
service:
app:
controller: app
ports:
http:
port: 6065
ingress:
app:
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: dav.aggtaa.com
paths:
- path: /
pathType: Prefix
service:
identifier: app
port: http
tls:
- secretName: webdav-tls
hosts:
- dav.aggtaa.com
persistence:
config-yml:
type: configMap
name: webdav-config
globalMounts:
- path: /config.yml
subPath: config.yml
readOnly: true
data:
type: persistentVolumeClaim
existingClaim: ganesha-export
globalMounts:
- path: /webdav/export
sda2:
type: persistentVolumeClaim
existingClaim: nas1-sda2
globalMounts:
- path: /webdav/nas1/sda2
sdb2:
type: persistentVolumeClaim
existingClaim: nas1-sdb2
globalMounts:
- path: /webdav/nas1/sdb2
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: webdav
namespace: webdav
spec:
interval: 1h
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- configmap.yaml
- secret.enc.yaml
- pv-pvc-data.yaml
- helmrelease.yaml
+109
View File
@@ -0,0 +1,109 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: ganesha-export-nfs
spec:
capacity:
storage: 1Ti
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=4.1
nfs:
server: 10.0.64.5
path: /export
claimRef:
namespace: webdav
name: ganesha-export
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ganesha-export
namespace: webdav
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: ganesha-export-nfs
resources:
requests:
storage: 1Ti
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nas1-sda2-nfs
spec:
capacity:
storage: 1Ti
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=3
- nolock
nfs:
server: 10.0.64.80
path: /mnt/sda2
claimRef:
namespace: webdav
name: nas1-sda2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nas1-sda2
namespace: webdav
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: nas1-sda2-nfs
resources:
requests:
storage: 1Ti
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nas1-sdb2-nfs
spec:
capacity:
storage: 1Ti
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=3
- nolock
nfs:
server: 10.0.64.80
path: /mnt/sdb2
claimRef:
namespace: webdav
name: nas1-sdb2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nas1-sdb2
namespace: webdav
annotations:
helm.sh/resource-policy: keep
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: nas1-sdb2-nfs
resources:
requests:
storage: 1Ti
+23
View File
@@ -0,0 +1,23 @@
apiVersion: v1
kind: Secret
metadata:
name: webdav-secrets
namespace: webdav
stringData:
WEBDAV_USERNAME: ENC[AES256_GCM,data:BeN5VS8=,iv:XpUr5PG9RWTRGXs7/EzDyLGwUm05Mr0YWpW7PwzNfm8=,tag:3py47tIcTeFxuuAidk2wBg==,type:str]
WEBDAV_PASSWORD: ENC[AES256_GCM,data:Rh1ryftcmxgJAHvC/N1o5QeagCvyb3XIpDS7jOTCpJm3Wb5rUcLNmtcW22f0xljw,iv:GmgQJEuzUzSEql+fx5KURKv8CnhXF/z5UlmEn4ta+K0=,tag:AZD1CEbVRm6DiBt66pB9Vg==,type:str]
sops:
age:
- recipient: age1y8t9v56yuvskznddqfsd4yddzzch0yf2dutvvt8rrx6rjh5f9g0s3qeays
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5UkRLNWVyZUpLREtZcm8r
V0VPTkZDN1pSWDNxWFZXOUlON1c3cHhSZFVzClpEMmhvQnlFMnF6aXV6MjJnMnBu
c2pxTDBGZVBUYjZNR1doQ3UraDQ3dlEKLS0tIE42dTZWMUhZdnVMYUNLTGdrSWZC
UFM0V08reGVmZTRMUWw5T1R2OUdGWW8KWAZq4TG36Njlw4//vPCxzM1Qqr/6Ievw
taBvLXpw9wkrjfZJ8gBU+0Q8WrsEy+tYtR9chfz1n/SmiGHCkskFGQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-29T18:57:24Z"
mac: ENC[AES256_GCM,data:bW2QvjSbA4JmfmZFzC1oIfEnVBxf1pE4jtKDsp2sf7mozVEdqMTD2nNpq4Pro9zRiXQfzxQAXAyzT48dBhIunXKDNIS7vbPpLUbs0nwSF4Dgp/q02ZX3Vrewx80iz5wdMYvLk6c2viZ9ndMwRqBbdBBsuSa5O9nIVj1edG9tj70=,iv:8bgXaA9RiQtbd1Xaht5r5NB3lFedX7Wi/X4z10VLLuA=,tag:Qnr8Ij6XN/FMcQf9Kx1IvA==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,87 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: zigbee2mqtt
namespace: zigbee2mqtt
spec:
interval: 30m
chart:
spec:
chart: zigbee2mqtt
version: "2.12.1"
sourceRef:
kind: HelmRepository
name: zigbee2mqtt
driftDetection:
mode: enabled
values:
service:
type: ClusterIP
port: 8080
statefulset:
storage:
enabled: true
size: 1Gi
storageClassName: longhorn
secrets:
name: zigbee2mqtt-secrets
configmap:
enabled: true
# devices.yaml / groups.yaml already exist on the PV and are the live source
# of truth (zigbee2mqtt mutates them as devices pair/rename). Do not set
# configmap.devices here — the chart only seeds devices.yaml from it when
# the file is missing, but leaving it unset avoids any risk of clobbering
# the real data and any drift between this file and reality.
zigbee2mqtt:
homeassistant:
enabled: true
legacy_entity_attributes: true
permit_join: false
timezone: Europe/Moscow
mqtt:
server: "mqtt://mosquitto.mqtt.svc.cluster.local:1883"
user: iot
password: "!secret.yaml mqtt_password"
include_device_information: true
keepalive: 60
reject_unauthorized: true
version: 4
serial:
port: "tcp://10.0.64.72:6638"
adapter: ember
baudrate: 115200
rtscts: false
frontend:
enabled: true
port: 8080
url: "https://zigbee2mqtt.aggtaa.com"
advanced:
channel: 11
pan_id: 7410
ext_pan_id: [46, 22, 201, 245, 48, 186, 40, 6]
network_key: "!secret.yaml network_key"
log_level: info
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: auth-authelia@kubernetescrd
hosts:
- host: zigbee2mqtt.aggtaa.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zigbee2mqtt-tls
hosts:
- zigbee2mqtt.aggtaa.com
@@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: zigbee2mqtt
namespace: zigbee2mqtt
spec:
interval: 1h
url: https://charts.zigbee2mqtt.io/
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease.yaml
@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: zigbee2mqtt-secrets
namespace: zigbee2mqtt
stringData:
secret.yaml: ENC[AES256_GCM,data:mDb7Vt4sOSaqn9+T3QEAR0yutRIFwiYpBBZDMQI4FMvAPWPkAdsYTIfvv+41JbpBafklDJ5gzPPKZJ6Qk1f8KBvjRuHP/bJqTUPrv+Q46ZKsuXECQq+qB8TKAj51FiJssyx0jYy9bCYgn+Z9r+WBT4YSAvv1XS/UHs4S8UA=,iv:8RHjs0g1ee38yUSHjvQv06Z7xbzMOG6LNzCJ/7s4+Pc=,tag:jb+VgAVApqIpN3lERqrfBg==,type:str]
sops:
age:
- recipient: age1tu4xgfefvgdda0vn2thaqndq4p9tuydq2ud6qa98tzzgnff6749qnlcx29
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzcUFqU2RpTXUwR3hsSEJr
WUV1NkpqQitDRDF6MUpWMWlnSS9TaStRZ1hNClNkSEIzOVVSTUh6LzdXdTdld3Zj
U1pVcE5IaFhuQ0lTZlZ0RzFDSHVkREUKLS0tIDdTbEJLbE9KUmxRei9lVlc4TW1t
TE0yUERpQkpqZFg3aGpvSkg4M04zMGcKcYmSgyzhpZ8BGAp+0zkQ7+ZnF2w/Nk9D
ZT8zwqCJcaRldg59JzmhLBfiuYAtnH2wqWj06LiIfvt+P3pgY79f3g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-19T09:01:11Z"
mac: ENC[AES256_GCM,data:fKfDsAg1Ya8c//x0xw9ytQR7hiF/BssuqlwA57PYiB1yjCW3Kddcdt95UF1amF+9tvJb2XwRjb2LwN4RrYj0RyScLmLa3u/l1r79EoUKQWQ4qF7GNdD5ZGaxqnWjgorxkfYcU7ahJBRDEIsvJXJivk6Wjm309ISzl8xR/tUpFCM=,iv:TmFo1gbRsATh6O0uJvJxwyj7hSzCfpPPuo55EkQQabk=,tag:CJ0cTn9BJs+87AvdwJ0MsA==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.13.2
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- letsencrypt-prod.yaml
@@ -0,0 +1,16 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
# server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@aggtaa.com
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: traefik
@@ -0,0 +1,13 @@
apiVersion: longhorn.io/v1beta2
kind: BackupTarget
metadata:
name: default
namespace: longhorn-system
spec:
# this CR (not defaultSettings.backupTarget in helmrelease.yaml) is what
# Longhorn actually reads - it pre-existed this repo's management,
# pointing at a long-dead minio host. defaultSettings only seeds it on
# a truly fresh install, so it never overwrote the stale live object.
backupTargetURL: "s3://longhorn-backups@us-east-1/"
credentialSecret: longhorn-backup-target-secret
pollInterval: 5m0s
@@ -0,0 +1,52 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: longhorn
namespace: longhorn-system
spec:
interval: 30m
timeout: 15m
chart:
spec:
chart: longhorn
version: "1.12.1"
sourceRef:
kind: HelmRepository
name: longhorn
namespace: longhorn-system
interval: 1h
driftDetection:
mode: enabled
install:
createNamespace: false # namespace создаём отдельным ресурсом с PSA-меткой
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
defaultSettings:
defaultReplicaCount: 3 # по числу нод control-plane
defaultDataPath: /var/lib/longhorn
guaranteedEngineManagerCPU: 12
guaranteedReplicaManagerCPU: 12
# points at the in-cluster rclone proxy (clusters/base/infra/longhorn/s3-backup-proxy),
# which itself encrypts and forwards to the real Wasabi bucket. "longhorn-backups" is
# a virtual bucket the proxy creates on demand under the crypt remote - already exists.
# region is a dummy value; the real endpoint comes from AWS_ENDPOINTS in the secret below.
backupTarget: "s3://longhorn-backups@us-east-1/"
backupTargetCredentialSecret: longhorn-backup-target-secret
# чтобы не разъезжались реплики при 3 нодах
replicaSoftAntiAffinity: false
persistence:
defaultClass: true
defaultClassReplicaCount: 3
defaultFsType: ext4
reclaimPolicy: Retain
csi:
attacherReplicaCount: 3
provisionerReplicaCount: 3
resizerReplicaCount: 3
snapshotterReplicaCount: 3
ingress:
enabled: false # exposed via Traefik explicitly
@@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: longhorn
namespace: longhorn-system
spec:
interval: 1h
url: https://charts.longhorn.io
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: longhorn
namespace: longhorn-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.middlewares: auth-authelia@kubernetescrd
spec:
ingressClassName: traefik
rules:
- host: longhorn.aggtaa.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: longhorn-frontend
port:
number: 80
tls:
- secretName: longhorn-tls
hosts:
- longhorn.aggtaa.com
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease.yaml
- ingress.yaml
- backuptarget.yaml
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: longhorn-backup-target-secret
namespace: longhorn-system
stringData:
AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:QPvQnrkMSKK4Whj7DaMONoDW7LQ=,iv:K1x/VDp4VZUnC7u86wpEfVBDW7T9cI4CZojMZMCaS+A=,tag:eFAAnCMCVjNBHnFAhzFS3w==,type:str]
AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:zFjxH1uqnYRrxM4Yrll9Na+mDj16aLtiJC2ZlNO+2r70JcXJqnTASQ==,iv:B7eV80h4APk3QCuoDR9EFpKs5dkMF8GjQ2v9lSF/ev8=,tag:xLbWN0Gy2idB9j2G8HZYKQ==,type:str]
AWS_ENDPOINTS: ENC[AES256_GCM,data:HWVPThBb5GLpiRqAuSLvmAat0XiUu5R5CMv2Fv6jdieZ11lUmbS2MBD6lx0vQDSIzC36hd6Tu0hgqtknQA==,iv:XLqWSNHgj8O0uQPeBbnN05Za8G9VW6F802CCPoUoq9A=,tag:dsKXCq21hDlJy4KTyQTp/w==,type:str]
sops:
age:
- recipient: age18hhmdmupj90zjgjpxk3z5cf3lx36hcznnmqsp0fpyg50823pudrqjupydt
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqRndobTFybElvakN3c013
T3d6ekVUM0V4OVNEMjl1ak5XTzNPS0xKRmtVCjBXMjVybEs0bFZKN3ByWmFpWm9K
TVVLVVBPYmE2NnJMT3Q3dVJRa250aXcKLS0tIEVKeXBGQ0x4ZDlIWENMa3k3S3Zz
YUdxZXoycXBPbm5raWxFZ3owc2xkQmsKNzw+5kn73FZhApzfN/RBj4yAd7GLl5/X
wyXVy0te83ZDeVQvqhXLkUPsbF8CYewC6xibZSdSY/WBh2ojedJXrQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-11T19:04:53Z"
mac: ENC[AES256_GCM,data:SO2tMq3UA0JcsTZyH3oNhmCsWZ2OhxEmJSn1b/3sH78cZvWalXUsdQqD1GBArFgS+oCSaSgarl2Ve06iNJtM8k6oq0fDkm0dxsLou+67qHlle4CbkP1KPFEQid2Nx8o/5b6cjI2zHz3bYmQTQh9DD7mE8roqo6lX3rYY35MpPJw=,iv:Ad13teWF+lZfzOY/PIALI3/Wohn/0BBoSuE0mtiH8/Y=,tag:sQVQfH0mFFMLnF4zbRC2VQ==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- recurringjob-backup.yaml
@@ -0,0 +1,21 @@
apiVersion: longhorn.io/v1beta2
kind: RecurringJob
metadata:
name: backup
namespace: longhorn-system
spec:
task: "backup"
cron: "0 3 * * *"
retain: 7
concurrency: 2
# "default" is a special group name: Longhorn auto-applies it to every
# volume that has NO recurring-job label at all - i.e. opt-out. To
# exclude a specific volume, give it any other (job-less) group label,
# e.g.:
# recurring-job-group.longhorn.io/no-backup: enabled
# (having *any* recurring-job label disqualifies it from "default" -
# there's no dedicated "disabled" flag, this placeholder-group is the
# documented workaround)
groups:
- default
labels: {}
@@ -0,0 +1,104 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: s3-backup-proxy
namespace: longhorn-system
spec:
interval: 10m
chartRef:
kind: OCIRepository
name: s3-backup-proxy
driftDetection:
mode: enabled
values:
controllers:
app:
containers:
app:
image:
repository: rclone/rclone
tag: "1.75.0"
command:
- sh
- -c
- |
set -eu
# crypt's password/password2 must be stored "obscured" (rclone's
# reversible obfuscation, not real encryption on its own - the
# actual protection is the passphrase's entropy). Generating it
# here at startup from the plaintext secret env vars means the
# real secret only ever needs to exist as one sops-encrypted
# k8s Secret, never hand-obscured and pasted into git.
CRYPT_PASSWORD_OBS=$(rclone obscure "$CRYPT_PASSWORD")
CRYPT_PASSWORD2_OBS=$(rclone obscure "$CRYPT_PASSWORD2")
cat > /tmp/rclone.conf <<EOF
[wasabi]
type = s3
provider = Wasabi
access_key_id = ${WASABI_ACCESS_KEY_ID}
secret_access_key = ${WASABI_SECRET_ACCESS_KEY}
endpoint = s3.eu-central-1.wasabisys.com
region = eu-central-1
[crypt]
type = crypt
remote = wasabi:kubebackup
password = ${CRYPT_PASSWORD_OBS}
password2 = ${CRYPT_PASSWORD2_OBS}
filename_encryption = standard
directory_name_encryption = true
EOF
exec rclone serve s3 crypt: \
--config /tmp/rclone.conf \
--addr :8080 \
--auth-key "${PROXY_ACCESS_KEY},${PROXY_SECRET_KEY}"
env:
WASABI_ACCESS_KEY_ID:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: WASABI_ACCESS_KEY_ID
WASABI_SECRET_ACCESS_KEY:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: WASABI_SECRET_ACCESS_KEY
CRYPT_PASSWORD:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: CRYPT_PASSWORD
CRYPT_PASSWORD2:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: CRYPT_PASSWORD2
PROXY_ACCESS_KEY:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: PROXY_ACCESS_KEY
PROXY_SECRET_KEY:
valueFrom:
secretKeyRef:
name: s3-backup-proxy-secrets
key: PROXY_SECRET_KEY
probes:
liveness:
enabled: true
readiness:
enabled: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 750m
memory: 512Mi
service:
app:
controller: app
ports:
http:
port: 8080
@@ -0,0 +1,10 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: s3-backup-proxy
namespace: longhorn-system
spec:
interval: 10m
url: oci://ghcr.io/bjw-s-labs/helm/app-template
ref:
tag: "5.0.1"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- secret.enc.yaml
- helmrelease.yaml
@@ -0,0 +1,27 @@
apiVersion: v1
kind: Secret
metadata:
name: s3-backup-proxy-secrets
namespace: longhorn-system
stringData:
WASABI_ACCESS_KEY_ID: ENC[AES256_GCM,data:YP9bOIarPh6DJVo0vevVN2T3c3A=,iv:bTKwVx/Ij+GMCisO4WEDCJz8rsHe8PtSET/h4+P2dyM=,tag:fdXr+wAx/SvKNwlYlmKvRw==,type:str]
WASABI_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:Aa0ZYxiVipW2g4mGzyhCspVF67m7hnouMg4Tcr6kSinY+ULpHtEDBA==,iv:txr1ual5z7dXi/27T3FOMsccQtSPObHpg+7IfxvfWuw=,tag:MW7fl0iRB/6flbN+iA+2QA==,type:str]
CRYPT_PASSWORD: ENC[AES256_GCM,data:x8kMqkX4OzduFbW1BGc2EcFxaVm4ah4BBgLNxzQfIHeuL3UuJXt+Cj8isg==,iv:xoFXHqUDK/yamkDaSaQaMIkC7p+q/4qtsrYNwKT7944=,tag:92nej0/10KVTsugwFzdB0w==,type:str]
CRYPT_PASSWORD2: ENC[AES256_GCM,data:TdcfVmkgd36Hq80XaLRM8lY2dUq95h4v4ONCy/O7DOMoRwVxDUktgCRgNA==,iv:9EluJOnjKjCo2272sIeJ58iFu54DKfbkX785ZySj3C0=,tag:WUMKDLASA9grCilKNLVmZA==,type:str]
PROXY_ACCESS_KEY: ENC[AES256_GCM,data:A1ZF8ejApPiH3nISLvrO7+vWTzM=,iv:fSFwYL/6zxtK4wkz+G3WItzq6P92eGviQ7WaVquDYuk=,tag:n9fjuW/cXU7QWe5IUkXf/w==,type:str]
PROXY_SECRET_KEY: ENC[AES256_GCM,data:iU6GHL09tzxgyxcl6wKlDNhaO6YVYS/8yy/qCPbSxE3RlHk0Yw9EnQ==,iv:i0R6SzOJlEDsyEhxLrLMVaJu7ZAzNsnnmdyrxbv6ys0=,tag:h290ZIcFevBQeImcfqQbjg==,type:str]
sops:
age:
- recipient: age15vc3geq4lsec69rnxczluvwgcexeqae02zxzjpswsuatthaasu7qzath48
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxR0hwMUhqVHFJSVBUckZW
cTdCeUx0bC8zdjB5dmhRNDJrc1ZOTnY4WmxVCjJNZEUrMEFEYmNGUWQxbHRZL3Bj
S3dnOGhENlJkV2oxR1RhSmdCWXczVWsKLS0tIHIxSnVSdGtKaE52UHNtaWVnRUNW
Unc4QUJ3YmF3N2xVVDg2VjBrZW80QmsKZ5HiOTiYRCrfWJf2Qpxyc4D+1wNWxHzK
hCe6G9ANAX/EAlGwmOIn3k7cFbN1VRt50TV/UCj+FsJBuifUYVMvTQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-11T18:28:22Z"
mac: ENC[AES256_GCM,data:eICyFKuJAyCE8ZbdpWc4rOHRxtnrFhdxTeznIWXie5CrHRyo+n21CPg5L/VQUy2LF1rXINIDJRNqQWcLXJEXDKWGpqyaL/YcW+OawpbCq0DMJAe/DsvBdy5ySwGFdRlwU5x7ivVjsHKnsr8UgcbHdE/ZVRq93nZOfZlUepjb1u0=,iv:l+SFwIobBbxeZNUmwK6U9W5YpLh4t5GOtnRIH10Z7W4=,tag:6Zs+tneNcF1d5i43ns6Rmw==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.11.0
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- longhorn-no-retain.yaml
- longhorn-single-replica.yaml
@@ -0,0 +1,12 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-no-retain
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: driver.longhorn.io
reclaimPolicy: Delete
allowVolumeExpansion: true
parameters:
numberOfReplicas: "3"
staleReplicaTimeout: "30"
@@ -0,0 +1,12 @@
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn-single-replica
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: driver.longhorn.io
reclaimPolicy: Retain
allowVolumeExpansion: true
parameters:
numberOfReplicas: "1"
staleReplicaTimeout: "30"
+75
View File
@@ -0,0 +1,75 @@
# a general pool for everything but mqtt and public IP facing ingress controller
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
# exclude 10.0.64.5, its in nfs-pool
- 10.0.64.6/32
# exclude 10.0.64.7, its in dmz-pool
# exclude 10.0.64.8, its in mqtt-pool
- 10.0.64.9-10.0.64.19
autoAssign: true
serviceAllocation:
serviceSelectors:
- matchLabels:
loadBalancerPool: default
---
# NFS ip
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: nfs-pool
namespace: metallb-system
spec:
addresses:
- 10.0.64.5/32
autoAssign: true
serviceAllocation:
serviceSelectors:
- matchLabels:
loadBalancerPool: nfs
---
# DMZ (not really, just for clarity) ip, will have ports forwarded from WAN router
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: dmz-pool
namespace: metallb-system
spec:
addresses:
- 10.0.64.7/32
autoAssign: true
serviceAllocation:
serviceSelectors:
- matchLabels:
loadBalancerPool: dmz
---
# reserve 10.0.64.8 (for historical reasons) for mqtt
# ! do not forget to label the mqtt service with "loadBalancerPool: mqtt" !
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: mqtt-pool
namespace: metallb-system
spec:
addresses:
- 10.0.64.8/32
serviceAllocation:
serviceSelectors:
- matchLabels:
loadBalancerPool: mqtt
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
- dmz-pool
- mqtt-pool
- nfs-pool
@@ -0,0 +1,43 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: metallb
namespace: metallb-system
spec:
interval: 10m
chart:
spec:
chart: metallb
version: "0.16.1"
sourceRef:
kind: HelmRepository
name: metallb
namespace: metallb-system
driftDetection:
mode: enabled
postRenderers:
- kustomize:
patches:
# frr's liveness/startup probes default to timeoutSeconds: 1,
# which is too tight when the node is under CPU pressure (an
# HTTPS healthz call can't always complete in 1s) - kubelet then
# kills+restarts the container, which itself adds more CPU load,
# compounding the problem. Give the probes more slack instead.
- target:
kind: DaemonSet
name: metallb-frr-k8s
patch: |
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: metallb-frr-k8s
spec:
template:
spec:
containers:
- name: frr
livenessProbe:
timeoutSeconds: 5
failureThreshold: 6
startupProbe:
timeoutSeconds: 5
@@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: metallb
namespace: metallb-system
spec:
interval: 1h
url: https://metallb.github.io/metallb
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helmrepository.yaml
- helmrelease.yaml
- config.yaml
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: ai
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: auth
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: finance

Some files were not shown because too many files have changed in this diff Show More