From dbc47d023104cdacc94a4db155c9ff6c7f213a64 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 07:52:11 -0800 Subject: [PATCH 01/12] Migrate ArgoCD repos from indri:2200 to forge.ops.eblu.me:2222 Update all ArgoCD application repo URLs and SSH known hosts to use the new Caddy-proxied forge endpoint instead of the legacy Tailscale MagicDNS hostname. Co-Authored-By: Claude Opus 4.5 --- argocd/apps/alloy-k8s.yaml | 2 +- argocd/apps/apps.yaml | 2 +- argocd/apps/argocd.yaml | 2 +- argocd/apps/blumeops-pg.yaml | 2 +- argocd/apps/cloudnative-pg.yaml | 4 ++-- argocd/apps/devpi.yaml | 2 +- argocd/apps/grafana-config.yaml | 2 +- argocd/apps/grafana.yaml | 4 ++-- argocd/apps/kiwix.yaml | 2 +- argocd/apps/kube-state-metrics.yaml | 2 +- argocd/apps/loki.yaml | 2 +- argocd/apps/miniflux.yaml | 2 +- argocd/apps/prometheus.yaml | 2 +- argocd/apps/tailscale-operator.yaml | 2 +- argocd/apps/teslamate.yaml | 2 +- argocd/apps/torrent.yaml | 2 +- argocd/manifests/argocd/README.md | 4 ++-- argocd/manifests/argocd/argocd-ssh-known-hosts-cm.yaml | 8 ++++---- argocd/manifests/argocd/repo-forge-secret.yaml.tpl | 4 ++-- 19 files changed, 26 insertions(+), 26 deletions(-) diff --git a/argocd/apps/alloy-k8s.yaml b/argocd/apps/alloy-k8s.yaml index 29d996c..9b652bc 100644 --- a/argocd/apps/alloy-k8s.yaml +++ b/argocd/apps/alloy-k8s.yaml @@ -6,7 +6,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/alloy-k8s destination: diff --git a/argocd/apps/apps.yaml b/argocd/apps/apps.yaml index c028062..0eebe54 100644 --- a/argocd/apps/apps.yaml +++ b/argocd/apps/apps.yaml @@ -8,7 +8,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/apps destination: diff --git a/argocd/apps/argocd.yaml b/argocd/apps/argocd.yaml index f056ef0..c5e89e8 100644 --- a/argocd/apps/argocd.yaml +++ b/argocd/apps/argocd.yaml @@ -8,7 +8,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/argocd destination: diff --git a/argocd/apps/blumeops-pg.yaml b/argocd/apps/blumeops-pg.yaml index 54f20c5..6a9e57e 100644 --- a/argocd/apps/blumeops-pg.yaml +++ b/argocd/apps/blumeops-pg.yaml @@ -12,7 +12,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/databases destination: diff --git a/argocd/apps/cloudnative-pg.yaml b/argocd/apps/cloudnative-pg.yaml index d2f6e81..73c3bf0 100644 --- a/argocd/apps/cloudnative-pg.yaml +++ b/argocd/apps/cloudnative-pg.yaml @@ -11,7 +11,7 @@ spec: project: default sources: # Helm chart from forge mirror (SSH via egress) - - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/cloudnative-pg-charts.git + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/cloudnative-pg-charts.git targetRevision: cloudnative-pg-v0.27.0 path: charts/cloudnative-pg helm: @@ -19,7 +19,7 @@ spec: valueFiles: - $values/argocd/manifests/cloudnative-pg/values.yaml # Values from our git repo - - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main ref: values destination: diff --git a/argocd/apps/devpi.yaml b/argocd/apps/devpi.yaml index e294f5b..4a15672 100644 --- a/argocd/apps/devpi.yaml +++ b/argocd/apps/devpi.yaml @@ -18,7 +18,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/devpi destination: diff --git a/argocd/apps/grafana-config.yaml b/argocd/apps/grafana-config.yaml index e363933..f98399c 100644 --- a/argocd/apps/grafana-config.yaml +++ b/argocd/apps/grafana-config.yaml @@ -13,7 +13,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/grafana-config destination: diff --git a/argocd/apps/grafana.yaml b/argocd/apps/grafana.yaml index 1a748d8..ec9262e 100644 --- a/argocd/apps/grafana.yaml +++ b/argocd/apps/grafana.yaml @@ -14,7 +14,7 @@ spec: project: default sources: # Helm chart from forge mirror (SSH via egress) - - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/grafana-helm-charts.git + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/grafana-helm-charts.git targetRevision: grafana-8.8.2 path: charts/grafana helm: @@ -22,7 +22,7 @@ spec: valueFiles: - $values/argocd/manifests/grafana/values.yaml # Values from our git repo - - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main ref: values destination: diff --git a/argocd/apps/kiwix.yaml b/argocd/apps/kiwix.yaml index 70be2c1..36e5b93 100644 --- a/argocd/apps/kiwix.yaml +++ b/argocd/apps/kiwix.yaml @@ -7,7 +7,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/kiwix destination: diff --git a/argocd/apps/kube-state-metrics.yaml b/argocd/apps/kube-state-metrics.yaml index 91df2cd..1644532 100644 --- a/argocd/apps/kube-state-metrics.yaml +++ b/argocd/apps/kube-state-metrics.yaml @@ -6,7 +6,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/kube-state-metrics destination: diff --git a/argocd/apps/loki.yaml b/argocd/apps/loki.yaml index cb9dd41..834c86c 100644 --- a/argocd/apps/loki.yaml +++ b/argocd/apps/loki.yaml @@ -6,7 +6,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/loki destination: diff --git a/argocd/apps/miniflux.yaml b/argocd/apps/miniflux.yaml index 36cff8d..d9165bb 100644 --- a/argocd/apps/miniflux.yaml +++ b/argocd/apps/miniflux.yaml @@ -16,7 +16,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/miniflux destination: diff --git a/argocd/apps/prometheus.yaml b/argocd/apps/prometheus.yaml index b53a243..3348736 100644 --- a/argocd/apps/prometheus.yaml +++ b/argocd/apps/prometheus.yaml @@ -6,7 +6,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/prometheus destination: diff --git a/argocd/apps/tailscale-operator.yaml b/argocd/apps/tailscale-operator.yaml index e3cc2c8..4ca5ea7 100644 --- a/argocd/apps/tailscale-operator.yaml +++ b/argocd/apps/tailscale-operator.yaml @@ -14,7 +14,7 @@ spec: jsonPointers: - /spec/externalName source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/tailscale-operator destination: diff --git a/argocd/apps/teslamate.yaml b/argocd/apps/teslamate.yaml index 9c22c42..6165b8e 100644 --- a/argocd/apps/teslamate.yaml +++ b/argocd/apps/teslamate.yaml @@ -21,7 +21,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/teslamate destination: diff --git a/argocd/apps/torrent.yaml b/argocd/apps/torrent.yaml index 91e5fdc..7fd4135 100644 --- a/argocd/apps/torrent.yaml +++ b/argocd/apps/torrent.yaml @@ -7,7 +7,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/torrent destination: diff --git a/argocd/manifests/argocd/README.md b/argocd/manifests/argocd/README.md index 42762df..344b1e2 100644 --- a/argocd/manifests/argocd/README.md +++ b/argocd/manifests/argocd/README.md @@ -32,7 +32,7 @@ argocd account update-password PRIV_KEY=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key?ssh-format=openssh")$'\n' && \ kubectl create secret generic repo-creds-forge -n argocd \ --from-literal=type=git \ - --from-literal=url='ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/' \ + --from-literal=url='ssh://forgejo@forge.ops.eblu.me:2222/eblume/' \ --from-literal=insecure=true \ --from-literal=sshPrivateKey="$PRIV_KEY" && \ kubectl label secret repo-creds-forge -n argocd argocd.argoproj.io/secret-type=repo-creds @@ -82,7 +82,7 @@ metadata: spec: project: default source: - repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/my-app destination: diff --git a/argocd/manifests/argocd/argocd-ssh-known-hosts-cm.yaml b/argocd/manifests/argocd/argocd-ssh-known-hosts-cm.yaml index 61525aa..cf3b728 100644 --- a/argocd/manifests/argocd/argocd-ssh-known-hosts-cm.yaml +++ b/argocd/manifests/argocd/argocd-ssh-known-hosts-cm.yaml @@ -1,5 +1,5 @@ -# Patch to add forge (indri) SSH host key to ArgoCD known_hosts -# Includes upstream defaults plus indri.tail8d86e.ts.net:2200 +# Patch to add forge SSH host key to ArgoCD known_hosts +# Includes upstream defaults plus forge.ops.eblu.me:2222 apiVersion: v1 kind: ConfigMap metadata: @@ -21,5 +21,5 @@ data: gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H - # Forge (indri) - Forgejo SSH on port 2200 - [indri.tail8d86e.ts.net]:2200 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDlGQT5w03XxlhmEiDVtGq2SkhLIZU4vYhdMey/T2tFLp7kEiOwCWgDgbBn12VDfqXTXJreykBuREqYNSx4tL4Znwap0+HjLOjTIVri8af2ZFF6IP52pcmJEOnxm/yUZhJCosu1wOZwLOoQEPBYM6sPN4OY9PFOsrsxMO2LWPJAZujPlnsfKOTsIS5iRpiT4yU7Z+oWB21rMxjZ9sXZRn8PI2MbUIs/Yazpah2XPJm2YJ7C+kqTLmld4mXQaQtHhzvPaRNB59RS8xyinuaRs618tD3DQq3Qpt8ZZKZydLVv4CIrGvjdqavt0l+4rsNGBh8dWvDR7l2Z6wo9ggDCej957+J6tInfZ82KHSW3ONdm2mUOHObUVSte2xUPlRpnIBFt3lcCapifPULE7PuN0Xdw4r+ewr+6R65RzdptqGfKyyAYsERhbq904ryNZ9fy30vH8+j9imL5AhMkCbP8S/UW49rDIdfN6MvZlX9MoBhmbrkv+kETB7qz9zaOrocEOZOE3fzB9iZxNwlXjstUnjkqi4P1yY/SKpyLC/yDCUpxC79FbCAKIJwar3C2mZaLeBGyqL31HPKOx175VsSxIbjeJX8uNO9WhbFPlcbRETeEoq+dczeU25OESCyyelGb72tTNJYObn2R8Br9NFPiwGZJX6TLlKqaE7x3D0M64ncTJQ== + # Forge - Forgejo SSH on port 2222 + [forge.ops.eblu.me]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDlGQT5w03XxlhmEiDVtGq2SkhLIZU4vYhdMey/T2tFLp7kEiOwCWgDgbBn12VDfqXTXJreykBuREqYNSx4tL4Znwap0+HjLOjTIVri8af2ZFF6IP52pcmJEOnxm/yUZhJCosu1wOZwLOoQEPBYM6sPN4OY9PFOsrsxMO2LWPJAZujPlnsfKOTsIS5iRpiT4yU7Z+oWB21rMxjZ9sXZRn8PI2MbUIs/Yazpah2XPJm2YJ7C+kqTLmld4mXQaQtHhzvPaRNB59RS8xyinuaRs618tD3DQq3Qpt8ZZKZydLVv4CIrGvjdqavt0l+4rsNGBh8dWvDR7l2Z6wo9ggDCej957+J6tInfZ82KHSW3ONdm2mUOHObUVSte2xUPlRpnIBFt3lcCapifPULE7PuN0Xdw4r+ewr+6R65RzdptqGfKyyAYsERhbq904ryNZ9fy30vH8+j9imL5AhMkCbP8S/UW49rDIdfN6MvZlX9MoBhmbrkv+kETB7qz9zaOrocEOZOE3fzB9iZxNwlXjstUnjkqi4P1yY/SKpyLC/yDCUpxC79FbCAKIJwar3C2mZaLeBGyqL31HPKOx175VsSxIbjeJX8uNO9WhbFPlcbRETeEoq+dczeU25OESCyyelGb72tTNJYObn2R8Br9NFPiwGZJX6TLlKqaE7x3D0M64ncTJQ== diff --git a/argocd/manifests/argocd/repo-forge-secret.yaml.tpl b/argocd/manifests/argocd/repo-forge-secret.yaml.tpl index e72b037..9d6187e 100644 --- a/argocd/manifests/argocd/repo-forge-secret.yaml.tpl +++ b/argocd/manifests/argocd/repo-forge-secret.yaml.tpl @@ -11,7 +11,7 @@ # PRIV_KEY=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key?ssh-format=openssh")$'\n' && \ # kubectl create secret generic repo-creds-forge -n argocd \ # --from-literal=type=git \ -# --from-literal=url='ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/' \ +# --from-literal=url='ssh://forgejo@forge.ops.eblu.me:2222/eblume/' \ # --from-literal=insecure=true \ # --from-literal=sshPrivateKey="$PRIV_KEY" && \ # kubectl label secret repo-creds-forge -n argocd argocd.argoproj.io/secret-type=repo-creds @@ -25,7 +25,7 @@ metadata: argocd.argoproj.io/secret-type: repo-creds stringData: type: git - url: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/ + url: ssh://forgejo@forge.ops.eblu.me:2222/eblume/ insecure: "true" sshPrivateKey: | # Key from 1Password: op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key -- 2.50.1 (Apple Git-155) From 5482f745006aee9f68dda125be10e4fd09c9477f Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 07:52:24 -0800 Subject: [PATCH 02/12] Add Immich photo management service Deploy Immich via Helm chart with: - PostgreSQL cluster with pgvecto.rs (immich-pg) for AI vector search - NFS storage on sifaka for photo library - Tailscale Ingress + Caddy proxy for photos.ops.eblu.me access - Machine learning service for face/object recognition Immich provides a self-hosted Google Photos/iCloud alternative with AI-powered search, face recognition, and support for RAW files. Co-Authored-By: Claude Opus 4.5 --- ansible/roles/caddy/defaults/main.yml | 3 + argocd/apps/immich-storage.yaml | 25 +++++ argocd/apps/immich.yaml | 38 +++++++ argocd/manifests/databases/README.md | 39 ++++++++ argocd/manifests/databases/immich-pg.yaml | 47 +++++++++ argocd/manifests/databases/kustomization.yaml | 1 + argocd/manifests/immich/README.md | 98 +++++++++++++++++++ .../manifests/immich/ingress-tailscale.yaml | 26 +++++ argocd/manifests/immich/kustomization.yaml | 11 +++ argocd/manifests/immich/pv-nfs.yaml | 22 +++++ argocd/manifests/immich/pvc.yaml | 15 +++ argocd/manifests/immich/secret-db.yaml.tpl | 12 +++ argocd/manifests/immich/values.yaml | 64 ++++++++++++ 13 files changed, 401 insertions(+) create mode 100644 argocd/apps/immich-storage.yaml create mode 100644 argocd/apps/immich.yaml create mode 100644 argocd/manifests/databases/immich-pg.yaml create mode 100644 argocd/manifests/immich/README.md create mode 100644 argocd/manifests/immich/ingress-tailscale.yaml create mode 100644 argocd/manifests/immich/kustomization.yaml create mode 100644 argocd/manifests/immich/pv-nfs.yaml create mode 100644 argocd/manifests/immich/pvc.yaml create mode 100644 argocd/manifests/immich/secret-db.yaml.tpl create mode 100644 argocd/manifests/immich/values.yaml diff --git a/ansible/roles/caddy/defaults/main.yml b/ansible/roles/caddy/defaults/main.yml index 08eb341..105b139 100644 --- a/ansible/roles/caddy/defaults/main.yml +++ b/ansible/roles/caddy/defaults/main.yml @@ -58,6 +58,9 @@ caddy_services: - name: teslamate host: "tesla.{{ caddy_domain }}" backend: "https://tesla.tail8d86e.ts.net" + - name: immich + host: "photos.{{ caddy_domain }}" + backend: "https://photos.tail8d86e.ts.net" # Layer 4 (TCP) services # Format: { port: external_port, backend: "host:port" } diff --git a/argocd/apps/immich-storage.yaml b/argocd/apps/immich-storage.yaml new file mode 100644 index 0000000..718b4c7 --- /dev/null +++ b/argocd/apps/immich-storage.yaml @@ -0,0 +1,25 @@ +# Immich Storage - PersistentVolume and PVC for photo library +# Must be synced BEFORE the main immich app +# +# Prerequisites: +# 1. NFS share on sifaka at /volume1/photos with permissions for indri +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: immich-storage + namespace: argocd +spec: + project: default + source: + repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git + targetRevision: main + path: argocd/manifests/immich + # Only deploy kustomization resources (PV/PVC/Ingress), not values.yaml + directory: + include: "{kustomization.yaml,pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}" + destination: + server: https://kubernetes.default.svc + namespace: immich + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/argocd/apps/immich.yaml b/argocd/apps/immich.yaml new file mode 100644 index 0000000..54d0789 --- /dev/null +++ b/argocd/apps/immich.yaml @@ -0,0 +1,38 @@ +# Immich - Self-hosted photo and video management +# High-performance Google Photos/iCloud alternative with AI features +# +# Chart mirrored from https://github.com/immich-app/immich-charts to forge +# +# Prerequisites: +# 1. Mirror immich-charts to forge: https://github.com/immich-app/immich-charts +# 2. Create immich namespace and secrets: +# kubectl create namespace immich +# op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f - +# 3. Create immich-pg database and user (see immich-pg app) +# 4. Mount photos directory from indri to minikube +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: immich + namespace: argocd +spec: + project: default + sources: + # Helm chart from forge mirror (SSH via egress) + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/immich-charts.git + targetRevision: immich-0.10.0 + path: charts/immich + helm: + releaseName: immich + valueFiles: + - $values/argocd/manifests/immich/values.yaml + # Values from our git repo + - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: immich + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/argocd/manifests/databases/README.md b/argocd/manifests/databases/README.md index c82f4d1..02bd792 100644 --- a/argocd/manifests/databases/README.md +++ b/argocd/manifests/databases/README.md @@ -2,6 +2,13 @@ PostgreSQL clusters managed by CloudNativePG operator. +## Clusters + +| Cluster | Image | Purpose | +|---------|-------|---------| +| blumeops-pg | cloudnative-pg/postgresql:18 | General services (miniflux, teslamate) | +| immich-pg | tensorchord/cloudnative-vectorchord:17 | Immich (requires pgvecto.rs extension) | + ## blumeops-pg Single-instance PostgreSQL cluster for blumeops services. @@ -99,3 +106,35 @@ from brew PostgreSQL (indri) to this k8s cluster. At that point: 1. Delete `service-tailscale.yaml` (the `k8s-pg` service) 2. Update/create a service with `tailscale.com/hostname: "pg"` 3. Verify the orphaned `k8s-pg` device is removed from tailnet + +## immich-pg + +PostgreSQL cluster for Immich with pgvecto.rs extension for AI-powered vector search. + +### Configuration + +- **Instances**: 1 (single-node for minikube) +- **Storage**: 10Gi on `standard` storage class +- **Image**: `tensorchord/cloudnative-vectorchord:17-v0.4.0` (includes pgvecto.rs) +- **Extensions**: `vectors`, `earthdistance` + +### Connection + +Immich connects via `immich-pg-rw.databases.svc.cluster.local:5432`. + +The `immich` user password is auto-generated by CloudNativePG and stored in `immich-pg-app` secret: + +```bash +# Get immich app credentials +kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d +``` + +### Status + +```bash +# Check cluster health +kubectl -n databases get cluster immich-pg + +# Check pods +kubectl -n databases get pods -l cnpg.io/cluster=immich-pg +``` diff --git a/argocd/manifests/databases/immich-pg.yaml b/argocd/manifests/databases/immich-pg.yaml new file mode 100644 index 0000000..d6e40d6 --- /dev/null +++ b/argocd/manifests/databases/immich-pg.yaml @@ -0,0 +1,47 @@ +# PostgreSQL Cluster for Immich +# Uses tensorchord/cloudnative-vectorchord for pgvecto.rs extension (required by Immich for AI features) +# Managed by CloudNativePG operator +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: immich-pg + namespace: databases +spec: + instances: 1 + # vectorchord image includes pgvecto.rs extension for vector similarity search + imageName: tensorchord/cloudnative-vectorchord:17-v0.4.0 + + storage: + size: 10Gi + storageClass: standard + + # Bootstrap creates initial database and owner + bootstrap: + initdb: + database: immich + owner: immich + postInitSQL: + - CREATE EXTENSION IF NOT EXISTS vectors; + - CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE; + + # Resource limits for minikube environment + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "500m" + + # PostgreSQL configuration + postgresql: + parameters: + max_connections: "50" + shared_buffers: "128MB" + password_encryption: "scram-sha-256" + # pgvecto.rs settings + shared_preload_libraries: "vectors.so" + pg_hba: + # Allow connections from k8s pods + - host all all 0.0.0.0/0 scram-sha-256 + - host all all ::/0 scram-sha-256 diff --git a/argocd/manifests/databases/kustomization.yaml b/argocd/manifests/databases/kustomization.yaml index e44bdaf..e2eaa0c 100644 --- a/argocd/manifests/databases/kustomization.yaml +++ b/argocd/manifests/databases/kustomization.yaml @@ -5,5 +5,6 @@ namespace: databases resources: - blumeops-pg.yaml + - immich-pg.yaml - service-tailscale.yaml - service-metrics-tailscale.yaml diff --git a/argocd/manifests/immich/README.md b/argocd/manifests/immich/README.md new file mode 100644 index 0000000..0c1bfef --- /dev/null +++ b/argocd/manifests/immich/README.md @@ -0,0 +1,98 @@ +# Immich + +Self-hosted photo and video management solution with AI-powered search and face recognition. + +## Prerequisites + +1. **NFS Share**: Create `/volume1/photos` on sifaka with NFS permissions for indri +2. **PostgreSQL**: The `immich-pg` cluster (with pgvecto.rs) must be healthy +3. **Secrets**: Create the database password secret + +## Deployment Order + +1. Sync `blumeops-pg` (to get CloudNativePG operator if not already running) +2. Sync `immich-storage` (creates PV, PVC, and Tailscale Ingress) +3. Wait for `immich-pg` cluster to be healthy +4. Create secrets (see below) +5. Sync `immich` (deploys the Helm chart) +6. Run `mise run provision-indri -- --tags caddy` to update Caddy config + +## Secret Setup + +```bash +# Create namespace +kubectl create namespace immich + +# Get the auto-generated immich password from CloudNativePG +kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d + +# Store that password in 1Password under blumeops/immich-pg, then: +op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f - +``` + +## Access + +- **URL**: https://photos.ops.eblu.me (after Caddy is updated) +- **Tailscale**: https://photos.tail8d86e.ts.net (direct) + +## First-Time Setup + +1. Navigate to https://photos.ops.eblu.me +2. Create an admin account +3. Configure external library (optional - for importing existing photos) + +## External Library (iCloud Photos) + +To import existing photos from iCloud sync on indri: + +1. In Immich Admin > External Libraries, create a new library +2. Set the import path to the location where iCloud photos sync +3. Configure scan schedule or trigger manual scan + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ +│ immich-server │────▶│ immich-pg │ +│ (web/api) │ │ (PostgreSQL │ +└────────┬────────┘ │ + pgvecto.rs) │ + │ └─────────────────┘ + │ +┌────────▼────────┐ ┌─────────────────┐ +│ immich-ml │ │ valkey │ +│ (ML inference) │ │ (Redis cache) │ +└─────────────────┘ └─────────────────┘ + │ +┌────────▼────────┐ +│ sifaka NFS │ +│ /volume1/photos│ +└─────────────────┘ +``` + +## Helm Values + +The Helm chart is configured via `values.yaml`. Key settings: + +- `image.tag`: Immich version (update manually) +- `immich.persistence.library.existingClaim`: Points to `immich-library` PVC +- `machine-learning.enabled`: AI features for face/object recognition +- `valkey.enabled`: Redis cache included in chart + +## Troubleshooting + +```bash +# Check pods +kubectl -n immich get pods + +# Check immich-pg cluster +kubectl -n databases get cluster immich-pg + +# View server logs +kubectl -n immich logs -l app.kubernetes.io/name=immich-server + +# View ML logs +kubectl -n immich logs -l app.kubernetes.io/name=immich-machine-learning + +# Check PVC binding +kubectl -n immich get pvc +``` diff --git a/argocd/manifests/immich/ingress-tailscale.yaml b/argocd/manifests/immich/ingress-tailscale.yaml new file mode 100644 index 0000000..007fb6c --- /dev/null +++ b/argocd/manifests/immich/ingress-tailscale.yaml @@ -0,0 +1,26 @@ +# Tailscale Ingress for Immich +# Exposes Immich at photos.tail8d86e.ts.net +# Caddy will proxy photos.ops.eblu.me to this endpoint +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: immich-tailscale + namespace: immich + annotations: + tailscale.com/funnel: "false" +spec: + ingressClassName: tailscale + rules: + - host: photos + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: immich-server + port: + number: 2283 + tls: + - hosts: + - photos diff --git a/argocd/manifests/immich/kustomization.yaml b/argocd/manifests/immich/kustomization.yaml new file mode 100644 index 0000000..1c1c6d8 --- /dev/null +++ b/argocd/manifests/immich/kustomization.yaml @@ -0,0 +1,11 @@ +# Immich non-Helm resources (storage) +# These must be deployed before the Helm chart +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: immich + +resources: + - pv-nfs.yaml + - pvc.yaml + - ingress-tailscale.yaml diff --git a/argocd/manifests/immich/pv-nfs.yaml b/argocd/manifests/immich/pv-nfs.yaml new file mode 100644 index 0000000..0bd6ee2 --- /dev/null +++ b/argocd/manifests/immich/pv-nfs.yaml @@ -0,0 +1,22 @@ +# NFS PersistentVolume for Immich photo library +# Requires: NFS share on sifaka at /volume1/photos with NFS permissions for indri +# +# To create on Synology: +# 1. Control Panel > Shared Folder > Create +# 2. Name: photos, Location: Volume 1 +# 3. Control Panel > File Services > NFS > NFS Rules +# 4. Add rule for "photos" share: Hostname=indri, Privilege=Read/Write, Squash=No mapping +apiVersion: v1 +kind: PersistentVolume +metadata: + name: immich-library-nfs-pv +spec: + capacity: + storage: 2Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + nfs: + server: sifaka + path: /volume1/photos diff --git a/argocd/manifests/immich/pvc.yaml b/argocd/manifests/immich/pvc.yaml new file mode 100644 index 0000000..c764636 --- /dev/null +++ b/argocd/manifests/immich/pvc.yaml @@ -0,0 +1,15 @@ +# PersistentVolumeClaim for Immich photo library +# Binds to the NFS PV for sifaka:/volume1/photos +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-library + namespace: immich +spec: + accessModes: + - ReadWriteMany + storageClassName: "" + volumeName: immich-library-nfs-pv + resources: + requests: + storage: 2Ti diff --git a/argocd/manifests/immich/secret-db.yaml.tpl b/argocd/manifests/immich/secret-db.yaml.tpl new file mode 100644 index 0000000..cec6328 --- /dev/null +++ b/argocd/manifests/immich/secret-db.yaml.tpl @@ -0,0 +1,12 @@ +# Immich database password secret +# Apply with: op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f - +apiVersion: v1 +kind: Secret +metadata: + name: immich-db + namespace: immich +type: Opaque +stringData: + # Password is auto-generated by CloudNativePG and stored in immich-pg-app secret + # Retrieve with: kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d + password: "{{ op://blumeops/immich-pg/password }}" diff --git a/argocd/manifests/immich/values.yaml b/argocd/manifests/immich/values.yaml new file mode 100644 index 0000000..16c9c49 --- /dev/null +++ b/argocd/manifests/immich/values.yaml @@ -0,0 +1,64 @@ +# Immich Helm values for blumeops +# Chart: https://github.com/immich-app/immich-charts +# +# Immich requires: +# - PostgreSQL with pgvecto.rs extension (separate immich-pg cluster) +# - Redis/Valkey (included in chart) +# - Library storage PVC (photos directory from indri) + +# Image version - explicitly set to avoid drift +image: + tag: v1.125.7 + +# Environment variables for all components +env: + TZ: "America/Los_Angeles" + # Database connection - uses immich-pg cluster + DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local" + DB_PORT: "5432" + DB_DATABASE_NAME: "immich" + DB_USERNAME: "immich" + # Password injected from secret + DB_PASSWORD: + valueFrom: + secretKeyRef: + name: immich-db + key: password + +# Immich server configuration +immich: + persistence: + library: + existingClaim: immich-library + +# Machine Learning service +machine-learning: + enabled: true + persistence: + cache: + type: persistentVolumeClaim + accessMode: ReadWriteOnce + size: 10Gi + resources: + requests: + memory: "512Mi" + cpu: "100m" + limits: + memory: "4Gi" + cpu: "2000m" + +# Valkey (Redis fork) - included in chart +valkey: + enabled: true + persistence: + size: 1Gi + +# Server resources for minikube +server: + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "2Gi" + cpu: "2000m" -- 2.50.1 (Apple Git-155) From b01400bcbb34e0858560a0da728ea5ea2e5f3822 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 08:13:48 -0800 Subject: [PATCH 03/12] Add rule: always use --context=minikube-indri with kubectl Prevent accidental operations against work clusters by requiring explicit context specification on all kubectl commands. Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 793ad55..5cc36aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,9 +10,11 @@ blumeops is Erich Blume's GitOps repository for personal infrastructure manageme ## Rules -1. At the start of every session, even if the user asked to do something else, run `mise run zk-docs -- --style=header --color=never --decorations=always` in order to review the `blumeops` documentation in the zettelkasten (zk). zk lives at `~/code/personal/zk`, and is managed via obsidian-sync (not git). +1. **CRITICAL: Always use `--context=minikube-indri` with kubectl commands.** The user has work contexts configured that must never be touched. Every kubectl command must explicitly specify the context to prevent accidental operations against the wrong cluster. -2. When making any changes, start by making sure you're on the `main` git branch and up-to-date, and then create a feature branch. Commit often while working, and create a PR using: +2. At the start of every session, even if the user asked to do something else, run `mise run zk-docs -- --style=header --color=never --decorations=always` in order to review the `blumeops` documentation in the zettelkasten (zk). zk lives at `~/code/personal/zk`, and is managed via obsidian-sync (not git). + +3. When making any changes, start by making sure you're on the `main` git branch and up-to-date, and then create a feature branch. Commit often while working, and create a PR using: ```fish tea pr create --title "Description of change" --description "$(cat <<'EOF' ## Summary @@ -33,17 +35,17 @@ mise run pr-comments ``` Address each unresolved comment before proceeding. The user will resolve comments on the Forge UI as they are addressed. -3. Always keep the zk cards up to date with any changes, and suggest new links to new cards whenever appropriate. Refer back to the zk docs often during the process of planning and making corrections to ensure accuracy, and if you make a mistake, figure out a way to guard against it using the zk. +4. Always keep the zk cards up to date with any changes, and suggest new links to new cards whenever appropriate. Refer back to the zk docs often during the process of planning and making corrections to ensure accuracy, and if you make a mistake, figure out a way to guard against it using the zk. -4. Use `Brewfile` and `mise.toml` to install tools needed on the development workstation (typically hostnamed "gilbert", username "eblume"). +5. Use `Brewfile` and `mise.toml` to install tools needed on the development workstation (typically hostnamed "gilbert", username "eblume"). -5. Services are hosted either on indri directly (via ansible) or in Kubernetes (via ArgoCD). See the "Service Deployment" section below for details. +6. Services are hosted either on indri directly (via ansible) or in Kubernetes (via ArgoCD). See the "Service Deployment" section below for details. -6. Try to always test changes before applying them. Use syntax checkers, do dry runs (`--check --diff`), run commands manually via `ssh indri 'some command'`, etc. +7. Try to always test changes before applying them. Use syntax checkers, do dry runs (`--check --diff`), run commands manually via `ssh indri 'some command'`, etc. -7. **Wait for user review before deploying.** After creating a PR, do not run deployment commands until the user has had a chance to review the changes. The user will indicate when they're ready to deploy. +8. **Wait for user review before deploying.** After creating a PR, do not run deployment commands until the user has had a chance to review the changes. The user will indicate when they're ready to deploy. -8. After deploying changes, try to verify the result. Use `mise run indri-services-check` to do a general service health check. +9. After deploying changes, try to verify the result. Use `mise run indri-services-check` to do a general service health check. ## Project Structure -- 2.50.1 (Apple Git-155) From 89e5edca5651245e7a839eed6765eeb1d6a2dc97 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 09:51:51 -0800 Subject: [PATCH 04/12] Fix immich-pg: remove shared_preload_libraries parameter CloudNativePG webhook rejects shared_preload_libraries as a "fixed" parameter - it's managed by the container image itself. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/databases/immich-pg.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/argocd/manifests/databases/immich-pg.yaml b/argocd/manifests/databases/immich-pg.yaml index d6e40d6..6f17935 100644 --- a/argocd/manifests/databases/immich-pg.yaml +++ b/argocd/manifests/databases/immich-pg.yaml @@ -39,8 +39,7 @@ spec: max_connections: "50" shared_buffers: "128MB" password_encryption: "scram-sha-256" - # pgvecto.rs settings - shared_preload_libraries: "vectors.so" + # Note: shared_preload_libraries is managed by the vectorchord image pg_hba: # Allow connections from k8s pods - host all all 0.0.0.0/0 scram-sha-256 -- 2.50.1 (Apple Git-155) From f1f45755547f5c71b19d43234c8c48c008497c4b Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 09:57:55 -0800 Subject: [PATCH 05/12] Fix immich-pg: use correct cloudnative-pgvecto.rs image The image is ghcr.io/tensorchord/cloudnative-pgvecto.rs:17.5, not tensorchord/cloudnative-vectorchord which doesn't exist. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/databases/README.md | 2 +- argocd/manifests/databases/immich-pg.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/argocd/manifests/databases/README.md b/argocd/manifests/databases/README.md index 02bd792..f6a54fb 100644 --- a/argocd/manifests/databases/README.md +++ b/argocd/manifests/databases/README.md @@ -115,7 +115,7 @@ PostgreSQL cluster for Immich with pgvecto.rs extension for AI-powered vector se - **Instances**: 1 (single-node for minikube) - **Storage**: 10Gi on `standard` storage class -- **Image**: `tensorchord/cloudnative-vectorchord:17-v0.4.0` (includes pgvecto.rs) +- **Image**: `ghcr.io/tensorchord/cloudnative-pgvecto.rs:17.5` (includes pgvecto.rs) - **Extensions**: `vectors`, `earthdistance` ### Connection diff --git a/argocd/manifests/databases/immich-pg.yaml b/argocd/manifests/databases/immich-pg.yaml index 6f17935..ffb4e55 100644 --- a/argocd/manifests/databases/immich-pg.yaml +++ b/argocd/manifests/databases/immich-pg.yaml @@ -8,8 +8,9 @@ metadata: namespace: databases spec: instances: 1 - # vectorchord image includes pgvecto.rs extension for vector similarity search - imageName: tensorchord/cloudnative-vectorchord:17-v0.4.0 + # cloudnative-pgvecto.rs image includes pgvecto.rs extension for vector similarity search + # See: https://github.com/tensorchord/cloudnative-pgvecto.rs + imageName: ghcr.io/tensorchord/cloudnative-pgvecto.rs:17.5 storage: size: 10Gi -- 2.50.1 (Apple Git-155) From 367b86710e80c238c626204582cd1b8f68032240 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:05:16 -0800 Subject: [PATCH 06/12] Fix immich-pg: use VectorChord with correct config - Use cloudnative-vectorchord:17.7-1.0.0 image (not pgvecto.rs) - Add shared_preload_libraries at postgresql level (not parameters) - Create vector, vchord, cube, earthdistance extensions Immich v1.133.0+ uses VectorChord as successor to pgvecto.rs. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/databases/README.md | 4 ++-- argocd/manifests/databases/immich-pg.yaml | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/argocd/manifests/databases/README.md b/argocd/manifests/databases/README.md index f6a54fb..a2799fd 100644 --- a/argocd/manifests/databases/README.md +++ b/argocd/manifests/databases/README.md @@ -115,8 +115,8 @@ PostgreSQL cluster for Immich with pgvecto.rs extension for AI-powered vector se - **Instances**: 1 (single-node for minikube) - **Storage**: 10Gi on `standard` storage class -- **Image**: `ghcr.io/tensorchord/cloudnative-pgvecto.rs:17.5` (includes pgvecto.rs) -- **Extensions**: `vectors`, `earthdistance` +- **Image**: `ghcr.io/tensorchord/cloudnative-vectorchord:17.7-1.0.0` (includes VectorChord) +- **Extensions**: `vector`, `vchord`, `cube`, `earthdistance` ### Connection diff --git a/argocd/manifests/databases/immich-pg.yaml b/argocd/manifests/databases/immich-pg.yaml index ffb4e55..713e0cb 100644 --- a/argocd/manifests/databases/immich-pg.yaml +++ b/argocd/manifests/databases/immich-pg.yaml @@ -1,5 +1,6 @@ # PostgreSQL Cluster for Immich -# Uses tensorchord/cloudnative-vectorchord for pgvecto.rs extension (required by Immich for AI features) +# Uses VectorChord (successor to pgvecto.rs) for AI-powered vector search +# See: https://github.com/immich-app/immich/discussions/9060 # Managed by CloudNativePG operator apiVersion: postgresql.cnpg.io/v1 kind: Cluster @@ -8,9 +9,9 @@ metadata: namespace: databases spec: instances: 1 - # cloudnative-pgvecto.rs image includes pgvecto.rs extension for vector similarity search - # See: https://github.com/tensorchord/cloudnative-pgvecto.rs - imageName: ghcr.io/tensorchord/cloudnative-pgvecto.rs:17.5 + # VectorChord image for PostgreSQL 17 + # See: https://github.com/tensorchord/VectorChord + imageName: ghcr.io/tensorchord/cloudnative-vectorchord:17.7-1.0.0 storage: size: 10Gi @@ -22,7 +23,10 @@ spec: database: immich owner: immich postInitSQL: - - CREATE EXTENSION IF NOT EXISTS vectors; + # Extensions required by Immich + - CREATE EXTENSION IF NOT EXISTS vector; + - CREATE EXTENSION IF NOT EXISTS vchord CASCADE; + - CREATE EXTENSION IF NOT EXISTS cube CASCADE; - CREATE EXTENSION IF NOT EXISTS earthdistance CASCADE; # Resource limits for minikube environment @@ -36,11 +40,13 @@ spec: # PostgreSQL configuration postgresql: + # VectorChord requires vchord.so in shared_preload_libraries + shared_preload_libraries: + - "vchord.so" parameters: max_connections: "50" shared_buffers: "128MB" password_encryption: "scram-sha-256" - # Note: shared_preload_libraries is managed by the vectorchord image pg_hba: # Allow connections from k8s pods - host all all 0.0.0.0/0 scram-sha-256 -- 2.50.1 (Apple Git-155) From 57b79baf269aaa1fbf5bf7be8c4996a98ed02084 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:09:19 -0800 Subject: [PATCH 07/12] Fix immich-storage: exclude kustomization.yaml from includes kustomization.yaml is not a K8s resource - ArgoCD was trying to apply it directly which fails. Only include the actual manifest files. Co-Authored-By: Claude Opus 4.5 --- argocd/apps/immich-storage.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/argocd/apps/immich-storage.yaml b/argocd/apps/immich-storage.yaml index 718b4c7..7227681 100644 --- a/argocd/apps/immich-storage.yaml +++ b/argocd/apps/immich-storage.yaml @@ -14,9 +14,9 @@ spec: repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git targetRevision: main path: argocd/manifests/immich - # Only deploy kustomization resources (PV/PVC/Ingress), not values.yaml + # Only deploy storage resources (PV/PVC/Ingress), not Helm values.yaml directory: - include: "{kustomization.yaml,pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}" + include: "{pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}" destination: server: https://kubernetes.default.svc namespace: immich -- 2.50.1 (Apple Git-155) From c0333458fa6968b774b2d27fe891952067b28115 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:11:36 -0800 Subject: [PATCH 08/12] Fix immich values.yaml: correct persistence structure - Add 'data:' key under valkey.persistence - Add 'enabled: true' for machine-learning cache Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/immich/values.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/argocd/manifests/immich/values.yaml b/argocd/manifests/immich/values.yaml index 16c9c49..90ae4fb 100644 --- a/argocd/manifests/immich/values.yaml +++ b/argocd/manifests/immich/values.yaml @@ -2,9 +2,9 @@ # Chart: https://github.com/immich-app/immich-charts # # Immich requires: -# - PostgreSQL with pgvecto.rs extension (separate immich-pg cluster) +# - PostgreSQL with VectorChord extension (separate immich-pg cluster) # - Redis/Valkey (included in chart) -# - Library storage PVC (photos directory from indri) +# - Library storage PVC (photos directory from sifaka NFS) # Image version - explicitly set to avoid drift image: @@ -36,6 +36,7 @@ machine-learning: enabled: true persistence: cache: + enabled: true type: persistentVolumeClaim accessMode: ReadWriteOnce size: 10Gi @@ -51,7 +52,10 @@ machine-learning: valkey: enabled: true persistence: - size: 1Gi + data: + enabled: true + type: emptyDir + size: 1Gi # Server resources for minikube server: -- 2.50.1 (Apple Git-155) From 4832044a65a8ed0c0586e690aab1f384a72fe776 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:12:08 -0800 Subject: [PATCH 09/12] Temp: point immich values at feature branch for testing Will reset to main after PR merge. Co-Authored-By: Claude Opus 4.5 --- argocd/apps/immich.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/argocd/apps/immich.yaml b/argocd/apps/immich.yaml index 54d0789..137d8d0 100644 --- a/argocd/apps/immich.yaml +++ b/argocd/apps/immich.yaml @@ -26,9 +26,9 @@ spec: releaseName: immich valueFiles: - $values/argocd/manifests/immich/values.yaml - # Values from our git repo + # Values from our git repo (use feature branch for testing, reset to main after merge) - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git - targetRevision: main + targetRevision: feature/immich ref: values destination: server: https://kubernetes.default.svc -- 2.50.1 (Apple Git-155) From 28187588174fbe5e8d5e3d4e8079c7e4dec9d533 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:17:14 -0800 Subject: [PATCH 10/12] Fix immich values: add DB env vars under controllers.main The bjw-s common library expects env vars under controllers.main.containers.main.env, not at the top level. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/immich/values.yaml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/argocd/manifests/immich/values.yaml b/argocd/manifests/immich/values.yaml index 90ae4fb..3c09cfe 100644 --- a/argocd/manifests/immich/values.yaml +++ b/argocd/manifests/immich/values.yaml @@ -10,20 +10,25 @@ image: tag: v1.125.7 -# Environment variables for all components +# Shared environment variables for all controllers env: TZ: "America/Los_Angeles" - # Database connection - uses immich-pg cluster - DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local" - DB_PORT: "5432" - DB_DATABASE_NAME: "immich" - DB_USERNAME: "immich" - # Password injected from secret - DB_PASSWORD: - valueFrom: - secretKeyRef: - name: immich-db - key: password + +# Database connection settings - applied to server and microservices +controllers: + main: + containers: + main: + env: + DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local" + DB_PORT: "5432" + DB_DATABASE_NAME: "immich" + DB_USERNAME: "immich" + DB_PASSWORD: + valueFrom: + secretKeyRef: + name: immich-db + key: password # Immich server configuration immich: -- 2.50.1 (Apple Git-155) From 18f56a8890b54a90996524cd614393d1865bb974 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:40:28 -0800 Subject: [PATCH 11/12] Update Immich to chart v0.10.3 and app v2.4.1 - Update Helm chart from immich-0.10.0 to immich-0.10.3 - Update Immich to v2.4.1 (latest) - Fix image tag structure for bjw-s common library Co-Authored-By: Claude Opus 4.5 --- argocd/apps/immich.yaml | 2 +- argocd/manifests/immich/values.yaml | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/argocd/apps/immich.yaml b/argocd/apps/immich.yaml index 137d8d0..b0597c3 100644 --- a/argocd/apps/immich.yaml +++ b/argocd/apps/immich.yaml @@ -20,7 +20,7 @@ spec: sources: # Helm chart from forge mirror (SSH via egress) - repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/immich-charts.git - targetRevision: immich-0.10.0 + targetRevision: immich-0.10.3 path: charts/immich helm: releaseName: immich diff --git a/argocd/manifests/immich/values.yaml b/argocd/manifests/immich/values.yaml index 3c09cfe..0cb4ecf 100644 --- a/argocd/manifests/immich/values.yaml +++ b/argocd/manifests/immich/values.yaml @@ -1,24 +1,22 @@ # Immich Helm values for blumeops -# Chart: https://github.com/immich-app/immich-charts +# Chart: https://github.com/immich-app/immich-charts (v0.10.3) # # Immich requires: # - PostgreSQL with VectorChord extension (separate immich-pg cluster) # - Redis/Valkey (included in chart) # - Library storage PVC (photos directory from sifaka NFS) -# Image version - explicitly set to avoid drift -image: - tag: v1.125.7 - -# Shared environment variables for all controllers +# Shared environment variables env: TZ: "America/Los_Angeles" -# Database connection settings - applied to server and microservices +# Shared controller settings - image tag and DB connection controllers: main: containers: main: + image: + tag: v2.4.1 env: DB_HOSTNAME: "immich-pg-rw.databases.svc.cluster.local" DB_PORT: "5432" -- 2.50.1 (Apple Git-155) From 6b9b8bcc6f8aa919cd399176e94399d111965d81 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 26 Jan 2026 10:47:20 -0800 Subject: [PATCH 12/12] Use VectorChord 0.5.0 for Immich compatibility Immich v2.4.1 requires VectorChord >=0.3 <0.6, but the 17.7-1.0.0 image has VectorChord 1.0.0. Use the 17-0.5.0 tag instead. Co-Authored-By: Claude Opus 4.5 --- argocd/manifests/databases/README.md | 4 ++-- argocd/manifests/databases/immich-pg.yaml | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/argocd/manifests/databases/README.md b/argocd/manifests/databases/README.md index a2799fd..b5794d2 100644 --- a/argocd/manifests/databases/README.md +++ b/argocd/manifests/databases/README.md @@ -109,13 +109,13 @@ from brew PostgreSQL (indri) to this k8s cluster. At that point: ## immich-pg -PostgreSQL cluster for Immich with pgvecto.rs extension for AI-powered vector search. +PostgreSQL cluster for Immich with VectorChord extension for AI-powered vector search. ### Configuration - **Instances**: 1 (single-node for minikube) - **Storage**: 10Gi on `standard` storage class -- **Image**: `ghcr.io/tensorchord/cloudnative-vectorchord:17.7-1.0.0` (includes VectorChord) +- **Image**: `ghcr.io/tensorchord/cloudnative-vectorchord:17-0.5.0` (VectorChord 0.5.0 for Immich compatibility) - **Extensions**: `vector`, `vchord`, `cube`, `earthdistance` ### Connection diff --git a/argocd/manifests/databases/immich-pg.yaml b/argocd/manifests/databases/immich-pg.yaml index 713e0cb..ec60387 100644 --- a/argocd/manifests/databases/immich-pg.yaml +++ b/argocd/manifests/databases/immich-pg.yaml @@ -9,9 +9,10 @@ metadata: namespace: databases spec: instances: 1 - # VectorChord image for PostgreSQL 17 + # VectorChord image for PostgreSQL 17 with VectorChord 0.5.0 + # Immich v2.4.1 requires VectorChord >=0.3 <0.6 # See: https://github.com/tensorchord/VectorChord - imageName: ghcr.io/tensorchord/cloudnative-vectorchord:17.7-1.0.0 + imageName: ghcr.io/tensorchord/cloudnative-vectorchord:17-0.5.0 storage: size: 10Gi -- 2.50.1 (Apple Git-155)