Compare commits
1 Commits
push-unvrq
...
exports-en
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63aceeeb4e |
@@ -1,10 +1,8 @@
|
|||||||
clanServices/.* @pinpox @kenji
|
clanServices/.* @pinpox @kenji
|
||||||
|
|
||||||
lib/test/container-test-driver/.* @DavHau @mic92
|
lib/test/container-test-driver/.* @DavHau @mic92
|
||||||
lib/inventory/.* @hsjobeki
|
lib/modules/inventory/.* @hsjobeki
|
||||||
lib/inventoryClass/.* @hsjobeki
|
lib/modules/inventoryClass/.* @hsjobeki
|
||||||
|
|
||||||
modules/.* @hsjobeki
|
|
||||||
|
|
||||||
pkgs/clan-app/ui/.* @hsjobeki @brianmcgee
|
pkgs/clan-app/ui/.* @hsjobeki @brianmcgee
|
||||||
pkgs/clan-app/clan_app/.* @qubasa @hsjobeki
|
pkgs/clan-app/clan_app/.* @qubasa @hsjobeki
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ in
|
|||||||
# Container Tests
|
# Container Tests
|
||||||
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
||||||
nixos-systemd-abstraction = self.clanLib.test.containerTest ./systemd-abstraction nixosTestArgs;
|
nixos-systemd-abstraction = self.clanLib.test.containerTest ./systemd-abstraction nixosTestArgs;
|
||||||
nixos-llm-test = self.clanLib.test.containerTest ./llm nixosTestArgs;
|
|
||||||
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
|
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
|
||||||
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
|
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
|
||||||
nixos-test-extra-python-packages = self.clanLib.test.containerTest ./test-extra-python-packages nixosTestArgs;
|
nixos-test-extra-python-packages = self.clanLib.test.containerTest ./test-extra-python-packages nixosTestArgs;
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
{ self, pkgs, ... }:
|
|
||||||
|
|
||||||
let
|
|
||||||
|
|
||||||
cli = self.packages.${pkgs.hostPlatform.system}.clan-cli-full;
|
|
||||||
|
|
||||||
ollama-model = pkgs.callPackage ./qwen3-4b-instruct.nix { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = "llm";
|
|
||||||
|
|
||||||
nodes = {
|
|
||||||
peer1 =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
users.users.text-user = {
|
|
||||||
isNormalUser = true;
|
|
||||||
linger = true;
|
|
||||||
uid = 1000;
|
|
||||||
extraGroups = [ "systemd-journal" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Set environment variables for user systemd
|
|
||||||
environment.extraInit = ''
|
|
||||||
if [ "$(id -u)" = "1000" ]; then
|
|
||||||
export XDG_RUNTIME_DIR="/run/user/1000"
|
|
||||||
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
|
|
||||||
|
|
||||||
ollama_dir="$HOME/.ollama"
|
|
||||||
mkdir -p "$ollama_dir"
|
|
||||||
ln -sf ${ollama-model}/models "$ollama_dir"/models
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Enable PAM for user systemd sessions
|
|
||||||
security.pam.services.systemd-user = {
|
|
||||||
startSession = true;
|
|
||||||
# Workaround for containers - use pam_permit to avoid helper binary issues
|
|
||||||
text = pkgs.lib.mkForce ''
|
|
||||||
account required pam_permit.so
|
|
||||||
session required pam_permit.so
|
|
||||||
session required pam_env.so conffile=/etc/pam/environment readenv=0
|
|
||||||
session required ${pkgs.systemd}/lib/security/pam_systemd.so
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
|
||||||
cli
|
|
||||||
pkgs.ollama
|
|
||||||
(cli.pythonRuntime.withPackages (
|
|
||||||
ps: with ps; [
|
|
||||||
pytest
|
|
||||||
pytest-xdist
|
|
||||||
(cli.pythonRuntime.pkgs.toPythonModule cli)
|
|
||||||
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib
|
|
||||||
]
|
|
||||||
))
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript =
|
|
||||||
{ ... }:
|
|
||||||
''
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
peer1.wait_for_unit("multi-user.target")
|
|
||||||
peer1.wait_for_unit("user@1000.service")
|
|
||||||
|
|
||||||
# Fix user journal permissions so text-user can read their own logs
|
|
||||||
peer1.succeed("chown text-user:systemd-journal /var/log/journal/*/user-1000.journal*")
|
|
||||||
peer1.succeed("chmod 640 /var/log/journal/*/user-1000.journal*")
|
|
||||||
# the -o adopts="" is needed to overwrite any args coming from pyproject.toml
|
|
||||||
# -p no:cacheprovider disables pytest's cacheprovider which tries to write to the nix store in this case
|
|
||||||
cmd = "su - text-user -c 'pytest -s -n0 -m service_runner -p no:cacheprovider -o addopts="" ${cli.passthru.sourceWithTests}/clan_lib/llm'"
|
|
||||||
print("Running tests with command: " + cmd)
|
|
||||||
|
|
||||||
# Run tests as text-user (environment variables are set automatically)
|
|
||||||
peer1.succeed(cmd)
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{ pkgs }:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Got them from https://github.com/Gholamrezadar/ollama-direct-downloader
|
|
||||||
|
|
||||||
# Download manifest
|
|
||||||
manifest = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/manifests/4b-instruct";
|
|
||||||
# You'll need to calculate this hash - run the derivation once and it will tell you the correct hash
|
|
||||||
hash = "sha256-Dtze80WT6sGqK+nH0GxDLc+BlFrcpeyi8nZiwY8Wi6A=";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Download blobs
|
|
||||||
blob1 = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:b72accf9724e93698c57cbd3b1af2d3341b3d05ec2089d86d273d97964853cd2";
|
|
||||||
hash = "sha256-tyrM+XJOk2mMV8vTsa8tM0Gz0F7CCJ2G0nPZeWSFPNI=";
|
|
||||||
};
|
|
||||||
|
|
||||||
blob2 = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:85e4a5b7b8ef0e48af0e8658f5aaab9c2324c76c1641493f4d1e25fce54b18b9";
|
|
||||||
hash = "sha256-heSlt7jvDkivDoZY9aqrnCMkx2wWQUk/TR4l/OVLGLk=";
|
|
||||||
};
|
|
||||||
|
|
||||||
blob3 = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:eade0a07cac7712787bbce23d12f9306adb4781d873d1df6e16f7840fa37afec";
|
|
||||||
hash = "sha256-6t4KB8rHcSeHu84j0S+TBq20eB2HPR324W94QPo3r+w=";
|
|
||||||
};
|
|
||||||
|
|
||||||
blob4 = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:d18a5cc71b84bc4af394a31116bd3932b42241de70c77d2b76d69a314ec8aa12";
|
|
||||||
hash = "sha256-0YpcxxuEvErzlKMRFr05MrQiQd5wx30rdtaaMU7IqhI=";
|
|
||||||
};
|
|
||||||
|
|
||||||
blob5 = pkgs.fetchurl {
|
|
||||||
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:0914c7781e001948488d937994217538375b4fd8c1466c5e7a625221abd3ea7a";
|
|
||||||
hash = "sha256-CRTHeB4AGUhIjZN5lCF1ODdbT9jBRmxeemJSIavT6no=";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
pname = "ollama-qwen3-4b-instruct";
|
|
||||||
version = "1.0";
|
|
||||||
|
|
||||||
dontUnpack = true;
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
mkdir -p $out/models/manifests/registry.ollama.ai/library/qwen3
|
|
||||||
mkdir -p $out/models/blobs
|
|
||||||
|
|
||||||
# Copy manifest
|
|
||||||
cp ${manifest} $out/models/manifests/registry.ollama.ai/library/qwen3/4b-instruct
|
|
||||||
|
|
||||||
# Copy blobs with correct names
|
|
||||||
cp ${blob1} $out/models/blobs/sha256-b72accf9724e93698c57cbd3b1af2d3341b3d05ec2089d86d273d97964853cd2
|
|
||||||
cp ${blob2} $out/models/blobs/sha256-85e4a5b7b8ef0e48af0e8658f5aaab9c2324c76c1641493f4d1e25fce54b18b9
|
|
||||||
cp ${blob3} $out/models/blobs/sha256-eade0a07cac7712787bbce23d12f9306adb4781d873d1df6e16f7840fa37afec
|
|
||||||
cp ${blob4} $out/models/blobs/sha256-d18a5cc71b84bc4af394a31116bd3932b42241de70c77d2b76d69a314ec8aa12
|
|
||||||
cp ${blob5} $out/models/blobs/sha256-0914c7781e001948488d937994217538375b4fd8c1466c5e7a625221abd3ea7a
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
# buildPhase already created everything in $out
|
|
||||||
:
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
|
||||||
description = "Qwen3 4B Instruct model for Ollama";
|
|
||||||
license = "apache-2.0";
|
|
||||||
platforms = platforms.all;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -62,6 +62,6 @@ in
|
|||||||
peer1.succeed("chmod 640 /var/log/journal/*/user-1000.journal*")
|
peer1.succeed("chmod 640 /var/log/journal/*/user-1000.journal*")
|
||||||
|
|
||||||
# Run tests as text-user (environment variables are set automatically)
|
# Run tests as text-user (environment variables are set automatically)
|
||||||
peer1.succeed("su - text-user -c 'pytest -p no:cacheprovider -o addopts="" -s -n0 ${cli.passthru.sourceWithTests}/clan_lib/service_runner'")
|
peer1.succeed("su - text-user -c 'pytest -s -n0 ${cli}/${cli.pythonRuntime.sitePackages}/clan_lib/service_runner'")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{
|
{ ... }:
|
||||||
clanLib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
sharedInterface =
|
sharedInterface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
@@ -54,15 +51,15 @@ let
|
|||||||
builtins.foldl' (
|
builtins.foldl' (
|
||||||
urls: name:
|
urls: name:
|
||||||
let
|
let
|
||||||
ip = clanLib.vars.getPublicValue {
|
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
|
||||||
flake = config.clan.core.settings.directory;
|
|
||||||
machine = name;
|
|
||||||
generator = "zerotier";
|
|
||||||
file = "zerotier-ip";
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
if ip != null then urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ] else urls
|
if builtins.pathExists ipPath then
|
||||||
|
let
|
||||||
|
ip = builtins.readFile ipPath;
|
||||||
|
in
|
||||||
|
urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ]
|
||||||
|
else
|
||||||
|
urls
|
||||||
) [ ] (builtins.attrNames ((roles.admin.machines or { }) // (roles.signer.machines or { })))
|
) [ ] (builtins.attrNames ((roles.admin.machines or { }) // (roles.signer.machines or { })))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -159,14 +156,9 @@ in
|
|||||||
readHostKey =
|
readHostKey =
|
||||||
machine:
|
machine:
|
||||||
let
|
let
|
||||||
publicKey = clanLib.vars.getPublicValue {
|
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
||||||
flake = config.clan.core.settings.directory;
|
|
||||||
inherit machine;
|
|
||||||
generator = "data-mesher-host-key";
|
|
||||||
file = "public_key";
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
builtins.elemAt (lib.splitString "\n" publicKey) 1;
|
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
34
clanServices/firewall/default.nix
Normal file
34
clanServices/firewall/default.nix
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{ clanLib, inventory, ... }:
|
||||||
|
{
|
||||||
|
manifest.name = "clan-core/firewall";
|
||||||
|
manifest.description = "Configures firewall rules based on exported endpoints from other services";
|
||||||
|
|
||||||
|
roles.default.description = "Configures firewall rules based on exported endpoints from other services";
|
||||||
|
|
||||||
|
perMachine =
|
||||||
|
# firewall instances
|
||||||
|
{
|
||||||
|
exports,
|
||||||
|
machine,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
instances = clanLib.resolveInstances machine inventory;
|
||||||
|
|
||||||
|
instancesTcpPorts = builtins.concatLists (
|
||||||
|
map (
|
||||||
|
instanceName:
|
||||||
|
lib.mapAttrsToList (_endpointName: cfg: cfg.port) exports.instances.${instanceName}.endpoints
|
||||||
|
) instances
|
||||||
|
);
|
||||||
|
machineTcpPorts = lib.mapAttrsToList (
|
||||||
|
_endpointName: cfg: cfg.port
|
||||||
|
) exports.instances.${machine.name}.endpoints;
|
||||||
|
|
||||||
|
allowedPorts = instancesTcpPorts ++ machineTcpPorts;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
nixosModule.networking.firewall.allowedTCPPorts = allowedPorts;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -45,6 +45,12 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
exports.endpoints.greeting = {
|
||||||
|
port = 80;
|
||||||
|
};
|
||||||
|
exports.endpoints.uptime = {
|
||||||
|
port = 80;
|
||||||
|
};
|
||||||
# Analog to 'perSystem' of flake-parts.
|
# Analog to 'perSystem' of flake-parts.
|
||||||
# For every instance of this service we will add a nixosModule to a morning-machine
|
# For every instance of this service we will add a nixosModule to a morning-machine
|
||||||
nixosModule =
|
nixosModule =
|
||||||
@@ -89,6 +95,9 @@
|
|||||||
perMachine =
|
perMachine =
|
||||||
{ machine, ... }:
|
{ machine, ... }:
|
||||||
{
|
{
|
||||||
|
exports.endpoints.core-online = {
|
||||||
|
port = 8080;
|
||||||
|
};
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
options = {
|
options = {
|
||||||
host = lib.mkOption {
|
host = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "";
|
|
||||||
description = ''
|
description = ''
|
||||||
ip address or hostname (domain) of the machine
|
ip address or hostname (domain) of the machine
|
||||||
'';
|
'';
|
||||||
@@ -32,15 +31,17 @@
|
|||||||
};
|
};
|
||||||
perInstance =
|
perInstance =
|
||||||
{
|
{
|
||||||
instanceName,
|
roles,
|
||||||
settings,
|
lib,
|
||||||
machine,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
exports.networking = {
|
||||||
exports."internet/${instanceName}/default/${machine.name}".networking = {
|
# TODO add user space network support to clan-cli
|
||||||
hosts = [ settings.host ];
|
peers = lib.mapAttrs (_name: machine: {
|
||||||
|
host.plain = machine.settings.host;
|
||||||
|
SSHOptions = map (_x: "-J x") machine.settings.jumphosts;
|
||||||
|
}) roles.default.machines;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,10 +44,8 @@
|
|||||||
pkgs.openssl
|
pkgs.openssl
|
||||||
];
|
];
|
||||||
|
|
||||||
# TODO: Implement automated certificate rotation instead of using a 100-year expiration
|
|
||||||
script = ''
|
script = ''
|
||||||
openssl req -x509 -nodes -newkey rsa:4096 \
|
openssl req -x509 -nodes -newkey rsa:4096 \
|
||||||
-days 36500 \
|
|
||||||
-keyout "$out"/key \
|
-keyout "$out"/key \
|
||||||
-out "$out"/crt \
|
-out "$out"/crt \
|
||||||
-subj "/C=US/ST=CA/L=San Francisco/O=Example Corp/OU=IT/CN=example.com"
|
-subj "/C=US/ST=CA/L=San Francisco/O=Example Corp/OU=IT/CN=example.com"
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
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
|
k1PNWTkgLsXO8DNkCudxcvPElXfmaw6zwaLrZys=
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data: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,iv:dGSmyDNBdVyF54bYS/Zxm2NNXZyGtLjkyYlrI9/nKvc=,tag:ip2fy76NjObWbW20HyuZUA==,type:str]",
|
"data": "ENC[AES256_GCM,data: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,iv:q3j8EAokyyxiszf+wyRqxEr2igaD1bX7YnFx/NbsGg8=,tag:HKKYWRJEUwW2/TxL+5dSng==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"age": [
|
"age": [
|
||||||
{
|
{
|
||||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQS96MUFubVdOR2VCc2xO\ncTc5QnNHNTFpdURnSnF3dVhBQXQ3bnBuRW1RCngzSVlhSW9rNUxoSWdKcEtKVXc3\nQitLZ2NDUXBSUmxtVWpYRUlvOHVXcW8KLS0tIGZaWlRVak9NYmt2elpwYStYenRE\nanlkT3BET1FjQ2lFZkp3SXFMSkJSaVkKKkr+MNNqs6Ve3K5OrZfBEGlnc7OAthqf\nOZrP9NYOTMgkvhFsZTVpUS0zskry0iwmTNt+KeluYf0Tko8K53Kx2A==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaeXRjU214aWk5ajl1aW9E\naGJlb1ViaVRmMTBHdkFDQUNDZS94WFZiNUNvCllmWTJBck9hR3U3V09VWDZwQ2xI\nd3ZEQnBIUG5ZSTVIdS8rQ2FMYVhyNk0KLS0tIEE1UG8rSzFyU01sVXhGVHpoaE9i\nSis4Qi9tMGFqbTNMTDZUVk1ZdXkrM28Km4VkfaOsZ69ckjvrg+os43H/O1IoWHzC\nt4LqZRz1Tk7/d1aLWavSPPjVYrCOMZeNBqGbQpGfjjuXrafClRNQdQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXd2dVYmJIbUVVeXk5Nk1E\nekFiUldVVUhRTmE4dHRiTHNDdEMyS1pRV1RrCkNScGdXVSs4UU5id29DV0pZWDQr\nenV1QmpnOFk5aFpTTUxmb0hDVHZDdFkKLS0tIHpmalJtRC94bEhaUStmeUlHT21w\nd3o3UzJHZklxK0RCYUUxc2c3aG1XclkKEPq1ZgyGiAK/Hy4zT7wfdDfPEE3vMHpR\nzwQV5y3M3DmlnKQEvJu0DpQ334CyAcubZC7cswQdUrM8TPqJhb/TuA==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R1RHTGViTnRLVVkyM3J0\nbm96cGVPTlo4NXBNL0g1eEVSNG9DUkgwVFRBCmRKVTlMRmV3Tmg2RTZIclBlWlcr\ndzI5MUxhcllzbE1IMDNxa08zVkpITmsKLS0tIG01Y2dyQkY3UmRudFk2d0p6bThn\nemlaWnZoS3p4VHhMTFFwTm9VN0ttYzQKVbLFgtK6NIRIiryWHeeOPD45iwUds4QD\n7b8xYYoxlo+DETggxK6Vz3IdT/BSK5bFtgAxl864b5gW+Aw4c6AO5w==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2025-10-21T13:17:17Z",
|
"lastmodified": "2025-09-18T14:33:37Z",
|
||||||
"mac": "ENC[AES256_GCM,data:wdAFURkJZvclbz3UFPSPV9fma7zrZVEhMhsRqylGQMLepX/WohEAr8nJgeHl05be1Q8M8biPXCCoL0vfwg4BRZOkhD8PusJh8iBI3+STNQe/S1qoIK1ByfBFhJD+tIsVsgduLp6G32e6SRNvkuX3UpJqyViuRUavfQd3b8LRU4I=,iv:S3sMNTz5Kg4TxHj1tnk/ayiFuO74dR4aPnnomtkGByo=,tag:uive2bYe42s6VtPd03jTMw==,type:str]",
|
"mac": "ENC[AES256_GCM,data:XKCnd0QrAlOCECSeSvbLYHMLbmUh4fMRnLaTb5ARoP4Zc9joWGsCaRZxokc2/sG4BXA/6pkbQXHyIOudKbcBpVjjvs9E+6Mnzt53nfRoH/iOkYPbN2EO49okVZJXW0M1rlBxrxvGuiDlz2p2p6L7neKLy4EB482pYea5+dUr2Yw=,iv:oj/MkZCfkvCmAb79uzEvKwEAm1bKtWhS4rPRAWSgRgw=,tag:h5TPPILXkhJplnDT2Gqtfw==,type:str]",
|
||||||
"version": "3.11.0"
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.10.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
perInstance =
|
perInstance =
|
||||||
{ settings, roles, ... }:
|
{ settings, ... }:
|
||||||
{
|
{
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{
|
{
|
||||||
@@ -38,19 +38,8 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
|
||||||
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
|
||||||
# Collect searchDomains from all servers in this instance
|
|
||||||
allServerSearchDomains = lib.flatten (
|
|
||||||
lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) (
|
|
||||||
roles.server.machines or { }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
# Merge client's searchDomains with all servers' searchDomains
|
|
||||||
searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains);
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) {
|
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||||
share = true;
|
share = true;
|
||||||
files.id_ed25519.deploy = false;
|
files.id_ed25519.deploy = false;
|
||||||
files."id_ed25519.pub" = {
|
files."id_ed25519.pub" = {
|
||||||
@@ -65,9 +54,9 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.ssh.knownHosts.ssh-ca = lib.mkIf (searchDomains != [ ]) {
|
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||||
certAuthority = true;
|
certAuthority = true;
|
||||||
extraHostNames = builtins.map (domain: "*.${domain}") searchDomains;
|
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificate.searchDomains;
|
||||||
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
|
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
This a test README just to appease the eval warnings if we don't have one
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
Set up a CA chain for the clan. There will be one root CA for each instance
|
|
||||||
of the ssl service, then each host has its own host CA that is signed by the
|
|
||||||
instance-wide root CA.
|
|
||||||
|
|
||||||
Trusting the root CA, will result in also trusting the individual host CAs,
|
|
||||||
as they are signed by it.
|
|
||||||
|
|
||||||
Hosts can then use their respective host CAs to expose SSL secured services.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
exports,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
_class = "clan.service";
|
|
||||||
manifest.name = "clan-core/ssl";
|
|
||||||
manifest.description = "Set up a CA infrastucture for your clan";
|
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
|
||||||
|
|
||||||
# Generate a root CA for each instances of the ssl module.
|
|
||||||
exports = lib.mapAttrs' (instanceName: _: {
|
|
||||||
"ssl/${instanceName}///".vars.generators.ssl-root-ca =
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
files.key = { };
|
|
||||||
files.cert.secret = false;
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
config.pkgs.pkgs.openssl
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
# Generate CA private key (4096-bit RSA)
|
|
||||||
openssl genrsa -out "$out/key" 4096
|
|
||||||
|
|
||||||
# Generate self-signed CA certificate (valid for 10 years)
|
|
||||||
openssl req -new -x509 \
|
|
||||||
-key "$out/key" \
|
|
||||||
-out "$out/cert" \
|
|
||||||
-days 3650 \
|
|
||||||
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Root CA" \
|
|
||||||
-sha256
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}) config.instances;
|
|
||||||
|
|
||||||
roles.default = {
|
|
||||||
description = "Generate a host CA, signed by the root CA and trust the root CA";
|
|
||||||
perInstance =
|
|
||||||
{
|
|
||||||
instanceName,
|
|
||||||
machine,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# Generate a host CA, which depends on (is signed by) the root CA
|
|
||||||
exports = {
|
|
||||||
"ssl/${instanceName}/default/${machine.name}/".vars.generators.ssl-host-ca =
|
|
||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
dependencies = {
|
|
||||||
ssl-root-ca = exports."ssl/${instanceName}///".vars.generators.ssl-root-ca;
|
|
||||||
};
|
|
||||||
|
|
||||||
files.key = { };
|
|
||||||
files.cert.secret = false;
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
config.pkgs.pkgs.openssl
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
# Generate intermediate CA private key (4096-bit RSA)
|
|
||||||
openssl genrsa -out "$out/key" 4096
|
|
||||||
|
|
||||||
# Generate Certificate Signing Request (CSR) for intermediate CA
|
|
||||||
openssl req -new \
|
|
||||||
-key "$out/key" \
|
|
||||||
-out "$out/csr" \
|
|
||||||
-subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Host CA"
|
|
||||||
|
|
||||||
# Sign the CSR with the root CA to create the intermediate certificate
|
|
||||||
openssl x509 -req \
|
|
||||||
-in "$out/csr" \
|
|
||||||
-CA "$dependencies/ssl-root-ca/cert" \
|
|
||||||
-CAkey "$dependencies/ssl-root-ca/key" \
|
|
||||||
-CAcreateserial \
|
|
||||||
-out "$out/cert" \
|
|
||||||
-days 3650 \
|
|
||||||
-sha256 \
|
|
||||||
-extfile <(printf "basicConstraints=CA:TRUE\nkeyUsage=keyCertSign,cRLSign")
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
nixosModule =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
# We trust the (public) root CA certificate on all machines with this role
|
|
||||||
security.pki.certificateFiles = [
|
|
||||||
exports."ssl/${instanceName}///".vars.generators.ssl-root-ca.files.cert.path
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
self,
|
|
||||||
inputs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
module = ./default.nix;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
clan.modules = {
|
|
||||||
ssl = module;
|
|
||||||
};
|
|
||||||
perSystem =
|
|
||||||
{ ... }:
|
|
||||||
let
|
|
||||||
# Module that contains the tests
|
|
||||||
# This module adds:
|
|
||||||
# - legacyPackages.<system>.eval-tests-ssl
|
|
||||||
# - checks.<system>.eval-tests-ssl
|
|
||||||
# unit-test-module = (
|
|
||||||
# self.clanLib.test.flakeModules.makeEvalChecks {
|
|
||||||
# inherit module;
|
|
||||||
# inherit inputs;
|
|
||||||
# fileset = lib.fileset.unions [
|
|
||||||
# # The ssl service being tested
|
|
||||||
# ../../clanServices/ssl
|
|
||||||
# # Required modules
|
|
||||||
# ../../nixosModules/clanCore
|
|
||||||
# ];
|
|
||||||
# testName = "ssl";
|
|
||||||
# tests = ./tests/eval-tests.nix;
|
|
||||||
# # Optional arguments passed to the test
|
|
||||||
# testArgs = { };
|
|
||||||
# }
|
|
||||||
# );
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# imports = [ unit-test-module ];
|
|
||||||
|
|
||||||
clan.nixosTests.ssl = {
|
|
||||||
imports = [ ./tests/vm/default.nix ];
|
|
||||||
|
|
||||||
clan.modules.ssl = module;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
name = "ssl";
|
|
||||||
|
|
||||||
clan = {
|
|
||||||
directory = ./.;
|
|
||||||
inventory = {
|
|
||||||
machines.peer1 = { };
|
|
||||||
machines.peer2 = { };
|
|
||||||
|
|
||||||
instances."test" = {
|
|
||||||
module.name = "ssl";
|
|
||||||
module.input = "self";
|
|
||||||
roles.default.machines.peer1 = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript =
|
|
||||||
{ ... }:
|
|
||||||
''
|
|
||||||
start_all()
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ in
|
|||||||
../../clanServices/syncthing
|
../../clanServices/syncthing
|
||||||
# Required modules
|
# Required modules
|
||||||
../../nixosModules/clanCore
|
../../nixosModules/clanCore
|
||||||
../../nixosModules/machineModules
|
|
||||||
# Dependencies like clan-cli
|
# Dependencies like clan-cli
|
||||||
../../pkgs/clan-cli
|
../../pkgs/clan-cli
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ let
|
|||||||
# In this case it is 'self-zerotier-redux'
|
# In this case it is 'self-zerotier-redux'
|
||||||
# This is usually only used internally, but we can use it to test the evaluation of service module in isolation
|
# This is usually only used internally, but we can use it to test the evaluation of service module in isolation
|
||||||
# evaluatedService =
|
# evaluatedService =
|
||||||
# testFlake.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-zerotier-redux.config;
|
# testFlake.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-zerotier-redux.config;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
test_simple = {
|
test_simple = {
|
||||||
inherit testFlake;
|
inherit testFlake;
|
||||||
|
|
||||||
expr =
|
expr =
|
||||||
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-wifi.config;
|
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-wifi.config;
|
||||||
expected = 1;
|
expected = 1;
|
||||||
|
|
||||||
# expr = {
|
# expr = {
|
||||||
|
|||||||
@@ -54,10 +54,7 @@
|
|||||||
- For other controllers: The controller's /56 subnet
|
- For other controllers: The controller's /56 subnet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{
|
{ ... }:
|
||||||
clanLib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
# Shared module for extraHosts configuration
|
# Shared module for extraHosts configuration
|
||||||
extraHostsModule =
|
extraHostsModule =
|
||||||
@@ -77,12 +74,10 @@ let
|
|||||||
controllerHosts = lib.mapAttrsToList (
|
controllerHosts = lib.mapAttrsToList (
|
||||||
name: _value:
|
name: _value:
|
||||||
let
|
let
|
||||||
prefix = clanLib.vars.getPublicValue {
|
prefix = builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = name;
|
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
);
|
||||||
file = "prefix";
|
|
||||||
};
|
|
||||||
# Controller IP is always ::1 in their subnet
|
# Controller IP is always ::1 in their subnet
|
||||||
ip = prefix + "::1";
|
ip = prefix + "::1";
|
||||||
in
|
in
|
||||||
@@ -93,24 +88,20 @@ let
|
|||||||
peerHosts = lib.mapAttrsToList (
|
peerHosts = lib.mapAttrsToList (
|
||||||
peerName: peerValue:
|
peerName: peerValue:
|
||||||
let
|
let
|
||||||
peerSuffix = clanLib.vars.getPublicValue {
|
peerSuffix = builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = peerName;
|
+ "/vars/per-machine/${peerName}/wireguard-network-${instanceName}/suffix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
);
|
||||||
file = "suffix";
|
|
||||||
};
|
|
||||||
# Determine designated controller
|
# Determine designated controller
|
||||||
designatedController =
|
designatedController =
|
||||||
if (builtins.length (builtins.attrNames roles.controller.machines) == 1) then
|
if (builtins.length (builtins.attrNames roles.controller.machines) == 1) then
|
||||||
(builtins.head (builtins.attrNames roles.controller.machines))
|
(builtins.head (builtins.attrNames roles.controller.machines))
|
||||||
else
|
else
|
||||||
peerValue.settings.controller;
|
peerValue.settings.controller;
|
||||||
controllerPrefix = clanLib.vars.getPublicValue {
|
controllerPrefix = builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = designatedController;
|
+ "/vars/per-machine/${designatedController}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
);
|
||||||
file = "prefix";
|
|
||||||
};
|
|
||||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||||
in
|
in
|
||||||
"${peerIP} ${peerName}.${domain}"
|
"${peerIP} ${peerName}.${domain}"
|
||||||
@@ -229,12 +220,10 @@ in
|
|||||||
lib.mapAttrsToList (
|
lib.mapAttrsToList (
|
||||||
ctrlName: _:
|
ctrlName: _:
|
||||||
let
|
let
|
||||||
controllerPrefix = clanLib.vars.getPublicValue {
|
controllerPrefix = builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = ctrlName;
|
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
);
|
||||||
file = "prefix";
|
|
||||||
};
|
|
||||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||||
in
|
in
|
||||||
"${peerIP}/56"
|
"${peerIP}/56"
|
||||||
@@ -245,22 +234,20 @@ in
|
|||||||
|
|
||||||
# Connect to all controllers
|
# Connect to all controllers
|
||||||
peers = lib.mapAttrsToList (name: value: {
|
peers = lib.mapAttrsToList (name: value: {
|
||||||
publicKey = clanLib.vars.getPublicValue {
|
publicKey = (
|
||||||
flake = config.clan.core.settings.directory;
|
builtins.readFile (
|
||||||
machine = name;
|
config.clan.core.settings.directory
|
||||||
generator = "wireguard-keys-${instanceName}";
|
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
||||||
file = "publickey";
|
)
|
||||||
};
|
);
|
||||||
|
|
||||||
# Allow each controller's /56 subnet
|
# Allow each controller's /56 subnet
|
||||||
allowedIPs = [
|
allowedIPs = [
|
||||||
"${
|
"${
|
||||||
clanLib.vars.getPublicValue {
|
builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = name;
|
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
)
|
||||||
file = "prefix";
|
|
||||||
}
|
|
||||||
}::/56"
|
}::/56"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -362,29 +349,25 @@ in
|
|||||||
if allPeers ? ${name} then
|
if allPeers ? ${name} then
|
||||||
# For peers: they now have our entire /56 subnet
|
# For peers: they now have our entire /56 subnet
|
||||||
{
|
{
|
||||||
publicKey = clanLib.vars.getPublicValue {
|
publicKey = (
|
||||||
flake = config.clan.core.settings.directory;
|
builtins.readFile (
|
||||||
machine = name;
|
config.clan.core.settings.directory
|
||||||
generator = "wireguard-keys-${instanceName}";
|
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
||||||
file = "publickey";
|
)
|
||||||
};
|
);
|
||||||
|
|
||||||
# Allow the peer's /96 range in ALL controller subnets
|
# Allow the peer's /96 range in ALL controller subnets
|
||||||
allowedIPs = lib.mapAttrsToList (
|
allowedIPs = lib.mapAttrsToList (
|
||||||
ctrlName: _:
|
ctrlName: _:
|
||||||
let
|
let
|
||||||
controllerPrefix = clanLib.vars.getPublicValue {
|
controllerPrefix = builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = ctrlName;
|
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
);
|
||||||
file = "prefix";
|
peerSuffix = builtins.readFile (
|
||||||
};
|
config.clan.core.settings.directory
|
||||||
peerSuffix = clanLib.vars.getPublicValue {
|
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/suffix/value"
|
||||||
flake = config.clan.core.settings.directory;
|
);
|
||||||
machine = name;
|
|
||||||
generator = "wireguard-network-${instanceName}";
|
|
||||||
file = "suffix";
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
"${controllerPrefix}:${peerSuffix}/96"
|
"${controllerPrefix}:${peerSuffix}/96"
|
||||||
) roles.controller.machines;
|
) roles.controller.machines;
|
||||||
@@ -394,21 +377,19 @@ in
|
|||||||
else
|
else
|
||||||
# For other controllers: use their /56 subnet
|
# For other controllers: use their /56 subnet
|
||||||
{
|
{
|
||||||
publicKey = clanLib.vars.getPublicValue {
|
publicKey = (
|
||||||
flake = config.clan.core.settings.directory;
|
builtins.readFile (
|
||||||
machine = name;
|
config.clan.core.settings.directory
|
||||||
generator = "wireguard-keys-${instanceName}";
|
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
||||||
file = "publickey";
|
)
|
||||||
};
|
);
|
||||||
|
|
||||||
allowedIPs = [
|
allowedIPs = [
|
||||||
"${
|
"${
|
||||||
clanLib.vars.getPublicValue {
|
builtins.readFile (
|
||||||
flake = config.clan.core.settings.directory;
|
config.clan.core.settings.directory
|
||||||
machine = name;
|
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
||||||
generator = "wireguard-network-${instanceName}";
|
)
|
||||||
file = "prefix";
|
|
||||||
}
|
|
||||||
}::/56"
|
}::/56"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,7 @@
|
|||||||
🚧🚧🚧 Experimental 🚧🚧🚧
|
|
||||||
|
|
||||||
Use at your own risk.
|
|
||||||
|
|
||||||
We are still refining its interfaces, instability and breakages are expected.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan.
|
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan.
|
||||||
|
|
||||||
Yggdrasil is designed to be a future-proof and decentralised alternative to the
|
Yggdrasil is designed to be a future-proof and decentralised alternative to
|
||||||
structured routing protocols commonly used today on the internet. Inside your
|
the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines.
|
||||||
clan, it will allow you to reach all of your machines.
|
|
||||||
|
|
||||||
If you have other services in your inventory which export peers (e.g. the
|
|
||||||
`internet` or the services) as [service
|
|
||||||
exports](https://docs.clan.lol/reference/options/clan_service/#exports), they
|
|
||||||
will be added as yggdrasil peers automatically. This allows using the stable
|
|
||||||
yggdrasil IPv6 address to refer to other hosts and letting yggdrasil decide on
|
|
||||||
the best routing based on available connections.
|
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,12 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
options.extraPeers = lib.mkOption {
|
options.peers = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
Additional static peers to configure for this host. If you use a
|
Static peers to configure for this host.
|
||||||
VPN clan service, it will automatically be added as peers to other hosts.
|
If not set, local peers will be auto-discovered
|
||||||
Local peers are also auto-discovered and don't need to be added.
|
|
||||||
'';
|
'';
|
||||||
example = [
|
example = [
|
||||||
"tcp://192.168.1.1:6443"
|
"tcp://192.168.1.1:6443"
|
||||||
@@ -46,74 +45,16 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
perInstance =
|
perInstance =
|
||||||
{
|
{ settings, ... }:
|
||||||
settings,
|
|
||||||
roles,
|
|
||||||
exports,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
|
||||||
clan-core,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
|
||||||
|
|
||||||
mkPeers = ip: [
|
|
||||||
# "tcp://${ip}:6443"
|
|
||||||
"quic://${ip}:6443"
|
|
||||||
"ws://${ip}:6443"
|
|
||||||
"tls://${ip}:6443"
|
|
||||||
];
|
|
||||||
|
|
||||||
select' = clan-core.inputs.nix-select.lib.select;
|
|
||||||
|
|
||||||
# TODO make it nicer @lassulus, @picnoir wants microlens
|
|
||||||
# Get a list of all exported IPs from all VPN modules
|
|
||||||
# exportedPeerIPs = builtins.foldl' (
|
|
||||||
# acc: e:
|
|
||||||
# if e == { } then
|
|
||||||
# acc
|
|
||||||
# else
|
|
||||||
# acc ++ (lib.flatten (builtins.filter (s: s != "") (lib.attrValues (select' "peers.*.plain" e))))
|
|
||||||
# ) [ ] (lib.attrValues (select' "*.networking.?peers.*.host.?plain" exports));
|
|
||||||
|
|
||||||
# exports."internet/${instanceName}/default/${machine.name}".networking = {
|
|
||||||
# hosts = [ settings.host ];
|
|
||||||
# };
|
|
||||||
|
|
||||||
# exportedPeerIPs = (select' "*".networking.hosts exports);
|
|
||||||
exportedPeerIPs = lib.flatten (builtins.attrValues (select' "*.networking.hosts" exports));
|
|
||||||
|
|
||||||
# Construct a list of peers in yggdrasil format
|
|
||||||
exportedPeers = lib.flatten (map mkPeers exportedPeerIPs);
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
|
|
||||||
# Set <yggdrasil ip> <hostname>.<tld> for all hosts.
|
|
||||||
# Networking modules will then add themselves as peers, so we can
|
|
||||||
# always use this to resolve a host via the best possible route,
|
|
||||||
# doing fail-over if needed.
|
|
||||||
networking.extraHosts = lib.strings.concatStringsSep "\n" (
|
|
||||||
lib.filter (n: n != "") (
|
|
||||||
map (
|
|
||||||
name:
|
|
||||||
let
|
|
||||||
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/yggdrasil/address/value";
|
|
||||||
in
|
|
||||||
if builtins.pathExists ipPath then
|
|
||||||
"${builtins.readFile ipPath} ${name}.${config.clan.core.settings.tld}"
|
|
||||||
else
|
|
||||||
""
|
|
||||||
) (lib.attrNames roles.default.machines)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
clan.core.vars.generators.yggdrasil = {
|
clan.core.vars.generators.yggdrasil = {
|
||||||
|
|
||||||
files.privateKey = { };
|
files.privateKey = { };
|
||||||
@@ -158,7 +99,7 @@
|
|||||||
settings = {
|
settings = {
|
||||||
PrivateKeyPath = "/key";
|
PrivateKeyPath = "/key";
|
||||||
IfName = "ygg";
|
IfName = "ygg";
|
||||||
Peers = lib.lists.unique (exportedPeers ++ settings.extraPeers);
|
Peers = settings.peers;
|
||||||
MulticastInterfaces = [
|
MulticastInterfaces = [
|
||||||
# Ethernet is preferred over WIFI
|
# Ethernet is preferred over WIFI
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,20 +17,6 @@
|
|||||||
roles.default.machines.peer1 = { };
|
roles.default.machines.peer1 = { };
|
||||||
roles.default.machines.peer2 = { };
|
roles.default.machines.peer2 = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
# Peers are set form exports of the internet service
|
|
||||||
instances."internet" = {
|
|
||||||
module.name = "internet";
|
|
||||||
roles.default.machines.peer1.settings.host = "peer1-internet";
|
|
||||||
roles.default.machines.peer2.settings.host = "peer2-internet";
|
|
||||||
};
|
|
||||||
|
|
||||||
instances."zerotier" = {
|
|
||||||
module.name = "zerotier";
|
|
||||||
roles.controller.machines.peer1 = { };
|
|
||||||
roles.peer.machines.peer2 = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/machines/peer1
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:ZkirPKTvLpV3+aMklbRIkafGCMISIRrqgFu8B0A1nQEdeqRR0bexoRuzLopuj95mqPKYHWT9ArF8zDqVW9t4UgazTgprK/coFlKk/2wO8dO2JmVcFlGZou2Hz6JVvt8xuELU350lpF+o4k1xmAqswqaRQyqgAIvVDnym/jZPj9hBZpSXr/IcUnH4cXcNv51Xt82Zvo132RoaU1warlNk1p3dr1DRHU56KtEwhkj9YxoIcS4K4BaEl9L87REXnFEBu5p8FeO1f3bp/ZFOxL7bYKROFHYhK4mIlSTVmYJg4a1CP0M7v842xm83C37Y6xgN8SltC/ld9TuxBNVhfzmHHotpBXvAbwxkCJE6ChJI,iv:M4jqMRvbjODcWGjJUMc3ys4Tra0KBwVXOVMoeXcAXuQ=,tag:irDJqWEeXlIXOv/DMZWlGQ==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1p8trv2dmpanl3gnzj294c4t5uysu7d6rfjncp5lmn6redyda8fns6p7kca",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwVGJGWlZOb05QL3AzSzFM\nUG4vV3RFK2RjVEhVd2QzQ3pTMUl0UmFLaURnCkRORDBuK0xUM1pYSFRFZXlpK1Na\nUHp6b3pWeEl0SkF2ZERaa3gyczh0RlkKLS0tIHFoanBkS1Jhc3ovQlJFV0lCQVpY\nUEUrcmZlbkhQa0lac3pqenBXWkpDZTgKNQ6Lu4L6zHKTN4pe2T3eg7lvTeZQ2/mf\nD33YfN15W/yuOb+LzVTwSj6wPgQuSaVRlgbCm/t1adzTnUZmruWxuA==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1YVMrSEkybzJpdXVHQWtP\nMzQ2QXZmQXJNL05ORDRobWZmQmdrTWtiVDJZCk9Wckg4eVJiU21BcFQ4MDhjTzlw\nVnh6b25NM3ZSNXRIQUEwd0RaSjg1MW8KLS0tICtqVWxpN09CSC9kcUdvRmw1RmRh\nOHlWQXEwYWFPY2VsM0Q0RzJyL2FWNUUK3f7t64UBdGtzxo0upCugNvA2vKUXL6gb\n0CJq4MG1s+lgFpvenRlozsaG3I8IxPHkFWuTA6OuUCCwaJqb0eT4ZA==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-10-31T14:34:48Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:+mMvTo1+4f9rQm1U6td5Sx7NYeuKJQeXcTpFOooAV8wt75XX2VhX059/S3krFJ8vIsMUqQ0PqPLipCNTaTi8cxkqHfsVQEGCcALGtisk5bnHWgipnFoaO6Ao9TKkmFBcQo9za9+Z40stNIzThOHWaZonvp9KWIVj92CFic62UT8=,iv:HhVf1rhN6Ocp6Bif1oXQScJUe4ndFw3Rv/obVYDx5aA=,tag:9M5iMVcj3ore3DQtwdJuMQ==,type:str]",
|
|
||||||
"version": "3.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/users/admin
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
fd06:8020:2351:b57:2899:9306:8020:2351
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
06802023510b5728
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/machines/peer2
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:gzHNCz/yRXD9sXRvqpGC18ZUF1JLvpBO44klfRjl6WzCPHLrC9Mp6cGFa+U3CZL2i/0JGKOtQGH+82Ra6oAkOiWEcSRN/xmAmcZaoVPTnvZ2tF7vvlRfR5hq+p/ZQw4+Y4V1TIuYj2dLNrVIIGYmWSabqI0mgVTTjyRsDJSB4YgqGTYismvZ9QXICSDxwROIrC2xl0Xx+MYWhxR1PVJ3B1HbJ8KEQCuBVq46Wki/INe0bD+ODlxCv9GCGPgaNjMwACOwQXo5WGP9zSDq2HEkTeg5YUmX1o1G6LwkG2fY/Hr5XMiLGU6G0remP/WbCOoLRXdB/Luevg/rTlQ/dNDawPARsbZZSjLmk/BHUOUJ,iv:zPeIyZi2ckbEcbX4FFhyN3ryWf4eoRu4XIafeAje28E=,tag:8/Vn0m+/wMGY706fYX55Vg==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age107mprppm3r9u7f26e6t5mhtdny0h5ugfmfjy8kac2tw9nrh9a3ksex0xca",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDYlU4cG1KYXZodFJYYXNo\ndjhNbUFzNEhySzI2NmduR0EwOUhENFRZN3o4CmtSNG5ObkM2bDJXaXk1QlFVWURK\nV1lRa1VVV0hNZlh0eVJpVHFqU3FXMzgKLS0tIFhtUjZnZVdMczNFVUMrL2Q0b1Rz\nRFlzTUFXVWZwM2gwRW1LTzd0a2lhQTAKHyakwS8kB4Gg4Vjs3PJsbF3VHzJjAbOR\nR+y6op3zPjQpr5QfsRn4MoES/ViGDPZWLYxXUSMctGVDxIfgdZxP9A==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1dHBaY015Q2J2NlRyRGEy\nRGtRcm1YckhYSm5mbU5GaGFaTjhRa1UraWpRCnFWSDBSYURFS21QYUYxVXdKdGVi\nY1hiN3c3eTlJUWo2dXZXUk9TN3g3ZVkKLS0tIGJneUlaMU1KeVVBcXN5L3FIMjNP\nYkpWTVA3d2k1a3Y5Yk9kUUF3SFo2V2sKGLQYVmX8HnDqX5K/tdbfgYnpVmaTArIY\nuhw+CtrXmEHhksZqgGCcjEoCz7cDMzMA42kVdqh/OfFzJNxrRfJjPA==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-10-31T14:59:28Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:MWpzOKUYXkmw2DX6YsN5pPIF9Y6GZ4rPnwq3uaOnFm40SOXPN2/JXSL7E9bGgaBeboUbChNwiGmBBRQX+7d2Te/NoItJAPw4YJTtquA+Rb7+sgPUoL6kYP7YZfjw1Z2hi61YMYXZH0/q4tBx6SNukt7o/uRYLu2LjyO09251uO4=,iv:YVXr5u2xwVEOlG+xYguAO1ZsCXvMx6rhXBV24CkFPv8=,tag:AOK4Pi2YYx4w0je9gALDLw==,type:str]",
|
|
||||||
"version": "3.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/users/admin
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
fd06:8020:2351:b57:2899:9340:7f3b:e1b3
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
{
|
{ ... }:
|
||||||
clanLib,
|
|
||||||
directory,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/zerotier";
|
manifest.name = "clan-core/zerotier";
|
||||||
@@ -17,23 +13,21 @@
|
|||||||
instanceName,
|
instanceName,
|
||||||
roles,
|
roles,
|
||||||
lib,
|
lib,
|
||||||
machine,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
exports.networking = {
|
||||||
exports."internet/${instanceName}/peer/${machine.name}".networking = {
|
priority = lib.mkDefault 900;
|
||||||
hosts = lib.flatten [
|
# TODO add user space network support to clan-cli
|
||||||
(clanLib.vars.getPublicValue {
|
module = "clan_lib.network.zerotier";
|
||||||
flake = directory;
|
peers = lib.mapAttrs (name: _machine: {
|
||||||
machine = machine.name;
|
host.var = {
|
||||||
|
machine = name;
|
||||||
generator = "zerotier";
|
generator = "zerotier";
|
||||||
file = "zerotier-ip";
|
file = "zerotier-ip";
|
||||||
# default = throw "kaputt";
|
};
|
||||||
})
|
}) roles.peer.machines;
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
@@ -45,7 +39,6 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
clanLib
|
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -97,7 +90,6 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
clanLib
|
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -150,7 +142,6 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
clanLib
|
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -169,16 +160,15 @@
|
|||||||
);
|
);
|
||||||
networkIps = builtins.foldl' (
|
networkIps = builtins.foldl' (
|
||||||
ips: name:
|
ips: name:
|
||||||
let
|
if
|
||||||
ztIp = clanLib.vars.getPublicValue {
|
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
||||||
flake = config.clan.core.settings.directory;
|
then
|
||||||
machine = name;
|
ips
|
||||||
generator = "zerotier";
|
++ [
|
||||||
file = "zerotier-ip";
|
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
||||||
default = null;
|
]
|
||||||
};
|
else
|
||||||
in
|
ips
|
||||||
if ztIp != null then ips ++ [ ztIp ] else ips
|
|
||||||
) [ ] machines;
|
) [ ] machines;
|
||||||
allHostIPs = settings.allowedIps ++ networkIps;
|
allHostIPs = settings.allowedIps ++ networkIps;
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ in
|
|||||||
../../clanServices/zerotier
|
../../clanServices/zerotier
|
||||||
# Required modules
|
# Required modules
|
||||||
../../nixosModules/clanCore
|
../../nixosModules/clanCore
|
||||||
../../nixosModules/machineModules
|
|
||||||
# Dependencies like clan-cli
|
# Dependencies like clan-cli
|
||||||
../../pkgs/clan-cli
|
../../pkgs/clan-cli
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
clanLib,
|
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
@@ -9,26 +8,20 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
|
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
|
||||||
networkId = clanLib.vars.getPublicValue {
|
networkIdPath = "${config.clan.core.settings.directory}/vars/per-machine/${controllerMachine}/zerotier/zerotier-network-id/value";
|
||||||
flake = config.clan.core.settings.directory;
|
networkId = if builtins.pathExists networkIdPath then builtins.readFile networkIdPath else null;
|
||||||
machine = controllerMachine;
|
|
||||||
generator = "zerotier";
|
|
||||||
file = "zerotier-network-id";
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
moons = lib.attrNames (roles.moon.machines or { });
|
moons = lib.attrNames (roles.moon.machines or { });
|
||||||
moonIps = builtins.foldl' (
|
moonIps = builtins.foldl' (
|
||||||
ips: name:
|
ips: name:
|
||||||
let
|
if
|
||||||
moonIp = clanLib.vars.getPublicValue {
|
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
||||||
flake = config.clan.core.settings.directory;
|
then
|
||||||
machine = name;
|
ips
|
||||||
generator = "zerotier";
|
++ [
|
||||||
file = "zerotier-ip";
|
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
||||||
default = null;
|
]
|
||||||
};
|
else
|
||||||
in
|
ips
|
||||||
if moonIp != null then ips ++ [ moonIp ] else ips
|
|
||||||
) [ ] moons;
|
) [ ] moons;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
24
devFlake/flake.lock
generated
24
devFlake/flake.lock
generated
@@ -3,10 +3,10 @@
|
|||||||
"clan-core-for-checks": {
|
"clan-core-for-checks": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761204206,
|
"lastModified": 1760361585,
|
||||||
"narHash": "sha256-A4KDudGblln1yh8c95OVow2NRlHtbGZXr/pgNenyrNc=",
|
"narHash": "sha256-v4PnSmt1hXW4dSgVWxcd1ZeEBlhO7NksNRC5cX7L5iw=",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"rev": "aabbe0dfac47b7cfbe2210bcb27fb7ecce93350f",
|
"rev": "7e7e58eb64ef61beb0a938a6622ec0122382131b",
|
||||||
"shallow": true,
|
"shallow": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.clan.lol/clan/clan-core"
|
"url": "https://git.clan.lol/clan/clan-core"
|
||||||
@@ -105,11 +105,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-dev": {
|
"nixpkgs-dev": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761748483,
|
"lastModified": 1760965023,
|
||||||
"narHash": "sha256-v7fttCB5lJ22Ok7+N7ZbLhDeM89QIz9YWtQP4XN7xgA=",
|
"narHash": "sha256-cpcgkeLApMGFCdp4jFqeIxTwlcGaSI+Zwmv8z2E85pY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "061c55856b29b8b9360e14231a0986c7f85f1130",
|
"rev": "40ef6b9aa73f70b265c29df083fafae66b9df351",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -128,11 +128,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761730856,
|
"lastModified": 1760652422,
|
||||||
"narHash": "sha256-t1i5p/vSWwueZSC0Z2BImxx3BjoUDNKyC2mk24krcMY=",
|
"narHash": "sha256-C88Pgz38QIl9JxQceexqL2G7sw9vodHWx1Uaq+NRJrw=",
|
||||||
"owner": "NuschtOS",
|
"owner": "NuschtOS",
|
||||||
"repo": "search",
|
"repo": "search",
|
||||||
"rev": "e29de6db0cb3182e9aee75a3b1fd1919d995d85b",
|
"rev": "3ebeebe8b6a49dfb11f771f761e0310f7c48d726",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -208,11 +208,11 @@
|
|||||||
"nixpkgs": []
|
"nixpkgs": []
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761311587,
|
"lastModified": 1760945191,
|
||||||
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ For the provide flake example, your flake should now look like this:
|
|||||||
self = self; # this needs to point at the repository root
|
self = self; # this needs to point at the repository root
|
||||||
specialArgs = {};
|
specialArgs = {};
|
||||||
meta.name = throw "Change me to something unique";
|
meta.name = throw "Change me to something unique";
|
||||||
meta.tld = throw "Change me to something unique";
|
|
||||||
|
|
||||||
machines = {
|
machines = {
|
||||||
berlin = {
|
berlin = {
|
||||||
|
|||||||
@@ -137,13 +137,12 @@ Description: None
|
|||||||
|
|
||||||
This confirms your setup is working correctly.
|
This confirms your setup is working correctly.
|
||||||
|
|
||||||
You can now change the default name and tld by editing the `meta.name` and `meta.tld` fields in your `clan.nix` file.
|
You can now change the default name by editing the `meta.name` field in your `clan.nix` file.
|
||||||
|
|
||||||
```{.nix title="clan.nix" hl_lines="3 4"}
|
```{.nix title="clan.nix" hl_lines="3"}
|
||||||
{
|
{
|
||||||
# Ensure this is unique among all clans you want to use.
|
# Ensure this is unique among all clans you want to use.
|
||||||
meta.name = "__CHANGE_ME__";
|
meta.name = "__CHANGE_ME__";
|
||||||
meta.tld = "changeme";
|
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
# elided
|
# elided
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ and how to define a remote builder for your machine closures.
|
|||||||
Set the machine’s `targetHost` to the reachable IP address of the new machine.
|
Set the machine’s `targetHost` to the reachable IP address of the new machine.
|
||||||
This eliminates the need to specify `--target-host` in CLI commands.
|
This eliminates the need to specify `--target-host` in CLI commands.
|
||||||
|
|
||||||
```{.nix title="clan.nix" hl_lines="10"}
|
```{.nix title="clan.nix" hl_lines="9"}
|
||||||
{
|
{
|
||||||
# Ensure this is unique among all clans you want to use.
|
# Ensure this is unique among all clans you want to use.
|
||||||
meta.name = "my-clan";
|
meta.name = "my-clan";
|
||||||
meta.tld = "ccc";
|
|
||||||
|
|
||||||
inventory.machines = {
|
inventory.machines = {
|
||||||
# Define machines here.
|
# Define machines here.
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ Configure Clan-wide settings and define machines. Here's an example `flake.nix`:
|
|||||||
# Define your Clan
|
# Define your Clan
|
||||||
clan = {
|
clan = {
|
||||||
meta.name = ""; # Required and must be unique
|
meta.name = ""; # Required and must be unique
|
||||||
meta.tld = ""; # Required and must be unique
|
|
||||||
|
|
||||||
machines = {
|
machines = {
|
||||||
jon = {
|
jon = {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ For the purpose of this guide we have two machines:
|
|||||||
inherit self;
|
inherit self;
|
||||||
|
|
||||||
meta.name = "myclan";
|
meta.name = "myclan";
|
||||||
meta.tld = "ccc";
|
|
||||||
|
|
||||||
inventory.machines = {
|
inventory.machines = {
|
||||||
controller = {};
|
controller = {};
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ To use `age` plugins with Clan, you need to configure them in your `flake.nix` f
|
|||||||
inherit self;
|
inherit self;
|
||||||
|
|
||||||
meta.name = "myclan";
|
meta.name = "myclan";
|
||||||
meta.tld = "ccc";
|
|
||||||
|
|
||||||
# Add YubiKey and FIDO2 HMAC plugins
|
# Add YubiKey and FIDO2 HMAC plugins
|
||||||
# Note: Plugins must be available in nixpkgs.
|
# Note: Plugins must be available in nixpkgs.
|
||||||
|
|||||||
24
flake.lock
generated
24
flake.lock
generated
@@ -71,11 +71,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761339987,
|
"lastModified": 1760721282,
|
||||||
"narHash": "sha256-IUaawVwItZKi64IA6kF6wQCLCzpXbk2R46dHn8sHkig=",
|
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
|
||||||
"owner": "nix-darwin",
|
"owner": "nix-darwin",
|
||||||
"repo": "nix-darwin",
|
"repo": "nix-darwin",
|
||||||
"rev": "7cd9aac79ee2924a85c211d21fafd394b06a38de",
|
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -99,11 +99,11 @@
|
|||||||
},
|
},
|
||||||
"nixos-facter-modules": {
|
"nixos-facter-modules": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761137276,
|
"lastModified": 1756491981,
|
||||||
"narHash": "sha256-4lDjGnWRBLwqKQ4UWSUq6Mvxu9r8DSqCCydodW/Jsi8=",
|
"narHash": "sha256-lXyDAWPw/UngVtQfgQ8/nrubs2r+waGEYIba5UX62+k=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-facter-modules",
|
"repo": "nixos-facter-modules",
|
||||||
"rev": "70bcd64225d167c7af9b475c4df7b5abba5c7de8",
|
"rev": "c1b29520945d3e148cd96618c8a0d1f850965d8c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -146,11 +146,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760998189,
|
"lastModified": 1760845571,
|
||||||
"narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=",
|
"narHash": "sha256-PwGzU3EOU65Ef1VvuNnVLie+l+P0g/fzf/PGUG82KbM=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3",
|
"rev": "9c9a9798be331ed3f4b2902933d7677d0659ee61",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -181,11 +181,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1761311587,
|
"lastModified": 1760945191,
|
||||||
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -68,7 +68,6 @@
|
|||||||
(
|
(
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
debug = true;
|
|
||||||
clan = {
|
clan = {
|
||||||
meta.name = "clan-core";
|
meta.name = "clan-core";
|
||||||
inventory = {
|
inventory = {
|
||||||
@@ -99,7 +98,6 @@
|
|||||||
./lib/filter-clan-core/flake-module.nix
|
./lib/filter-clan-core/flake-module.nix
|
||||||
./lib/flake-module.nix
|
./lib/flake-module.nix
|
||||||
./lib/flake-parts/clan-nixos-test.nix
|
./lib/flake-parts/clan-nixos-test.nix
|
||||||
./modules/flake-module.nix
|
|
||||||
./nixosModules/clanCore/vars/flake-module.nix
|
./nixosModules/clanCore/vars/flake-module.nix
|
||||||
./nixosModules/flake-module.nix
|
./nixosModules/flake-module.nix
|
||||||
./pkgs/clan-cli/clan_cli/tests/flake-module.nix
|
./pkgs/clan-cli/clan_cli/tests/flake-module.nix
|
||||||
|
|||||||
@@ -39,10 +39,44 @@ in
|
|||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
clan-core.modules.clan.default
|
clan-core.modules.clan.default
|
||||||
|
{
|
||||||
|
checks.minNixpkgsVersion = {
|
||||||
|
assertion = lib.versionAtLeast nixpkgs.lib.version "25.11";
|
||||||
|
message = ''
|
||||||
|
Nixpkgs version: ${nixpkgs.lib.version} is incompatible with clan-core. (>= 25.11 is recommended)
|
||||||
|
---
|
||||||
|
Your version of 'nixpkgs' seems too old for clan-core.
|
||||||
|
Please read: https://docs.clan.lol/guides/nixpkgs-flake-input
|
||||||
|
|
||||||
|
You can ignore this check by setting:
|
||||||
|
clan.checks.minNixpkgsVersion.ignore = true;
|
||||||
|
---
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# TODO: Pinpox
|
||||||
|
checks.conflictingPorts =
|
||||||
|
let
|
||||||
|
conflicts = [ ];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assertion = builtins.length conflicts == 0;
|
||||||
|
message = ''
|
||||||
|
The following endpoints have conflicting port assignments:
|
||||||
|
---
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
# Important: !This logic needs to be kept in sync with lib.clan function!
|
apply =
|
||||||
apply = config: clan-core.lib.checkConfig config.checks config;
|
config:
|
||||||
|
lib.deepSeq (lib.mapAttrs (
|
||||||
|
id: check:
|
||||||
|
if check.ignore || check.assertion then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "clan.checks.${id} failed with message\n${check.message}"
|
||||||
|
) config.checks) config;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Mapped flake toplevel outputs
|
# Mapped flake toplevel outputs
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
/**
|
|
||||||
Function to assert clan configuration checks.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
- 'checks' attribute of clan configuration
|
|
||||||
- Any: the returned configuration (can be anything, is just passed through)
|
|
||||||
*/
|
|
||||||
checks:
|
|
||||||
lib.deepSeq (
|
|
||||||
lib.mapAttrs (
|
|
||||||
id: check:
|
|
||||||
if check.ignore || check.assertion then
|
|
||||||
null
|
|
||||||
else
|
|
||||||
throw "clan.checks.${id} failed with message\n${check.message}"
|
|
||||||
) checks
|
|
||||||
)
|
|
||||||
@@ -33,23 +33,20 @@
|
|||||||
let
|
let
|
||||||
nixpkgs = self.inputs.nixpkgs or clan-core.inputs.nixpkgs;
|
nixpkgs = self.inputs.nixpkgs or clan-core.inputs.nixpkgs;
|
||||||
nix-darwin = self.inputs.nix-darwin or clan-core.inputs.nix-darwin;
|
nix-darwin = self.inputs.nix-darwin or clan-core.inputs.nix-darwin;
|
||||||
configuration = (
|
|
||||||
lib.evalModules {
|
|
||||||
class = "clan";
|
|
||||||
specialArgs = {
|
|
||||||
inherit
|
|
||||||
self
|
|
||||||
;
|
|
||||||
inherit
|
|
||||||
nixpkgs
|
|
||||||
nix-darwin
|
|
||||||
;
|
|
||||||
};
|
|
||||||
modules = [
|
|
||||||
clan-core.modules.clan.default
|
|
||||||
m
|
|
||||||
];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
in
|
||||||
clan-core.clanLib.checkConfig configuration.config.checks configuration
|
lib.evalModules {
|
||||||
|
class = "clan";
|
||||||
|
specialArgs = {
|
||||||
|
inherit
|
||||||
|
self
|
||||||
|
;
|
||||||
|
inherit
|
||||||
|
nixpkgs
|
||||||
|
nix-darwin
|
||||||
|
;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
clan-core.modules.clan.default
|
||||||
|
m
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,12 +137,6 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
type = types.submoduleWith {
|
type = types.submoduleWith {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
self = throw ''
|
|
||||||
'self' is banned in the use of clan.services
|
|
||||||
Use 'exports' instead: https://docs.clan.lol/reference/options/clan_service/#exports
|
|
||||||
---
|
|
||||||
If you really need to used 'self' here, that makes the module less portable
|
|
||||||
'';
|
|
||||||
inherit (config.clanSettings)
|
inherit (config.clanSettings)
|
||||||
clan-core
|
clan-core
|
||||||
nixpkgs
|
nixpkgs
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ lib.fix (
|
|||||||
*/
|
*/
|
||||||
callLib = file: args: import file ({ inherit lib clanLib; } // args);
|
callLib = file: args: import file ({ inherit lib clanLib; } // args);
|
||||||
|
|
||||||
checkConfig = clanLib.callLib ./clan/checkConfig.nix { };
|
evalService = clanLib.callLib ./modules/inventory/distributed-service/evalService.nix { };
|
||||||
|
|
||||||
evalService = clanLib.callLib ./evalService.nix { };
|
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
# ClanLib functions
|
# ClanLib functions
|
||||||
inventory = clanLib.callLib ./inventory { };
|
inventory = clanLib.callLib ./modules/inventory { };
|
||||||
test = clanLib.callLib ./test { };
|
test = clanLib.callLib ./test { };
|
||||||
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
|
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
|
||||||
# Custom types
|
# Custom types
|
||||||
@@ -32,17 +30,12 @@ lib.fix (
|
|||||||
jsonschema = import ./jsonschema { inherit lib; };
|
jsonschema = import ./jsonschema { inherit lib; };
|
||||||
docs = import ./docs.nix { inherit lib; };
|
docs = import ./docs.nix { inherit lib; };
|
||||||
|
|
||||||
vars = import ./vars.nix { inherit lib; };
|
|
||||||
|
|
||||||
# flakes
|
# flakes
|
||||||
flakes = clanLib.callLib ./flakes.nix { };
|
flakes = clanLib.callLib ./flakes.nix { };
|
||||||
|
|
||||||
# TODO: Flatten our lib functions like this:
|
# TODO: Flatten our lib functions like this:
|
||||||
resolveModule = clanLib.callLib ./resolve-module { };
|
resolveModule = clanLib.callLib ./resolve-module { };
|
||||||
|
|
||||||
# Functions to help define exports
|
|
||||||
exports = clanLib.callLib ./exports.nix { };
|
|
||||||
|
|
||||||
fs = {
|
fs = {
|
||||||
inherit (builtins) pathExists readDir;
|
inherit (builtins) pathExists readDir;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
{ lib }:
|
|
||||||
let
|
|
||||||
/**
|
|
||||||
Creates a scope string for global exports
|
|
||||||
|
|
||||||
At least one of serviceName or machineName must be set.
|
|
||||||
|
|
||||||
The scope string has the format:
|
|
||||||
|
|
||||||
"/SERVICE/INSTANCE/ROLE/MACHINE"
|
|
||||||
|
|
||||||
If the parameter is not set, the corresponding part is left empty.
|
|
||||||
Semantically this means "all".
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
mkScope { serviceName = "A"; }
|
|
||||||
-> "/A///"
|
|
||||||
|
|
||||||
mkScope { machineName = "jon"; }
|
|
||||||
-> "///jon"
|
|
||||||
|
|
||||||
mkScope { serviceName = "A"; instanceName = "i1"; roleName = "peer"; machineName = "jon"; }
|
|
||||||
-> "/A/i1/peer/jon"
|
|
||||||
*/
|
|
||||||
mkScope =
|
|
||||||
{
|
|
||||||
serviceName ? "",
|
|
||||||
instanceName ? "",
|
|
||||||
roleName ? "",
|
|
||||||
machineName ? "",
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
parts = [
|
|
||||||
serviceName
|
|
||||||
instanceName
|
|
||||||
roleName
|
|
||||||
machineName
|
|
||||||
];
|
|
||||||
checkedParts = lib.map (
|
|
||||||
part:
|
|
||||||
lib.throwIf (builtins.match ".?/.?" part != null) ''
|
|
||||||
clanLib.exports.mkScope: ${part} cannot contain the "/" character
|
|
||||||
''
|
|
||||||
) parts;
|
|
||||||
in
|
|
||||||
lib.throwIf ((serviceName == "" && machineName == "")) ''
|
|
||||||
clanLib.exports.mkScope requires at least 'serviceName' or 'machineName' to be set
|
|
||||||
|
|
||||||
In case your use case requires neither
|
|
||||||
'' (lib.join "/" checkedParts);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Parses a scope string into its components
|
|
||||||
|
|
||||||
Returns an attribute set with the keys:
|
|
||||||
- serviceName
|
|
||||||
- instanceName
|
|
||||||
- roleName
|
|
||||||
- machineName
|
|
||||||
|
|
||||||
Example:
|
|
||||||
parseScope "A/i1/peer/jon"
|
|
||||||
->
|
|
||||||
{
|
|
||||||
serviceName = "A";
|
|
||||||
instanceName = "i1";
|
|
||||||
roleName = "peer";
|
|
||||||
machineName = "jon";
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
parseScope =
|
|
||||||
scopeStr:
|
|
||||||
let
|
|
||||||
parts = lib.splitString "/" scopeStr;
|
|
||||||
checkedParts = lib.throwIf (lib.length parts != 4) ''
|
|
||||||
clanLib.exports.parseScope: invalid scope string format, expected 4 parts separated by 3 "/"
|
|
||||||
'' (parts);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
serviceName = lib.elemAt 0 checkedParts;
|
|
||||||
instanceName = lib.elemAt 1 checkedParts;
|
|
||||||
roleName = lib.elemAt 2 checkedParts;
|
|
||||||
machineName = lib.elemAt 3 checkedParts;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit mkScope parseScope;
|
|
||||||
}
|
|
||||||
@@ -10,11 +10,12 @@ in
|
|||||||
rec {
|
rec {
|
||||||
# TODO: automatically generate this from the directory conventions
|
# TODO: automatically generate this from the directory conventions
|
||||||
imports = [
|
imports = [
|
||||||
|
./modules/flake-module.nix
|
||||||
./clanTest/flake-module.nix
|
./clanTest/flake-module.nix
|
||||||
./introspection/flake-module.nix
|
./introspection/flake-module.nix
|
||||||
|
./modules/inventory/flake-module.nix
|
||||||
./jsonschema/flake-module.nix
|
./jsonschema/flake-module.nix
|
||||||
./types/flake-module.nix
|
./types/flake-module.nix
|
||||||
./inventory/flake-module.nix
|
|
||||||
];
|
];
|
||||||
flake.clanLib =
|
flake.clanLib =
|
||||||
let
|
let
|
||||||
@@ -77,6 +78,9 @@ rec {
|
|||||||
../lib
|
../lib
|
||||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../.)
|
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../.)
|
||||||
../flakeModules
|
../flakeModules
|
||||||
|
# ../../nixosModules/clanCore
|
||||||
|
# ../../machines
|
||||||
|
# ../../inventory.json
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -97,41 +101,6 @@ rec {
|
|||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests-build-clan
|
|
||||||
legacyPackages.evalTests-build-clan = import ./tests.nix {
|
|
||||||
inherit lib;
|
|
||||||
clan-core = self;
|
|
||||||
};
|
|
||||||
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests-build-clan
|
|
||||||
legacyPackages.eval-exports = import ./new_exports.nix {
|
|
||||||
inherit lib;
|
|
||||||
clan-core = self;
|
|
||||||
};
|
|
||||||
checks = {
|
|
||||||
eval-lib-build-clan = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
|
||||||
export HOME="$(realpath .)"
|
|
||||||
|
|
||||||
nix-unit --eval-store "$HOME" \
|
|
||||||
--extra-experimental-features flakes \
|
|
||||||
--show-trace \
|
|
||||||
${inputOverrides} \
|
|
||||||
--flake ${
|
|
||||||
self.filter {
|
|
||||||
include = [
|
|
||||||
"flakeModules"
|
|
||||||
"inventory.json"
|
|
||||||
"lib"
|
|
||||||
"machines"
|
|
||||||
"nixosModules"
|
|
||||||
"modules"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}#legacyPackages.${system}.evalTests-build-clan
|
|
||||||
|
|
||||||
touch $out
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
lib/modules/clan/default.nix
Normal file
12
lib/modules/clan/default.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{ clan-core }:
|
||||||
|
{
|
||||||
|
_class = "clan";
|
||||||
|
_module.args = {
|
||||||
|
inherit clan-core;
|
||||||
|
inherit (clan-core) clanLib;
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
./module.nix
|
||||||
|
./interface.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
4
lib/modules/clan/flake-module.nix
Normal file
4
lib/modules/clan/flake-module.nix
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{ self, lib, ... }:
|
||||||
|
{
|
||||||
|
flake.modules.clan.default = lib.modules.importApply ./default.nix { clan-core = self; };
|
||||||
|
}
|
||||||
@@ -110,7 +110,9 @@ in
|
|||||||
|
|
||||||
# TODO: make this writable by moving the options from inventoryClass into clan.
|
# TODO: make this writable by moving the options from inventoryClass into clan.
|
||||||
exports = lib.mkOption {
|
exports = lib.mkOption {
|
||||||
type = types.lazyAttrsOf (types.submoduleWith { modules = [ config.exportsModule ]; });
|
readOnly = true;
|
||||||
|
visible = false;
|
||||||
|
internal = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
exportsModule = lib.mkOption {
|
exportsModule = lib.mkOption {
|
||||||
@@ -118,86 +120,108 @@ in
|
|||||||
visible = false;
|
visible = false;
|
||||||
type = types.deferredModule;
|
type = types.deferredModule;
|
||||||
default = {
|
default = {
|
||||||
options.networking = {
|
options.endpoints = lib.mkOption {
|
||||||
|
type = types.attrsWith {
|
||||||
priority = lib.mkOption {
|
placeholder = "endpointName";
|
||||||
type = lib.types.int;
|
elemType = (
|
||||||
default = 1000;
|
types.submodule {
|
||||||
description = ''
|
options = {
|
||||||
priority with which this network should be tried.
|
port = lib.mkOption {
|
||||||
higher priority means it gets used earlier in the chain
|
type = types.int;
|
||||||
'';
|
description = "The port the service is running on";
|
||||||
|
};
|
||||||
|
protocol = lib.mkOption {
|
||||||
|
type = types.enum [
|
||||||
|
"tcp"
|
||||||
|
"udp"
|
||||||
|
];
|
||||||
|
default = "tcp";
|
||||||
|
description = "The protocol used to access the service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
module = lib.mkOption {
|
default = { };
|
||||||
# type = lib.types.enum [
|
};
|
||||||
# "clan_lib.network.direct"
|
options.networking = lib.mkOption {
|
||||||
# "clan_lib.network.tor"
|
default = null;
|
||||||
# ];
|
type = lib.types.nullOr (
|
||||||
type = lib.types.str;
|
lib.types.submodule {
|
||||||
default = "clan_lib.network.direct";
|
options = {
|
||||||
description = ''
|
priority = lib.mkOption {
|
||||||
the technology this network uses to connect to the target
|
type = lib.types.int;
|
||||||
This is used for userspace networking with socks proxies.
|
default = 1000;
|
||||||
'';
|
description = ''
|
||||||
};
|
priority with which this network should be tried.
|
||||||
# should we call this machines? hosts?
|
higher priority means it gets used earlier in the chain
|
||||||
|
'';
|
||||||
hosts = lib.mkOption {
|
};
|
||||||
type = lib.types.listOf lib.types.str;
|
module = lib.mkOption {
|
||||||
default = [ ];
|
# type = lib.types.enum [
|
||||||
};
|
# "clan_lib.network.direct"
|
||||||
|
# "clan_lib.network.tor"
|
||||||
# peers = lib.mkOption {
|
# ];
|
||||||
#
|
type = lib.types.str;
|
||||||
# # <name>
|
default = "clan_lib.network.direct";
|
||||||
# type = lib.types.attrsOf (
|
description = ''
|
||||||
# lib.types.submodule (
|
the technology this network uses to connect to the target
|
||||||
# { name, ... }:
|
This is used for userspace networking with socks proxies.
|
||||||
# {
|
'';
|
||||||
# options = {
|
};
|
||||||
# name = lib.mkOption {
|
# should we call this machines? hosts?
|
||||||
# type = lib.types.str;
|
peers = lib.mkOption {
|
||||||
# default = name;
|
# <name>
|
||||||
# };
|
type = lib.types.attrsOf (
|
||||||
# SSHOptions = lib.mkOption {
|
lib.types.submodule (
|
||||||
# type = lib.types.listOf lib.types.str;
|
{ name, ... }:
|
||||||
# default = [ ];
|
{
|
||||||
# };
|
options = {
|
||||||
#
|
name = lib.mkOption {
|
||||||
# host = lib.mkOption {
|
type = lib.types.str;
|
||||||
# description = '''';
|
default = name;
|
||||||
# type = lib.types.attrTag {
|
};
|
||||||
# plain = lib.mkOption {
|
SSHOptions = lib.mkOption {
|
||||||
# type = lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
# description = ''
|
default = [ ];
|
||||||
# a plain value, which can be read directly from the config
|
};
|
||||||
# '';
|
host = lib.mkOption {
|
||||||
# };
|
description = '''';
|
||||||
# var = lib.mkOption {
|
type = lib.types.attrTag {
|
||||||
# type = lib.types.submodule {
|
plain = lib.mkOption {
|
||||||
# options = {
|
type = lib.types.str;
|
||||||
# machine = lib.mkOption {
|
description = ''
|
||||||
# type = lib.types.str;
|
a plain value, which can be read directly from the config
|
||||||
# example = "jon";
|
'';
|
||||||
# };
|
};
|
||||||
# generator = lib.mkOption {
|
var = lib.mkOption {
|
||||||
# type = lib.types.str;
|
type = lib.types.submodule {
|
||||||
# example = "tor-ssh";
|
options = {
|
||||||
# };
|
machine = lib.mkOption {
|
||||||
# file = lib.mkOption {
|
type = lib.types.str;
|
||||||
# type = lib.types.str;
|
example = "jon";
|
||||||
# example = "hostname";
|
};
|
||||||
# };
|
generator = lib.mkOption {
|
||||||
# };
|
type = lib.types.str;
|
||||||
# };
|
example = "tor-ssh";
|
||||||
# };
|
};
|
||||||
# };
|
file = lib.mkOption {
|
||||||
# };
|
type = lib.types.str;
|
||||||
# };
|
example = "hostname";
|
||||||
# }
|
};
|
||||||
# )
|
};
|
||||||
# );
|
};
|
||||||
# };
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
description = ''
|
description = ''
|
||||||
@@ -288,7 +312,7 @@ in
|
|||||||
Global information about the clan.
|
Global information about the clan.
|
||||||
'';
|
'';
|
||||||
type = types.deferredModuleWith {
|
type = types.deferredModuleWith {
|
||||||
staticModules = [ ../inventoryClass/meta.nix ];
|
staticModules = [ ../inventoryClass/meta-interface.nix ];
|
||||||
};
|
};
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
@@ -100,7 +100,7 @@ let
|
|||||||
_: machine:
|
_: machine:
|
||||||
machine.extendModules {
|
machine.extendModules {
|
||||||
modules = [
|
modules = [
|
||||||
(lib.modules.importApply ../../nixosModules/machineModules/overridePkgs.nix {
|
(lib.modules.importApply ../machineModules/overridePkgs.nix {
|
||||||
pkgs = pkgsFor.${system};
|
pkgs = pkgsFor.${system};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@@ -167,9 +167,6 @@ in
|
|||||||
{ ... }@args:
|
{ ... }@args:
|
||||||
let
|
let
|
||||||
_class =
|
_class =
|
||||||
# _class was added in https://github.com/NixOS/nixpkgs/pull/395141
|
|
||||||
# Clan relies on it to determine which modules to load
|
|
||||||
# people need to use at least that version of nixpkgs
|
|
||||||
args._class or (throw ''
|
args._class or (throw ''
|
||||||
Your version of nixpkgs is incompatible with the latest clan.
|
Your version of nixpkgs is incompatible with the latest clan.
|
||||||
Please update nixpkgs input to the latest nixos-unstable or nixpkgs-unstable.
|
Please update nixpkgs input to the latest nixos-unstable or nixpkgs-unstable.
|
||||||
@@ -179,7 +176,7 @@ in
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(lib.modules.importApply ../../nixosModules/machineModules/forName.nix {
|
(lib.modules.importApply ../machineModules/forName.nix {
|
||||||
inherit (config.inventory) meta;
|
inherit (config.inventory) meta;
|
||||||
inherit
|
inherit
|
||||||
name
|
name
|
||||||
@@ -219,44 +216,43 @@ in
|
|||||||
inherit nixosConfigurations;
|
inherit nixosConfigurations;
|
||||||
inherit darwinConfigurations;
|
inherit darwinConfigurations;
|
||||||
|
|
||||||
|
exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports;
|
||||||
|
|
||||||
clanInternals = {
|
clanInternals = {
|
||||||
inventoryClass =
|
inventoryClass =
|
||||||
let
|
let
|
||||||
flakeInputs = config.self.inputs;
|
flakeInputs = config.self.inputs;
|
||||||
# Compute the relative directory path
|
|
||||||
selfStr = toString config.self;
|
|
||||||
dirStr = toString directory;
|
|
||||||
relativeDirectory =
|
|
||||||
if selfStr == dirStr then
|
|
||||||
""
|
|
||||||
else if lib.hasPrefix selfStr dirStr then
|
|
||||||
lib.removePrefix (selfStr + "/") dirStr
|
|
||||||
else
|
|
||||||
# This shouldn't happen in normal usage, but can occur when
|
|
||||||
# the flake is copied (e.g., in tests). Fall back to empty string.
|
|
||||||
"";
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
_module.args = {
|
_module.args = {
|
||||||
inherit clanLib;
|
inherit clanLib;
|
||||||
};
|
};
|
||||||
imports = [
|
imports = [
|
||||||
../inventoryClass/default.nix
|
../inventoryClass/builder/default.nix
|
||||||
|
(lib.modules.importApply ../inventoryClass/service-list-from-inputs.nix {
|
||||||
|
inherit flakeInputs clanLib;
|
||||||
|
})
|
||||||
{
|
{
|
||||||
inherit
|
inherit inventory directory;
|
||||||
inventory
|
|
||||||
directory
|
|
||||||
flakeInputs
|
|
||||||
relativeDirectory
|
|
||||||
;
|
|
||||||
exportsModule = config.exportsModule;
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
{ ... }:
|
let
|
||||||
|
clanConfig = config;
|
||||||
|
in
|
||||||
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
staticModules = clan-core.clan.modules;
|
staticModules = clan-core.clan.modules;
|
||||||
|
|
||||||
|
distributedServices = clanLib.inventory.mapInstances {
|
||||||
|
inherit (clanConfig) inventory exportsModule;
|
||||||
|
inherit flakeInputs directory;
|
||||||
|
clanCoreModules = clan-core.clan.modules;
|
||||||
|
prefix = [ "distributedServices" ];
|
||||||
|
};
|
||||||
|
machines = config.distributedServices.allMachines;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
../inventoryClass/inventory-introspection.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,28 +1,3 @@
|
|||||||
/**
|
|
||||||
The templates submodule
|
|
||||||
|
|
||||||
'clan.templates'
|
|
||||||
|
|
||||||
Different kinds supported:
|
|
||||||
|
|
||||||
- clan templates: 'clan.templates.clan'
|
|
||||||
- disko templates: 'clan.templates.disko'
|
|
||||||
- machine templates: 'clan.templates.machine'
|
|
||||||
|
|
||||||
A template has the form:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
{
|
|
||||||
description: string; # short summary what the template contains
|
|
||||||
path: path; # path to the template
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The clan API copies the template from the given 'path'
|
|
||||||
into a target folder. For example,
|
|
||||||
|
|
||||||
`./machines/<machine-name>` for 'machine' templates.
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
lib ? import <nixpkgs/lib>,
|
lib ? import <nixpkgs/lib>,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
clanLibOrig = (import ./. { inherit lib; }).__unfix__;
|
clanLibOrig = (import ./.. { inherit lib; }).__unfix__;
|
||||||
clanLibWithFs =
|
clanLibWithFs =
|
||||||
{ virtual_fs }:
|
{ virtual_fs }:
|
||||||
lib.fix (
|
lib.fix (
|
||||||
@@ -11,19 +11,19 @@ let
|
|||||||
let
|
let
|
||||||
clan-core = {
|
clan-core = {
|
||||||
clanLib = final;
|
clanLib = final;
|
||||||
modules.clan.default = lib.modules.importApply ../modules/clan { inherit clan-core; };
|
modules.clan.default = lib.modules.importApply ./clan { inherit clan-core; };
|
||||||
|
|
||||||
# Note: Can add other things to "clan-core"
|
# Note: Can add other things to "clan-core"
|
||||||
# ... Not needed for this test
|
# ... Not needed for this test
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan = import ./clan {
|
clan = import ../clan {
|
||||||
inherit lib clan-core;
|
inherit lib clan-core;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Override clanLib.fs for unit-testing against a virtual filesystem
|
# Override clanLib.fs for unit-testing against a virtual filesystem
|
||||||
fs = import ./clanTest/virtual-fs.nix { inherit lib; } {
|
fs = import ../clanTest/virtual-fs.nix { inherit lib; } {
|
||||||
inherit rootPath virtual_fs;
|
inherit rootPath virtual_fs;
|
||||||
# Example of a passthru
|
# Example of a passthru
|
||||||
# passthru = [
|
# passthru = [
|
||||||
@@ -53,12 +53,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}).clan
|
}).clan
|
||||||
{
|
{ config.directory = rootPath; };
|
||||||
directory = rootPath;
|
|
||||||
self = {
|
|
||||||
inputs.nixpkgs.lib.version = "25.11";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit vclan;
|
inherit vclan;
|
||||||
@@ -99,12 +94,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}).clan
|
}).clan
|
||||||
{
|
{ config.directory = rootPath; };
|
||||||
directory = rootPath;
|
|
||||||
self = {
|
|
||||||
inputs.nixpkgs.lib.version = "25.11";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit vclan;
|
inherit vclan;
|
||||||
@@ -1,24 +1,19 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
clanModule,
|
|
||||||
clanLib,
|
|
||||||
clan-core,
|
clan-core,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
eval = lib.evalModules {
|
eval = lib.evalModules {
|
||||||
modules = [
|
modules = [
|
||||||
clanModule
|
clan-core.modules.clan.default
|
||||||
];
|
];
|
||||||
specialArgs = {
|
|
||||||
self = clan-core;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
evalDocs = pkgs.nixosOptionsDoc {
|
evalDocs = pkgs.nixosOptionsDoc {
|
||||||
options = eval.options;
|
options = eval.options;
|
||||||
warningsAreErrors = false;
|
warningsAreErrors = false;
|
||||||
transformOptions = clanLib.docs.stripStorePathsFromDeclarations;
|
transformOptions = clan-core.clanLib.docs.stripStorePathsFromDeclarations;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
58
lib/modules/flake-module.nix
Normal file
58
lib/modules/flake-module.nix
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./clan/flake-module.nix
|
||||||
|
];
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
jsonDocs = import ./eval-docs.nix {
|
||||||
|
inherit pkgs lib;
|
||||||
|
clan-core = self;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
legacyPackages.clan-options = jsonDocs.optionsJSON;
|
||||||
|
|
||||||
|
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests-build-clan
|
||||||
|
legacyPackages.evalTests-build-clan = import ./tests.nix {
|
||||||
|
inherit lib;
|
||||||
|
clan-core = self;
|
||||||
|
};
|
||||||
|
checks = {
|
||||||
|
eval-lib-build-clan = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||||
|
export HOME="$(realpath .)"
|
||||||
|
|
||||||
|
nix-unit --eval-store "$HOME" \
|
||||||
|
--extra-experimental-features flakes \
|
||||||
|
--show-trace \
|
||||||
|
${inputOverrides} \
|
||||||
|
--flake ${
|
||||||
|
self.filter {
|
||||||
|
include = [
|
||||||
|
"flakeModules"
|
||||||
|
"inventory.json"
|
||||||
|
"lib"
|
||||||
|
"machines"
|
||||||
|
"nixosModules"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}#legacyPackages.${system}.evalTests-build-clan
|
||||||
|
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
lib,
|
lib,
|
||||||
clanLib,
|
clanLib,
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
services = clanLib.callLib ./distributed-service/inventory-adapter.nix { };
|
||||||
|
in
|
||||||
{
|
{
|
||||||
|
inherit (services) mapInstances;
|
||||||
inventoryModule = {
|
inventoryModule = {
|
||||||
_file = "clanLib.inventory.module";
|
_file = "clanLib.inventory.module";
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/inventoryClass/inventory.nix
|
../inventoryClass/inventory.nix
|
||||||
];
|
];
|
||||||
_module.args = { inherit clanLib; };
|
_module.args = { inherit clanLib; };
|
||||||
};
|
};
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
{
|
{
|
||||||
# TODO: consume directly from clan.config
|
# TODO: consume directly from clan.config
|
||||||
directory,
|
directory,
|
||||||
exports,
|
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
@@ -18,10 +17,10 @@ in
|
|||||||
{
|
{
|
||||||
# TODO: merge these options into clan options
|
# TODO: merge these options into clan options
|
||||||
options = {
|
options = {
|
||||||
# exportsModule = mkOption {
|
exportsModule = mkOption {
|
||||||
# type = types.deferredModule;
|
type = types.deferredModule;
|
||||||
# readOnly = true;
|
readOnly = true;
|
||||||
# };
|
};
|
||||||
mappedServices = mkOption {
|
mappedServices = mkOption {
|
||||||
visible = false;
|
visible = false;
|
||||||
type = attrsWith {
|
type = attrsWith {
|
||||||
@@ -29,11 +28,9 @@ in
|
|||||||
elemType = submoduleWith {
|
elemType = submoduleWith {
|
||||||
class = "clan.service";
|
class = "clan.service";
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
|
exports = config.exports;
|
||||||
|
directory = directory;
|
||||||
clanLib = specialArgs.clanLib;
|
clanLib = specialArgs.clanLib;
|
||||||
inherit
|
|
||||||
exports
|
|
||||||
directory
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
(
|
(
|
||||||
@@ -54,13 +51,34 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
exports = mkOption {
|
exports = mkOption {
|
||||||
type = types.lazyAttrsOf types.deferredModule;
|
type = submoduleWith {
|
||||||
|
modules = [
|
||||||
# collect exports from all services
|
{
|
||||||
# zipAttrs is needed until we use the record type.
|
options = {
|
||||||
default = lib.zipAttrsWith (_name: values: { imports = values; }) (
|
instances = lib.mkOption {
|
||||||
lib.mapAttrsToList (_name: service: service.exports) config.mappedServices
|
default = { };
|
||||||
);
|
# instances.<instanceName>...
|
||||||
|
type = types.attrsOf (submoduleWith {
|
||||||
|
modules = [
|
||||||
|
config.exportsModule
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
# instances.<machineName>...
|
||||||
|
machines = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf (submoduleWith {
|
||||||
|
modules = [
|
||||||
|
config.exportsModule
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ lib.mapAttrsToList (_: service: service.exports) config.mappedServices;
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,9 @@ lib.evalModules {
|
|||||||
specialArgs._ctx = prefix;
|
specialArgs._ctx = prefix;
|
||||||
modules = [
|
modules = [
|
||||||
# Base module
|
# Base module
|
||||||
./inventory/distributed-service/service-module.nix
|
./service-module.nix
|
||||||
# Feature modules
|
# Feature modules
|
||||||
(lib.modules.importApply ./inventory/distributed-service/api-feature.nix {
|
(lib.modules.importApply ./api-feature.nix {
|
||||||
inherit clanLib prefix;
|
inherit clanLib prefix;
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
@@ -13,18 +13,16 @@ in
|
|||||||
let
|
let
|
||||||
# Common filtered source for inventory tests
|
# Common filtered source for inventory tests
|
||||||
inventoryTestsSrc = lib.fileset.toSource {
|
inventoryTestsSrc = lib.fileset.toSource {
|
||||||
root = ../../..;
|
root = ../../../..;
|
||||||
fileset = lib.fileset.unions [
|
fileset = lib.fileset.unions [
|
||||||
../../../flake.nix
|
../../../../flake.nix
|
||||||
../../../flake.lock
|
../../../../flake.lock
|
||||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
|
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../../..)
|
||||||
../../../flakeModules
|
../../../../flakeModules
|
||||||
../../../lib
|
../../../../lib
|
||||||
../../../nixosModules/clanCore
|
../../../../nixosModules/clanCore
|
||||||
../../../nixosModules/machineModules
|
../../../../machines
|
||||||
../../../machines
|
../../../../inventory.json
|
||||||
../../../inventory.json
|
|
||||||
../../../modules
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
171
lib/modules/inventory/distributed-service/inventory-adapter.nix
Normal file
171
lib/modules/inventory/distributed-service/inventory-adapter.nix
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Adapter function between the inventory.instances and the clan.service module
|
||||||
|
#
|
||||||
|
# Data flow:
|
||||||
|
# - inventory.instances -> Adapter -> clan.service module -> Service Resources (i.e. NixosModules per Machine, Vars per Service, etc.)
|
||||||
|
#
|
||||||
|
# What this file does:
|
||||||
|
#
|
||||||
|
# - Resolves the [Module] to an actual module-path and imports it.
|
||||||
|
# - Groups together all the same modules into a single import and creates all instances for it.
|
||||||
|
# - Resolves the inventory tags into machines. Tags don't exist at the service level.
|
||||||
|
# Also combines the settings for 'machines' and 'tags'.
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
clanLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
mapInstances =
|
||||||
|
{
|
||||||
|
# This is used to resolve the module imports from 'flake.inputs'
|
||||||
|
flakeInputs,
|
||||||
|
# The clan inventory
|
||||||
|
inventory,
|
||||||
|
directory,
|
||||||
|
clanCoreModules,
|
||||||
|
prefix ? [ ],
|
||||||
|
exportsModule,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags;
|
||||||
|
|
||||||
|
# map the instances into the module
|
||||||
|
importedModuleWithInstances = lib.mapAttrs (
|
||||||
|
instanceName: instance:
|
||||||
|
let
|
||||||
|
resolvedModule = clanLib.resolveModule {
|
||||||
|
moduleSpec = instance.module;
|
||||||
|
inherit flakeInputs clanCoreModules;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Every instance includes machines via roles
|
||||||
|
# :: { client :: ... }
|
||||||
|
instanceRoles = lib.mapAttrs (
|
||||||
|
roleName: role:
|
||||||
|
let
|
||||||
|
resolvedMachines = clanLib.inventory.resolveTags {
|
||||||
|
members = {
|
||||||
|
# Explicit members
|
||||||
|
machines = lib.attrNames role.machines;
|
||||||
|
# Resolved Members
|
||||||
|
tags = lib.attrNames role.tags;
|
||||||
|
};
|
||||||
|
inherit (inventory) machines;
|
||||||
|
inherit instanceName roleName;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
# instances.<instanceName>.roles.<roleName> =
|
||||||
|
# Remove "tags", they are resolved into "machines"
|
||||||
|
(removeAttrs role [ "tags" ])
|
||||||
|
// {
|
||||||
|
machines = lib.genAttrs resolvedMachines.machines (
|
||||||
|
machineName:
|
||||||
|
let
|
||||||
|
machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { };
|
||||||
|
in
|
||||||
|
# TODO: tag settings
|
||||||
|
# Wait for this feature until option introspection for 'settings' is done.
|
||||||
|
# This might get too complex to handle otherwise.
|
||||||
|
# settingsViaTags = lib.filterAttrs (
|
||||||
|
# tagName: _: machineHasTag machineName tagName
|
||||||
|
# ) instance.roles.${roleName}.tags;
|
||||||
|
{
|
||||||
|
# TODO: Do we want to wrap settings with
|
||||||
|
# setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}";
|
||||||
|
settings = {
|
||||||
|
imports = [
|
||||||
|
machineSettings
|
||||||
|
]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
) instance.roles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit (instance) module;
|
||||||
|
inherit resolvedModule instanceRoles;
|
||||||
|
}
|
||||||
|
) inventory.instances or { };
|
||||||
|
|
||||||
|
# Group the instances by the module they resolve to
|
||||||
|
# This is necessary to evaluate the module in a single pass
|
||||||
|
# :: { <module.input>_<module.name> :: [ { name, value } ] }
|
||||||
|
# Since 'perMachine' needs access to all the instances we should include them as a whole
|
||||||
|
grouped = lib.foldlAttrs (
|
||||||
|
acc: instanceName: instance:
|
||||||
|
let
|
||||||
|
inputName = if instance.module.input == null then "<clan-core>" else instance.module.input;
|
||||||
|
id = inputName + "-" + instance.module.name;
|
||||||
|
in
|
||||||
|
acc
|
||||||
|
// {
|
||||||
|
${id} = acc.${id} or [ ] ++ [
|
||||||
|
{
|
||||||
|
inherit instanceName instance;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
) { } importedModuleWithInstances;
|
||||||
|
|
||||||
|
# servicesEval.config.mappedServices.self-A.result.final.jon.nixosModule
|
||||||
|
allMachines = lib.mapAttrs (machineName: _: {
|
||||||
|
# This is the list of nixosModules for each machine
|
||||||
|
machineImports = lib.foldlAttrs (
|
||||||
|
acc: _module_ident: serviceModule:
|
||||||
|
acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ]
|
||||||
|
) [ ] servicesEval.config.mappedServices;
|
||||||
|
}) inventory.machines or { };
|
||||||
|
|
||||||
|
evalServices =
|
||||||
|
{ modules, prefix }:
|
||||||
|
lib.evalModules {
|
||||||
|
class = "clan";
|
||||||
|
specialArgs = {
|
||||||
|
inherit clanLib;
|
||||||
|
_ctx = prefix;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
(import ./all-services-wrapper.nix { inherit directory; })
|
||||||
|
]
|
||||||
|
++ modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
servicesEval = evalServices {
|
||||||
|
inherit prefix;
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
inherit exportsModule;
|
||||||
|
mappedServices = lib.mapAttrs (_module_ident: instances: {
|
||||||
|
imports = [
|
||||||
|
# Import the resolved module.
|
||||||
|
# i.e. clan.modules.admin
|
||||||
|
{
|
||||||
|
options.module = lib.mkOption {
|
||||||
|
type = lib.types.raw;
|
||||||
|
default = (builtins.head instances).instance.module;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(builtins.head instances).instance.resolvedModule
|
||||||
|
] # Include all the instances that correlate to the resolved module
|
||||||
|
++ (builtins.map (v: {
|
||||||
|
instances.${v.instanceName}.roles = v.instance.instanceRoles;
|
||||||
|
}) instances);
|
||||||
|
}) grouped;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
importedModulesEvaluated = servicesEval.config.mappedServices;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit
|
||||||
|
servicesEval
|
||||||
|
importedModuleWithInstances
|
||||||
|
# Exposed for testing
|
||||||
|
grouped
|
||||||
|
allMachines
|
||||||
|
importedModulesEvaluated
|
||||||
|
;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -81,7 +81,6 @@ let
|
|||||||
applySettings =
|
applySettings =
|
||||||
instanceName: instance:
|
instanceName: instance:
|
||||||
lib.mapAttrs (roleName: role: {
|
lib.mapAttrs (roleName: role: {
|
||||||
settings = config.instances.${instanceName}.roles.${roleName}.finalSettings.config;
|
|
||||||
machines = lib.mapAttrs (machineName: _v: {
|
machines = lib.mapAttrs (machineName: _v: {
|
||||||
settings =
|
settings =
|
||||||
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config;
|
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config;
|
||||||
@@ -159,29 +158,6 @@ in
|
|||||||
(
|
(
|
||||||
{ name, ... }@role:
|
{ name, ... }@role:
|
||||||
{
|
{
|
||||||
options.finalSettings = mkOption {
|
|
||||||
default = evalMachineSettings instance.name role.name null role.config.settings { };
|
|
||||||
type = types.raw;
|
|
||||||
description = ''
|
|
||||||
Final evaluated settings of the curent-machine
|
|
||||||
|
|
||||||
This contains the merged and evaluated settings of the role interface,
|
|
||||||
the role settings and the machine settings.
|
|
||||||
|
|
||||||
Type: 'configuration' as returned by 'lib.evalModules'
|
|
||||||
'';
|
|
||||||
apply = lib.warn ''
|
|
||||||
=== WANRING ===
|
|
||||||
'roles.<roleName>.settings' do not contain machine specific settings.
|
|
||||||
|
|
||||||
Prefer `machines.<machineName>.settings` instead. (i.e `perInstance: roles.<roleName>.machines.<machineName>.settings`)
|
|
||||||
|
|
||||||
If you have a use-case that requires access to the original role settings without machine overrides.
|
|
||||||
Contact us via matrix (https://matrix.to/#/#clan:clan.lol) or file an issue: https://git.clan.lol
|
|
||||||
|
|
||||||
This feature will be removed in the next release
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
# instances.{instanceName}.roles.{roleName}.machines
|
# instances.{instanceName}.roles.{roleName}.machines
|
||||||
options.machines = mkOption {
|
options.machines = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
@@ -240,7 +216,7 @@ in
|
|||||||
|
|
||||||
options.extraModules = lib.mkOption {
|
options.extraModules = lib.mkOption {
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf types.deferredModule;
|
type = types.listOf (types.either types.deferredModule types.str);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -504,12 +480,9 @@ in
|
|||||||
staticModules = [
|
staticModules = [
|
||||||
({
|
({
|
||||||
options.exports = mkOption {
|
options.exports = mkOption {
|
||||||
type = types.lazyAttrsOf types.deferredModule;
|
type = types.deferredModule;
|
||||||
default = { };
|
default = { };
|
||||||
description = ''
|
description = ''
|
||||||
!!! Danger "Experimental Feature"
|
|
||||||
This feature is experimental and will change in the future.
|
|
||||||
|
|
||||||
export modules defined in 'perInstance'
|
export modules defined in 'perInstance'
|
||||||
mapped to their instance name
|
mapped to their instance name
|
||||||
|
|
||||||
@@ -634,21 +607,10 @@ in
|
|||||||
type = types.deferredModuleWith {
|
type = types.deferredModuleWith {
|
||||||
staticModules = [
|
staticModules = [
|
||||||
({
|
({
|
||||||
# exports."///".generator.name = { _file ... import = []; _type = }
|
|
||||||
# exports."///".networking = { _file ... import = []; }
|
|
||||||
|
|
||||||
# generators."///".name = { name, ...}: { _file ... import = [];}
|
|
||||||
# networks."///" = { _file ... import = []; }
|
|
||||||
|
|
||||||
# { _file ... import = []; }
|
|
||||||
# { _file ... import = []; }
|
|
||||||
options.exports = mkOption {
|
options.exports = mkOption {
|
||||||
type = types.lazyAttrsOf types.deferredModule;
|
type = types.deferredModule;
|
||||||
default = { };
|
default = { };
|
||||||
description = ''
|
description = ''
|
||||||
!!! Danger "Experimental Feature"
|
|
||||||
This feature is experimental and will change in the future.
|
|
||||||
|
|
||||||
export modules defined in 'perMachine'
|
export modules defined in 'perMachine'
|
||||||
mapped to their machine name
|
mapped to their machine name
|
||||||
|
|
||||||
@@ -750,9 +712,6 @@ in
|
|||||||
|
|
||||||
exports = mkOption {
|
exports = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
!!! Danger "Experimental Feature"
|
|
||||||
This feature is experimental and will change in the future.
|
|
||||||
|
|
||||||
This services exports.
|
This services exports.
|
||||||
Gets merged with all other services exports.
|
Gets merged with all other services exports.
|
||||||
|
|
||||||
@@ -775,38 +734,79 @@ in
|
|||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
default = { };
|
default = { };
|
||||||
type = types.lazyAttrsOf (
|
type = types.submoduleWith {
|
||||||
types.deferredModuleWith {
|
# Static modules
|
||||||
# staticModules = [];
|
modules = [
|
||||||
# lib.concatLists (
|
{
|
||||||
# lib.concatLists (
|
options.instances = mkOption {
|
||||||
# lib.mapAttrsToList (
|
type = types.attrsOf types.deferredModule;
|
||||||
# _roleName: role:
|
description = ''
|
||||||
# lib.mapAttrsToList (
|
export modules defined in 'perInstance'
|
||||||
# _instanceName: instance: lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines
|
mapped to their instance name
|
||||||
# ) role.allInstances
|
|
||||||
# ) config.result.allRoles
|
Example
|
||||||
# )
|
|
||||||
# )
|
with instances:
|
||||||
# ++
|
|
||||||
}
|
```nix
|
||||||
);
|
instances.A = { ... };
|
||||||
# # Lazy default via imports
|
instances.B= { ... };
|
||||||
# # should probably be moved to deferredModuleWith { staticModules = [ ]; }
|
|
||||||
# imports =
|
roles.peer.perInstance = { instanceName, machine, ... }:
|
||||||
# if config._docs_rendering then
|
{
|
||||||
# [ ]
|
exports.foo = 1;
|
||||||
# else
|
}
|
||||||
# lib.mapAttrsToList (_roleName: role: {
|
|
||||||
# instances = lib.mapAttrs (_instanceName: instance: {
|
This yields all other services can access these exports
|
||||||
# imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines;
|
=>
|
||||||
# }) role.allInstances;
|
exports.instances.A.foo = 1;
|
||||||
# }) config.result.allRoles
|
exports.instances.B.foo = 1;
|
||||||
# ++ lib.mapAttrsToList (machineName: machine: {
|
```
|
||||||
# machines.${machineName} = machine.exports;
|
'';
|
||||||
# }) config.result.allMachines;
|
};
|
||||||
# }
|
options.machines = mkOption {
|
||||||
# ];
|
type = types.attrsOf types.deferredModule;
|
||||||
|
description = ''
|
||||||
|
export modules defined in 'perMachine'
|
||||||
|
mapped to their machine name
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
with machines:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
instances.A = { roles.peer.machines.jon = ... };
|
||||||
|
instances.B = { roles.peer.machines.jon = ... };
|
||||||
|
|
||||||
|
perMachine = { machine, ... }:
|
||||||
|
{
|
||||||
|
exports.foo = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
This yields all other services can access these exports
|
||||||
|
=>
|
||||||
|
exports.machines.jon.foo = 1;
|
||||||
|
exports.machines.sara.foo = 1;
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# Lazy default via imports
|
||||||
|
# should probably be moved to deferredModuleWith { staticModules = [ ]; }
|
||||||
|
imports =
|
||||||
|
if config._docs_rendering then
|
||||||
|
[ ]
|
||||||
|
else
|
||||||
|
lib.mapAttrsToList (_roleName: role: {
|
||||||
|
instances = lib.mapAttrs (_instanceName: instance: {
|
||||||
|
imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines;
|
||||||
|
}) role.allInstances;
|
||||||
|
}) config.result.allRoles
|
||||||
|
++ lib.mapAttrsToList (machineName: machine: {
|
||||||
|
machines.${machineName} = machine.exports;
|
||||||
|
}) config.result.allMachines;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
# ---
|
# ---
|
||||||
# Place the result in _module.result to mark them as "internal" and discourage usage/overrides
|
# Place the result in _module.result to mark them as "internal" and discourage usage/overrides
|
||||||
@@ -850,11 +850,7 @@ in
|
|||||||
instanceRes.nixosModule
|
instanceRes.nixosModule
|
||||||
]
|
]
|
||||||
++ (map (
|
++ (map (
|
||||||
s:
|
s: if builtins.typeOf s == "string" then "${directory}/${s}" else s
|
||||||
if builtins.typeOf s == "string" then
|
|
||||||
lib.warn "String types for 'extraModules' will be deprecated - ${s}" "${directory}/${s}"
|
|
||||||
else
|
|
||||||
lib.setDefaultModuleLocation "via inventory.instances.${instanceName}.roles.${roleName}" s
|
|
||||||
) instanceCfg.roles.${roleName}.extraModules);
|
) instanceCfg.roles.${roleName}.extraModules);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -991,39 +987,5 @@ in
|
|||||||
}
|
}
|
||||||
) config.result.allMachines;
|
) config.result.allMachines;
|
||||||
};
|
};
|
||||||
|
|
||||||
debug = mkOption {
|
|
||||||
default = lib.zipAttrsWith (_name: values: { imports = values; }) (
|
|
||||||
lib.mapAttrsToList (_machineName: machine: machine.exports) config.result.allMachines
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
|
||||||
{
|
|
||||||
# collect exports from all machines
|
|
||||||
# zipAttrs is needed until we use the record type.
|
|
||||||
exports = lib.zipAttrsWith (_name: values: { imports = values; }) (
|
|
||||||
lib.mapAttrsToList (_machineName: machine: machine.exports) config.result.allMachines
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# collect exports from all instances, roles and machines
|
|
||||||
# zipAttrs is needed until we use the record type.
|
|
||||||
exports = lib.zipAttrsWith (_name: values: { imports = values; }) (
|
|
||||||
lib.concatLists (
|
|
||||||
lib.concatLists (
|
|
||||||
lib.mapAttrsToList (
|
|
||||||
_roleName: role:
|
|
||||||
lib.mapAttrsToList (
|
|
||||||
_instanceName: instance: lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines
|
|
||||||
) role.allInstances
|
|
||||||
) config.result.allRoles
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,53 +4,63 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
inherit (lib)
|
||||||
|
evalModules
|
||||||
|
;
|
||||||
|
|
||||||
flakeInputsFixture = {
|
evalInventory =
|
||||||
upstream.clan.modules = {
|
m:
|
||||||
uzzi = {
|
(evalModules {
|
||||||
_class = "clan.service";
|
# Static modules
|
||||||
manifest = {
|
modules = [
|
||||||
name = "uzzi-from-upstream";
|
clanLib.inventory.inventoryModule
|
||||||
};
|
{
|
||||||
};
|
_file = "test file";
|
||||||
};
|
tags.all = [ ];
|
||||||
};
|
tags.nixos = [ ];
|
||||||
|
tags.darwin = [ ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
modules.test = { };
|
||||||
|
}
|
||||||
|
m
|
||||||
|
];
|
||||||
|
}).config;
|
||||||
|
|
||||||
createTestClan =
|
callInventoryAdapter =
|
||||||
testClan:
|
inventoryModule:
|
||||||
let
|
let
|
||||||
res = clanLib.clan ({
|
inventory = evalInventory inventoryModule;
|
||||||
# Static / mocked
|
flakeInputsFixture = {
|
||||||
specialArgs = {
|
self.clan.modules = inventoryModule.modules or { };
|
||||||
clan-core = {
|
# Example upstream module
|
||||||
clan.modules = { };
|
upstream.clan.modules = {
|
||||||
|
uzzi = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "uzzi-from-upstream";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
self.inputs = flakeInputsFixture // {
|
};
|
||||||
self.clan = res.config;
|
|
||||||
};
|
|
||||||
directory = ./.;
|
|
||||||
exportsModule = { };
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
testClan
|
|
||||||
];
|
|
||||||
});
|
|
||||||
in
|
in
|
||||||
res;
|
clanLib.inventory.mapInstances {
|
||||||
|
directory = ./.;
|
||||||
|
clanCoreModules = { };
|
||||||
|
flakeInputs = flakeInputsFixture;
|
||||||
|
inherit inventory;
|
||||||
|
exportsModule = { };
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
extraModules = import ./extraModules.nix { inherit clanLib; };
|
extraModules = import ./extraModules.nix { inherit clanLib; };
|
||||||
exports = import ./exports.nix { inherit lib clanLib; };
|
exports = import ./exports.nix { inherit lib clanLib; };
|
||||||
settings = import ./settings.nix { inherit lib createTestClan; };
|
settings = import ./settings.nix { inherit lib callInventoryAdapter; };
|
||||||
specialArgs = import ./specialArgs.nix { inherit lib createTestClan; };
|
specialArgs = import ./specialArgs.nix { inherit lib callInventoryAdapter; };
|
||||||
resolve_module_spec = import ./import_module_spec.nix {
|
resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; };
|
||||||
inherit lib createTestClan;
|
|
||||||
};
|
|
||||||
test_simple =
|
test_simple =
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
# It isn't exactly doing anything but it's a valid module that produces an output
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
@@ -61,7 +71,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
# User config
|
# User config
|
||||||
inventory.instances."instance_foo" = {
|
instances."instance_foo" = {
|
||||||
module = {
|
module = {
|
||||||
name = "simple-module";
|
name = "simple-module";
|
||||||
};
|
};
|
||||||
@@ -71,7 +81,7 @@ in
|
|||||||
{
|
{
|
||||||
# Test that the module is mapped into the output
|
# Test that the module is mapped into the output
|
||||||
# We might change the attribute name in the future
|
# We might change the attribute name in the future
|
||||||
expr = res.config._services.mappedServices ? "<clan-core>-simple-module";
|
expr = res.importedModulesEvaluated ? "<clan-core>-simple-module";
|
||||||
expected = true;
|
expected = true;
|
||||||
inherit res;
|
inherit res;
|
||||||
};
|
};
|
||||||
@@ -82,7 +92,7 @@ in
|
|||||||
# All instances should be included within one evaluation to make all of them available
|
# All instances should be included within one evaluation to make all of them available
|
||||||
test_module_grouping =
|
test_module_grouping =
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
# It isn't exactly doing anything but it's a valid module that produces an output
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
@@ -102,19 +112,18 @@ in
|
|||||||
|
|
||||||
perMachine = { }: { };
|
perMachine = { }: { };
|
||||||
};
|
};
|
||||||
|
|
||||||
# User config
|
# User config
|
||||||
inventory.instances."instance_foo" = {
|
instances."instance_foo" = {
|
||||||
module = {
|
module = {
|
||||||
name = "A";
|
name = "A";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
inventory.instances."instance_bar" = {
|
instances."instance_bar" = {
|
||||||
module = {
|
module = {
|
||||||
name = "B";
|
name = "B";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
inventory.instances."instance_baz" = {
|
instances."instance_baz" = {
|
||||||
module = {
|
module = {
|
||||||
name = "A";
|
name = "A";
|
||||||
};
|
};
|
||||||
@@ -124,16 +133,16 @@ in
|
|||||||
{
|
{
|
||||||
# Test that the module is mapped into the output
|
# Test that the module is mapped into the output
|
||||||
# We might change the attribute name in the future
|
# We might change the attribute name in the future
|
||||||
expr = lib.attrNames res.config._services.mappedServices;
|
expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped;
|
||||||
expected = [
|
expected = {
|
||||||
"<clan-core>-A"
|
"<clan-core>-A" = 2;
|
||||||
"<clan-core>-B"
|
"<clan-core>-B" = 1;
|
||||||
];
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
test_creates_all_instances =
|
test_creates_all_instances =
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
# It isn't exactly doing anything but it's a valid module that produces an output
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
@@ -145,24 +154,22 @@ in
|
|||||||
|
|
||||||
perMachine = { }: { };
|
perMachine = { }: { };
|
||||||
};
|
};
|
||||||
inventory = {
|
instances."instance_foo" = {
|
||||||
instances."instance_foo" = {
|
module = {
|
||||||
module = {
|
name = "A";
|
||||||
name = "A";
|
input = "self";
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
instances."instance_bar" = {
|
};
|
||||||
module = {
|
instances."instance_bar" = {
|
||||||
name = "A";
|
module = {
|
||||||
input = "self";
|
name = "A";
|
||||||
};
|
input = "self";
|
||||||
};
|
};
|
||||||
instances."instance_zaza" = {
|
};
|
||||||
module = {
|
instances."instance_zaza" = {
|
||||||
name = "B";
|
module = {
|
||||||
input = null;
|
name = "B";
|
||||||
};
|
input = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -170,7 +177,7 @@ in
|
|||||||
{
|
{
|
||||||
# Test that the module is mapped into the output
|
# Test that the module is mapped into the output
|
||||||
# We might change the attribute name in the future
|
# We might change the attribute name in the future
|
||||||
expr = lib.attrNames res.config._services.mappedServices.self-A.instances;
|
expr = lib.attrNames res.importedModulesEvaluated.self-A.instances;
|
||||||
expected = [
|
expected = [
|
||||||
"instance_bar"
|
"instance_bar"
|
||||||
"instance_foo"
|
"instance_foo"
|
||||||
@@ -180,7 +187,7 @@ in
|
|||||||
# Membership via roles
|
# Membership via roles
|
||||||
test_add_machines_directly =
|
test_add_machines_directly =
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
# It isn't exactly doing anything but it's a valid module that produces an output
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
@@ -195,40 +202,38 @@ in
|
|||||||
|
|
||||||
# perMachine = {}: {};
|
# perMachine = {}: {};
|
||||||
};
|
};
|
||||||
inventory = {
|
machines = {
|
||||||
machines = {
|
jon = { };
|
||||||
jon = { };
|
sara = { };
|
||||||
sara = { };
|
hxi = { };
|
||||||
hxi = { };
|
};
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
};
|
};
|
||||||
instances."instance_foo" = {
|
roles.peer.machines.jon = { };
|
||||||
module = {
|
};
|
||||||
name = "A";
|
instances."instance_bar" = {
|
||||||
input = "self";
|
module = {
|
||||||
};
|
name = "A";
|
||||||
roles.peer.machines.jon = { };
|
input = "self";
|
||||||
};
|
};
|
||||||
instances."instance_bar" = {
|
roles.peer.machines.sara = { };
|
||||||
module = {
|
};
|
||||||
name = "A";
|
instances."instance_zaza" = {
|
||||||
input = "self";
|
module = {
|
||||||
};
|
name = "B";
|
||||||
roles.peer.machines.sara = { };
|
input = null;
|
||||||
};
|
|
||||||
instances."instance_zaza" = {
|
|
||||||
module = {
|
|
||||||
name = "B";
|
|
||||||
input = null;
|
|
||||||
};
|
|
||||||
roles.peer.tags.all = { };
|
|
||||||
};
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Test that the module is mapped into the output
|
# Test that the module is mapped into the output
|
||||||
# We might change the attribute name in the future
|
# We might change the attribute name in the future
|
||||||
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
|
expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines;
|
||||||
expected = [
|
expected = [
|
||||||
"jon"
|
"jon"
|
||||||
"sara"
|
"sara"
|
||||||
@@ -238,7 +243,7 @@ in
|
|||||||
# Membership via tags
|
# Membership via tags
|
||||||
test_add_machines_via_tags =
|
test_add_machines_via_tags =
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
# It isn't exactly doing anything but it's a valid module that produces an output
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
@@ -252,37 +257,35 @@ in
|
|||||||
|
|
||||||
# perMachine = {}: {};
|
# perMachine = {}: {};
|
||||||
};
|
};
|
||||||
inventory = {
|
machines = {
|
||||||
machines = {
|
jon = {
|
||||||
jon = {
|
tags = [ "foo" ];
|
||||||
tags = [ "foo" ];
|
|
||||||
};
|
|
||||||
sara = {
|
|
||||||
tags = [ "foo" ];
|
|
||||||
};
|
|
||||||
hxi = { };
|
|
||||||
};
|
};
|
||||||
instances."instance_foo" = {
|
sara = {
|
||||||
module = {
|
tags = [ "foo" ];
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.tags.foo = { };
|
|
||||||
};
|
};
|
||||||
instances."instance_zaza" = {
|
hxi = { };
|
||||||
module = {
|
};
|
||||||
name = "B";
|
instances."instance_foo" = {
|
||||||
input = null;
|
module = {
|
||||||
};
|
name = "A";
|
||||||
roles.peer.tags.all = { };
|
input = "self";
|
||||||
};
|
};
|
||||||
|
roles.peer.tags.foo = { };
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
input = null;
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Test that the module is mapped into the output
|
# Test that the module is mapped into the output
|
||||||
# We might change the attribute name in the future
|
# We might change the attribute name in the future
|
||||||
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
|
expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines;
|
||||||
expected = [
|
expected = [
|
||||||
"jon"
|
"jon"
|
||||||
"sara"
|
"sara"
|
||||||
@@ -290,9 +293,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
machine_imports = import ./machine_imports.nix { inherit lib clanLib; };
|
machine_imports = import ./machine_imports.nix { inherit lib clanLib; };
|
||||||
per_machine_args = import ./per_machine_args.nix { inherit lib createTestClan; };
|
per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; };
|
||||||
per_instance_args = import ./per_instance_args.nix {
|
per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; };
|
||||||
inherit lib;
|
|
||||||
callInventoryAdapter = createTestClan;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{ createTestClan, ... }:
|
{ callInventoryAdapter, ... }:
|
||||||
let
|
let
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
@@ -23,13 +23,10 @@ let
|
|||||||
|
|
||||||
resolve =
|
resolve =
|
||||||
spec:
|
spec:
|
||||||
createTestClan {
|
callInventoryAdapter {
|
||||||
inherit modules;
|
inherit modules machines;
|
||||||
inventory = {
|
instances."instance_foo" = {
|
||||||
inherit machines;
|
module = spec;
|
||||||
instances."instance_foo" = {
|
|
||||||
module = spec;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -39,16 +36,25 @@ in
|
|||||||
(resolve {
|
(resolve {
|
||||||
name = "A";
|
name = "A";
|
||||||
input = "self";
|
input = "self";
|
||||||
}).config._services.mappedServices.self-A.manifest.name;
|
}).importedModuleWithInstances.instance_foo.resolvedModule;
|
||||||
expected = "network";
|
expected = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "network";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
test_import_remote_module_by_name = {
|
test_import_remote_module_by_name = {
|
||||||
expr =
|
expr =
|
||||||
(resolve {
|
(resolve {
|
||||||
name = "uzzi";
|
name = "uzzi";
|
||||||
input = "upstream";
|
input = "upstream";
|
||||||
}).config._services.mappedServices.upstream-uzzi.manifest.name;
|
}).importedModuleWithInstances.instance_foo.resolvedModule;
|
||||||
expected = "uzzi-from-upstream";
|
expected = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "uzzi-from-upstream";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -58,43 +58,39 @@ let
|
|||||||
sara = { };
|
sara = { };
|
||||||
};
|
};
|
||||||
res = callInventoryAdapter {
|
res = callInventoryAdapter {
|
||||||
inherit modules;
|
inherit modules machines;
|
||||||
|
instances."instance_foo" = {
|
||||||
inventory = {
|
module = {
|
||||||
inherit machines;
|
name = "A";
|
||||||
instances."instance_foo" = {
|
input = "self";
|
||||||
module = {
|
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.machines.jon = {
|
|
||||||
settings.timeout = lib.mkForce "foo-peer-jon";
|
|
||||||
};
|
|
||||||
roles.peer = {
|
|
||||||
settings.timeout = "foo-peer";
|
|
||||||
};
|
|
||||||
roles.controller.machines.jon = { };
|
|
||||||
};
|
};
|
||||||
instances."instance_bar" = {
|
roles.peer.machines.jon = {
|
||||||
module = {
|
settings.timeout = lib.mkForce "foo-peer-jon";
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.machines.jon = {
|
|
||||||
settings.timeout = "bar-peer-jon";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
# TODO: move this into a seperate test.
|
roles.peer = {
|
||||||
# Seperate out the check that this module is never imported
|
settings.timeout = "foo-peer";
|
||||||
# import the module "B" (undefined)
|
|
||||||
# All machines have this instance
|
|
||||||
instances."instance_zaza" = {
|
|
||||||
module = {
|
|
||||||
name = "B";
|
|
||||||
input = null;
|
|
||||||
};
|
|
||||||
roles.peer.tags.all = { };
|
|
||||||
};
|
};
|
||||||
|
roles.controller.machines.jon = { };
|
||||||
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = "bar-peer-jon";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# TODO: move this into a seperate test.
|
||||||
|
# Seperate out the check that this module is never imported
|
||||||
|
# import the module "B" (undefined)
|
||||||
|
# All machines have this instance
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
input = null;
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,10 +105,9 @@ in
|
|||||||
{
|
{
|
||||||
# settings should evaluate
|
# settings should evaluate
|
||||||
test_per_instance_arguments = {
|
test_per_instance_arguments = {
|
||||||
inherit res;
|
|
||||||
expr = {
|
expr = {
|
||||||
instanceName =
|
instanceName =
|
||||||
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
|
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
# Below we access:
|
# Below we access:
|
||||||
@@ -120,11 +115,11 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
settings =
|
settings =
|
||||||
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
|
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
|
||||||
machine =
|
machine =
|
||||||
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
|
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
|
||||||
roles =
|
roles =
|
||||||
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
|
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
instanceName = "instance_foo";
|
instanceName = "instance_foo";
|
||||||
@@ -142,7 +137,6 @@ in
|
|||||||
settings = { };
|
settings = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
settings = { };
|
|
||||||
};
|
};
|
||||||
peer = {
|
peer = {
|
||||||
machines = {
|
machines = {
|
||||||
@@ -152,9 +146,6 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
settings = {
|
|
||||||
timeout = "foo-peer";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
settings = {
|
settings = {
|
||||||
@@ -165,9 +156,9 @@ in
|
|||||||
|
|
||||||
# TODO: Cannot be tested like this anymore
|
# TODO: Cannot be tested like this anymore
|
||||||
test_per_instance_settings_vendoring = {
|
test_per_instance_settings_vendoring = {
|
||||||
x = res.config._services.mappedServices.self-A;
|
x = res.importedModulesEvaluated.self-A;
|
||||||
expr =
|
expr =
|
||||||
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
|
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
|
||||||
expected = {
|
expected = {
|
||||||
timeout = "config.thing";
|
timeout = "config.thing";
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{ lib, createTestClan }:
|
{ lib, callInventoryAdapter }:
|
||||||
let
|
let
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
@@ -39,40 +39,36 @@ let
|
|||||||
jon = { };
|
jon = { };
|
||||||
sara = { };
|
sara = { };
|
||||||
};
|
};
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
inherit modules;
|
inherit modules machines;
|
||||||
inventory = {
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
inherit machines;
|
name = "A";
|
||||||
instances."instance_foo" = {
|
input = "self";
|
||||||
module = {
|
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.machines.jon = {
|
|
||||||
settings.timeout = lib.mkForce "foo-peer-jon";
|
|
||||||
};
|
|
||||||
roles.peer = {
|
|
||||||
settings.timeout = "foo-peer";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
instances."instance_bar" = {
|
roles.peer.machines.jon = {
|
||||||
module = {
|
settings.timeout = lib.mkForce "foo-peer-jon";
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.machines.jon = {
|
|
||||||
settings.timeout = "bar-peer-jon";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
instances."instance_zaza" = {
|
roles.peer = {
|
||||||
module = {
|
settings.timeout = "foo-peer";
|
||||||
name = "B";
|
|
||||||
input = null;
|
|
||||||
};
|
|
||||||
roles.peer.tags.all = { };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = "bar-peer-jon";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
input = null;
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
||||||
@@ -83,7 +79,7 @@ in
|
|||||||
inherit res;
|
inherit res;
|
||||||
expr = {
|
expr = {
|
||||||
hasMachineSettings =
|
hasMachineSettings =
|
||||||
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
|
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -92,10 +88,10 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
specificMachineSettings =
|
specificMachineSettings =
|
||||||
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
|
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
|
||||||
|
|
||||||
hasRoleSettings =
|
hasRoleSettings =
|
||||||
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
|
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -104,25 +100,20 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = *
|
# machines = *
|
||||||
specificRoleSettings =
|
specificRoleSettings =
|
||||||
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
|
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = rec {
|
||||||
hasMachineSettings = true;
|
hasMachineSettings = true;
|
||||||
hasRoleSettings = true;
|
hasRoleSettings = false;
|
||||||
specificMachineSettings = {
|
specificMachineSettings = {
|
||||||
timeout = "foo-peer-jon";
|
timeout = "foo-peer-jon";
|
||||||
};
|
};
|
||||||
specificRoleSettings = {
|
specificRoleSettings = {
|
||||||
machines = {
|
machines = {
|
||||||
jon = {
|
jon = {
|
||||||
settings = {
|
settings = specificMachineSettings;
|
||||||
timeout = "foo-peer-jon";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
settings = {
|
|
||||||
timeout = "foo-peer";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{ createTestClan, lib, ... }:
|
{ callInventoryAdapter, lib, ... }:
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
modules."A" = {
|
modules."A" = {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest = {
|
manifest = {
|
||||||
@@ -21,31 +21,28 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
inventory = {
|
machines = {
|
||||||
|
jon = { };
|
||||||
machines = {
|
sara = { };
|
||||||
jon = { };
|
};
|
||||||
sara = { };
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
};
|
};
|
||||||
instances."instance_foo" = {
|
# Settings for both jon and sara
|
||||||
module = {
|
roles.peer.settings = {
|
||||||
name = "A";
|
timeout = 40;
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
# Settings for both jon and sara
|
|
||||||
roles.peer.settings = {
|
|
||||||
timeout = 40;
|
|
||||||
};
|
|
||||||
# Jon overrides timeout
|
|
||||||
roles.peer.machines.jon = {
|
|
||||||
settings.timeout = lib.mkForce 42;
|
|
||||||
};
|
|
||||||
roles.peer.machines.sara = { };
|
|
||||||
};
|
};
|
||||||
|
# Jon overrides timeout
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = lib.mkForce 42;
|
||||||
|
};
|
||||||
|
roles.peer.machines.sara = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = res.config._services.mappedServices.self-A;
|
config = res.servicesEval.config.mappedServices.self-A;
|
||||||
|
|
||||||
#
|
#
|
||||||
applySettings =
|
applySettings =
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{ createTestClan, lib, ... }:
|
{ callInventoryAdapter, lib, ... }:
|
||||||
let
|
let
|
||||||
res = createTestClan {
|
res = callInventoryAdapter {
|
||||||
modules."A" = m: {
|
modules."A" = m: {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
config = {
|
config = {
|
||||||
@@ -14,21 +14,19 @@ let
|
|||||||
default = m;
|
default = m;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
inventory = {
|
machines = {
|
||||||
machines = {
|
jon = { };
|
||||||
jon = { };
|
};
|
||||||
};
|
instances."instance_foo" = {
|
||||||
instances."instance_foo" = {
|
module = {
|
||||||
module = {
|
name = "A";
|
||||||
name = "A";
|
input = "self";
|
||||||
input = "self";
|
|
||||||
};
|
|
||||||
roles.peer.machines.jon = { };
|
|
||||||
};
|
};
|
||||||
|
roles.peer.machines.jon = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
specialArgs = lib.attrNames res.config._services.mappedServices.self-A.test.specialArgs;
|
specialArgs = lib.attrNames res.servicesEval.config.mappedServices.self-A.test.specialArgs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
test_simple = {
|
test_simple = {
|
||||||
5
lib/modules/inventoryClass/builder/default.nix
Normal file
5
lib/modules/inventoryClass/builder/default.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./interface.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
28
lib/modules/inventoryClass/builder/interface.nix
Normal file
28
lib/modules/inventoryClass/builder/interface.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
submodule = m: types.submoduleWith { modules = [ m ]; };
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
directory = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
distributedServices = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
};
|
||||||
|
inventory = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
};
|
||||||
|
machines = mkOption {
|
||||||
|
type = types.attrsOf (submodule ({
|
||||||
|
options = {
|
||||||
|
machineImports = mkOption {
|
||||||
|
type = types.listOf types.raw;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
20
lib/modules/inventoryClass/inventory-introspection.nix
Normal file
20
lib/modules/inventoryClass/inventory-introspection.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
clanLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options.introspection = lib.mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
# TODO: use options.inventory instead of the evaluate config attribute
|
||||||
|
default =
|
||||||
|
builtins.removeAttrs (clanLib.introspection.getPrios { options = config.inventory.options; })
|
||||||
|
# tags are freeformType which is not supported yet.
|
||||||
|
# services is removed and throws an error if accessed.
|
||||||
|
[
|
||||||
|
"tags"
|
||||||
|
"services"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -115,7 +115,7 @@ in
|
|||||||
meta = lib.mkOption {
|
meta = lib.mkOption {
|
||||||
type = lib.types.submoduleWith {
|
type = lib.types.submoduleWith {
|
||||||
modules = [
|
modules = [
|
||||||
./meta.nix
|
./meta-interface.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -167,7 +167,7 @@ in
|
|||||||
'';
|
'';
|
||||||
type = types.submoduleWith {
|
type = types.submoduleWith {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit (config) machines clanLib;
|
inherit (config) machines;
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
{
|
{
|
||||||
@@ -359,7 +359,7 @@ in
|
|||||||
inherit clanLib;
|
inherit clanLib;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(import ./role.nix { })
|
(import ./roles-interface.nix { })
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -31,20 +31,6 @@ let
|
|||||||
Under construction, will be used for the UI
|
Under construction, will be used for the UI
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
tld = lib.mkOption {
|
|
||||||
type = types.strMatching "[a-z]+";
|
|
||||||
default = "clan";
|
|
||||||
example = "ccc";
|
|
||||||
description = ''
|
|
||||||
Top level domain (TLD) of the clan. It should be set to a valid, but
|
|
||||||
not already existing TLD.
|
|
||||||
|
|
||||||
It will be used to provide clan-internal services and resolve each host of the
|
|
||||||
clan with:
|
|
||||||
|
|
||||||
<hostname>.<tld>
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -44,6 +44,12 @@ in
|
|||||||
description = ''
|
description = ''
|
||||||
List of additionally imported `.nix` expressions.
|
List of additionally imported `.nix` expressions.
|
||||||
|
|
||||||
|
Supported types:
|
||||||
|
|
||||||
|
- **Strings**: Interpreted relative to the 'directory' passed to `lib.clan`.
|
||||||
|
- **Paths**: should be relative to the current file.
|
||||||
|
- **Any**: Nix expression must be serializable to JSON.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
**The import only happens if the machine is part of the service or role.**
|
**The import only happens if the machine is part of the service or role.**
|
||||||
|
|
||||||
@@ -67,8 +73,15 @@ in
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
|
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf types.raw;
|
type = types.listOf (
|
||||||
|
types.oneOf [
|
||||||
|
types.str
|
||||||
|
types.path
|
||||||
|
(types.attrsOf types.anything)
|
||||||
|
]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
70
lib/modules/inventoryClass/service-list-from-inputs.nix
Normal file
70
lib/modules/inventoryClass/service-list-from-inputs.nix
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
flakeInputs,
|
||||||
|
clanLib,
|
||||||
|
}:
|
||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
inspectModule =
|
||||||
|
inputName: moduleName: module:
|
||||||
|
let
|
||||||
|
eval = clanLib.evalService {
|
||||||
|
modules = [ module ];
|
||||||
|
prefix = [
|
||||||
|
inputName
|
||||||
|
"clan"
|
||||||
|
"modules"
|
||||||
|
moduleName
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
manifest = eval.config.manifest;
|
||||||
|
roles = lib.mapAttrs (_n: v: { inherit (v) description; }) eval.config.roles;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.staticModules = lib.mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
|
||||||
|
apply = moduleSet: lib.mapAttrs (inspectModule "<clan-core>") moduleSet;
|
||||||
|
};
|
||||||
|
options.modulesPerSource = lib.mkOption {
|
||||||
|
# { sourceName :: { moduleName :: {} }}
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
inputsWithModules = lib.filterAttrs (_inputName: v: v ? clan.modules) flakeInputs;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (
|
||||||
|
inputName: v: lib.mapAttrs (inspectModule inputName) v.clan.modules
|
||||||
|
) inputsWithModules;
|
||||||
|
};
|
||||||
|
options.moduleSchemas = lib.mkOption {
|
||||||
|
# { sourceName :: { moduleName :: { roleName :: Schema }}}
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
default = lib.mapAttrs (
|
||||||
|
_inputName: moduleSet:
|
||||||
|
lib.mapAttrs (
|
||||||
|
_moduleName: module:
|
||||||
|
(clanLib.evalService {
|
||||||
|
modules = [ module ];
|
||||||
|
prefix = [ ];
|
||||||
|
}).config.result.api.schema
|
||||||
|
) moduleSet
|
||||||
|
) config.modulesPerSource;
|
||||||
|
};
|
||||||
|
options.templatesPerSource = lib.mkOption {
|
||||||
|
# { sourceName :: { moduleName :: {} }}
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
inputsWithTemplates = lib.filterAttrs (_inputName: v: v ? clan.templates) flakeInputs;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (_inputName: v: lib.mapAttrs (_n: t: t) v.clan.templates) inputsWithTemplates;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
directory,
|
directory,
|
||||||
meta,
|
meta,
|
||||||
}:
|
}:
|
||||||
# The following is a nixos/darwin module
|
|
||||||
{
|
{
|
||||||
_class,
|
_class,
|
||||||
lib,
|
lib,
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
clan.core.settings = {
|
clan.core.settings = {
|
||||||
inherit (meta) name icon tld;
|
inherit (meta) name icon;
|
||||||
inherit directory;
|
inherit directory;
|
||||||
machine = {
|
machine = {
|
||||||
inherit name;
|
inherit name;
|
||||||
@@ -81,7 +81,6 @@ in
|
|||||||
description = null;
|
description = null;
|
||||||
icon = null;
|
icon = null;
|
||||||
name = "test";
|
name = "test";
|
||||||
tld = "clan";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,7 +105,7 @@ in
|
|||||||
self = {
|
self = {
|
||||||
inputs = { };
|
inputs = { };
|
||||||
};
|
};
|
||||||
directory = ../.;
|
directory = ../../.;
|
||||||
meta.name = "test-clan-core";
|
meta.name = "test-clan-core";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -124,7 +123,7 @@ in
|
|||||||
self = {
|
self = {
|
||||||
inputs = { };
|
inputs = { };
|
||||||
};
|
};
|
||||||
directory = ../.;
|
directory = ../../.;
|
||||||
meta.name = "test-clan-core";
|
meta.name = "test-clan-core";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -212,87 +211,6 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
test_clan_check_simple_fail =
|
|
||||||
let
|
|
||||||
eval = clan {
|
|
||||||
checks.constFail = {
|
|
||||||
assertion = false;
|
|
||||||
message = "This is a constant failure";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
result = eval;
|
|
||||||
expr = eval.config;
|
|
||||||
expectedError.type = "ThrownError";
|
|
||||||
expectedError.msg = "This is a constant failure";
|
|
||||||
};
|
|
||||||
test_clan_check_simple_pass =
|
|
||||||
let
|
|
||||||
eval = clan {
|
|
||||||
checks.constFail = {
|
|
||||||
assertion = true;
|
|
||||||
message = "This is a constant success";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
result = eval;
|
|
||||||
expr = lib.seq eval.config 42;
|
|
||||||
expected = 42;
|
|
||||||
};
|
|
||||||
|
|
||||||
test_get_var_machine =
|
|
||||||
let
|
|
||||||
varsLib = import ./vars.nix { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
expr = varsLib.getPublicValue {
|
|
||||||
backend = "in_repo";
|
|
||||||
default = "test";
|
|
||||||
shared = false;
|
|
||||||
generator = "test-generator";
|
|
||||||
machine = "test-machine";
|
|
||||||
file = "test-file";
|
|
||||||
flake = ./vars-test-flake;
|
|
||||||
};
|
|
||||||
expected = "foo-machine";
|
|
||||||
};
|
|
||||||
|
|
||||||
test_get_var_shared =
|
|
||||||
let
|
|
||||||
varsLib = import ./vars.nix { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
expr = varsLib.getPublicValue {
|
|
||||||
backend = "in_repo";
|
|
||||||
default = "test";
|
|
||||||
shared = true;
|
|
||||||
generator = "test-generator";
|
|
||||||
machine = "test-machine";
|
|
||||||
file = "test-file";
|
|
||||||
flake = ./vars-test-flake;
|
|
||||||
};
|
|
||||||
expected = "foo-shared";
|
|
||||||
};
|
|
||||||
|
|
||||||
test_get_var_default =
|
|
||||||
let
|
|
||||||
varsLib = import ./vars.nix { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
expr = varsLib.getPublicValue {
|
|
||||||
backend = "in_repo";
|
|
||||||
default = "test-default";
|
|
||||||
shared = true;
|
|
||||||
generator = "test-generator-wrong";
|
|
||||||
machine = "test-machine";
|
|
||||||
file = "test-file";
|
|
||||||
flake = ./vars-test-flake;
|
|
||||||
};
|
|
||||||
expected = "test-default";
|
|
||||||
};
|
|
||||||
|
|
||||||
test_clan_all_machines_laziness =
|
test_clan_all_machines_laziness =
|
||||||
let
|
let
|
||||||
eval = clan {
|
eval = clan {
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
{
|
|
||||||
clan-core,
|
|
||||||
lib,
|
|
||||||
}:
|
|
||||||
# TODO: TEST: define a clan without machines
|
|
||||||
{
|
|
||||||
test_simple =
|
|
||||||
let
|
|
||||||
eval = clan-core.clanLib.clan {
|
|
||||||
exports."///".foo = lib.mkForce eval.config.exports."///".bar;
|
|
||||||
|
|
||||||
directory = ./.;
|
|
||||||
self = {
|
|
||||||
clan = eval.config;
|
|
||||||
inputs = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
machines.jon = { };
|
|
||||||
machines.sara = { };
|
|
||||||
|
|
||||||
exportsModule =
|
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
options.foo = lib.mkOption {
|
|
||||||
type = lib.types.number;
|
|
||||||
default = 0;
|
|
||||||
};
|
|
||||||
options.bar = lib.mkOption {
|
|
||||||
type = lib.types.number;
|
|
||||||
default = 0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
####### Service module "A"
|
|
||||||
modules.service-A =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
# config.exports
|
|
||||||
manifest.name = "A";
|
|
||||||
|
|
||||||
roles.default = {
|
|
||||||
# TODO: Remove automapping
|
|
||||||
# Currently exports are automapped
|
|
||||||
# scopes "/service=A/instance=hello/role=default/machine=jon"
|
|
||||||
# perInstance.exports.foo = 7;
|
|
||||||
|
|
||||||
# New style:
|
|
||||||
# Explizit scope
|
|
||||||
# perInstance.exports."service=A/instance=hello/role=default/machine=jon".foo = 7;
|
|
||||||
perInstance =
|
|
||||||
{ instanceName, machine, exports, ... }:
|
|
||||||
{
|
|
||||||
exports."A/${instanceName}/default/${machine.name}" = {
|
|
||||||
foo = 7;
|
|
||||||
# define export depending on B
|
|
||||||
bar = exports."B/B/default/${machine.name}".foo + 35;
|
|
||||||
};
|
|
||||||
# exports."A/${instanceName}/default/${machine.name}".
|
|
||||||
|
|
||||||
# default behavior
|
|
||||||
# exports = scope.mkExports { foo = 7; };
|
|
||||||
|
|
||||||
# We want to export things for different scopes from this scope;
|
|
||||||
# If this scope is used.
|
|
||||||
#
|
|
||||||
# Explicit scope; different from the function scope above
|
|
||||||
# exports = clanLib.scopedExport {
|
|
||||||
# # Different role export
|
|
||||||
# role = "peer";
|
|
||||||
# serviceName = config.manifest.name;
|
|
||||||
# inherit instanceName machineName;
|
|
||||||
# } { foo = 7; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
perMachine =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
#
|
|
||||||
# exports = scope.mkExports { foo = 7; };
|
|
||||||
# exports."A///${machine.name}".foo = 42;
|
|
||||||
# exports."B///".foo = 42;
|
|
||||||
};
|
|
||||||
|
|
||||||
# scope "/service=A/instance=??/role=??/machine=jon"
|
|
||||||
# perMachine.exports.foo = 42;
|
|
||||||
|
|
||||||
# scope "/service=A/instance=??/role=??/machine=??"
|
|
||||||
# exports."///".foo = 10;
|
|
||||||
};
|
|
||||||
####### Service module "A"
|
|
||||||
modules.service-B =
|
|
||||||
{ exports, ... }:
|
|
||||||
{
|
|
||||||
# config.exports
|
|
||||||
manifest.name = "B";
|
|
||||||
|
|
||||||
roles.default = {
|
|
||||||
# TODO: Remove automapping
|
|
||||||
# Currently exports are automapped
|
|
||||||
# scopes "/service=A/instance=hello/role=default/machine=jon"
|
|
||||||
# perInstance.exports.foo = 7;
|
|
||||||
|
|
||||||
# New style:
|
|
||||||
# Explizit scope
|
|
||||||
# perInstance.exports."service=A/instance=hello/role=default/machine=jon".foo = 7;
|
|
||||||
perInstance =
|
|
||||||
{ instanceName, machine, ... }:
|
|
||||||
{
|
|
||||||
# TODO: Test non-existing scope
|
|
||||||
# define export depending on A
|
|
||||||
exports."B/${instanceName}/default/${machine.name}".foo = exports."///".foo + exports."A/A/default/${machine.name}".foo;
|
|
||||||
# exports."B/B/default/jon".foo = exports."A/A/default/jon".foo;
|
|
||||||
|
|
||||||
# default behavior
|
|
||||||
# exports = scope.mkExports { foo = 7; };
|
|
||||||
|
|
||||||
# We want to export things for different scopes from this scope;
|
|
||||||
# If this scope is used.
|
|
||||||
#
|
|
||||||
# Explicit scope; different from the function scope above
|
|
||||||
# exports = clanLib.scopedExport {
|
|
||||||
# # Different role export
|
|
||||||
# role = "peer";
|
|
||||||
# serviceName = config.manifest.name;
|
|
||||||
# inherit instanceName machineName;
|
|
||||||
# } { foo = 7; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
perMachine =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
# exports = scope.mkExports { foo = 7; };
|
|
||||||
# exports."A///${machine.name}".foo = 42;
|
|
||||||
# exports."B///".foo = 42;
|
|
||||||
};
|
|
||||||
|
|
||||||
# scope "/service=A/instance=??/role=??/machine=jon"
|
|
||||||
# perMachine.exports.foo = 42;
|
|
||||||
|
|
||||||
# scope "/service=A/instance=??/role=??/machine=??"
|
|
||||||
exports."///".foo = 10;
|
|
||||||
};
|
|
||||||
#######
|
|
||||||
|
|
||||||
inventory = {
|
|
||||||
instances.A = {
|
|
||||||
module.name = "service-A";
|
|
||||||
module.input = "self";
|
|
||||||
roles.default.tags = [ "all" ];
|
|
||||||
};
|
|
||||||
instances.B = {
|
|
||||||
module.name = "service-B";
|
|
||||||
module.input = "self";
|
|
||||||
roles.default.tags = [ "all" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# <- inventory
|
|
||||||
#
|
|
||||||
# -> exports
|
|
||||||
/**
|
|
||||||
Current state
|
|
||||||
{
|
|
||||||
instances = {
|
|
||||||
hello = { networking = null; };
|
|
||||||
};
|
|
||||||
machines = {
|
|
||||||
jon = { networking = null; };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
Target state: (Flat attribute set)
|
|
||||||
|
|
||||||
tdlr;
|
|
||||||
|
|
||||||
# roles / instance level definitions may not exist on their own
|
|
||||||
# role and instance names are completely arbitrary.
|
|
||||||
# For example what does it mean: this is a export for all "peer" roles of all service-instances? That would be magic on the roleName.
|
|
||||||
# Or exports for all instances with name "ifoo" ? That would be magic on the instanceName.
|
|
||||||
|
|
||||||
# Practical combinations
|
|
||||||
# always include either the service name or the machine name
|
|
||||||
|
|
||||||
exports = {
|
|
||||||
# Clan level (1)
|
|
||||||
"///" networks generators
|
|
||||||
|
|
||||||
# Service anchored (8) : min 1 instance is needed ; machines may not exist
|
|
||||||
"A///" <- service specific
|
|
||||||
"A/instance//" <- instance of a service
|
|
||||||
"A//peer/" <- role of a service
|
|
||||||
"A/instance/peer/" <- instance+role of a service
|
|
||||||
"A///machine" <- machine of a service
|
|
||||||
"A/instance//machine" <- machine + instance of a service
|
|
||||||
"A//role/machine" <- machine + role of a service
|
|
||||||
"A/instance/role/machine" <- machine + role + instance of a service
|
|
||||||
|
|
||||||
# Machine anchored (1 or 2)
|
|
||||||
"///jon" <- this machine
|
|
||||||
"A///jon" <- role on a machine (dupped with service anchored)
|
|
||||||
|
|
||||||
# Unpractical; probably not needed (5)
|
|
||||||
"//peer/jon" <- role on a machine
|
|
||||||
"/instance//jon" <- role on a machine
|
|
||||||
"/instance//" <- instance: All "foo" instances everywhere?
|
|
||||||
"//role/" <- role: All "peer" roles everywhere?
|
|
||||||
"/instance/role/" <- instance role: Applies to all services, whose instance name has "ifoo" and role is "peer" (double magic)
|
|
||||||
|
|
||||||
# TODO: lazyattrs poc
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval;
|
|
||||||
expected = 42;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -62,9 +62,6 @@
|
|||||||
# Core libraries
|
# Core libraries
|
||||||
(root + "/lib")
|
(root + "/lib")
|
||||||
|
|
||||||
# modules directory
|
|
||||||
(root + "/modules")
|
|
||||||
|
|
||||||
# User-provided fileset
|
# User-provided fileset
|
||||||
fileset
|
fileset
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,21 +1,4 @@
|
|||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
|
||||||
inherit (lib)
|
|
||||||
mapAttrs
|
|
||||||
attrNames
|
|
||||||
showOption
|
|
||||||
setDefaultModuleLocation
|
|
||||||
mkOptionType
|
|
||||||
isAttrs
|
|
||||||
filterAttrs
|
|
||||||
intersectAttrs
|
|
||||||
mapAttrsToList
|
|
||||||
mkOptionDefault
|
|
||||||
zipAttrsWith
|
|
||||||
seq
|
|
||||||
fix
|
|
||||||
;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
A custom type for deferred modules that guarantee to be JSON serializable.
|
A custom type for deferred modules that guarantee to be JSON serializable.
|
||||||
@@ -29,7 +12,7 @@ in
|
|||||||
- Enforces that the definition is JSON serializable
|
- Enforces that the definition is JSON serializable
|
||||||
- Disallows nested imports
|
- Disallows nested imports
|
||||||
*/
|
*/
|
||||||
uniqueDeferredSerializableModule = fix (
|
uniqueDeferredSerializableModule = lib.fix (
|
||||||
self:
|
self:
|
||||||
let
|
let
|
||||||
checkDef =
|
checkDef =
|
||||||
@@ -40,18 +23,19 @@ in
|
|||||||
def;
|
def;
|
||||||
in
|
in
|
||||||
# Essentially the "raw" type, but with a custom name and check
|
# Essentially the "raw" type, but with a custom name and check
|
||||||
mkOptionType {
|
lib.mkOptionType {
|
||||||
name = "deferredModule";
|
name = "deferredModule";
|
||||||
description = "deferred custom module. Must be JSON serializable.";
|
description = "deferred custom module. Must be JSON serializable.";
|
||||||
descriptionClass = "noun";
|
descriptionClass = "noun";
|
||||||
# Unfortunately, tryEval doesn't catch JSON errors
|
# Unfortunately, tryEval doesn't catch JSON errors
|
||||||
check = value: seq (builtins.toJSON value) (isAttrs value);
|
check = value: lib.seq (builtins.toJSON value) (lib.isAttrs value);
|
||||||
merge = lib.options.mergeUniqueOption {
|
merge = lib.options.mergeUniqueOption {
|
||||||
message = "------";
|
message = "------";
|
||||||
merge = loc: defs: {
|
merge = loc: defs: {
|
||||||
imports = map (
|
imports = map (
|
||||||
def:
|
def:
|
||||||
seq (checkDef loc def) setDefaultModuleLocation "${def.file}, via option ${showOption loc}"
|
lib.seq (checkDef loc def) lib.setDefaultModuleLocation
|
||||||
|
"${def.file}, via option ${lib.showOption loc}"
|
||||||
def.value
|
def.value
|
||||||
) defs;
|
) defs;
|
||||||
};
|
};
|
||||||
@@ -64,113 +48,4 @@ in
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
New submodule type that allows merging at the attribute level.
|
|
||||||
|
|
||||||
:::note
|
|
||||||
'record' type adopted from https://github.com/NixOS/nixpkgs/pull/334680
|
|
||||||
:::
|
|
||||||
|
|
||||||
It applies additional constraints to immediate child options:
|
|
||||||
|
|
||||||
- No support for 'readOnly'
|
|
||||||
- No support for 'apply'
|
|
||||||
- No support for type-merging: That means the modules options must be pre-declared directly.
|
|
||||||
*/
|
|
||||||
record =
|
|
||||||
{
|
|
||||||
optional ? { },
|
|
||||||
required ? { },
|
|
||||||
wildcardType ? null,
|
|
||||||
}:
|
|
||||||
mkOptionType {
|
|
||||||
name = "record";
|
|
||||||
description =
|
|
||||||
if wildcardType == null then "record" else "open record of ${wildcardType.description}";
|
|
||||||
descriptionClass = if wildcardType == null then "noun" else "composite";
|
|
||||||
check = isAttrs;
|
|
||||||
merge.v2 =
|
|
||||||
{ loc, defs }:
|
|
||||||
let
|
|
||||||
pushPositions = map (
|
|
||||||
def:
|
|
||||||
mapAttrs (_n: v: {
|
|
||||||
inherit (def) file;
|
|
||||||
value = v;
|
|
||||||
}) def.value
|
|
||||||
);
|
|
||||||
|
|
||||||
# Checks
|
|
||||||
intersection = intersectAttrs optional required;
|
|
||||||
optionalDefault = filterAttrs (_: opt: opt ? default) optional;
|
|
||||||
|
|
||||||
# Definitions + option defaults
|
|
||||||
allDefs =
|
|
||||||
defs
|
|
||||||
++ (mapAttrsToList (name: opt: {
|
|
||||||
file = (builtins.unsafeGetAttrPos name required).file or "<unknown-file>";
|
|
||||||
value = {
|
|
||||||
${name} = mkOptionDefault opt.default;
|
|
||||||
};
|
|
||||||
}) (filterAttrs (_n: opt: opt ? default) required));
|
|
||||||
|
|
||||||
merged = zipAttrsWith (
|
|
||||||
name: defs:
|
|
||||||
let
|
|
||||||
elemType = optional.${name}.type or required.${name}.type or wildcardType;
|
|
||||||
in
|
|
||||||
lib.modules.mergeDefinitions (loc ++ [ name ]) elemType defs
|
|
||||||
) (pushPositions allDefs);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
headError =
|
|
||||||
if intersection != { } then
|
|
||||||
{
|
|
||||||
message = "The following attributes of '${showOption loc}' are both declared in 'optional' and in 'required': ${lib.concatStringsSep ", " (attrNames intersection)}";
|
|
||||||
}
|
|
||||||
else if optionalDefault != { } then
|
|
||||||
{
|
|
||||||
message = "The following attributes of '${showOption loc}' are declared in 'optional' cannot have a default value: ${lib.concatStringsSep ", " (attrNames optionalDefault)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
# TODO: expose fields, fieldValues and extraValues
|
|
||||||
valueMeta = {
|
|
||||||
attrs = mapAttrs (_n: v: v.checkedAndMerged.valueMeta) merged;
|
|
||||||
};
|
|
||||||
value = mapAttrs (
|
|
||||||
name: v:
|
|
||||||
let
|
|
||||||
elemType = optional.${name}.type or required.${name}.type or wildcardType;
|
|
||||||
in
|
|
||||||
if required ? ${name} then
|
|
||||||
# Non-optional, lazy ?
|
|
||||||
v.mergedValue
|
|
||||||
else
|
|
||||||
# Optional, lazy
|
|
||||||
v.optionalValue.value or elemType.emptyValue.value or v.mergedValue
|
|
||||||
) merged;
|
|
||||||
};
|
|
||||||
nestedTypes = lib.optionalAttrs (wildcardType != null) {
|
|
||||||
inherit wildcardType;
|
|
||||||
};
|
|
||||||
getSubOptions =
|
|
||||||
prefix:
|
|
||||||
# Since this type doesn't support type merging, we can safely use the original attrs to display documentation.
|
|
||||||
mapAttrs (
|
|
||||||
name: opt:
|
|
||||||
(
|
|
||||||
opt
|
|
||||||
// {
|
|
||||||
loc = prefix ++ [ name ];
|
|
||||||
inherit name;
|
|
||||||
declarations = [
|
|
||||||
(builtins.unsafeGetAttrPos name optional).file or (builtins.unsafeGetAttrPos name required).file
|
|
||||||
or "<unknown-file>"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) (optional // required);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
{ lib, clanLib, ... }:
|
|
||||||
let
|
|
||||||
inherit (lib) evalModules mkOption;
|
|
||||||
inherit (clanLib.types) record;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
test_simple =
|
|
||||||
let
|
|
||||||
eval = evalModules {
|
|
||||||
modules = [
|
|
||||||
{
|
|
||||||
options.foo = mkOption {
|
|
||||||
type = record { };
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expected = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
test_wildcard =
|
|
||||||
let
|
|
||||||
eval = evalModules {
|
|
||||||
modules = [
|
|
||||||
{
|
|
||||||
options.foo = mkOption {
|
|
||||||
type = record { };
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expected = { };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,92 @@
|
|||||||
{ lib, clanLib, ... }:
|
{ lib, clanLib, ... }:
|
||||||
|
let
|
||||||
|
evalSettingsModule =
|
||||||
|
m:
|
||||||
|
lib.evalModules {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
options.foo = lib.mkOption {
|
||||||
|
type = clanLib.types.uniqueDeferredSerializableModule;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
m
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
unique = import ./unique_tests.nix { inherit lib clanLib; };
|
test_simple =
|
||||||
record = import ./record_tests.nix { inherit lib clanLib; };
|
let
|
||||||
|
eval = evalSettingsModule {
|
||||||
|
foo = { };
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit eval;
|
||||||
|
expr = eval.config.foo;
|
||||||
|
expected = {
|
||||||
|
# Foo has imports
|
||||||
|
# This can only ever be one module due to the type of foo
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
# This is the result of 'setDefaultModuleLocation'
|
||||||
|
# Which also returns exactly one module
|
||||||
|
_file = "<unknown-file>, via option foo";
|
||||||
|
imports = [
|
||||||
|
{ }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test_no_nested_imports =
|
||||||
|
let
|
||||||
|
eval = evalSettingsModule {
|
||||||
|
foo = {
|
||||||
|
imports = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit eval;
|
||||||
|
expr = eval.config.foo;
|
||||||
|
expectedError = {
|
||||||
|
type = "ThrownError";
|
||||||
|
message = "*nested imports";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test_no_function_modules =
|
||||||
|
let
|
||||||
|
eval = evalSettingsModule {
|
||||||
|
foo =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit eval;
|
||||||
|
expr = eval.config.foo;
|
||||||
|
expectedError = {
|
||||||
|
type = "TypeError";
|
||||||
|
message = "cannot convert a function to JSON";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test_non_attrs_module =
|
||||||
|
let
|
||||||
|
eval = evalSettingsModule {
|
||||||
|
foo = "foo.nix";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit eval;
|
||||||
|
expr = eval.config.foo;
|
||||||
|
expectedError = {
|
||||||
|
type = "ThrownError";
|
||||||
|
message = ".*foo.* is not of type";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
{ lib, clanLib, ... }:
|
|
||||||
let
|
|
||||||
evalSettingsModule =
|
|
||||||
m:
|
|
||||||
lib.evalModules {
|
|
||||||
modules = [
|
|
||||||
{
|
|
||||||
options.foo = lib.mkOption {
|
|
||||||
type = clanLib.types.uniqueDeferredSerializableModule;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
m
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
test_not_defined =
|
|
||||||
let
|
|
||||||
eval = evalSettingsModule {
|
|
||||||
foo = { };
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expected = {
|
|
||||||
# Foo has imports
|
|
||||||
# This can only ever be one module due to the type of foo
|
|
||||||
imports = [
|
|
||||||
{
|
|
||||||
# This is the result of 'setDefaultModuleLocation'
|
|
||||||
# Which also returns exactly one module
|
|
||||||
_file = "<unknown-file>, via option foo";
|
|
||||||
imports = [
|
|
||||||
{ }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
test_no_nested_imports =
|
|
||||||
let
|
|
||||||
eval = evalSettingsModule {
|
|
||||||
foo = {
|
|
||||||
imports = [ ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expectedError = {
|
|
||||||
type = "ThrownError";
|
|
||||||
message = "*nested imports";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
test_no_function_modules =
|
|
||||||
let
|
|
||||||
eval = evalSettingsModule {
|
|
||||||
foo =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expectedError = {
|
|
||||||
type = "TypeError";
|
|
||||||
message = "cannot convert a function to JSON";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
test_non_attrs_module =
|
|
||||||
let
|
|
||||||
eval = evalSettingsModule {
|
|
||||||
foo = "foo.nix";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit eval;
|
|
||||||
expr = eval.config.foo;
|
|
||||||
expectedError = {
|
|
||||||
type = "ThrownError";
|
|
||||||
message = ".*foo.* is not of type";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
foo-machine
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
foo-shared
|
|
||||||
25
lib/vars.nix
25
lib/vars.nix
@@ -1,25 +0,0 @@
|
|||||||
_: {
|
|
||||||
getPublicValue =
|
|
||||||
{
|
|
||||||
|
|
||||||
backend ? "in_repo",
|
|
||||||
default ? throw "getPublicValue: Public value ${machine}/${generator}/${file} not found!",
|
|
||||||
shared ? false,
|
|
||||||
generator,
|
|
||||||
machine,
|
|
||||||
file,
|
|
||||||
flake,
|
|
||||||
}:
|
|
||||||
|
|
||||||
if backend == "in_repo" then
|
|
||||||
let
|
|
||||||
path =
|
|
||||||
if shared then
|
|
||||||
"${flake}/vars/shared/${generator}/${file}/value"
|
|
||||||
else
|
|
||||||
"${flake}/vars/per-machine/${machine}/${generator}/${file}/value";
|
|
||||||
in
|
|
||||||
if builtins.pathExists path then builtins.readFile path else default
|
|
||||||
else
|
|
||||||
throw "backend ${backend} does not implement getPublicValue";
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user