Add immich

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-09-30 10:13:59 +01:00
parent 6ce084b652
commit 492b643d8b
18 changed files with 479 additions and 31 deletions

View File

@@ -136,6 +136,7 @@ in
"outline"
"shlink"
"comentario"
"immich"
];
};
}

View File

@@ -16,6 +16,7 @@ in
(import ./comentario { inherit user home; })
(import ./gitea { inherit user home; })
(import ./grafana { inherit user home; })
(import ./immich { inherit user home; })
(import ./littlelink { inherit user home; })
(import ./lore { inherit user home; })
(import ./media { inherit user home; })

View File

@@ -0,0 +1,215 @@
{ user, home }:
{
config,
inputs,
pkgs,
lib,
...
}:
let
hmConfig = config.home-manager.users.${user};
inherit (hmConfig.virtualisation.quadlet) volumes containers networks;
autheliaClientId = "kwrm5k1Bgwqd4BCXiWp0feL6adpthOn0GGgQ9iIVW7IH1UIj7bA2HVj9Jv42hUheoYoE8wWJpQi8woPomrSJIauTmsBMMFTTrI6r";
in
{
home-manager.users.${user} = {
sops = {
secrets = {
"immich/smtp".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/postgresql".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/admin".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/authelia/password".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
"immich/authelia/digest".sopsFile = "${inputs.secrets}/hosts/jupiter/secrets.yaml";
};
templates = {
immich-postgresql-env.content = ''
POSTGRES_PASSWORD=${hmConfig.sops.placeholder."immich/postgresql"}
'';
immich-env.content = ''
DB_PASSWORD=${hmConfig.sops.placeholder."immich/postgresql"}
IMMICH_ADMIN_PASSWORD=${hmConfig.sops.placeholder."immich/admin"}
'';
immich.content = builtins.readFile (
(pkgs.formats.json { }).generate "config.json" {
ffmpeg = {
accel = "nvenc";
accelDecode = true;
};
oauth = {
enabled = true;
buttonText = "Login with Authelia";
clientId = autheliaClientId;
clientSecret = hmConfig.sops.placeholder."immich/authelia/password";
issuerUrl = "https://id.karaolidis.com/.well-known/openid-configuration";
scope = lib.strings.concatStringsSep " " [
"openid"
"profile"
"email"
];
};
passwordLogin.enabled = true;
newVersionCheck.enabled = false;
library.watch.enabled = true;
server.externalDomain = "https://photos.karaolidis.com";
notifications.smtp = {
enabled = true;
from = "jupiter@karaolidis.com";
transport = {
host = "smtp.protonmail.ch";
port = 587;
username = "jupiter@karaolidis.com";
password = hmConfig.sops.placeholder."immich/smtp";
};
};
}
);
authelia-immich.content = builtins.readFile (
(pkgs.formats.yaml { }).generate "immich.yaml" {
identity_providers.oidc = {
authorization_policies.immich = {
default_policy = "deny";
rules = [
{
policy = "one_factor";
subject = "group:immich";
}
];
};
clients = [
{
client_id = autheliaClientId;
client_name = "immich";
client_secret = hmConfig.sops.placeholder."immich/authelia/digest";
redirect_uris = [
"https://photos.karaolidis.com/auth/login"
"https://photos.karaolidis.com/user-settings"
"app.immich:///oauth-callback"
];
authorization_policy = "immich";
scopes = [
"openid"
"profile"
"email"
];
token_endpoint_auth_method = "client_secret_post";
pre_configured_consent_duration = "1 year";
}
];
};
}
);
};
};
systemd.user.tmpfiles.rules = [
"d /mnt/storage/private/storm/containers/storage/volumes/immich/_data 700 storm storm"
];
virtualisation.quadlet = {
networks.immich = { };
volumes = {
immich-redis = { };
immich-postgresql = { };
immich-machine-learning-cache = { };
};
containers = {
immich = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.immich}";
volumes =
let
postStart = pkgs.writeTextFile {
name = "post-start.sh";
executable = true;
text = builtins.readFile ./post-start.sh;
};
in
[
"${hmConfig.sops.templates.immich.path}:/etc/immich/config.json:ro"
"${postStart}:/etc/immich/post-start.sh:ro"
"/mnt/storage/private/storm/containers/storage/volumes/immich/_data:/var/lib/immich"
];
networks = [
networks.immich.ref
networks.traefik.ref
];
labels = [
"traefik.enable=true"
"traefik.http.routers.immich.rule=Host(`photos.karaolidis.com`)"
];
environments = {
DB_HOSTNAME = "immich-postgresql";
DB_USERNAME = "immich";
DB_DATABASE_NAME = "immich";
REDIS_HOSTNAME = "immich-redis";
IMMICH_ADMIN_EMAIL = "jupiter@karaolidis.com";
IMMICH_ADMIN_NAME = "Admin";
};
environmentFiles = [ hmConfig.sops.templates.immich-env.path ];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
unitConfig = {
After = [
"${containers.immich-postgresql._serviceName}.service"
"${containers.immich-redis._serviceName}.service"
"sops-nix.service"
];
Requires = [
"${containers.immich-postgresql._serviceName}.service"
"${containers.immich-redis._serviceName}.service"
];
};
};
immich-machine-learning.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.immich-machine-learning}";
volumes = [ "${volumes.immich-machine-learning-cache.ref}:/tmp/immich-machine-learning" ];
networks = [ networks.immich.ref ];
podmanArgs = [ "--cdi-spec-dir=/run/cdi" ];
devices = [ "nvidia.com/gpu=all" ];
};
immich-postgresql = {
containerConfig = {
image = "docker-archive:${pkgs.dockerImages.postgresql-vectorchord}";
networks = [ networks.immich.ref ];
volumes = [ "${volumes.immich-postgresql.ref}:/var/lib/postgresql/data" ];
environments = {
POSTGRES_DB = "immich";
POSTGRES_USER = "immich";
};
environmentFiles = [ hmConfig.sops.templates.immich-postgresql-env.path ];
};
unitConfig.After = [ "sops-nix.service" ];
};
immich-redis.containerConfig = {
image = "docker-archive:${pkgs.dockerImages.redis}";
networks = [ networks.immich.ref ];
volumes = [ "${volumes.immich-redis.ref}:/var/lib/redis" ];
exec = [ "--save 60 1" ];
};
authelia.containerConfig.volumes = [
"${hmConfig.sops.templates.authelia-immich.path}:/etc/authelia/conf.d/immich.yaml:ro"
];
};
};
};
}

View File

@@ -0,0 +1,22 @@
# shellcheck shell=sh
IMMICH_HOST="${IMMICH_HOST:-http://localhost:2283}"
IMMICH_ADMIN_NAME="${IMMICH_ADMIN_NAME:-Admin}"
until response="$(curl -sf "$IMMICH_HOST/api/server/config")"; do
echo "Waiting for Immich to be ready..."
sleep 1
done
is_initialized="$(echo "$response" | jq -r '.isInitialized')"
if [ "$is_initialized" = "false" ]; then
curl -sf "$IMMICH_HOST/api/auth/admin-sign-up" \
-X POST \
-H 'Content-Type: application/json' \
--data-raw '{
"email":"'"$IMMICH_ADMIN_EMAIL"'",
"password":"'"$IMMICH_ADMIN_PASSWORD"'",
"name":"'"$IMMICH_ADMIN_NAME"'"
}'
fi