Add Immich photo management + migrate forge URLs (#62)
## Summary - Migrate all ArgoCD app repo URLs from `indri.tail8d86e.ts.net:2200` to `forge.ops.eblu.me:2222` - Add Immich self-hosted photo management service with: - Helm chart deployment via ArgoCD - PostgreSQL cluster with pgvecto.rs for AI vector search (immich-pg) - NFS storage on sifaka for photo library (2Ti) - Tailscale Ingress + Caddy proxy for `photos.ops.eblu.me` - Machine learning service for face/object recognition ## Deployment and Testing - [x] Update ArgoCD repo-creds-forge secret with new URL (one-time manual step) - [ ] Sync `apps` to pick up new applications - [ ] Sync all existing apps to verify new forge URL works - [ ] Sync `blumeops-pg` to deploy immich-pg cluster - [ ] Wait for immich-pg to be healthy - [ ] Create immich-db secret from auto-generated password - [ ] Sync `immich-storage` (PV, PVC, Ingress) - [ ] Sync `immich` (Helm chart) - [ ] Run `mise run provision-indri -- --tags caddy` to add photos.ops.eblu.me - [ ] Verify Immich UI is accessible 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/62
This commit is contained in:
parent
c8b655f177
commit
8621996343
33 changed files with 451 additions and 34 deletions
18
CLAUDE.md
18
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 <pr_number>
|
|||
```
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
25
argocd/apps/immich-storage.yaml
Normal file
25
argocd/apps/immich-storage.yaml
Normal file
|
|
@ -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 storage resources (PV/PVC/Ingress), not Helm values.yaml
|
||||
directory:
|
||||
include: "{pv-nfs.yaml,pvc.yaml,ingress-tailscale.yaml}"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: immich
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
38
argocd/apps/immich.yaml
Normal file
38
argocd/apps/immich.yaml
Normal file
|
|
@ -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.3
|
||||
path: charts/immich
|
||||
helm:
|
||||
releaseName: immich
|
||||
valueFiles:
|
||||
- $values/argocd/manifests/immich/values.yaml
|
||||
# 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: feature/immich
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: immich
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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==
|
||||
|
|
|
|||
|
|
@ -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,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 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-0.5.0` (VectorChord 0.5.0 for Immich compatibility)
|
||||
- **Extensions**: `vector`, `vchord`, `cube`, `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
|
||||
```
|
||||
|
|
|
|||
54
argocd/manifests/databases/immich-pg.yaml
Normal file
54
argocd/manifests/databases/immich-pg.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# PostgreSQL Cluster for Immich
|
||||
# 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
|
||||
metadata:
|
||||
name: immich-pg
|
||||
namespace: databases
|
||||
spec:
|
||||
instances: 1
|
||||
# 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-0.5.0
|
||||
|
||||
storage:
|
||||
size: 10Gi
|
||||
storageClass: standard
|
||||
|
||||
# Bootstrap creates initial database and owner
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: immich
|
||||
owner: immich
|
||||
postInitSQL:
|
||||
# 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
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
|
||||
# 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"
|
||||
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
|
||||
|
|
@ -5,5 +5,6 @@ namespace: databases
|
|||
|
||||
resources:
|
||||
- blumeops-pg.yaml
|
||||
- immich-pg.yaml
|
||||
- service-tailscale.yaml
|
||||
- service-metrics-tailscale.yaml
|
||||
|
|
|
|||
98
argocd/manifests/immich/README.md
Normal file
98
argocd/manifests/immich/README.md
Normal file
|
|
@ -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
|
||||
```
|
||||
26
argocd/manifests/immich/ingress-tailscale.yaml
Normal file
26
argocd/manifests/immich/ingress-tailscale.yaml
Normal file
|
|
@ -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
|
||||
11
argocd/manifests/immich/kustomization.yaml
Normal file
11
argocd/manifests/immich/kustomization.yaml
Normal file
|
|
@ -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
|
||||
22
argocd/manifests/immich/pv-nfs.yaml
Normal file
22
argocd/manifests/immich/pv-nfs.yaml
Normal file
|
|
@ -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
|
||||
15
argocd/manifests/immich/pvc.yaml
Normal file
15
argocd/manifests/immich/pvc.yaml
Normal file
|
|
@ -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
|
||||
12
argocd/manifests/immich/secret-db.yaml.tpl
Normal file
12
argocd/manifests/immich/secret-db.yaml.tpl
Normal file
|
|
@ -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 }}"
|
||||
71
argocd/manifests/immich/values.yaml
Normal file
71
argocd/manifests/immich/values.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Immich Helm values for blumeops
|
||||
# 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)
|
||||
|
||||
# Shared environment variables
|
||||
env:
|
||||
TZ: "America/Los_Angeles"
|
||||
|
||||
# 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"
|
||||
DB_DATABASE_NAME: "immich"
|
||||
DB_USERNAME: "immich"
|
||||
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:
|
||||
enabled: true
|
||||
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:
|
||||
data:
|
||||
enabled: true
|
||||
type: emptyDir
|
||||
size: 1Gi
|
||||
|
||||
# Server resources for minikube
|
||||
server:
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
Loading…
Add table
Add a link
Reference in a new issue