diff --git a/hosts/sire/default.nix b/hosts/sire/default.nix index 1dd172c..51af57a 100644 --- a/hosts/sire/default.nix +++ b/hosts/sire/default.nix @@ -129,8 +129,10 @@ // mkMicrovm "paperless" { enablePaperlessDataset = true; } + // mkMicrovm "immich" { + enableStorageDataset = true; + } #// mkMicrovm "minecraft" - #// mkMicrovm "immich" #// mkMicrovm "firefly" #// mkMicrovm "fasten-health" ); diff --git a/hosts/sire/guests/immich.nix b/hosts/sire/guests/immich.nix index c645955..0045868 100644 --- a/hosts/sire/guests/immich.nix +++ b/hosts/sire/guests/immich.nix @@ -1,19 +1,54 @@ { + pkgs, config, nodes, ... }: let sentinelCfg = nodes.sentinel.config; immichDomain = "immich.${sentinelCfg.repo.secrets.local.personalDomain}"; + + ipImmichMachineLearning = "10.89.0.10"; + ipImmichMicroservices = "10.89.0.11"; + ipImmichPostgres = "10.89.0.12"; + ipImmichRedis = "10.89.0.13"; + ipImmichServer = "10.89.0.14"; + + version = "v1.93.3"; + environment = { + DB_DATABASE_NAME = "immich"; + DB_HOSTNAME = ipImmichPostgres; + DB_PASSWORD_FILE = config.age.secrets.postgres_password.path; + DB_USERNAME = "postgres"; + IMMICH_VERSION = "${version}"; + UPLOAD_LOCATION = upload_folder; + IMMICH_SERVER_URL = "http://${ipImmichServer}:3001/"; + IMMICH_MACHINE_LEARNING_URL = "http://${ipImmichMachineLearning}:3003"; + REDIS_HOSTNAME = ipImmichRedis; + }; + + upload_folder = "/storage/immich"; + pgdata_folder = "/persist/immich/pgdata"; + model_folder = "/state/immich/modeldata"; + + serviceConfig = { + serviceConfig.Restart = "always"; + after = ["podman-network-immich-default.service"]; + requires = ["podman-network-immich-default.service"]; + partOf = ["podman-compose-immich-root.target"]; + wantedBy = ["podman-compose-immich-root.target"]; + }; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.immich.web_port]; + microvm.mem = 1024 * 8; + microvm.vcpu = 20; + + meta.wireguard-proxy.sentinel.allowedTCPPorts = [2283]; nodes.sentinel = { networking.providedDomains.immich = immichDomain; services.nginx = { upstreams.immich = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.immich.settings.bind_port}" = {}; + servers."${config.meta.wireguard.proxy-sentinel.ipv4}:2283" = {}; extraConfig = '' zone immich 64k; keepalive 2; @@ -32,9 +67,156 @@ in { }; }; - services.immich = { - enable = true; + systemd.tmpfiles.settings = { + "10-immich" = { + ${upload_folder}.d = { + mode = "0770"; + }; + ${pgdata_folder}.d = { + mode = "0770"; + }; + ${model_folder}.d = { + mode = "0770"; + }; + }; }; - systemd.services.grafana.serviceConfig.RestartSec = "600"; # Retry every 10 minutes + age.secrets.postgres_password.generator.script = "alnum"; + + # Runtime + virtualisation.podman = { + enable = true; + autoPrune.enable = true; + dockerCompat = true; + }; + virtualisation.oci-containers.backend = "podman"; + + # Containers + virtualisation.oci-containers.containers."immich_machine_learning" = { + image = "ghcr.io/immich-app/immich-machine-learning:${version}"; + inherit environment; + volumes = [ + "${model_folder}:/cache:rw" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich-machine-learning" + "--network=immich-default" + "--ip=${ipImmichMachineLearning}" + ]; + }; + systemd.services."podman-immich_machine_learning" = serviceConfig; + virtualisation.oci-containers.containers."immich_microservices" = { + image = "ghcr.io/immich-app/immich-server:${version}"; + inherit environment; + volumes = [ + "${config.age.secrets.postgres_password.path}:${config.age.secrets.postgres_password.path}:ro" + "/etc/localtime:/etc/localtime:ro" + "${upload_folder}:/usr/src/app/upload:rw" + ]; + cmd = ["start.sh" "microservices"]; + dependsOn = [ + "immich_postgres" + "immich_redis" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich-microservices" + "--network=immich-default" + "--ip=${ipImmichMicroservices}" + ]; + }; + systemd.services."podman-immich_microservices" = + serviceConfig + // { + unitConfig.UpheldBy = [ + "podman-immich_postgres.service" + "podman-immich_redis.service" + ]; + }; + virtualisation.oci-containers.containers."immich_postgres" = { + image = "tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee"; + environment = { + POSTGRES_DB = environment.DB_DATABASE_NAME; + POSTGRES_PASSWORD_FILE = environment.DB_PASSWORD_FILE; + POSTGRES_USER = environment.DB_USERNAME; + }; + volumes = [ + "${config.age.secrets.postgres_password.path}:${config.age.secrets.postgres_password.path}:ro" + "${pgdata_folder}:/var/lib/postgresql/data:rw" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich_postgres" + "--network=immich-default" + "--ip=${ipImmichPostgres}" + ]; + }; + systemd.services."podman-immich_postgres" = serviceConfig; + virtualisation.oci-containers.containers."immich_redis" = { + image = "redis:6.2-alpine@sha256:c5a607fb6e1bb15d32bbcf14db22787d19e428d59e31a5da67511b49bb0f1ccc"; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich_redis" + "--network=immich-default" + "--ip=${ipImmichRedis}" + ]; + }; + systemd.services."podman-immich_redis" = serviceConfig; + virtualisation.oci-containers.containers."immich_server" = { + image = "ghcr.io/immich-app/immich-server:${version}"; + inherit environment; + volumes = [ + "${config.age.secrets.postgres_password.path}:${config.age.secrets.postgres_password.path}:ro" + "/etc/localtime:/etc/localtime:ro" + "${upload_folder}:/usr/src/app/upload:rw" + ]; + ports = [ + "2283:3001/tcp" + ]; + cmd = ["start.sh" "immich"]; + dependsOn = [ + "immich_postgres" + "immich_redis" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=immich-server" + "--network=immich-default" + "--ip=${ipImmichServer}" + ]; + }; + systemd.services."podman-immich_server" = + serviceConfig + // { + unitConfig.UpheldBy = [ + "podman-immich_postgres.service" + "podman-immich_redis.service" + ]; + }; + + # Networks + systemd.services."podman-network-immich-default" = { + path = [pkgs.podman]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "${pkgs.podman}/bin/podman network rm -f immich-default"; + }; + script = '' + podman network inspect immich-default || podman network create immich-default --opt isolate=true --subnet=10.89.0.0/24 + ''; + partOf = ["podman-compose-immich-root.target"]; + wantedBy = ["podman-compose-immich-root.target"]; + }; + + # Root service + # When started, this will automatically create all resources and start + # the containers. When stopped, this will teardown all resources. + systemd.targets."podman-compose-immich-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = ["multi-user.target"]; + }; } diff --git a/hosts/sire/guests/paperless.nix b/hosts/sire/guests/paperless.nix index 72e4d7f..ecdc90f 100644 --- a/hosts/sire/guests/paperless.nix +++ b/hosts/sire/guests/paperless.nix @@ -115,6 +115,8 @@ in { before = ["restic-backups-storage-box-dusk.service"]; }; + # Needed so we don't run out of tmpfs space for large backups. + # Technically this could be cleared each boot but whatever. environment.persistence."/state".directories = [ { directory = paperlessBackupDir; diff --git a/hosts/sire/secrets/immich/host.pub b/hosts/sire/secrets/immich/host.pub new file mode 100644 index 0000000..9c7563b --- /dev/null +++ b/hosts/sire/secrets/immich/host.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKE+geXK2RVVNwZVoYOuX7pW+6mbgCa9SIghJCdHmbSB diff --git a/hosts/ward/guests/kanidm.nix b/hosts/ward/guests/kanidm.nix index cf52f31..0ea07ce 100644 --- a/hosts/ward/guests/kanidm.nix +++ b/hosts/ward/guests/kanidm.nix @@ -148,6 +148,7 @@ in { groups.web-sentinel = {}; groups."web-sentinel.adguardhome" = {}; groups."web-sentinel.influxdb" = {}; + groups."web-sentinel.immich" = {}; systems.oauth2.web-sentinel = { displayName = "Web Sentinel"; originUrl = "https://oauth2.${personalDomain}"; @@ -156,6 +157,7 @@ in { supplementaryScopeMaps = { "web-sentinel.adguardhome" = ["access_adguardhome"]; "web-sentinel.influxdb" = ["access_influxdb"]; + "web-sentinel.immich" = ["access_immich"]; }; }; }; diff --git a/hosts/ward/guests/vaultwarden.nix b/hosts/ward/guests/vaultwarden.nix index adb4560..15706c8 100644 --- a/hosts/ward/guests/vaultwarden.nix +++ b/hosts/ward/guests/vaultwarden.nix @@ -84,6 +84,8 @@ in { RestartSec = "600"; # Retry every 10 minutes }; + # Needed so we don't run out of tmpfs space for large backups. + # Technically this could be cleared each boot but whatever. environment.persistence."/state".directories = [ { directory = config.services.vaultwarden.backupDir; diff --git a/modules/config/users.nix b/modules/config/users.nix index af179af..df68bed 100644 --- a/modules/config/users.nix +++ b/modules/config/users.nix @@ -29,5 +29,6 @@ msr = uidGid 980; fwupd-refresh = uidGid 979; radicale = uidGid 978; + podman = uidGid 977; }; } diff --git a/secrets/generated/sentinel/loki-basic-auth-hashes.age b/secrets/generated/sentinel/loki-basic-auth-hashes.age index 7f63ee8..d641be9 100644 Binary files a/secrets/generated/sentinel/loki-basic-auth-hashes.age and b/secrets/generated/sentinel/loki-basic-auth-hashes.age differ diff --git a/secrets/generated/sire-immich/postgres_password.age b/secrets/generated/sire-immich/postgres_password.age new file mode 100644 index 0000000..414a5f7 --- /dev/null +++ b/secrets/generated/sire-immich/postgres_password.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> X25519 lxcs64hgn5qiaMjFfFKIdS7a4DYzsSIp2rYWu9Sxg1o +DWoXP55lOfYh26BQyMTWtJpZXD6RAYYT4ArCNy2RmPQ +-> piv-p256 xqSe8Q ApSmxT6ujEnuH3c0Avr7g/DGdbSf906OFhOiMvi9ONmt +UmLDT5AJkIc8GgLgaVgS6KWk7d0rf/P29V4l2JU1lgI +-> )-grease p4kI HFcVp +dPSUp6CWnLW6gpi6a2g+mWKIZ+OEYiRvTc6YcSLY +--- 7pImagsw0LryRQOHCqzwJxCQNyoESpJsrztMaUqrwPg +NbǬ Е7MNPr>+8zJĒo\LWsڳz0;ipRiK.wvK \ No newline at end of file diff --git a/secrets/generated/sire-immich/promtail-loki-basic-auth-password.age b/secrets/generated/sire-immich/promtail-loki-basic-auth-password.age new file mode 100644 index 0000000..fe40ad7 --- /dev/null +++ b/secrets/generated/sire-immich/promtail-loki-basic-auth-password.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> X25519 ks4qXV3qPJeADOguclgvlOS/81Wd7fgcVKdlhg3FgzI +PLKauYQ+46t8RSPD73M18RUOL/z4SjikoNsDbL3X/cc +-> piv-p256 xqSe8Q A2xu0dbsgARRnDBak5Cd6YG3JF5zOuZlqzPdbg9lgP2x ++tbb9URbCrPqIWOp0O26ptbXRUh/6koKhdONNz5p494 +-> Yy),Z@(\-grease %R <|> I]- G +1t94Jtghka4vBg2VMzDqPO2qwzpovhNT0W+fe0K82obdDaajCa3pfiFz4Nrfbm+7 +hsJyVaViVGio7BVDPso +--- 8JU+s4Yn2DCmSHfRx4EPn8pa8RRWTn8BfEkCLhVn+DI +~loLNu앵 zt5žcW0g8,{5tK>mH? \ No newline at end of file diff --git a/secrets/generated/sire-immich/telegraf-influxdb-token.age b/secrets/generated/sire-immich/telegraf-influxdb-token.age new file mode 100644 index 0000000..3f3dfa9 --- /dev/null +++ b/secrets/generated/sire-immich/telegraf-influxdb-token.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> X25519 SQp3X/pCRAK0LDysYw/iO5XbD5DknZDqrfYVsF4Wryc +MToFiMkEqhq0uJlDE3peo/1r2eG8SGfYj6XZDcHJHsE +-> piv-p256 xqSe8Q AjwjDiFrzD8Zvc7Xx9fA7WcxG+nsF2mUgmMlFswinh/l +ONrzVVrUpWWfy4on9Vouz3D3VxSYu4Rb2+DnRSyBtWU +-> J)C?2Kps-grease ", ++w4vydh7txIfUxLLwNztvo6nDva4zEfkJJZn2Kbh1agtjfiaqVh9hznQyHjY+bKX +TTro7bJoHrdBOj7RX6CV1BO0w2ToeQ1XnkhZv/8GE2xm3aehsSEFt5AqU8f2ucrt + +--- ISE4KQBHYV8vazUMaRG9y8BTag3zVtMN32n3lwiTp48 + dѲTAvuW 9lPFg. T FS!5ۤTo LvΦ,2TH7a!lj*GG \ No newline at end of file diff --git a/secrets/global.nix.age b/secrets/global.nix.age index 4cc52dd..ade9174 100644 Binary files a/secrets/global.nix.age and b/secrets/global.nix.age differ diff --git a/secrets/wireguard/proxy-sentinel/keys/sire-immich.age b/secrets/wireguard/proxy-sentinel/keys/sire-immich.age new file mode 100644 index 0000000..7b614db Binary files /dev/null and b/secrets/wireguard/proxy-sentinel/keys/sire-immich.age differ diff --git a/secrets/wireguard/proxy-sentinel/keys/sire-immich.pub b/secrets/wireguard/proxy-sentinel/keys/sire-immich.pub new file mode 100644 index 0000000..1c369d4 --- /dev/null +++ b/secrets/wireguard/proxy-sentinel/keys/sire-immich.pub @@ -0,0 +1 @@ +slaNaddkDDEeC9Y69VTKqAhYJcjc2u0UbbwxNzaZNR4= diff --git a/secrets/wireguard/proxy-sentinel/psks/sentinel+sire-immich.age b/secrets/wireguard/proxy-sentinel/psks/sentinel+sire-immich.age new file mode 100644 index 0000000..31a4330 --- /dev/null +++ b/secrets/wireguard/proxy-sentinel/psks/sentinel+sire-immich.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> X25519 YIhkI6nWo8Ud/fjZVBXO5g0NOmaXVbmLiFvSLJ/cdFg +4RZpFKtM40Q81tSAIq1xUjMy4GmGeIZ+335KiFf28M8 +-> piv-p256 xqSe8Q AuTq/W1xNTEYrBAbLgffA95slATEeMRMUIwuMyicmwcA +QlR1jm1BC/MDfSF82oJibcS5huJx0lRtdbO/dHfIkKE +-> XNC"-grease ~K@0bKg{+/0:\Ðs-5jg J =p6C?迯 \ No newline at end of file