diff --git a/clan.nix b/clan.nix index 88ad92a..f0ec880 100644 --- a/clan.nix +++ b/clan.nix @@ -9,7 +9,7 @@ exportInterfaces = { persistence = import ./interfaces/persistence.nix; - servarr = import ./interfaces/servarr.nix; + gateway = import ./interfaces/gateway.nix; }; inventory.machines = { @@ -17,19 +17,25 @@ name = "aule"; description = "Planned build server."; machineClass = "nixos"; - tags = ["planned" "build"]; + tags = []; }; mandos = { name = "mandos"; description = "Living room Steam box."; machineClass = "nixos"; - tags = ["gaming" "living-room"]; + tags = [ + "capability:mobility:stationary" + "operational:availability:wake-on-demand" + ]; }; manwe = { name = "manwe"; description = "Main desktop."; machineClass = "nixos"; - tags = ["desktop"]; + tags = [ + "capability:mobility:stationary" + "operational:availability:manual" + ]; }; melkor = { name = "melkor"; @@ -41,19 +47,30 @@ name = "orome"; description = "Work laptop."; machineClass = "nixos"; - tags = ["laptop" "work"]; + tags = [ + "capability:mobility:portable" + "operational:availability:manual" + ]; }; tulkas = { name = "tulkas"; description = "Steam Deck."; machineClass = "nixos"; - tags = ["gaming" "handheld"]; + tags = [ + "capability:mobility:portable" + "operational:availability:manual" + ]; }; ulmo = { name = "ulmo"; description = "Primary self-hosted services machine."; machineClass = "nixos"; - tags = ["server" "services"]; + tags = [ + "capability:mobility:stationary" + "operational:availability:always-on" + "operational:storage:large" + "operational:role:gateway" + ]; }; varda = { name = "varda"; @@ -69,6 +86,17 @@ }; }; + inventory.tags = { + config, + machines, + ... + }: { + # tag_name = [ "list" "of" "machines" ] + "capability:hardware:gpu" = [""]; + "capability:hardware:audio" = [""]; + "capability:hardware:bluetooth" = [""]; + }; + inventory.instances = { users-chris = { module = { @@ -89,6 +117,44 @@ }; }; + clanDns = { + module = { + name = "dm-dns"; + input = "clan-core"; + }; + + roles.default.tags = ["all"]; + }; + + gateway = { + module = { + name = "gateway"; + input = "self"; + }; + + roles.default = { + tags = ["operational:role:gateway"]; + + settings = { + driver = "caddy"; + }; + }; + }; + + identity = { + module = { + name = "identity"; + input = "self"; + }; + + roles.default = { + tags = ["operational:availability:always-on"]; + + settings = { + }; + }; + }; + persistence = { module = { name = "persistence"; @@ -96,7 +162,7 @@ }; # TODO :: Convert to use tags instead - roles.default.machines.ulmo.settings = {}; + roles.default.tags = ["operational:availability:always-on" "operational:storage:large"]; }; servarr = { @@ -105,12 +171,14 @@ input = "self"; }; - # TODO :: Convert to use tags instead roles.default = { - machines.ulmo.settings = {}; + tags = ["operational:availability:always-on"]; settings = { enable = true; + + persistence_instance = "persistence"; + services = { sonarr = { rootFolders = [ diff --git a/clanServices/caddy/default.nix b/clanServices/caddy/default.nix deleted file mode 100644 index fc3ae7a..0000000 --- a/clanServices/caddy/default.nix +++ /dev/null @@ -1,23 +0,0 @@ -{...}: { - _class = "clan.service"; - manifest = { - name = "arda/caddy"; - description = '' - Configuration of reverse proxy. - ''; - categories = [ "Service", "Media" ]; - readme = builtins.readFile ./README.md; - }; - - roles.default = { - description = ''''; - - interface = {...}: { - options = {}; - }; - - perInstance = {...}: { - nixosModule = {...}: {}; - }; - }; -} diff --git a/clanServices/caddy/flake-module.nix b/clanServices/caddy/flake-module.nix deleted file mode 100644 index 10a5a52..0000000 --- a/clanServices/caddy/flake-module.nix +++ /dev/null @@ -1,13 +0,0 @@ -{...}: let - module = ./default.nix; -in { - clan.modules.caddy = module; - - # perSystem = {...}: { - # clan.nixosTests.caddy = { - # imports = []; - - # clan.modules."@arda/caddy" = module; - # }; - # }; -} diff --git a/clanServices/caddy/README.md b/clanServices/gateway/README.md similarity index 100% rename from clanServices/caddy/README.md rename to clanServices/gateway/README.md diff --git a/clanServices/gateway/default.nix b/clanServices/gateway/default.nix new file mode 100644 index 0000000..ce837fd --- /dev/null +++ b/clanServices/gateway/default.nix @@ -0,0 +1,94 @@ +{ + lib, + clanLib, + exports, + ... +}: let + inherit (builtins) toString; +in { + _class = "clan.service"; + manifest = { + name = "arda/gateway"; + description = '' + ''; + readme = builtins.readFile ./README.md; + exports = { + inputs = []; + out = []; + }; + }; + + roles.default = { + description = ''''; + + interface = {lib, ...}: let + inherit (lib) mkOption types; + in { + options = { + driver = mkOption { + type = types.enum ["caddy" "nginx"]; + }; + + hosts = mkOption { + type = types.attrsOf types.str; + default = {}; + }; + }; + }; + + perInstance = { + mkExports, + machine, + settings, + ... + }: let + reverse_proxies = + exports + |> clanLib.selectExports (_scope: true) + |> lib.mapAttrsToList (_: value: (value.gateway.services or {}) |> lib.attrValues) + |> lib.concatLists + |> lib.map ({ + name, + protocol, + host, + port, + }: { + name = "${name}.${machine.name}.arda"; + value = { + extraConfig = '' + reverse_proxy ${protocol}://${host}:${toString port} + ''; + }; + }) + |> lib.listToAttrs; + in { + # exports = + # mkExports { + # }; + + nixosModule = { + lib, + pkgs, + ... + }: let + inherit (lib) mkMerge mkIf; + + caddyPackage = pkgs.caddy.withPlugins { + plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; + hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; + }; + in { + config = mkMerge [ + (lib.mkIf (settings.driver == "caddy") { + services.caddy = { + enable = true; + package = caddyPackage; + + virtualHosts = reverse_proxies // {}; + }; + }) + ]; + }; + }; + }; +} diff --git a/clanServices/gateway/flake-module.nix b/clanServices/gateway/flake-module.nix new file mode 100644 index 0000000..a53d5d7 --- /dev/null +++ b/clanServices/gateway/flake-module.nix @@ -0,0 +1,13 @@ +{...}: let + module = ./default.nix; +in { + clan.modules.gateway = module; + + # perSystem = {...}: { + # clan.nixosTests.gateway = { + # imports = []; + + # clan.modules."@arda/gateway" = module; + # }; + # }; +} diff --git a/clanServices/identity/README.md b/clanServices/identity/README.md new file mode 100644 index 0000000..e69de29 diff --git a/clanServices/identity/default.nix b/clanServices/identity/default.nix new file mode 100644 index 0000000..1c07781 --- /dev/null +++ b/clanServices/identity/default.nix @@ -0,0 +1,138 @@ +{ + lib, + clanLib, + exports, + ... +}: let + inherit (builtins) toString; +in { + _class = "clan.service"; + manifest = { + name = "arda/identity"; + description = '' + ''; + readme = builtins.readFile ./README.md; + exports = { + inputs = ["persistence"]; + out = ["gateway"]; + }; + }; + + roles.default = { + description = ''''; + + interface = {lib, ...}: let + inherit (lib) mkOption types; + in { + options = { + driver = mkOption { + type = types.enum ["zitadel"]; + default = "zitadel"; + }; + + port = mkOption { + type = types.port; + default = 9092; + }; + }; + }; + + perInstance = { + mkExports, + settings, + ... + }: let + database = + exports + |> clanLib.getExport { + serviceName = "arda/persistence"; + roleName = "default"; + machineName = machine.name; + instanceName = settings.persistence_instance; + } + |> (v: v.persistence.driver.postgresql); + in { + exports = mkExports { + gateway.services.identity = {port = settings.port;}; + }; + + nixosModule = { + lib, + pkgs, + config, + ... + }: let + inherit (lib) mkMerge mkIf; + in { + config = mkMerge [ + (lib.mkIf (settings.driver == "zitadel") { + clan.core.vars.generators.zitadel = { + dependencies = ["persistence"]; + + files = { + masterKey = { + deploy = true; + owner = "zitadel"; + group = "zitadel"; + restartUnits = ["zitadel.service"]; + }; + + settings = { + deploy = true; + owner = "zitadel"; + group = "zitadel"; + restartUnits = ["zitadel.service"]; + }; + }; + + runtimeInputs = with pkgs; [pwgen]; + script = '' + pwgen -s 32 1 > $out/masterKey + + cat << EOL > $out/settings + Database: + postgres: + User: + Password: $(cat $in/persistence/zitadel_password) + Admin: + Password: $(cat $in/persistence/zitadel_password) + EOL + ''; + }; + + environment.systemPackages = with pkgs; [ + zitadel + ]; + + services.zitadel = { + enable = true; + masterKeyFile = config.clan.core.vars.generators.zitadel.files.masterKey.path; + + tlsMode = "external"; + + extraSettingsPaths = [ + config.clan.core.vars.generators.zitadel.files.settings.path + ]; + + settings = { + Port = settings.port; + + Database.postgres = { + Host = database.host; + Port = database.port; + Databae = "zitadel"; + User = { + Username = "zitadel"; + }; + Admin = { + Username = "zitadel"; + }; + }; + }; + }; + }) + ]; + }; + }; + }; +} diff --git a/clanServices/identity/flake-module.nix b/clanServices/identity/flake-module.nix new file mode 100644 index 0000000..1dd8972 --- /dev/null +++ b/clanServices/identity/flake-module.nix @@ -0,0 +1,13 @@ +{...}: let + module = ./default.nix; +in { + clan.modules.identity = module; + + # perSystem = {...}: { + # clan.nixosTests.identity = { + # imports = []; + + # clan.modules."@arda/identity" = module; + # }; + # }; +} diff --git a/clanServices/peristence/default.nix b/clanServices/peristence/default.nix index c3b5d9e..9150e75 100644 --- a/clanServices/peristence/default.nix +++ b/clanServices/peristence/default.nix @@ -95,7 +95,7 @@ in { owner = "postgres"; group = "postgres"; mode = "0600"; - restartUnits = ["service.postgresql"]; + restartUnits = ["postgresql.service"]; }; } // password_files; diff --git a/clanServices/servarr/default.nix b/clanServices/servarr/default.nix index ccdbb66..634d4f6 100644 --- a/clanServices/servarr/default.nix +++ b/clanServices/servarr/default.nix @@ -1,8 +1,11 @@ { exports, + clanLib, lib, ... -}: { +}: let + inherit (lib) toString; +in { _class = "clan.service"; manifest = { name = "arda/servarr"; @@ -10,8 +13,8 @@ categories = ["Service" "Media"]; readme = builtins.readFile ./README.md; exports = { - inputs = []; - out = ["servarr" "persistence"]; + inputs = ["persistence"]; + out = ["gateway" "persistence"]; }; }; @@ -24,13 +27,8 @@ options = { enable = mkEnableOption "Enable configured *arr services"; - database = { - host = mkOption { - type = types.str; - }; - port = mkOption { - type = types.port; - }; + persistence_instance = mkOption { + type = types.str; }; services = mkOption { @@ -62,17 +60,25 @@ ... }: { exports = mkExports { + # endpoints.hosts = + # settings.services + # |> lib.attrNames + # |> (s: lib.concat s ["sabnzbd" "qbittorrent" "flaresolverr"]) + # |> lib.map (service: "${service}.${machine.name}.arda"); + persistence.databases = settings.services |> lib.attrNames; - servarr.services = + gateway.services = settings.services |> lib.attrNames - |> lib.concat ["sabnzbd" "qbittorrent" "flaresolverr"] + # |> (s: lib.concat s ["sabnzbd" "qbittorrent" "flaresolverr"]) |> lib.imap1 (i: name: { inherit name; - value = {port = 2000 + i;}; + value = { + port = 2000 + i; + }; }) |> lib.listToAttrs; }; @@ -83,9 +89,20 @@ pkgs, ... }: let - servarr = import ./lib.nix (args // {inherit settings;}); services = settings.services |> lib.attrNames; service_count = services |> lib.length; + + database = + exports + |> clanLib.getExport { + serviceName = "arda/persistence"; + roleName = "default"; + machineName = machine.name; + instanceName = settings.persistence_instance; + } + |> (v: v.persistence.driver.postgresql); + + servarr = import ./lib.nix (args // {inherit settings database;}); in { imports = [ (import ./sabnzbd.nix (args diff --git a/clanServices/servarr/lib.nix b/clanServices/servarr/lib.nix index 0abda3c..5bcd6a5 100644 --- a/clanServices/servarr/lib.nix +++ b/clanServices/servarr/lib.nix @@ -4,6 +4,7 @@ lib, pkgs, settings, + database, ... }: let inherit (lib) mkIf; @@ -51,7 +52,7 @@ in { enable = true; - openFirewall = true; + # openFirewall = true; environmentFiles = [ config.clan.core.vars.generators.${service}.files."config.env".path @@ -61,14 +62,14 @@ auth.authenticationMethod = "External"; server = { - bindaddress = "0.0.0.0"; + bindaddress = "[::1]"; port = options.port; }; # Password provided via environment file postgres = { - host = settings.database.host; - port = toString settings.database.port; + host = database.host; + port = toString database.port; user = service; maindb = service; logdb = service; @@ -322,6 +323,10 @@ in { clan.core.vars.generators.${service} = createGenerator (args // {inherit service options;}); services.${service} = createService (args // {inherit service options;}); + # services.caddy.virtualHosts."${service}.ulmo.arda".extraConfig = '' + # reverse_proxy http://[::1]:${toString options.port} + # ''; + systemd.services."${service}-apply-infra" = lib.mkIf settings.enable (createSystemdService (args // {inherit service options;})); }) |> lib.mkMerge; diff --git a/docs/plans/tagging-strategy.md b/docs/plans/tagging-strategy.md index eb77376..cb217f9 100644 --- a/docs/plans/tagging-strategy.md +++ b/docs/plans/tagging-strategy.md @@ -74,6 +74,56 @@ The intention is: - `capability:*` describes stable machine traits - `operational:*` describes automation-relevant policy or availability behavior +## Tag catalog + +This is the current list of tags discussed so far, grouped by status. + +### Agreed capability tags + +- `capability:runtime:interactive` +- `capability:runtime:headless` +- `capability:hardware:gpu` +- `capability:hardware:audio` +- `capability:hardware:bluetooth` +- `capability:mobility:portable` +- `capability:mobility:stationary` + +### Agreed operational tags + +- `operational:availability:always-on` +- `operational:availability:wake-on-demand` +- `operational:availability:manual` +- `operational:workload:interruptible` + +### Explicitly rejected or deferred + +- GPU vendor-specific tags such as AMD- or NVIDIA-specific variants +- service-presence tags such as Jellyfin, Grafana, Forgejo, or PostgreSQL +- service-topology tags such as NFS producer or consumer +- application-presence tags such as Discord or TeamSpeak +- desktop-environment tags such as Plasma or Gamescope +- location tags such as "living room" unless location later becomes a deliberate scheduling dimension + +## Current static tags in `clan.nix` + +These are the manually assigned tags currently present in the inventory. Settings-derived tags are intentionally not listed here because they are meant to be computed rather than maintained by hand. + +- `mandos` + - `capability:mobility:stationary` + - `operational:availability:wake-on-demand` +- `manwe` + - `capability:mobility:stationary` + - `operational:availability:manual` +- `orome` + - `capability:mobility:portable` + - `operational:availability:manual` +- `tulkas` + - `capability:mobility:portable` + - `operational:availability:manual` +- `ulmo` + - `capability:mobility:stationary` + - `operational:availability:always-on` + ## Capability tags These are the strongest candidates for machine tags. diff --git a/interfaces/gateway.nix b/interfaces/gateway.nix new file mode 100644 index 0000000..5dcdce9 --- /dev/null +++ b/interfaces/gateway.nix @@ -0,0 +1,47 @@ +{lib, ...}: let + inherit (lib) mkOption types; +in { + options = { + services = mkOption { + type = types.attrsOf (types.submodule ({name, ...}: { + options = { + name = mkOption { + type = types.str; + default = name; + }; + + protocol = mkOption { + type = types.str; + default = "http"; + }; + + host = mkOption { + type = types.str; + default = "[::1]"; + }; + + port = mkOption { + type = types.port; + }; + }; + })); + default = {}; + }; + + functions = mkOption { + type = types.attrsOf (types.submodule ({name, ...}: { + options = { + name = mkOption { + type = types.str; + default = name; + }; + + body = mkOption { + type = types.str; + }; + }; + })); + default = {}; + }; + }; +} diff --git a/interfaces/servarr.nix b/interfaces/servarr.nix deleted file mode 100644 index 3cd824a..0000000 --- a/interfaces/servarr.nix +++ /dev/null @@ -1,16 +0,0 @@ -{lib, ...}: let - inherit (lib) mkOption types; -in { - options = { - services = mkOption { - type = types.attrsOf (types.submodule { - options = { - port = mkOption { - type = types.port; - }; - }; - }); - default = {}; - }; - }; -}