diff --git a/modules/server/containers/apps/.todo.md b/modules/server/containers/apps/.todo.md index 8f14b24..02a7c37 100644 --- a/modules/server/containers/apps/.todo.md +++ b/modules/server/containers/apps/.todo.md @@ -3,5 +3,6 @@ RSS: TTRSS / FreshRSS Monitoring: Telegraf + InfluxDB https://github.com/tarampampam/error-pages ? +kavita + mylar ? kapowarr ? - Transmission Cfg and API/Token handling diff --git a/modules/server/containers/apps/influx.nix b/modules/server/containers/apps/influx.nix index 78469df..48e64c4 100644 --- a/modules/server/containers/apps/influx.nix +++ b/modules/server/containers/apps/influx.nix @@ -1,44 +1,53 @@ { config, containerCfg, pkgs, lib, builder, name, ... }: let serverCfg = config.syscfg.server; - influxPkg = pkgs.influxdb2; - - image = pkgs.dockerTools.streamLayeredImage { - name = influxPkg.name; - tag = influxPkg.version; - contents = [ ]; - config = { - Entrypoint = [ "${influxPkg}/bin/influxd" ]; - ExposedPorts = { - "8086/tcp" = {}; # Combined Engine and UI port - }; - }; - }; + version = "latest"; in { sops = true; + db = true; paths = [{ path = "${serverCfg.configPath}/influxdb/"; mode = "0700"; + }{ + path = "${serverCfg.dataPath}/influxdb/"; + owner = "1500:1500"; + mode = "0700"; }]; containers = { server = builder.mkContainer { subdomain = containerCfg.subdomain; - imageStream = image; - port = 8086; + image = "influxdata/influxdb3-ui:${version}"; + port = 8888; secret = name; extraEnv = { - INFLUXD_CONFIG_PATH = "var/lib/influxdb2/config"; - INFLUXD_BOLT_PATH = "/var/lib/influxdb2/influxdb.bolt"; - INFLUXD_ENGINE_PATH = "/var/lib/influxdb2/engine"; + SESSION_SECRET_KEY = "MOVE TO SOPS"; + DATABASE_URL = "/db/sqlite.db"; }; overrides = { + cmd = [ "--mode=admin" ]; volumes = [ - "${serverCfg.configPath}/influxdb/:/var/lib/influxdb2" + "${serverCfg.dataPath}/influxdb:/db" + "${serverCfg.configPath}/influxdb/:/app-root/config:ro" ]; }; }; }; + setup = { + trigger = "server"; + script = pkgs.writeShellScript "setup" '' + + cat > ${serverCfg.configPath}/influxdb/config.json << 'EOF' +{ + "DEFAULT_INFLUX_SERVER": "http://${builder.host}:8182", + "DEFAULT_INFLUX_DATABASE": "main", + "DEFAULT_API_TOKEN": "b27686e85a883437666f61586e084f7deb763958497739479ca48bc913ee90afd1a920332156133c89fb8674cb197ced17706074e6a42fc7ce6b2d54ac6119c9", + "DEFAULT_SERVER_NAME": "${serverCfg.domain}" +} +EOF + ''; + }; + } \ No newline at end of file diff --git a/modules/server/containers/apps/openhab.nix b/modules/server/containers/apps/openhab.nix index 27a20a1..765b1d3 100644 --- a/modules/server/containers/apps/openhab.nix +++ b/modules/server/containers/apps/openhab.nix @@ -1,3 +1,55 @@ -{... }:{ - +{ config, containerCfg, pkgs, lib, builder, name,... }: +let + serverCfg = config.syscfg.server; + version = "5.1.4"; + +in { + sops = false; + db = false; + paths = [ + { path="${serverCfg.configPath}/openhab/conf"; owner="1000:1000"; mode = "0755"; } + { path="${serverCfg.configPath}/openhab/userdata"; owner="1000:1000"; mode = "0755"; } + { path="${serverCfg.configPath}/openhab/addons"; owner="1000:1000"; mode = "0755"; } + ]; + + containers = { + server = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "openhab/openhab:${version}"; + port = 8080; + extraEnv = { + "USER_ID" = "1000"; + "GROUP_ID" = "1000"; + "CRYPTO_POLICY" = "unlimited"; + "OPENHAB_HTTP_PORT" = "8080"; + }; + overrides = { + volumes = [ + "${serverCfg.configPath}/openhab/conf:/openhab/conf" + "${serverCfg.configPath}/openhab/userdata:/openhab/userdata" + "${serverCfg.configPath}/openhab/addons:/opt/openhab/addons" + ]; + }; + }; + }; + + setup = { + trigger = "server"; + script = pkgs.writeShellScript "setup" '' + # Pre-generate openHAB directories on the host + OHAB="${pkgs.podman}/bin/podman --events-backend=none exec openhab-server /openhab/runtime/bin/client -u openhab -p habopen" + sleep 20 + exit 0 + $OHAB feature:list + #TODO: Install DB (influx, mapdb,...) + #TODO: Install! telegram, matter, mqtt, bluetooth, chromecast, PublicTransportSwitzerland, zigbee, OpenMeteoWeatherForecast + + #IF APPLE DEVICE: HomeKit (siri/apple bridge) + #IF UBIQUITY NET: Unifi + UnifiProtect (net/cam bridge) + #IF YAMAHA+EPSON: EpsonProjector + Yamaha (projector and sound) + #IF BAMBULAB DEVICE: BambuLab (notify print state) + #IF GARDENA DEVICE: Gardena (smart watering) + #IF AndroidTV/Jellyfin: Bind with lights + more + ''; + }; } \ No newline at end of file diff --git a/modules/server/containers/apps/servarr.nix b/modules/server/containers/apps/servarr.nix index 383f6f9..91b710b 100644 --- a/modules/server/containers/apps/servarr.nix +++ b/modules/server/containers/apps/servarr.nix @@ -1,6 +1,7 @@ { config, containerCfg, pkgs, lib, builder, name, ... }: let serverCfg = config.syscfg.server; + defaultModules = ["prowlarr" "sonarr" "radarr" "lidarr" "flaresolverr" ]; mkServarrImage = appName: appPkg: binaryPath: pkgs.dockerTools.streamLayeredImage { name = appPkg.name; @@ -25,7 +26,9 @@ let "${serverCfg.dataPath}/media:/media" # Fast hardlinking requires a single shared root "${serverCfg.configPath}/servarr:/config-root" ]; -in { +in + assert containerCfg.subpath == null || throw "Error: Servarr does not support subpath."; +{ sops = true; # db = [ "prowlarr" "sonarr" "radarr" ]; -> one db for each @@ -37,6 +40,7 @@ in { ]; containers = { + }// lib.optionalAttrs (builtins.elem "prowlarr" (containerCfg.extra.modules or defaultModules)) { prowlarr = builder.mkContainer { subdomain = containerCfg.subdomain; subpath = "prowlarr"; @@ -48,7 +52,7 @@ in { "PROWLARR__AUTH__METHOD" = "External"; "PROWLARR__SERVER__PORT" = "8989"; "PROWLARR__SERVER__URLBASE" = "/prowlarr"; - "RADARR__LOG__ANALYTICSENABLED" = "False"; + "PROWLARR__LOG__ANALYTICSENABLED" = "False"; }; extraOptions = [ "--user=0:0" @@ -58,6 +62,7 @@ in { overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/prowlarr:/config" ]; }; + }// lib.optionalAttrs (builtins.elem "radarr" (containerCfg.extra.modules or defaultModules)) { radarr = builder.mkContainer { subdomain = containerCfg.subdomain; subpath = "radarr"; @@ -79,6 +84,7 @@ in { overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/radarr:/config" ]; }; + }// lib.optionalAttrs (builtins.elem "sonarr" (containerCfg.extra.modules or defaultModules)) { sonarr = builder.mkContainer { subdomain = containerCfg.subdomain; subpath = "sonarr"; @@ -90,7 +96,7 @@ in { "SONARR__AUTH__METHOD" = "External"; "SONARR__SERVER__PORT" = "8989"; "SONARR__SERVER__URLBASE" = "/sonarr"; - "RADARR__LOG__ANALYTICSENABLED" = "False"; + "SONARR__LOG__ANALYTICSENABLED" = "False"; }; extraOptions = [ "--user=0:0" @@ -100,8 +106,15 @@ in { overrides.volumes = sharedVolumes ++ [ "${serverCfg.configPath}/servarr/sonarr:/config" ]; }; - #bazarr = ... - + }// lib.optionalAttrs (builtins.elem "readarr" (containerCfg.extra.modules or defaultModules)) { + readarr = throw "Not Implemented"; + }// lib.optionalAttrs (builtins.elem "mylarr" (containerCfg.extra.modules or defaultModules)) { + mylarr = throw "Not Implemented"; + }// lib.optionalAttrs (builtins.elem "bazarr" (containerCfg.extra.modules or defaultModules)) { + bazarr = throw "Not Implemented"; + }// lib.optionalAttrs (builtins.elem "seerr" (containerCfg.extra.modules or defaultModules)) { + seerr = throw "Not Implemented"; + }// lib.optionalAttrs (builtins.elem "flaresolverr" (containerCfg.extra.modules or defaultModules)) { flaresolverr = builder.mkContainer { image = "ghcr.io/flaresolverr/flaresolverr:latest"; port = 8191; @@ -111,12 +124,69 @@ in { }; }; -# setup = { -# trigger = "prowlarr"; # Triggers atomic environment verification on main controller -# envFile = config.sops.secrets."SERVARR".path; -# script = pkgs.writeShellScript "setup-servarr" '' -# echo "Validating multi-container path permission nodes..." -# # mkdir -p ${serverCfg.configPath}/servarr/{prowlarr,radarr,sonarr} -# ''; -# }; -} \ No newline at end of file + setup = { + trigger = "prowlarr"; # Triggers atomic environment verification on main controller + envFile = config.sops.secrets."SERVARR".path; + script = pkgs.writeShellScript "setup-servarr" '' + + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" "flaresolverr" ]) '' + PROWL_PROXY=$(${pkgs.curl}/bin/curl -s -X GET 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/indexerProxy' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json') + + ${pkgs.curl}/bin/curl -s -X POST 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/indexerProxy' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -d '{"onHealthIssue":false,"supportsOnHealthIssue":false,"includeHealthWarnings":false,"name":"FlareSolverr","fields":[{"name":"host","value":"http://servarr-flaresolverr:8191/"},{"name":"requestTimeout","value":60}],"implementationName":"FlareSolverr","implementation":"FlareSolverr","configContract":"FlareSolverrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#flaresolverr","tags":[]}' + ''} + + PROWL_APPS=$(${pkgs.curl}/bin/curl -s -X GET 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/applications' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json') + + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" "sonarr" ]) '' + ${pkgs.curl}/bin/curl -s -X POST 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/applications' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989"},{"name":"baseUrl","value":"https://servarr-sonarr:8989"},{"name":"apiKey","value":"'"$SONARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Sonarr","implementation":"Sonarr","configContract":"SonarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#sonarr","tags":[],"name":"Sonarr"}' + ''} + + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" "lidarr" ]) '' + ${pkgs.curl}/bin/curl -s -X POST 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/applications' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989"},{"name":"baseUrl","value":"https://servarr-lidarr:8989"},{"name":"apiKey","value":"'"$LIDARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Lidarr","implementation":"Lidarr","configContract":"LidarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#lidarr","tags":[],"name":"Lidarr"}' + ''} + + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" "radarr" ]) '' + ${pkgs.curl}/bin/curl -s -X POST 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/applications' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -d '{"syncLevel":"fullSync","enable":true,"fields":[{"name":"prowlarrUrl","value":"http://servarr-prowlarr:8989"},{"name":"baseUrl","value":"https://servarr-radarr:8989"},{"name":"apiKey","value":"'"$RADARR__AUTH__APIKEY"'"},{"name":"syncCategories","value":[5000,5010,5020,5030,5040,5045,5050,5090]},{"name":"animeSyncCategories","value":[5070]},{"name":"syncAnimeStandardFormatSearch","value":true},{"name":"syncRejectBlocklistedTorrentHashesWhileGrabbing","value":false}],"implementationName":"Radarr","implementation":"Radarr","configContract":"RadarrSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#lidarr","tags":[],"name":"Radarr"}' + ''} + + + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" "flaresolverr" ]) '' + ''} + + ${lib.optionalString ((lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" ]) && serverCfg.containers?transmission ) '' + PROWL_DL=$(${pkgs.curl}/bin/curl -s -X GET 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/downloadclient' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json') + + ${pkgs.curl}/bin/curl -s -X POST 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/downloadclient' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json' \ + -d '{"enable":true,"protocol":"torrent","priority":1,"categories":[],"supportsCategories":true,"name":"Transmission","fields":[{"name":"host","value":"transmission-server"},{"name":"port","value":9091},{"name":"useSsl","value":false},{"name":"urlBase","value":"/transmission/"},{"name":"username"},{"name":"password"},{"name":"category"},{"name":"directory"},{"name":"priority","value":0},{"name":"addPaused","value":false}],"implementationName":"Transmission","implementation":"Transmission","configContract":"TransmissionSettings","infoLink":"https://wiki.servarr.com/prowlarr/supported#transmission","tags":[]}' + ''} + ${lib.optionalString (lib.all (x: builtins.elem x (containerCfg.extra.modules or defaultModules)) [ "prowlarr" ]) '' + PROWL_IDX=$(${pkgs.curl}/bin/curl -s -X GET 'https://${containerCfg.subdomain}.${serverCfg.domain}/prowlarr/api/v1/indexer' \ + -H "X-Api-Key: $PROWLARR__AUTH__APIKEY" -H 'X-Prowlarr-Client: true' \ + -H 'Accept: application/json' -H 'Content-Type: application/json') + echo $PROWL_IDX + #... For extra.indexer -> ... + ''} + + ''; + }; +} diff --git a/modules/server/containers/builder.nix b/modules/server/containers/builder.nix index ce6ce01..f46eb9b 100644 --- a/modules/server/containers/builder.nix +++ b/modules/server/containers/builder.nix @@ -40,7 +40,7 @@ let ] ++ extraOptions; }; in lib.recursiveUpdate base overrides; - vmBuilder = { name, vm }: (import "${pkgs.path}/nixos/lib/eval-config.nix" { + vmBuilder = { name, vm }: ((import "${pkgs.path}/nixos/lib/eval-config.nix" { system = "x86_64-linux"; modules = [ vm.cfg ({ config, lib, modulesPath, ... }: { @@ -65,7 +65,7 @@ let in if (vm ? portForward && vm.portForward != null) then map parsePortString vm.portForward else []; };}) ]; - }.config.system.build.vm); + }).config.system.build.vm); in { mkContainer = contBuilder; mkVm = vmBuilder; diff --git a/modules/server/database/default.nix b/modules/server/database/default.nix index 6bd3b46..a818b96 100644 --- a/modules/server/database/default.nix +++ b/modules/server/database/default.nix @@ -39,6 +39,27 @@ in { bind = "*"; settings.protected-mode = "no"; }; + + systemd.services.influxdb3 = { + description = "InfluxDB 3 Time Series Database Engine"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + INFLUXDB3_NODE_IDENTIFIER_PREFIX = "node0"; + INFLUXDB3_OBJECT_STORE = "file"; + INFLUXDB3_DATA_DIR = "${config.syscfg.server.dataPath}/influxdb"; + INFLUXDB3_DB_DIR = "${config.syscfg.server.dataPath}/influxdb"; + INFLUXDB3_BEARER_TOKEN = "b27686e85a883437666f61586e084f7deb763958497739479ca48bc913ee90afd1a920332156133c89fb8674cb197ced17706074e6a42fc7ce6b2d54ac6119c9"; + }; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.influxdb3}/bin/influxdb3 serve"; + Restart = "on-failure"; + StateDirectory = "influxdb3"; + PrivateTmp = true; + NoNewPrivileges = true; + }; + }; systemd.services.postgresql-init = {