From af36497035cbd081fdf2916d9ea7723c5176cfb8 Mon Sep 17 00:00:00 2001 From: soraefir Date: Tue, 19 May 2026 22:09:36 +0200 Subject: [PATCH] wip --- .../server/containers/apps/homeassistant.nix | 72 ++++++++---- modules/server/containers/apps/openhab.nix | 3 + .../server/containers/apps/transmission.nix | 11 +- modules/server/containers/builder.nix | 106 +++++++++++------- modules/server/containers/default.nix | 26 ++++- modules/shared/syscfg/server.nix | 1 + systems/sandbox/cfg.nix | 3 +- 7 files changed, 155 insertions(+), 67 deletions(-) create mode 100644 modules/server/containers/apps/openhab.nix diff --git a/modules/server/containers/apps/homeassistant.nix b/modules/server/containers/apps/homeassistant.nix index cb10b36..629451e 100644 --- a/modules/server/containers/apps/homeassistant.nix +++ b/modules/server/containers/apps/homeassistant.nix @@ -4,36 +4,60 @@ let serverCfg = config.syscfg.server; in { - sops = true; - db = false; - - paths = [{ - path = "${serverCfg.configPath}/homeassistant/"; - mode = "0755"; - }]; + vm = { + portForward = [ 8123 ]; + cfg = {cfg,...}:{ + services.home-assistant = { + enable = true; + openFirewall = true; + + extraComponents = [ + "matter" "thread" "cast" "zha" + "default_config" "met" "esphome" "radio_browser" + "telegram_bot" "swiss_public_transport" "nextcloud" "jellyfin" + ] ++ (if containerCfg.extra ? components then containerCfg.extra.components else []); + - containers = { - server = builder.mkContainer { - subdomain = containerCfg.subdomain; - image = "ghcr.io/home-assistant/home-assistant:${version}"; - port = 8123; - secret = name; - extraOptions = [ - "--network=host" # Shares host IP: fixes timeouts & MDNS discovery - "--cap-add=NET_ADMIN" # Grants administrative network rights to fix DHCP packets - "--cap-add=NET_RAW" # Allows raw socket parsing needed for network sniffing - ]; - overrides = { - volumes = [ - "${serverCfg.configPath}/homeassistant/:/config" - "/run/dbus:/run/dbus:ro" + extraPackages = pp: with pp; [ + python-telegram gtts ]; + lovelaceConfig = {}; + + config = { + homeassistant = { + name = "Home"; + latitude = "${if containerCfg.extra ? latitude then toString containerCfg.extra.latitude else toString 0}"; + longitude = "${if containerCfg.extra ? longitude then toString containerCfg.extra.longitude else toString 0}"; + elevation = "${if containerCfg.extra ? elevation then toString containerCfg.extra.elevation else toString 0}"; + unit_system = "metric"; + time_zone = config.time.timeZone; + }; + lovelace = { mode = "yaml"; }; + customLovelaceModules = []; + + # default_config = {}; + http = { + use_x_forwarded_for = true; + trusted_proxies = [ "10.0.0.0/8" "127.0.0.1" ]; + }; + }; }; }; }; + containers = { + dummy = builder.mkContainer { + subdomain = containerCfg.subdomain; + image = "alpine:latest"; + extraLabels = { + "traefik.http.services.${containerCfg.subdomain}.loadbalancer.server.url" = "http://${builder.hostIp}:8123"; + }; + overrides = {cmd = [ "sleep" "infinity" ];}; + }; + }; + setup = { - trigger = "server"; + trigger = "dummy"; envFile = config.sops.secrets."CUSTOM".path; script = pkgs.writeShellScript "setup" '' @@ -60,6 +84,7 @@ in { -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"time_zone":"${config.time.timeZone}"}' > /dev/null 2>&1 || true + # We can configure many more things above ! ${pkgs.curl} -s -X POST "$HASS_URL/api/onboarding/analytics" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ @@ -69,7 +94,6 @@ in { -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"client_id":"'"$HASS_URL"'","redirect_uri":"'"$HASS_URL"'/?auth_callback=1"}' > /dev/null 2>&1 || true - fi ''; }; diff --git a/modules/server/containers/apps/openhab.nix b/modules/server/containers/apps/openhab.nix new file mode 100644 index 0000000..27a20a1 --- /dev/null +++ b/modules/server/containers/apps/openhab.nix @@ -0,0 +1,3 @@ +{... }:{ + +} \ No newline at end of file diff --git a/modules/server/containers/apps/transmission.nix b/modules/server/containers/apps/transmission.nix index c63b42a..d8f511e 100644 --- a/modules/server/containers/apps/transmission.nix +++ b/modules/server/containers/apps/transmission.nix @@ -13,6 +13,9 @@ let }; }; }; + routerName = if subpath != null + then "${subdomain}-${lib.strings.sanitizeDerivationName subpath}" + else subdomain; in { paths = [{ path = "${serverCfg.dataPath}/transmission/complete"; @@ -41,8 +44,12 @@ in { WHITELIST = "";# 127.0.0.1,::1,10.*"; # HOST_WHITELIST = "traefik-server,authentik-server,authentik-worker"; }; - extraLabels = { } // (if serverCfg.containers ? authentik then { - "traefik.http.routers.${containerCfg.subdomain}.middlewares" = "authentik"; + extraLabels = { + "traefik.http.routers.${routerName}.middlewares" = "transmission-rewrite"; + "traefik.http.middlewares.transmission-rewrite.replacepathregex.regex=^/p2p(.*)" + "traefik.http.middlewares.transmission-rewrite.replacepathregex.replacement=/transmission/web$$1" + } // (if serverCfg.containers ? authentik then { + "traefik.http.routers.${routerName}.middlewares" = "authentik,transmission-rewrite"; } else {}); overrides = { diff --git a/modules/server/containers/builder.nix b/modules/server/containers/builder.nix index 9f6858d..ce6ce01 100644 --- a/modules/server/containers/builder.nix +++ b/modules/server/containers/builder.nix @@ -1,47 +1,74 @@ { config, lib, pkgs, serverCfg }: let - builder = - { image ? null, imageStream ? null, imageFile ? null - , secret ? null - , subdomain ? null, subpath?null, port ? 0 - , extraEnv ? { }, extraLabels ? { }, extraOptions ? [ ] - , overrides ? { } - }: - let - routerName = if subpath != null - then "${subdomain}-${lib.strings.sanitizeDerivationName subpath}" - else subdomain; - base = { - image = if imageStream != null then "${imageStream.imageName}:${imageStream.imageTag}" - else if imageFile != null then "${imageFile.imageName}:${imageFile.imageTag}" else image; - imageStream = imageStream; - imageFile = imageFile; + contBuilder = + { image ? null, imageStream ? null, imageFile ? null + , secret ? null + , subdomain ? null, subpath?null, port ? null + , extraEnv ? { }, extraLabels ? { }, extraOptions ? [ ] + , overrides ? { } + }: + let + routerName = if subpath != null + then "${subdomain}-${lib.strings.sanitizeDerivationName subpath}" + else subdomain; + base = { + image = if imageStream != null then "${imageStream.imageName}:${imageStream.imageTag}" + else if imageFile != null then "${imageFile.imageName}:${imageFile.imageTag}" else image; + imageStream = imageStream; + imageFile = imageFile; - environmentFiles = if secret!=null then [ config.sops.secrets."${lib.toUpper secret}".path ] else []; - environment = { - TZ = config.time.timeZone; - } // extraEnv; + environmentFiles = if secret!=null then [ config.sops.secrets."${lib.toUpper secret}".path ] else []; + environment = { + TZ = config.time.timeZone; + } // extraEnv; - labels = (if subdomain!=null then ({ - "traefik.enable" = "true"; - "traefik.http.routers.${routerName}.entrypoints" = "web-secure"; - "traefik.http.routers.${routerName}.rule" = if subpath != null - then "Host(`${subdomain}.${serverCfg.domain}`) && PathPrefix(`/${subpath}`)" - else "Host(`${subdomain}.${serverCfg.domain}`)"; - "traefik.http.routers.${routerName}.tls" = "true"; - } // lib.optionalAttrs (port!=null) { - "traefik.http.services.${routerName}.loadbalancer.server.port" = toString port; - }) else { - "traefik.enable" = "false"; - }) // extraLabels; + labels = (if subdomain!=null then ({ + "traefik.enable" = "true"; + "traefik.http.routers.${routerName}.entrypoints" = "web-secure"; + "traefik.http.routers.${routerName}.rule" = if subpath != null + then "Host(`${subdomain}.${serverCfg.domain}`) && PathPrefix(`/${subpath}`)" + else "Host(`${subdomain}.${serverCfg.domain}`)"; + "traefik.http.routers.${routerName}.tls" = "true"; + } // lib.optionalAttrs (port!=null) { + "traefik.http.services.${routerName}.loadbalancer.server.port" = toString port; + }) else { + "traefik.enable" = "false"; + }) // extraLabels; - extraOptions = extraOptions ++ [ - "--add-host=host.containers.internal:host-gateway" - ]; - }; - in lib.recursiveUpdate base overrides; + extraOptions = [ + "--add-host=host.containers.internal:host-gateway" + ] ++ extraOptions; + }; + in lib.recursiveUpdate base overrides; + vmBuilder = { name, vm }: (import "${pkgs.path}/nixos/lib/eval-config.nix" { + system = "x86_64-linux"; + modules = [ vm.cfg + ({ config, lib, modulesPath, ... }: { + imports = [ + "${modulesPath}/profiles/qemu-guest.nix" + "${modulesPath}/virtualisation/qemu-vm.nix" + ]; + networking.hostName = name; + networking.useDHCP = true; + networking.firewall.enable = false; + services.qemuGuest.enable = true; + system.stateVersion = "25.11"; + virtualisation = { + memorySize = vm.memory or 2048; + cores = vm.cores or 2; + forwardPorts = let + parsePortString = port: { + from = "host"; + host.port = port; + guest.port = port; + }; + in if (vm ? portForward && vm.portForward != null) then map parsePortString vm.portForward else []; + };}) + ]; + }.config.system.build.vm); in { - mkContainer = builder; + mkContainer = contBuilder; + mkVm = vmBuilder; mkData = { name, dir, vars?{} }: pkgs.runCommand name vars '' mkdir -p $out cp -r ${./data + "/${dir}"}/. $out/ @@ -52,4 +79,7 @@ in { done ''; host = "host.containers.internal"; + hostIp = if (config.virtualisation.podman.defaultNetwork.settings ? subnets) + then (builtins.elemAt config.virtualisation.podman.defaultNetwork.settings.subnets 0).gateway + else "10.88.0.1"; } \ No newline at end of file diff --git a/modules/server/containers/default.nix b/modules/server/containers/default.nix index 5fafaba..b0905e8 100644 --- a/modules/server/containers/default.nix +++ b/modules/server/containers/default.nix @@ -17,6 +17,7 @@ in{ allPathConfigs = lib.concatMap (app: app.paths) appsList; allSetupConfigs = lib.concatMap (app: if app.setup?script then [({name = app.name; envFile="";} // app.setup)] else []) appsList; allCronsConfigs = lib.concatMap (app: app.cron) appsList; + allVMConfigs = builtins.filter (app: app.vm != null) appsList; in{ virtualisation.oci-containers = { backend = "podman"; @@ -47,7 +48,30 @@ in{ ''; startAt = "weekly"; }; - } // lib.listToAttrs (lib.concatMap (e: [{ + } + // lib.listToAttrs (lib.concatMap (e: [{ + name = "${e.name}-vm"; + value = { + description = "Isolated NixOS Guest VM for ${e.name}"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + QEMU_VM_REG_SND = "0"; + NIX_DISK_IMAGE = "/media/data/kvm/${e.name}-guest.qcw2"; + }; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = "10s"; + ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /media/data/kvm"; + ExecStart = '' + ${builder.mkVm { name = e.name; vm = e.vm; }}/bin/run-${e.name}-vm -nographic + ''; + }; + }; + }]) allVMConfigs) + // lib.listToAttrs (lib.concatMap (e: [{ name = "${e.name}-setup"; value = { description = "Run ${e.name} setup"; diff --git a/modules/shared/syscfg/server.nix b/modules/shared/syscfg/server.nix index 0253134..0f9da90 100644 --- a/modules/shared/syscfg/server.nix +++ b/modules/shared/syscfg/server.nix @@ -29,6 +29,7 @@ in with lib; { paths = lib.mkOption {type = lib.types.listOf lib.types.attrs; default = [ ];}; containers = lib.mkOption {type = lib.types.attrsOf lib.types.attrs; default = { };}; + vm = lib.mkOption {type = lib.types.nullOr lib.types.attrs; default = null;}; cron = lib.mkOption {type = lib.types.listOf lib.types.str; default = [ ];}; setup = { diff --git a/systems/sandbox/cfg.nix b/systems/sandbox/cfg.nix index 835c5d7..879f213 100644 --- a/systems/sandbox/cfg.nix +++ b/systems/sandbox/cfg.nix @@ -53,8 +53,7 @@ # ===== DEV ===== gitea.subdomain = "git"; # ===== HOME ===== - # homeassistant.subdomain = "hass"; - # frigate = { subdomain = "hass"; subpath = "cam"; }; + openhab.subdomain = "hass"; # trmnl = { subdomain = "hass"; subpath = "trmnl"; }; # influx.subdomain = "metrum"; };