diff --git a/modules/nixos/services/media/default.nix b/modules/nixos/services/media/default.nix index d257aea..c880580 100644 --- a/modules/nixos/services/media/default.nix +++ b/modules/nixos/services/media/default.nix @@ -3,12 +3,16 @@ lib, namespace, config, + inputs, + system, ... }: let inherit (lib) mkIf mkEnableOption mkOption; inherit (lib.types) str; cfg = config.${namespace}.services.media; + + arr = ["radarr"]; in { options.${namespace}.services.media = { enable = mkEnableOption "Enable media services"; @@ -56,75 +60,285 @@ in { }; systemd.tmpfiles.rules = [ - # "d '${cfg.path}/series' 0770 ${cfg.user} ${cfg.group} - -" - # "d '${cfg.path}/movies' 0770 ${cfg.user} ${cfg.group} - -" - # "d '${cfg.path}/music' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.path}/qbittorrent' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.path}/sabnzbd' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.path}/downloads/incomplete' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.path}/downloads/done' 0770 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/series' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/movies' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/music' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/qbittorrent' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/sabnzbd' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/reiverr/config' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/downloads/incomplete' 0700 ${cfg.user} ${cfg.group} - -" + "d '${cfg.path}/downloads/done' 0700 ${cfg.user} ${cfg.group} - -" + "d /var/lib/radarrApplyTerraform 0755 ${cfg.user} ${cfg.group} -" ]; #========================================================================= # Services #========================================================================= - services = { - bazarr = { - enable = true; - openFirewall = true; - user = cfg.user; - group = cfg.group; - listenPort = 2005; - }; + services = let + arr-services = + arr + |> lib.imap (i: service: { + name = service; + value = + { + enable = true; + openFirewall = true; - flaresolverr = { - enable = true; - openFirewall = true; - port = 2007; - }; + environmentFiles = [ + config.sops.templates."${service}/config.env".path + ]; - # port is harcoded in nixpkgs module - jellyfin = { - enable = true; - openFirewall = true; - user = cfg.user; - group = cfg.group; - }; + settings = { + auth.authenticationMethod = "External"; - postgresql = { - enable = true; - }; + server = { + bindaddress = "0.0.0.0"; + port = 2000 + i; + }; - caddy = { - enable = true; - virtualHosts = { - "jellyfin.kruining.eu".extraConfig = '' - reverse_proxy http://[::1]:8096 - ''; + postgres = { + host = "localhost"; + port = "5432"; + user = service; + maindb = service; + logdb = service; + }; + }; + } + // ( + if service != "prowlarr" + then { + user = cfg.user; + group = cfg.group; + } + else {} + ); + }) + |> lib.listToAttrs; + in + arr-services + // { + bazarr = { + enable = true; + openFirewall = true; + user = cfg.user; + group = cfg.group; + listenPort = 2005; }; + + # port is harcoded in nixpkgs module + jellyfin = { + enable = true; + openFirewall = true; + user = cfg.user; + group = cfg.group; + }; + + flaresolverr = { + enable = true; + openFirewall = true; + port = 2007; + }; + + qbittorrent = { + enable = true; + openFirewall = true; + webuiPort = 2008; + + serverConfig = { + LegalNotice.Accepted = true; + + Prefecences.WebUI = { + Username = "admin"; + }; + }; + + user = cfg.user; + group = cfg.group; + }; + + # port is harcoded in nixpkgs module + sabnzbd = { + enable = true; + openFirewall = true; + configFile = "${cfg.path}/sabnzbd/config.ini"; + + user = cfg.user; + group = cfg.group; + }; + + postgresql = let + databases = arr |> lib.concatMap (s: [s "${s}-log"]); + in { + enable = true; + ensureDatabases = arr; + ensureUsers = + arr + |> lib.map (service: { + name = service; + ensureDBOwnership = true; + }); + }; + + caddy = { + enable = true; + virtualHosts = { + "jellyfin.kruining.eu".extraConfig = '' + reverse_proxy http://[::1]:8096 + ''; + }; + }; + }; + + systemd.services.radarrApplyTerraform = let + # this is a nix package, the generated json file to be exact + terraformConfiguration = inputs.terranix.lib.terranixConfiguration { + inherit system; + + modules = [ + ({ + config, + lib, + ... + }: { + config = { + variable = { + api_key = { + type = "string"; + description = "Radarr api key"; + }; + }; + + terraform.required_providers.radarr = { + source = "devopsarr/radarr"; + version = "2.2.0"; + }; + + provider.radarr = { + url = "http://127.0.0.1:2001"; + api_key = lib.tfRef "var.api_key"; + }; + + resource = { + radarr_root_folder.local = { + path = "/var/media/movies"; + }; + }; + }; + }) + ]; + }; + in { + description = "Radarr terraform apply"; + + wantedBy = ["multi-user.target"]; + wants = ["radarr.service"]; + + script = '' + #!/usr/bin/env bash + + if [ "$(systemctl is-active radarr)" != "active" ]; then + echo "Radarr is not running" + exit 1 + fi + + # Sleep for a bit to give radarr the chance to start up + sleep 5s + + # Print the path to the source for easier debugging + echo "config location: ${terraformConfiguration}" + + # Copy infra code into workspace + cp -f ${terraformConfiguration} config.tf.json + + # Initialize OpenTofu + ${lib.getExe pkgs.opentofu} init + + # Run the infrastructure code + # ${lib.getExe pkgs.opentofu} plan -var-file='${config.sops.templates."radarr/config.tfvars".path}' + ${lib.getExe pkgs.opentofu} apply -auto-approve -var-file='${config.sops.templates."radarr/config.tfvars".path}' + ''; + + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + + WorkingDirectory = "/var/lib/radarrApplyTerraform"; + + EnvironmentFile = [ + config.sops.templates."radarr/config.env".path + ]; }; }; systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL"; sops = { - secrets = { - # "qbittorrent/password" = {}; - "qbittorrent/password_hash" = {}; - }; - - templates = { - "qbittorrent/password.conf" = { - owner = cfg.user; - group = cfg.group; - restartUnits = ["qbittorrent.service"]; - path = "${config.services.qbittorrent.profileDir}/qBittorrent/config/password.conf"; - content = '' - [Preferences] - WebUI\Password_PBKDF2="${config.sops.placeholder."qbittorrent/password_hash"}" - ''; + secrets = let + arrSecrets = + arr + |> lib.map (service: { + name = "${service}/apikey"; + value = { + owner = cfg.user; + group = cfg.group; + restartUnits = ["${service}.service"]; + }; + }) + |> lib.listToAttrs; + in + arrSecrets + // { + # "qbittorrent/password" = {}; + "qbittorrent/password_hash" = {}; }; - }; + + templates = let + apikeys = + arr + |> lib.map (service: { + name = "${service}/config.env"; + value = { + owner = cfg.user; + group = cfg.group; + restartUnits = ["${service}.service"]; + content = '' + ${lib.toUpper service}__AUTH__APIKEY="${config.sops.placeholder."${service}/apikey"}" + ''; + }; + }) + |> lib.listToAttrs; + + tfvars = + arr + |> lib.map (service: { + name = "${service}/config.tfvars"; + value = { + owner = cfg.user; + group = cfg.group; + restartUnits = ["${service}ApplyTerraform.service"]; + content = '' + api_key = "${config.sops.placeholder."${service}/apikey"}" + ''; + }; + }) + |> lib.listToAttrs; + + qbittorrent = { + "qbittorrent/password.conf" = { + owner = cfg.user; + group = cfg.group; + restartUnits = ["qbittorrent.service"]; + path = "${config.services.qbittorrent.profileDir}/qBittorrent/config/password.conf"; + content = '' + [Preferences] + WebUI\Password_PBKDF2="${config.sops.placeholder."qbittorrent/password_hash"}" + ''; + }; + }; + in + apikeys // tfvars // qbittorrent; }; }; } diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix deleted file mode 100644 index 333035d..0000000 --- a/modules/nixos/services/media/glance/default.nix +++ /dev/null @@ -1,183 +0,0 @@ -{ - config, - lib, - namespace, - ... -}: let - inherit (lib) mkIf mkEnableOption; - - cfg = config.${namespace}.services.media.glance; -in { - options.${namespace}.services.media.glance = { - enable = mkEnableOption "Enable Glance"; - }; - - config = mkIf cfg.enable { - services.glance = { - enable = true; - openFirewall = true; - - environmentFile = config.sops.templates."glance/secrets.env".path; - - settings = { - server = { - host = "0.0.0.0"; - port = 2000; - }; - - theme = { - # Teal city predefined theme (https://github.com/glanceapp/glance/blob/main/docs/themes.md#teal-city) - background-color = "225 14 15"; - primary-color = "157 47 65"; - contrast-multiplier = 1.1; - }; - - pages = [ - { - name = "Home"; - columns = [ - { - size = "small"; - widgets = [ - { - type = "calendar"; - first-day-of-the-week = "monday"; - } - ]; - } - - { - size = "full"; - widgets = [ - { - type = "monitor"; - cache = "1m"; - title = "Services"; - sites = [ - { - title = "Zitadel"; - url = "https://auth.kruining.eu"; - icon = "sh:zitadel"; - } - { - title = "Forgejo"; - url = "https://git.amarth.cloud/chris"; - icon = "sh:forgejo"; - } - { - title = "Vaultwarden"; - url = "https://vault.kruining.eu"; - icon = "sh:vaultwarden"; - } - ]; - } - { - type = "monitor"; - cache = "1m"; - title = "Observability"; - sites = [ - { - title = "Grafana"; - url = "http://${config.networking.hostName}:${builtins.toString config.services.grafana.settings.server.http_port}"; - icon = "sh:grafana"; - } - { - title = "Prometheus"; - url = "http://${config.networking.hostName}:${builtins.toString config.services.prometheus.port}"; - icon = "sh:prometheus"; - } - ]; - } - { - type = "monitor"; - cache = "1m"; - title = "Media"; - sites = [ - { - title = "Jellyfin"; - url = "http://${config.networking.hostName}:8096"; - icon = "sh:jellyfin"; - } - { - title = "Radarr"; - url = "http://${config.networking.hostName}:2001"; - icon = "sh:radarr"; - } - { - title = "Sonarr"; - url = "http://${config.networking.hostName}:2002"; - icon = "sh:sonarr"; - } - { - title = "Lidarr"; - url = "http://${config.networking.hostName}:2003"; - icon = "sh:lidarr"; - } - { - title = "Prowlarr"; - url = "http://${config.networking.hostName}:2004"; - icon = "sh:prowlarr"; - } - { - title = "qBittorrent"; - url = "http://${config.networking.hostName}:${builtins.toString config.services.qbittorrent.webuiPort}"; - icon = "sh:qbittorrent"; - } - { - title = "SABnzbd"; - url = "http://${config.networking.hostName}:8080"; - icon = "sh:sabnzbd"; - } - ]; - } - { - type = "videos"; - channels = [ - "UCXuqSBlHAE6Xw-yeJA0Tunw" # Linus Tech Tips - "UCR-DXc1voovS8nhAvccRZhg" # Jeff Geerling - "UCsBjURrPoezykLs9EqgamOA" # Fireship - "UCBJycsmduvYEL83R_U4JriQ" # Marques Brownlee - "UCHnyfMqiRRG1u-2MsSQLbXA" # Veritasium - ]; - } - ]; - } - - { - size = "small"; - widgets = [ - { - type = "weather"; - location = "Amsterdam, The Netherlands"; - units = "metric"; - hour-format = "24h"; - } - - { - type = "server-stats"; - servers = [ - { - type = "local"; - name = "Ulmo"; - } - ]; - } - ]; - } - ]; - } - ]; - }; - }; - - sops.templates."glance/secrets.env" = { - # owner = config.services.glance.user; - # group = config.services.glance.group; - content = '' - RADARR_KEY="${config.sops.placeholder."radarr/apikey"}" - SONARR_KEY="${config.sops.placeholder."sonarr/apikey"}" - LIDARR_KEY="${config.sops.placeholder."lidarr/apikey"}" - ''; - }; - }; -} diff --git a/modules/nixos/services/media/homer/default.nix b/modules/nixos/services/media/homer/default.nix new file mode 100644 index 0000000..79633ab --- /dev/null +++ b/modules/nixos/services/media/homer/default.nix @@ -0,0 +1,161 @@ +{ config, lib, namespace, ... }: +let + inherit (lib) mkIf mkEnableOption; + + cfg = config.${namespace}.services.media.homer; +in +{ + options.${namespace}.services.media.homer = { + enable = mkEnableOption "Enable homer"; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 2000 ]; + + services = { + homer = { + enable = true; + + virtualHost = { + caddy.enable = true; + domain = "http://:2000"; + }; + + settings = { + title = "Ulmo dashboard"; + + columns = 4; + connectivityCheck = true; + + links = []; + + services = [ + { + name = "Services"; + items = [ + { + name = "Zitadel"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/zitadel.svg"; + tag = "app"; + url = "https://auth.kruining.eu"; + target = "_blank"; + } + + { + name = "Forgejo"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/forgejo.svg"; + tag = "app"; + type = "Gitea"; + url = "https://git.amarth.cloud"; + target = "_blank"; + } + + { + name = "Vaultwarden"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/vaultwarden.svg"; + type = "Vaultwarden"; + tag = "app"; + url = "https://vault.kruining.eu"; + target = "_blank"; + } + ]; + } + + { + name = "Observability"; + items = [ + { + name = "Grafana"; + type = "Grafana"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/grafana.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.grafana.settings.server.http_port}"; + target = "_blank"; + } + + { + name = "Prometheus"; + type = "Prometheus"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/prometheus.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.prometheus.port}"; + target = "_blank"; + } + ]; + } + + { + name = "Media"; + items = [ + { + name = "Jellyfin (Movies)"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/jellyfin.svg"; + tag = "app"; + type = "Emby"; + url = "http://${config.networking.hostName}:8096"; + apikey = "e3ceed943eeb409ba8342738db7cc1f5"; + libraryType = "movies"; + target = "_blank"; + } + + { + name = "Radarr"; + type = "Radarr"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/radarr.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:2001"; + target = "_blank"; + } + + { + name = "Sonarr"; + type = "Sonarr"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/sonarr.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:2002"; + target = "_blank"; + } + + { + name = "Lidarr"; + type = "Lidarr"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/lidarr.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:2003"; + target = "_blank"; + } + + { + name = "Prowlarr"; + type = "Prowlarr"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/prowlarr.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:2004"; + target = "_blank"; + } + + { + name = "qBittorrent"; + type = "qBittorrent"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/qbittorrent.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.qbittorrent.webuiPort}"; + target = "_blank"; + } + + { + name = "SABnzbd"; + type = "SABnzbd"; + logo = "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/sabnzdb-light.svg"; + tag = "app"; + url = "http://${config.networking.hostName}:8080"; + target = "_blank"; + } + ]; + } + ]; + }; + }; + }; + }; +} diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 373e09b..733fe99 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -41,68 +41,36 @@ in { port, ... }: (mkIf enable { - "${service}" = - { - enable = true; - openFirewall = true; + "${service}" = { + enable = true; + openFirewall = true; - environmentFiles = [ - config.sops.templates."${service}/config.env".path - ]; + environmentFiles = [ + config.sops.templates."${service}/config.env".path + ]; - settings = { - auth.authenticationMethod = "External"; + settings = { + auth.authenticationMethod = "External"; - server = { - bindaddress = "0.0.0.0"; - port = port; - }; - - postgres = { - host = "localhost"; - port = "5432"; - user = service; - maindb = service; - logdb = service; - }; + server = { + bindaddress = "0.0.0.0"; + port = port; }; - } - // (lib.optionalAttrs (service != "prowlarr") { - user = service; - group = "media"; - }); + + postgres = { + host = "localhost"; + port = "5432"; + user = service; + maindb = service; + logdb = service; + }; + }; + }; })) - |> lib.mkMerge + |> lib.mergeAttrsList |> (set: set // { - qbittorrent = { - enable = true; - openFirewall = true; - webuiPort = 2008; - - serverConfig = { - LegalNotice.Accepted = true; - - Prefecences.WebUI = { - Username = "admin"; - }; - }; - - user = "qbittorrent"; - group = "media"; - }; - - # port is harcoded in nixpkgs module - sabnzbd = { - enable = true; - openFirewall = true; - configFile = "${cfg.path}/sabnzbd/config.ini"; - - user = "sabnzbd"; - group = "media"; - }; - postgresql = { ensureDatabases = cfg |> lib.attrNames; ensureUsers = @@ -115,7 +83,7 @@ in { }; }); - systemd.services = + systemd = cfg |> lib.mapAttrsToList (service: { enable, @@ -124,7 +92,11 @@ in { rootFolders, ... }: (mkIf enable { - "${service}ApplyTerraform" = let + tmpfiles.rules = [ + "d /var/lib/${service}ApplyTerraform 0755 ${service} ${service} -" + ]; + + services."${service}ApplyTerraform" = let terraformConfiguration = inputs.terranix.lib.terranixConfiguration { inherit system; @@ -144,17 +116,7 @@ in { terraform.required_providers.${service} = { source = "devopsarr/${service}"; - version = - { - radarr = "2.3.3"; - sonarr = "3.4.0"; - prowlarr = "3.1.0"; - lidarr = "1.13.0"; - readarr = "2.1.0"; - whisparr = "1.2.0"; - }.${ - service - }; + version = "2.2.0"; }; provider.${service} = { @@ -163,11 +125,10 @@ in { }; resource = { - "${service}_root_folder" = mkIf (lib.elem service ["radarr" "sonarr" "whisparr"]) ( + "${service}_root_folder" = rootFolders |> lib.imap (i: f: lib.nameValuePair "local${toString i}" {path = f;}) - |> lib.listToAttrs - ); + |> lib.listToAttrs; }; }; }) @@ -179,16 +140,9 @@ in { wantedBy = ["multi-user.target"]; wants = ["${service}.service"]; - preStart = '' - install -d -m 0770 -o ${service} -g media /var/lib/${service}ApplyTerraform - ${ - rootFolders - |> lib.map (folder: "install -d -m 0770 -o media -g media ${folder}") - |> lib.join "\n" - } - ''; - script = '' + #!/usr/bin/env bash + # Sleep for a bit to give the service a chance to start up sleep 5s @@ -204,7 +158,7 @@ in { cp -f ${terraformConfiguration} config.tf.json # Initialize OpenTofu - ${lib.getExe pkgs.opentofu} init -upgrade + ${lib.getExe pkgs.opentofu} init # Run the infrastructure code ${lib.getExe pkgs.opentofu} \ @@ -219,7 +173,7 @@ in { serviceConfig = { Type = "oneshot"; User = service; - Group = "media"; + Group = service; WorkingDirectory = "/var/lib/${service}ApplyTerraform"; @@ -229,33 +183,28 @@ in { }; }; })) - |> lib.mkMerge; + |> lib.mergeAttrsList; - users = + users.users = cfg |> lib.mapAttrsToList (service: {enable, ...}: (mkIf enable { - users.${service} = { - isSystemUser = true; - group = lib.mkDefault service; - extraGroups = ["media"]; - }; - groups.${service} = {}; + "${service}".extraGroups = ["media"]; })) - |> lib.mkMerge; + |> lib.mergeAttrsList; sops = cfg |> lib.mapAttrsToList (service: {enable, ...}: (mkIf enable { secrets."${service}/apikey" = { owner = service; - group = "media"; + group = service; restartUnits = ["${service}.service"]; }; templates = { "${service}/config.env" = { owner = service; - group = "media"; + group = service; restartUnits = ["${service}.service"]; content = '' ${lib.toUpper service}__AUTH__APIKEY="${config.sops.placeholder."${service}/apikey"}" @@ -264,7 +213,7 @@ in { "${service}/config.tfvars" = { owner = service; - group = "media"; + group = service; restartUnits = ["${service}.service"]; content = '' api_key = "${config.sops.placeholder."${service}/apikey"}" @@ -272,6 +221,15 @@ in { }; }; })) - |> lib.mkMerge; + |> lib.mergeAttrsList; }; + + # cfg + # |> lib.mapAttrsToList (service: { enable, debug, port, rootFolders, ... }: (mkIf enable { + + # # sops = { + # # }; + # })) + # |> lib.mergeAttrsList + # ; } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 9d12de8..93171d8 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -161,17 +161,13 @@ networking.ssh.enable = true; media.enable = true; - media.glance.enable = true; + media.homer.enable = true; media.mydia.enable = true; media.nfs.enable = true; media.servarr = { - radarr = { - enable = true; - port = 2001; - rootFolders = [ - "/var/media/movies" - ]; - }; + # radarr = { + # port = 2001; + # }; sonarr = { enable = true;