really loving clan!

This commit is contained in:
Chris Kruining 2026-04-02 17:24:18 +02:00
parent a8a639db6e
commit d60d4badf3
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
15 changed files with 474 additions and 81 deletions

View file

@ -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 = [

View file

@ -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 = {...}: {};
};
};
}

View file

@ -1,13 +0,0 @@
{...}: let
module = ./default.nix;
in {
clan.modules.caddy = module;
# perSystem = {...}: {
# clan.nixosTests.caddy = {
# imports = [];
# clan.modules."@arda/caddy" = module;
# };
# };
}

View file

@ -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 // {};
};
})
];
};
};
};
}

View file

@ -0,0 +1,13 @@
{...}: let
module = ./default.nix;
in {
clan.modules.gateway = module;
# perSystem = {...}: {
# clan.nixosTests.gateway = {
# imports = [];
# clan.modules."@arda/gateway" = module;
# };
# };
}

View file

View file

@ -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";
};
};
};
};
})
];
};
};
};
}

View file

@ -0,0 +1,13 @@
{...}: let
module = ./default.nix;
in {
clan.modules.identity = module;
# perSystem = {...}: {
# clan.nixosTests.identity = {
# imports = [];
# clan.modules."@arda/identity" = module;
# };
# };
}

View file

@ -95,7 +95,7 @@ in {
owner = "postgres";
group = "postgres";
mode = "0600";
restartUnits = ["service.postgresql"];
restartUnits = ["postgresql.service"];
};
}
// password_files;

View file

@ -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

View file

@ -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;

View file

@ -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.

47
interfaces/gateway.nix Normal file
View file

@ -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 = {};
};
};
}

View file

@ -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 = {};
};
};
}