From fb3ce760dc34f2c8f53335135526a991c40cc796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Gro=C3=9Fmann?= Date: Sat, 20 Jan 2024 21:07:00 +0100 Subject: [PATCH] feat: started immich impl --- hosts/elisabeth/guests.nix | 25 ++ .../immich/generated/postgrespasswd.age | 18 ++ .../secrets/immich/generated/redispasswd.age | 16 ++ modules/services/immich.nix | 192 ++++++++++++++ modules/services/paperless.nix | 11 +- modules/services/vaultwarden.nix | 10 +- secrets/secrets.nix.age | Bin 5093 -> 5036 bytes ßa | 239 ++++++++++++++++++ 8 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 hosts/elisabeth/secrets/immich/generated/postgrespasswd.age create mode 100644 hosts/elisabeth/secrets/immich/generated/redispasswd.age create mode 100644 modules/services/immich.nix create mode 100644 ßa diff --git a/hosts/elisabeth/guests.nix b/hosts/elisabeth/guests.nix index 4291515..c032678 100644 --- a/hosts/elisabeth/guests.nix +++ b/hosts/elisabeth/guests.nix @@ -12,6 +12,7 @@ giteadomain = "git.${config.secrets.secrets.global.domains.web}"; vaultwardendomain = "pw.${config.secrets.secrets.global.domains.web}"; paperlessdomain = "ppl.${config.secrets.secrets.global.domains.web}"; + immichdomain = "immich.${config.secrets.secrets.global.domains.web}"; ipOf = hostName: lib.net.cidr.host config.secrets.secrets.global.net.ips."${config.guests.${hostName}.nodeName}" config.secrets.secrets.global.net.privateSubnet; in { services.nginx = { @@ -58,6 +59,27 @@ in { ''; }; + upstreams.immich = { + servers."${ipOf "immich"}:3000" = {}; + + extraConfig = '' + zone gitea 64k ; + keepalive 5 ; + ''; + }; + virtualHosts.${immichdomain} = { + forceSSL = true; + useACMEHost = "web"; + locations."/" = { + proxyPass = "http://immich"; + proxyWebsockets = true; + }; + extraConfig = '' + client_max_body_size 1G ; + deny all + ''; + }; + upstreams.adguardhome = { servers."${ipOf "adguardhome"}:3000" = {}; @@ -211,6 +233,9 @@ in { // mkContainer "gitea" { enablePanzer = true; } + // mkContainer "immich" { + enablePanzer = true; + } // mkContainer "samba" { enablePanzer = true; enableRenaultFT = true; diff --git a/hosts/elisabeth/secrets/immich/generated/postgrespasswd.age b/hosts/elisabeth/secrets/immich/generated/postgrespasswd.age new file mode 100644 index 0000000..a33a2ca --- /dev/null +++ b/hosts/elisabeth/secrets/immich/generated/postgrespasswd.age @@ -0,0 +1,18 @@ +age-encryption.org/v1 +-> X25519 uQckpLpQc6L2KMOBzjC3lgcRAmXtNHERJBcD33tM0jI +qc++8oENAWvSYUhsNvbSefoJ6rd56WRlApfRgvHRuFg +-> piv-p256 XTQkUA AjIw8UqwQZEUr/EPpT8CWI2P5mFjo8fepPh80tEGF/Yr +4LhH/+lZDxTk/V8F+uWu3652JwTd7YkEbuLj20VkNJQ +-> piv-p256 ZFgiIw A6gCOLgGeT61yRRZisfi6v0wvmrAz2vza52E/JTviyQO +sezvuVxnqor+WwIJYHASFDJnPquOe43gZb3gKulAEzA +-> piv-p256 5vmPtQ A90ZF04slB5oVa1wT5gQgyHJMJBws/pC5D5JK3vvUs4s +hHhmKV7dfhc/wTr910/viLlljou5zhG/xKDyetFBq0Y +-> piv-p256 ZFgiIw AjA3IHUjqd5f2oazyGV2X5QIVGyRLdtTqmGvkRP2MEjz +oO++dTfOmkLU8xyTY70mvOnX3V3eWZvJtKLzwYKE2w8 +-> a1e"-grease 6fF8K3QU +UnlhF8+Uqi3r08EtXEFJOooq0qKpKXj5lKkBx5UdIfRzL/hQ3Fa3FOrqnv7c9kbl +ORCZ0XkX3oIiOW7AkJ4kQGwD +--- 4D7N/t7MmD+b0jtnfChSdW5yo9OoO/djgvVNL2ulhIQ +$&1+vi\τb%/>";mS+q +ͼ-YN +0vî[ÎLe9Z! \ No newline at end of file diff --git a/hosts/elisabeth/secrets/immich/generated/redispasswd.age b/hosts/elisabeth/secrets/immich/generated/redispasswd.age new file mode 100644 index 0000000..c9ce007 --- /dev/null +++ b/hosts/elisabeth/secrets/immich/generated/redispasswd.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> X25519 BkYxjAbKQaQgVcGvDxk/ri/zZM0sS0FMqkfpV4RfvQY +E/uSI6TbDna6eUBrxCMkn60OdG6PKbOKU2z4AhiOGG8 +-> piv-p256 XTQkUA Agg7UC1VD2/tVEos1xf01dYoEU7G/MDawQAPKKLV1kkd +Eiq7oNJb24i8WVimVKmZJT7d4Y7xLun7Bd0nFLx+y6c +-> piv-p256 ZFgiIw AkOsNsFUAc7ivk7Oy0p9B6RbLgnbYQBC7LxVCOhSwpW+ +eSv3RwntC/YPXYvYfQu/YLNv5i82NN9I9mBO+O+4Dlk +-> piv-p256 5vmPtQ A82tmdg/DaheCyDtiPgoMJC47fmVRPdjqmyDgqY3QorA +R/r5HhVSxgpHiCsvvZFadcQMGkk1ivygf+Q6y00/+gA +-> piv-p256 ZFgiIw A+q6l6gnwWQweNUvUDnf3jQ0gbO1543NULl8Ht24u7ZH +p/TX92gED+6O2jo9O7g1p+VSYoVTVjbjtMD7vQ5LtnA +-> #b!8--grease +JO/hQWKVdfndshTCfe0SnxADKqm/6htoeyivmK60hiXXqZJcrhGnNF4aoY2TIYQ/ +Uuu4WUWr0zggK0iSMqrcA8yp7Tt6KFbQ8c2JVq1KcecsWbRBXSHFdvOh +--- RwweJRo9SY18ZvLtGpOE7Mh3rWElaSJe1kepjAYn7OQ +(qӂjcw78ÇUWn]G_/aTOwUem&n%;tǡRIQ GV \ No newline at end of file diff --git a/modules/services/immich.nix b/modules/services/immich.nix new file mode 100644 index 0000000..75ec25c --- /dev/null +++ b/modules/services/immich.nix @@ -0,0 +1,192 @@ +# Auto-generated using compose2nix v0.1.6. +{ + pkgs, + config, + ... +}: let + version = "v1.93.3"; + environment = { + DB_DATABASE_NAME = "immich"; + DB_HOSTNAME = "immich_postgres"; + DB_PASSWORD_FILE = config.age.secrets.postgrespasswd.path; + DB_USERNAME = "postgres"; + IMMICH_VERSION = "${version}"; + REDIS_HOSTNAME = "immich_redis"; + REDIS_PASSWORD_FILE = config.age.secrets.redispasswd.path; + UPLOAD_LOCATION = upload_folder; + }; + upload_folder = "/panzer/immich"; + pgdata_folder = "/state/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 { + networking.firewall.allowedTCPPorts = [2283]; + systemd.tmpfiles.settings = { + "10-immich" = { + ${upload_folder}.d = { + mode = "0770"; + }; + ${pgdata_folder}.d = { + mode = "0770"; + }; + ${model_folder}.d = { + mode = "0770"; + }; + }; + }; + age.secrets.postgrespasswd = { + generator.script = "alnum"; + }; + age.secrets.redispasswd = { + generator.script = "alnum"; + }; + # Runtime + virtualisation.podman = { + enable = true; + autoPrune.enable = true; + dockerCompat = true; + defaultNetwork.settings = { + # Required for container networking to be able to use names. + dns_enabled = 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" + ]; + }; + systemd.services."podman-immich_machine_learning" = serviceConfig; + virtualisation.oci-containers.containers."immich_microservices" = { + image = "ghcr.io/immich-app/immich-server:${version}"; + inherit environment; + volumes = [ + "/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" + ]; + }; + 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 = [ + "${pgdata_folder}:/var/lib/postgresql/data:rw" + ]; + log-driver = "journald"; + extraOptions = [ + "--network-alias=database" + "--network=immich-default" + ]; + }; + 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=redis" + "--network=immich-default" + ]; + }; + systemd.services."podman-immich_redis" = serviceConfig; + virtualisation.oci-containers.containers."immich_server" = { + image = "ghcr.io/immich-app/immich-server:${version}"; + inherit environment; + volumes = [ + "/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" + ]; + }; + 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 + ''; + 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/modules/services/paperless.nix b/modules/services/paperless.nix index 41fd77b..c40d516 100644 --- a/modules/services/paperless.nix +++ b/modules/services/paperless.nix @@ -20,7 +20,7 @@ in { }; services.restic.backups = { main = { - inherit (config.services.paperless) user; + user = "root"; timerConfig = { OnCalendar = "06:00"; Persistent = true; @@ -58,6 +58,7 @@ in { }; inherit (cfg) environment; requiredBy = ["restic-backups-main.service"]; + before = ["restic-backups-main.service"]; }; networking.firewall.allowedTCPPorts = [3000]; @@ -101,4 +102,12 @@ in { mode = "0750"; } ]; + environment.persistence."/state".directories = [ + { + directory = paperlessBackupDir; + user = "paperless"; + group = "paperless"; + mode = "0770"; + } + ]; } diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index dd8c3e9..f281847 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -28,7 +28,7 @@ in { }; services.restic.backups = { main = { - user = "vaultwarden"; + user = "root"; timerConfig = { OnCalendar = "06:00"; Persistent = true; @@ -86,4 +86,12 @@ in { StateDirectory = lib.mkForce "vaultwarden"; RestartSec = "600"; # Retry every 10 minutes }; + environment.persistence."/state".directories = [ + { + directory = config.services.vaultwarden.backupDir; + user = "vaultwarden"; + group = "vaultwarden"; + mode = "0770"; + } + ]; } diff --git a/secrets/secrets.nix.age b/secrets/secrets.nix.age index 93dd8b3d00371e604c262ff3c9fc7b6112352969..d4e15b286bf58345fcd86397c7268c627a45283b 100644 GIT binary patch delta 5033 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