296 lines
9.6 KiB
YAML
296 lines
9.6 KiB
YAML
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
|
|
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. Same
|
|
# pattern as clusters/base/apps/nfs-photo.
|
|
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
|
|
tag: release
|
|
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
|
|
# one-shot migration copy from the old NAS-hosted docker install
|
|
# into this install's own volumes (see "persistence" below).
|
|
# rsync is idempotent, so a pod restart mid-copy just resumes
|
|
# cleanly; once done it sleeps forever instead of exiting, so the
|
|
# container doesn't restart-loop and the status page stays up.
|
|
# (starts after all initContainers - fix-permissions and the
|
|
# mergerfs sidecar's startupProbe - complete, same as "app")
|
|
rsync:
|
|
image:
|
|
repository: python
|
|
tag: 3.13-alpine
|
|
command:
|
|
- sh
|
|
- -c
|
|
- apk add --no-cache rsync >/dev/null && python3 /app/rsync.py
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
memory: 1Gi
|
|
rsync-web:
|
|
image:
|
|
repository: nginx
|
|
tag: 1.27-alpine
|
|
probes:
|
|
# explicit port: this controller has two services (app:2283,
|
|
# rsync-web:80) and probe auto-detection picks the
|
|
# controller's primary service port rather than this
|
|
# container's own - without this it checks the wrong port.
|
|
liveness:
|
|
enabled: true
|
|
port: 80
|
|
readiness:
|
|
enabled: true
|
|
port: 80
|
|
resources:
|
|
requests:
|
|
cpu: 10m
|
|
memory: 16Mi
|
|
limits:
|
|
memory: 64Mi
|
|
|
|
service:
|
|
app:
|
|
controller: server
|
|
ports:
|
|
http:
|
|
port: 2283
|
|
rsync-web:
|
|
controller: server
|
|
ports:
|
|
http:
|
|
port: 80
|
|
|
|
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
|
|
rsync-web:
|
|
className: traefik
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
|
hosts:
|
|
- host: rsync.photo.aggtaa.com
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
service:
|
|
identifier: rsync-web
|
|
port: http
|
|
tls:
|
|
- secretName: immich-rsync-tls
|
|
hosts:
|
|
- rsync.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
|
|
rsync:
|
|
- 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
|
|
rsync:
|
|
- 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
|
|
# everything immich keeps outside "library" (backups, encoded-video,
|
|
# profile, thumbs, upload) - plain volume, no mergerfs involved.
|
|
other:
|
|
type: persistentVolumeClaim
|
|
accessMode: ReadWriteOnce
|
|
size: 100Gi
|
|
storageClass: longhorn-single-replica
|
|
retain: true
|
|
advancedMounts:
|
|
server:
|
|
app:
|
|
- path: /data
|
|
rsync:
|
|
- path: /data
|
|
nas-source:
|
|
type: persistentVolumeClaim
|
|
existingClaim: immich-nas-source
|
|
advancedMounts:
|
|
server:
|
|
rsync:
|
|
- path: /source
|
|
readOnly: true
|
|
rsync-script:
|
|
type: configMap
|
|
name: immich-rsync-script
|
|
advancedMounts:
|
|
server:
|
|
rsync:
|
|
- path: /app/rsync.py
|
|
subPath: rsync.py
|
|
readOnly: true
|
|
rsync-www:
|
|
type: emptyDir
|
|
advancedMounts:
|
|
server:
|
|
rsync:
|
|
- path: /www
|
|
rsync-web:
|
|
- path: /usr/share/nginx/html
|