Compare commits
205 Commits
exports-en
...
9a05d2a072
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a05d2a072 | |||
|
|
62b64c3b3e | ||
|
|
19a1ad6081 | ||
|
|
a2df5db3d6 | ||
|
|
ac46f890ea | ||
|
|
19abf8d288 | ||
|
|
e5105e31c4 | ||
|
|
0f847b4799 | ||
|
|
40a8a823b8 | ||
|
|
e3adb3fc71 | ||
|
|
a569a1d147 | ||
|
|
64718b77ca | ||
|
|
7b34c39736 | ||
|
|
4d6ab60793 | ||
|
|
35bffee544 | ||
|
|
16917fd79b | ||
|
|
895c116c01 | ||
|
|
e67151f7b9 | ||
|
|
8d26ec1760 | ||
|
|
7a9062b629 | ||
|
|
de07454a0a | ||
|
|
6fe60f61cf | ||
|
|
3fa74847e4 | ||
|
|
fc37140b52 | ||
|
|
83406c61f3 | ||
|
|
6d736e7e80 | ||
|
|
7b6cec4100 | ||
|
|
e21a6516b5 | ||
|
|
6ffe8ea5f6 | ||
|
|
0a2fefd141 | ||
|
|
0c885d05b6 | ||
|
|
58d85b117a | ||
|
|
ad58d7b6e9 | ||
|
|
7a63cb9642 | ||
|
|
196b98da36 | ||
|
|
42acbe95b8 | ||
|
|
b6b065e365 | ||
|
|
4b1955b189 | ||
|
|
ef7ef8b843 | ||
|
|
38c1367322 | ||
|
|
8e72c086fd | ||
|
|
c454b1339d | ||
|
|
d1b2d43e5b | ||
|
|
da98ca0f1c | ||
|
|
1953540d08 | ||
|
|
be31b9ce21 | ||
|
|
169b4016e6 | ||
|
|
2e55028a1b | ||
|
|
1d228231f2 | ||
|
|
affb926450 | ||
|
|
c7f65e929f | ||
|
|
ba4ff493e8 | ||
|
|
eb08803e2a | ||
|
|
bbc9486f0e | ||
|
|
999d709350 | ||
|
|
0b1a330cc2 | ||
|
|
995b7cf50d | ||
|
|
bc290fe59f | ||
|
|
5477b13233 | ||
|
|
d6170e5efb | ||
|
|
18fe117363 | ||
|
|
33a868acc2 | ||
|
|
11372d35e1 | ||
|
|
b7508b2b43 | ||
|
|
183817b769 | ||
|
|
591e53e9be | ||
|
|
a6a6415e31 | ||
|
|
0060ead876 | ||
|
|
224e41d3ad | ||
|
|
b3323007b2 | ||
|
|
3e950bc66f | ||
|
|
9503b46b21 | ||
|
|
a2cec323a2 | ||
|
|
4239f4d27f | ||
|
|
8ac8264997 | ||
|
|
544a53ae9c | ||
|
|
89e18482ed | ||
|
|
a8217b5a32 | ||
|
|
bdd5de5628 | ||
|
|
61d8bfd0d1 | ||
|
|
b8d79c7fc2 | ||
|
|
fb25ab028b | ||
|
|
5b136ecaf0 | ||
|
|
d4733dbb0a | ||
|
|
bfb30251e6 | ||
|
|
33115f76b7 | ||
|
|
9e9208e699 | ||
|
|
6b3fd57174 | ||
|
|
3be5237cf6 | ||
|
|
368f80eaae | ||
|
|
4d7079534c | ||
|
|
7d4cf1c551 | ||
|
|
ea088b95e9 | ||
|
|
a7a37f5320 | ||
|
|
8bda4880a7 | ||
|
|
6eb83618c0 | ||
|
|
1fe3833779 | ||
|
|
e63f5c966e | ||
|
|
69241183ac | ||
|
|
0a7c65cd27 | ||
|
|
bd13eb3e23 | ||
|
|
1e5191a16c | ||
|
|
1e4bf0dd4e | ||
|
|
4d66dc59aa | ||
|
|
9a442c15e9 | ||
|
|
157af90a56 | ||
|
|
24b94965d8 | ||
|
|
46bcad9267 | ||
|
|
1aba0577dc | ||
|
|
383088af2d | ||
|
|
c3456c1f0c | ||
|
|
183de9209f | ||
|
|
1df5c5ff60 | ||
|
|
e55a3b4fc6 | ||
|
|
6ee4657da3 | ||
|
|
7294d8bcbe | ||
|
|
3fec5aa5b3 | ||
|
|
63e55b8631 | ||
|
|
dd771f8dd9 | ||
|
|
d31a4cc7d8 | ||
|
|
73d4cf51af | ||
|
|
d015218226 | ||
|
|
f50475fcfd | ||
|
|
ae5efd9e2f | ||
|
|
c2c2874e82 | ||
|
|
1f8c2a3722 | ||
|
|
50aa7eb0cf | ||
|
|
920b39a124 | ||
|
|
c159ef79d2 | ||
|
|
7a95b169c1 | ||
|
|
0c3fd40120 | ||
|
|
7268697dc5 | ||
|
|
ec395bada4 | ||
|
|
757552671c | ||
|
|
04b52d5bee | ||
|
|
ffeb8b892a | ||
|
|
26806b5750 | ||
|
|
6e70054566 | ||
|
|
93268e8592 | ||
|
|
a420c6ca25 | ||
|
|
69fd13a76f | ||
|
|
ab3f262c22 | ||
|
|
aabbe0dfac | ||
|
|
35cb99a3a2 | ||
|
|
90e6d77e26 | ||
|
|
5fb4751bd8 | ||
|
|
03640e44a4 | ||
|
|
51fd60917e | ||
|
|
2d7e659953 | ||
|
|
c638df8ed9 | ||
|
|
ec269a48f3 | ||
|
|
fc4c9287cb | ||
|
|
fee62373a9 | ||
|
|
f075b339b5 | ||
|
|
872a622f71 | ||
|
|
4a41c4cefb | ||
|
|
a5cd36e845 | ||
|
|
0dd6c08e33 | ||
|
|
dc0b7fc3bf | ||
|
|
0d4bbbd17e | ||
|
|
90797ffa7d | ||
|
|
7f2bd809d6 | ||
|
|
da7ff9a40a | ||
|
|
410d0d0532 | ||
|
|
a25d983c87 | ||
|
|
3953fa4047 | ||
|
|
ea93cb9987 | ||
|
|
c13278f3c2 | ||
|
|
66fdf937e3 | ||
|
|
843f55f844 | ||
|
|
5a5633d779 | ||
|
|
8310433342 | ||
|
|
51141772b3 | ||
|
|
58b88e874f | ||
|
|
5fb616efb4 | ||
|
|
6c6afd6f4b | ||
|
|
4010953041 | ||
|
|
6b74c66292 | ||
|
|
fd35adbc3e | ||
|
|
f86b0ec3da | ||
|
|
5f6e0540cd | ||
|
|
c7ec9a9715 | ||
|
|
841e9135fe | ||
|
|
9299cd9666 | ||
|
|
9851993b82 | ||
|
|
55d1807f07 | ||
|
|
ee0abdc7f4 | ||
|
|
6c9ab63842 | ||
|
|
d70db5af79 | ||
|
|
eae858dec6 | ||
|
|
2ec035a1cb | ||
|
|
bd6c227bbe | ||
|
|
02f3474a58 | ||
|
|
c838e08d77 | ||
|
|
346e3d816a | ||
|
|
a15959fad2 | ||
|
|
0e0c2ead1f | ||
|
|
c42381d810 | ||
|
|
587ce7258a | ||
|
|
d0bb804843 | ||
|
|
410eecb988 | ||
|
|
98136142b4 | ||
|
|
37da9fb3e4 | ||
|
|
4566ad9789 | ||
|
|
6faacc7dde |
@@ -1,8 +1,10 @@
|
|||||||
clanServices/.* @pinpox @kenji
|
clanServices/.* @pinpox @kenji
|
||||||
|
|
||||||
lib/test/container-test-driver/.* @DavHau @mic92
|
lib/test/container-test-driver/.* @DavHau @mic92
|
||||||
lib/modules/inventory/.* @hsjobeki
|
lib/inventory/.* @hsjobeki
|
||||||
lib/modules/inventoryClass/.* @hsjobeki
|
lib/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,6 +87,7 @@ 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;
|
||||||
|
|||||||
@@ -58,51 +58,53 @@
|
|||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
pkgs.glibcLocales
|
pkgs.glibcLocales
|
||||||
pkgs.kbd.out
|
pkgs.kbd.out
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
|
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
|
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.FileSlurp
|
||||||
pkgs.bubblewrap
|
pkgs.bubblewrap
|
||||||
|
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
|
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript.drvPath
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Skip flash test on aarch64-linux for now as it's too slow
|
# Skip flash test on aarch64-linux for now as it's too slow
|
||||||
checks = lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.hostPlatform.system != "aarch64-linux") {
|
checks =
|
||||||
nixos-test-flash = self.clanLib.test.baseTest {
|
lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.stdenv.hostPlatform.system != "aarch64-linux")
|
||||||
name = "flash";
|
{
|
||||||
nodes.target = {
|
nixos-test-flash = self.clanLib.test.baseTest {
|
||||||
virtualisation.emptyDiskImages = [ 4096 ];
|
name = "flash";
|
||||||
virtualisation.memorySize = 4096;
|
nodes.target = {
|
||||||
|
virtualisation.emptyDiskImages = [ 4096 ];
|
||||||
|
virtualisation.memorySize = 4096;
|
||||||
|
|
||||||
virtualisation.useNixStoreImage = true;
|
virtualisation.useNixStoreImage = true;
|
||||||
virtualisation.writableStore = true;
|
virtualisation.writableStore = true;
|
||||||
|
|
||||||
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||||
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||||
|
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
hashed-mirrors = null;
|
hashed-mirrors = null;
|
||||||
connect-timeout = lib.mkForce 3;
|
connect-timeout = lib.mkForce 3;
|
||||||
flake-registry = "";
|
flake-registry = "";
|
||||||
experimental-features = [
|
experimental-features = [
|
||||||
"nix-command"
|
"nix-command"
|
||||||
"flakes"
|
"flakes"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
|
||||||
|
# Some distros like to automount disks with spaces
|
||||||
|
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
|
||||||
|
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.stdenv.hostPlatform.system}")
|
||||||
|
'';
|
||||||
|
} { inherit pkgs self; };
|
||||||
};
|
};
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
|
|
||||||
# Some distros like to automount disks with spaces
|
|
||||||
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
|
|
||||||
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
|
|
||||||
'';
|
|
||||||
} { inherit pkgs self; };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,9 +160,9 @@
|
|||||||
closureInfo = pkgs.closureInfo {
|
closureInfo = pkgs.closureInfo {
|
||||||
rootPaths = [
|
rootPaths = [
|
||||||
privateInputs.clan-core-for-checks
|
privateInputs.clan-core-for-checks
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.initialRamdisk
|
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.initialRamdisk
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
82
checks/llm/default.nix
Normal file
82
checks/llm/default.nix
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{ self, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
cli = self.packages.${pkgs.stdenv.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.stdenv.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)
|
||||||
|
'';
|
||||||
|
}
|
||||||
70
checks/llm/qwen3-4b-instruct.nix
Normal file
70
checks/llm/qwen3-4b-instruct.nix
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{ 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
cli = self.packages.${pkgs.hostPlatform.system}.clan-cli-full;
|
cli = self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
name = "systemd-abstraction";
|
name = "systemd-abstraction";
|
||||||
@@ -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 -s -n0 ${cli}/${cli.pythonRuntime.sitePackages}/clan_lib/service_runner'")
|
peer1.succeed("su - text-user -c 'pytest -p no:cacheprovider -o addopts="" -s -n0 ${cli.passthru.sourceWithTests}/clan_lib/service_runner'")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,9 +115,9 @@
|
|||||||
let
|
let
|
||||||
closureInfo = pkgs.closureInfo {
|
closureInfo = pkgs.closureInfo {
|
||||||
rootPaths = [
|
rootPaths = [
|
||||||
self.packages.${pkgs.hostPlatform.system}.clan-cli
|
self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli
|
||||||
self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks
|
self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
self.clanInternals.machines.${pkgs.stdenv.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
imports = [ self.nixosModules.test-update-machine ];
|
imports = [ self.nixosModules.test-update-machine ];
|
||||||
};
|
};
|
||||||
extraPythonPackages = _p: [
|
extraPythonPackages = _p: [
|
||||||
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib
|
self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixosTestLib
|
||||||
];
|
];
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
|
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
"--to",
|
"--to",
|
||||||
"ssh://root@192.168.1.1",
|
"ssh://root@192.168.1.1",
|
||||||
"--no-check-sigs",
|
"--no-check-sigs",
|
||||||
f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}",
|
f"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}",
|
||||||
"--extra-experimental-features", "nix-command flakes",
|
"--extra-experimental-features", "nix-command flakes",
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
"-o", "UserKnownHostsFile=/dev/null",
|
"-o", "UserKnownHostsFile=/dev/null",
|
||||||
"-o", "StrictHostKeyChecking=no",
|
"-o", "StrictHostKeyChecking=no",
|
||||||
f"root@192.168.1.1",
|
f"root@192.168.1.1",
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan",
|
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
|
|
||||||
# Run clan update command
|
# Run clan update command
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
@@ -297,7 +297,7 @@
|
|||||||
|
|
||||||
# Run clan update command with --build-host
|
# Run clan update command with --build-host
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
|
|
||||||
This service sets up a certificate authority (CA) that can issue certificates to
|
This service sets up a certificate authority (CA) that can issue certificates to
|
||||||
other machines in your clan. For this the `ca` role is used.
|
other machines in your clan. For this the `ca` role is used.
|
||||||
It additionally provides a `default` role, that can be applied to all machines
|
It additionally provides a `default` role, that can be applied to all machines
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
|
|
||||||
This module enables hosting clan-internal services easily, which can be resolved
|
This module enables hosting clan-internal services easily, which can be resolved
|
||||||
inside your VPN. This allows defining a custom top-level domain (e.g. `.clan`)
|
inside your VPN. This allows defining a custom top-level domain (e.g. `.clan`)
|
||||||
and exposing endpoints from a machine to others, which will be
|
and exposing endpoints from a machine to others, which will be
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{ ... }:
|
{
|
||||||
|
clanLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
sharedInterface =
|
sharedInterface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
@@ -51,15 +54,15 @@ let
|
|||||||
builtins.foldl' (
|
builtins.foldl' (
|
||||||
urls: name:
|
urls: name:
|
||||||
let
|
let
|
||||||
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
|
ip = clanLib.vars.getPublicValue {
|
||||||
|
flake = config.clan.core.settings.directory;
|
||||||
|
machine = name;
|
||||||
|
generator = "zerotier";
|
||||||
|
file = "zerotier-ip";
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
if builtins.pathExists ipPath then
|
if ip != null then urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ] else urls
|
||||||
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 { })))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -156,9 +159,14 @@ in
|
|||||||
readHostKey =
|
readHostKey =
|
||||||
machine:
|
machine:
|
||||||
let
|
let
|
||||||
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
publicKey = clanLib.vars.getPublicValue {
|
||||||
|
flake = config.clan.core.settings.directory;
|
||||||
|
inherit machine;
|
||||||
|
generator = "data-mesher-host-key";
|
||||||
|
file = "public_key";
|
||||||
|
};
|
||||||
in
|
in
|
||||||
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
builtins.elemAt (lib.splitString "\n" publicKey) 1;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -1 +1,83 @@
|
|||||||
This a test README just to appease the eval warnings if we don't have one
|
!!! Danger "Experimental"
|
||||||
|
This service is for demonstration purpose only and may change in the future.
|
||||||
|
|
||||||
|
The Hello-World Clan Service is a minimal example showing how to build and register your own service.
|
||||||
|
|
||||||
|
It serves as a reference implementation and is used in clan-core CI tests to ensure compatibility.
|
||||||
|
|
||||||
|
## What it demonstrates
|
||||||
|
|
||||||
|
- How to define a basic Clan-compatible service.
|
||||||
|
- How to structure your service for discovery and configuration.
|
||||||
|
- How Clan services interact with nixos.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
This service demonstrates two levels of testing to ensure quality and stability across releases:
|
||||||
|
|
||||||
|
1. **Unit & Integration Testing** — via [`nix-unit`](https://github.com/nix-community/nix-unit)
|
||||||
|
2. **End-to-End Testing** — via **NixOS VM tests**, which we extended to support **container virtualization** for better performance.
|
||||||
|
|
||||||
|
We highly advocate following the [Practical Testing Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html):
|
||||||
|
|
||||||
|
* Write **unit tests** for core logic and invariants.
|
||||||
|
* Add **one or two end-to-end (E2E)** tests to confirm your service starts and behaves correctly in a real NixOS environment.
|
||||||
|
|
||||||
|
NixOS is **untyped** and frequently changes; tests are the safest way to ensure long-term stability of services.
|
||||||
|
|
||||||
|
```
|
||||||
|
/ \
|
||||||
|
/ \
|
||||||
|
/ E2E \
|
||||||
|
/-------\
|
||||||
|
/ \
|
||||||
|
/Integration\
|
||||||
|
/-------------\
|
||||||
|
/ \
|
||||||
|
/ Unit Tests \
|
||||||
|
-------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
### nix-unit
|
||||||
|
|
||||||
|
We highly advocate the usage of
|
||||||
|
|
||||||
|
[nix-unit](https://github.com/nix-community/nix-unit)
|
||||||
|
|
||||||
|
Example in: tests/eval-tests.nix
|
||||||
|
|
||||||
|
If you use flake-parts you can use the [native integration](https://flake.parts/options/nix-unit.html)
|
||||||
|
|
||||||
|
If nix-unit succeeds you'r nixos evaluation should be mostly correct.
|
||||||
|
|
||||||
|
!!! Tip
|
||||||
|
- Ensure most used 'settings' and variants are tested.
|
||||||
|
- Think about some important edge-cases your system should handle.
|
||||||
|
|
||||||
|
### NixOS VM / Container Test
|
||||||
|
|
||||||
|
!!! Warning "Early Vars & clanTest"
|
||||||
|
The testing system around vars is experimental
|
||||||
|
|
||||||
|
`clanTest` is still experimental and enables container virtualization by default.
|
||||||
|
This is still early and might have some limitations.
|
||||||
|
|
||||||
|
Some minimal boilerplate is needed to use `clanTest`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nixosLib = import (inputs.nixpkgs + "/nixos/lib") { }
|
||||||
|
nixosLib.runTest (
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.modules.nixosTest.clanTest
|
||||||
|
# Example in tests/vm/default.nix
|
||||||
|
testModule
|
||||||
|
];
|
||||||
|
hostPkgs = pkgs;
|
||||||
|
|
||||||
|
# Uncomment if you don't want or cannot use containers
|
||||||
|
# test.useContainers = false;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/hello-word";
|
manifest.name = "clan-core/hello-word";
|
||||||
manifest.description = "This is a test";
|
manifest.description = "Minimal example clan service that greets the world";
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
# This service provides two roles: "morning" and "evening". Roles can be
|
# This service provides two roles: "morning" and "evening". Roles can be
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ in
|
|||||||
# The hello-world service being tested
|
# The hello-world service being tested
|
||||||
../../clanServices/hello-world
|
../../clanServices/hello-world
|
||||||
# Required modules
|
# Required modules
|
||||||
../../nixosModules/clanCore
|
../../nixosModules
|
||||||
];
|
];
|
||||||
testName = "hello-world";
|
testName = "hello-world";
|
||||||
tests = ./tests/eval-tests.nix;
|
tests = ./tests/eval-tests.nix;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
testFlake = clanLib.clan {
|
testClan = clanLib.clan {
|
||||||
self = { };
|
self = { };
|
||||||
# Point to the folder of the module
|
# Point to the folder of the module
|
||||||
# TODO: make this optional
|
# TODO: make this optional
|
||||||
@@ -33,10 +33,20 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
test_simple = {
|
/**
|
||||||
config = testFlake.config;
|
We highly advocate the usage of:
|
||||||
|
https://github.com/nix-community/nix-unit
|
||||||
|
|
||||||
expr = { };
|
If you use flake-parts you can use the native integration: https://flake.parts/options/nix-unit.html
|
||||||
expected = { };
|
*/
|
||||||
|
test_simple = {
|
||||||
|
# Allows inspection via the nix-repl
|
||||||
|
# Ignored by nix-unit; it only looks at 'expr' and 'expected'
|
||||||
|
inherit testClan;
|
||||||
|
|
||||||
|
# Assert that jon has the
|
||||||
|
# configured greeting in 'environment.etc.hello.text'
|
||||||
|
expr = testClan.config.nixosConfigurations.jon.config.environment.etc."hello".text;
|
||||||
|
expected = "Good evening World!";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
🚧🚧🚧 Experimental 🚧🚧🚧
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
Use at your own risk.
|
|
||||||
|
|
||||||
We are still refining its interfaces, instability and breakages are expected.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
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
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -44,8 +44,10 @@
|
|||||||
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=
|
D1hOs4gOQ853+83L/Ej4ESTj0B04NLVMlzMGtl3qcA==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
{
|
{
|
||||||
"data": "ENC[AES256_GCM,data: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,iv:q3j8EAokyyxiszf+wyRqxEr2igaD1bX7YnFx/NbsGg8=,tag:HKKYWRJEUwW2/TxL+5dSng==,type:str]",
|
"data": "ENC[AES256_GCM,data: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,iv:dGSmyDNBdVyF54bYS/Zxm2NNXZyGtLjkyYlrI9/nKvc=,tag:ip2fy76NjObWbW20HyuZUA==,type:str]",
|
||||||
"sops": {
|
"sops": {
|
||||||
"age": [
|
"age": [
|
||||||
{
|
{
|
||||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaeXRjU214aWk5ajl1aW9E\naGJlb1ViaVRmMTBHdkFDQUNDZS94WFZiNUNvCllmWTJBck9hR3U3V09VWDZwQ2xI\nd3ZEQnBIUG5ZSTVIdS8rQ2FMYVhyNk0KLS0tIEE1UG8rSzFyU01sVXhGVHpoaE9i\nSis4Qi9tMGFqbTNMTDZUVk1ZdXkrM28Km4VkfaOsZ69ckjvrg+os43H/O1IoWHzC\nt4LqZRz1Tk7/d1aLWavSPPjVYrCOMZeNBqGbQpGfjjuXrafClRNQdQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQS96MUFubVdOR2VCc2xO\ncTc5QnNHNTFpdURnSnF3dVhBQXQ3bnBuRW1RCngzSVlhSW9rNUxoSWdKcEtKVXc3\nQitLZ2NDUXBSUmxtVWpYRUlvOHVXcW8KLS0tIGZaWlRVak9NYmt2elpwYStYenRE\nanlkT3BET1FjQ2lFZkp3SXFMSkJSaVkKKkr+MNNqs6Ve3K5OrZfBEGlnc7OAthqf\nOZrP9NYOTMgkvhFsZTVpUS0zskry0iwmTNt+KeluYf0Tko8K53Kx2A==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R1RHTGViTnRLVVkyM3J0\nbm96cGVPTlo4NXBNL0g1eEVSNG9DUkgwVFRBCmRKVTlMRmV3Tmg2RTZIclBlWlcr\ndzI5MUxhcllzbE1IMDNxa08zVkpITmsKLS0tIG01Y2dyQkY3UmRudFk2d0p6bThn\nemlaWnZoS3p4VHhMTFFwTm9VN0ttYzQKVbLFgtK6NIRIiryWHeeOPD45iwUds4QD\n7b8xYYoxlo+DETggxK6Vz3IdT/BSK5bFtgAxl864b5gW+Aw4c6AO5w==\n-----END AGE ENCRYPTED FILE-----\n"
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXd2dVYmJIbUVVeXk5Nk1E\nekFiUldVVUhRTmE4dHRiTHNDdEMyS1pRV1RrCkNScGdXVSs4UU5id29DV0pZWDQr\nenV1QmpnOFk5aFpTTUxmb0hDVHZDdFkKLS0tIHpmalJtRC94bEhaUStmeUlHT21w\nd3o3UzJHZklxK0RCYUUxc2c3aG1XclkKEPq1ZgyGiAK/Hy4zT7wfdDfPEE3vMHpR\nzwQV5y3M3DmlnKQEvJu0DpQ334CyAcubZC7cswQdUrM8TPqJhb/TuA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastmodified": "2025-09-18T14:33:37Z",
|
"lastmodified": "2025-10-21T13:17:17Z",
|
||||||
"mac": "ENC[AES256_GCM,data:XKCnd0QrAlOCECSeSvbLYHMLbmUh4fMRnLaTb5ARoP4Zc9joWGsCaRZxokc2/sG4BXA/6pkbQXHyIOudKbcBpVjjvs9E+6Mnzt53nfRoH/iOkYPbN2EO49okVZJXW0M1rlBxrxvGuiDlz2p2p6L7neKLy4EB482pYea5+dUr2Yw=,iv:oj/MkZCfkvCmAb79uzEvKwEAm1bKtWhS4rPRAWSgRgw=,tag:h5TPPILXkhJplnDT2Gqtfw==,type:str]",
|
"mac": "ENC[AES256_GCM,data:wdAFURkJZvclbz3UFPSPV9fma7zrZVEhMhsRqylGQMLepX/WohEAr8nJgeHl05be1Q8M8biPXCCoL0vfwg4BRZOkhD8PusJh8iBI3+STNQe/S1qoIK1ByfBFhJD+tIsVsgduLp6G32e6SRNvkuX3UpJqyViuRUavfQd3b8LRU4I=,iv:S3sMNTz5Kg4TxHj1tnk/ayiFuO74dR4aPnnomtkGByo=,tag:uive2bYe42s6VtPd03jTMw==,type:str]",
|
||||||
"unencrypted_suffix": "_unencrypted",
|
"version": "3.11.0"
|
||||||
"version": "3.10.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
perInstance =
|
perInstance =
|
||||||
{ settings, ... }:
|
{ settings, roles, ... }:
|
||||||
{
|
{
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{
|
{
|
||||||
@@ -38,8 +38,19 @@
|
|||||||
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 (settings.certificate.searchDomains != [ ]) {
|
clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) {
|
||||||
share = true;
|
share = true;
|
||||||
files.id_ed25519.deploy = false;
|
files.id_ed25519.deploy = false;
|
||||||
files."id_ed25519.pub" = {
|
files."id_ed25519.pub" = {
|
||||||
@@ -54,9 +65,9 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
programs.ssh.knownHosts.ssh-ca = lib.mkIf (searchDomains != [ ]) {
|
||||||
certAuthority = true;
|
certAuthority = true;
|
||||||
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificate.searchDomains;
|
extraHostNames = builtins.map (domain: "*.${domain}") 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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
🚧🚧🚧 Experimental 🚧🚧🚧
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
Use at your own risk.
|
|
||||||
|
|
||||||
We are still refining its interfaces, instability and breakages are expected.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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.importedModulesEvaluated.self-zerotier-redux.config;
|
# testFlake.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-zerotier-redux.config;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
test_simple = {
|
test_simple = {
|
||||||
inherit testFlake;
|
inherit testFlake;
|
||||||
|
|
||||||
expr =
|
expr =
|
||||||
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-wifi.config;
|
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-wifi.config;
|
||||||
expected = 1;
|
expected = 1;
|
||||||
|
|
||||||
# expr = {
|
# expr = {
|
||||||
|
|||||||
@@ -54,7 +54,10 @@
|
|||||||
- 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 =
|
||||||
@@ -74,10 +77,12 @@ let
|
|||||||
controllerHosts = lib.mapAttrsToList (
|
controllerHosts = lib.mapAttrsToList (
|
||||||
name: _value:
|
name: _value:
|
||||||
let
|
let
|
||||||
prefix = builtins.readFile (
|
prefix = clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
machine = name;
|
||||||
);
|
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
|
||||||
@@ -88,20 +93,24 @@ let
|
|||||||
peerHosts = lib.mapAttrsToList (
|
peerHosts = lib.mapAttrsToList (
|
||||||
peerName: peerValue:
|
peerName: peerValue:
|
||||||
let
|
let
|
||||||
peerSuffix = builtins.readFile (
|
peerSuffix = clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${peerName}/wireguard-network-${instanceName}/suffix/value"
|
machine = peerName;
|
||||||
);
|
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 = builtins.readFile (
|
controllerPrefix = clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${designatedController}/wireguard-network-${instanceName}/prefix/value"
|
machine = designatedController;
|
||||||
);
|
generator = "wireguard-network-${instanceName}";
|
||||||
|
file = "prefix";
|
||||||
|
};
|
||||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||||
in
|
in
|
||||||
"${peerIP} ${peerName}.${domain}"
|
"${peerIP} ${peerName}.${domain}"
|
||||||
@@ -220,10 +229,12 @@ in
|
|||||||
lib.mapAttrsToList (
|
lib.mapAttrsToList (
|
||||||
ctrlName: _:
|
ctrlName: _:
|
||||||
let
|
let
|
||||||
controllerPrefix = builtins.readFile (
|
controllerPrefix = clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
|
machine = ctrlName;
|
||||||
);
|
generator = "wireguard-network-${instanceName}";
|
||||||
|
file = "prefix";
|
||||||
|
};
|
||||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||||
in
|
in
|
||||||
"${peerIP}/56"
|
"${peerIP}/56"
|
||||||
@@ -234,20 +245,22 @@ in
|
|||||||
|
|
||||||
# Connect to all controllers
|
# Connect to all controllers
|
||||||
peers = lib.mapAttrsToList (name: value: {
|
peers = lib.mapAttrsToList (name: value: {
|
||||||
publicKey = (
|
publicKey = clanLib.vars.getPublicValue {
|
||||||
builtins.readFile (
|
flake = config.clan.core.settings.directory;
|
||||||
config.clan.core.settings.directory
|
machine = name;
|
||||||
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
generator = "wireguard-keys-${instanceName}";
|
||||||
)
|
file = "publickey";
|
||||||
);
|
};
|
||||||
|
|
||||||
# Allow each controller's /56 subnet
|
# Allow each controller's /56 subnet
|
||||||
allowedIPs = [
|
allowedIPs = [
|
||||||
"${
|
"${
|
||||||
builtins.readFile (
|
clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
machine = name;
|
||||||
)
|
generator = "wireguard-network-${instanceName}";
|
||||||
|
file = "prefix";
|
||||||
|
}
|
||||||
}::/56"
|
}::/56"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -349,25 +362,29 @@ 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 = (
|
publicKey = clanLib.vars.getPublicValue {
|
||||||
builtins.readFile (
|
flake = config.clan.core.settings.directory;
|
||||||
config.clan.core.settings.directory
|
machine = name;
|
||||||
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
generator = "wireguard-keys-${instanceName}";
|
||||||
)
|
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 = builtins.readFile (
|
controllerPrefix = clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
|
machine = ctrlName;
|
||||||
);
|
generator = "wireguard-network-${instanceName}";
|
||||||
peerSuffix = builtins.readFile (
|
file = "prefix";
|
||||||
config.clan.core.settings.directory
|
};
|
||||||
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/suffix/value"
|
peerSuffix = clanLib.vars.getPublicValue {
|
||||||
);
|
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;
|
||||||
@@ -377,19 +394,21 @@ in
|
|||||||
else
|
else
|
||||||
# For other controllers: use their /56 subnet
|
# For other controllers: use their /56 subnet
|
||||||
{
|
{
|
||||||
publicKey = (
|
publicKey = clanLib.vars.getPublicValue {
|
||||||
builtins.readFile (
|
flake = config.clan.core.settings.directory;
|
||||||
config.clan.core.settings.directory
|
machine = name;
|
||||||
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
generator = "wireguard-keys-${instanceName}";
|
||||||
)
|
file = "publickey";
|
||||||
);
|
};
|
||||||
|
|
||||||
allowedIPs = [
|
allowedIPs = [
|
||||||
"${
|
"${
|
||||||
builtins.readFile (
|
clanLib.vars.getPublicValue {
|
||||||
config.clan.core.settings.directory
|
flake = config.clan.core.settings.directory;
|
||||||
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
machine = name;
|
||||||
)
|
generator = "wireguard-network-${instanceName}";
|
||||||
|
file = "prefix";
|
||||||
|
}
|
||||||
}::/56"
|
}::/56"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
|
!!! Danger "Experimental"
|
||||||
|
This service is experimental and will change in the future.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
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
|
Yggdrasil is designed to be a future-proof and decentralised alternative to the
|
||||||
the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines.
|
structured routing protocols commonly used today on the internet. Inside your
|
||||||
|
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,12 +29,13 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
options.peers = lib.mkOption {
|
options.extraPeers = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = ''
|
description = ''
|
||||||
Static peers to configure for this host.
|
Additional static peers to configure for this host. If you use a
|
||||||
If not set, local peers will be auto-discovered
|
VPN clan service, it will automatically be added as peers to other hosts.
|
||||||
|
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"
|
||||||
@@ -45,16 +46,67 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
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' "instances.*.networking.?peers.*.host.?plain" 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 = { };
|
||||||
@@ -99,7 +151,7 @@
|
|||||||
settings = {
|
settings = {
|
||||||
PrivateKeyPath = "/key";
|
PrivateKeyPath = "/key";
|
||||||
IfName = "ygg";
|
IfName = "ygg";
|
||||||
Peers = settings.peers;
|
Peers = lib.lists.unique (exportedPeers ++ settings.extraPeers);
|
||||||
MulticastInterfaces = [
|
MulticastInterfaces = [
|
||||||
# Ethernet is preferred over WIFI
|
# Ethernet is preferred over WIFI
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,13 @@
|
|||||||
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";
|
||||||
|
roles.default.machines.peer2.settings.host = "peer2";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
{ ... }:
|
{
|
||||||
|
clanLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/zerotier";
|
manifest.name = "clan-core/zerotier";
|
||||||
@@ -39,6 +42,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
|
clanLib
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -90,6 +94,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
|
clanLib
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -142,6 +147,7 @@
|
|||||||
imports = [
|
imports = [
|
||||||
(import ./shared.nix {
|
(import ./shared.nix {
|
||||||
inherit
|
inherit
|
||||||
|
clanLib
|
||||||
instanceName
|
instanceName
|
||||||
roles
|
roles
|
||||||
config
|
config
|
||||||
@@ -160,15 +166,16 @@
|
|||||||
);
|
);
|
||||||
networkIps = builtins.foldl' (
|
networkIps = builtins.foldl' (
|
||||||
ips: name:
|
ips: name:
|
||||||
if
|
let
|
||||||
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
ztIp = clanLib.vars.getPublicValue {
|
||||||
then
|
flake = config.clan.core.settings.directory;
|
||||||
ips
|
machine = name;
|
||||||
++ [
|
generator = "zerotier";
|
||||||
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
file = "zerotier-ip";
|
||||||
]
|
default = null;
|
||||||
else
|
};
|
||||||
ips
|
in
|
||||||
|
if ztIp != null then ips ++ [ ztIp ] else ips
|
||||||
) [ ] machines;
|
) [ ] machines;
|
||||||
allHostIPs = settings.allowedIps ++ networkIps;
|
allHostIPs = settings.allowedIps ++ networkIps;
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
clanLib,
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
@@ -8,20 +9,26 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
|
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
|
||||||
networkIdPath = "${config.clan.core.settings.directory}/vars/per-machine/${controllerMachine}/zerotier/zerotier-network-id/value";
|
networkId = clanLib.vars.getPublicValue {
|
||||||
networkId = if builtins.pathExists networkIdPath then builtins.readFile networkIdPath else null;
|
flake = config.clan.core.settings.directory;
|
||||||
|
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:
|
||||||
if
|
let
|
||||||
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
|
moonIp = clanLib.vars.getPublicValue {
|
||||||
then
|
flake = config.clan.core.settings.directory;
|
||||||
ips
|
machine = name;
|
||||||
++ [
|
generator = "zerotier";
|
||||||
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
|
file = "zerotier-ip";
|
||||||
]
|
default = null;
|
||||||
else
|
};
|
||||||
ips
|
in
|
||||||
|
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": 1760361585,
|
"lastModified": 1761204206,
|
||||||
"narHash": "sha256-v4PnSmt1hXW4dSgVWxcd1ZeEBlhO7NksNRC5cX7L5iw=",
|
"narHash": "sha256-A4KDudGblln1yh8c95OVow2NRlHtbGZXr/pgNenyrNc=",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"rev": "7e7e58eb64ef61beb0a938a6622ec0122382131b",
|
"rev": "aabbe0dfac47b7cfbe2210bcb27fb7ecce93350f",
|
||||||
"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": 1760965023,
|
"lastModified": 1762168314,
|
||||||
"narHash": "sha256-cpcgkeLApMGFCdp4jFqeIxTwlcGaSI+Zwmv8z2E85pY=",
|
"narHash": "sha256-+DX6mIF47gRGoK0mqkTg1Jmcjcup0CAXJFHVkdUx8YA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "40ef6b9aa73f70b265c29df083fafae66b9df351",
|
"rev": "94fc102d2c15d9c1a861e59de550807c65358e1b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -128,11 +128,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760652422,
|
"lastModified": 1761730856,
|
||||||
"narHash": "sha256-C88Pgz38QIl9JxQceexqL2G7sw9vodHWx1Uaq+NRJrw=",
|
"narHash": "sha256-t1i5p/vSWwueZSC0Z2BImxx3BjoUDNKyC2mk24krcMY=",
|
||||||
"owner": "NuschtOS",
|
"owner": "NuschtOS",
|
||||||
"repo": "search",
|
"repo": "search",
|
||||||
"rev": "3ebeebe8b6a49dfb11f771f761e0310f7c48d726",
|
"rev": "e29de6db0cb3182e9aee75a3b1fd1919d995d85b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -208,11 +208,11 @@
|
|||||||
"nixpkgs": []
|
"nixpkgs": []
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760945191,
|
"lastModified": 1761311587,
|
||||||
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ 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 = {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Make sure you have the following:
|
|||||||
**Note:** This creates a new directory in your current location
|
**Note:** This creates a new directory in your current location
|
||||||
|
|
||||||
```shellSession
|
```shellSession
|
||||||
nix run https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli --refresh -- flakes create
|
nix run "https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli" --refresh -- flakes create
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Enter a **name** in the prompt:
|
3. Enter a **name** in the prompt:
|
||||||
@@ -137,12 +137,13 @@ Description: None
|
|||||||
|
|
||||||
This confirms your setup is working correctly.
|
This confirms your setup is working correctly.
|
||||||
|
|
||||||
You can now change the default name by editing the `meta.name` field in your `clan.nix` file.
|
You can now change the default name and tld by editing the `meta.name` and `meta.tld` fields in your `clan.nix` file.
|
||||||
|
|
||||||
```{.nix title="clan.nix" hl_lines="3"}
|
```{.nix title="clan.nix" hl_lines="3 4"}
|
||||||
{
|
{
|
||||||
# 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,10 +10,11 @@ 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="9"}
|
```{.nix title="clan.nix" hl_lines="10"}
|
||||||
{
|
{
|
||||||
# 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.
|
||||||
|
|||||||
@@ -150,10 +150,61 @@ Those are very similar to NixOS VM tests, as in they run virtualized nixos machi
|
|||||||
As of now the container test driver is a downstream development in clan-core.
|
As of now the container test driver is a downstream development in clan-core.
|
||||||
Basically everything stated under the NixOS VM tests sections applies here, except some limitations.
|
Basically everything stated under the NixOS VM tests sections applies here, except some limitations.
|
||||||
|
|
||||||
Limitations:
|
### Using Container Tests vs VM Tests
|
||||||
|
|
||||||
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the container.
|
Container tests are **enabled by default** for all tests using the clan testing framework.
|
||||||
- setuid binaries don't work
|
They offer significant performance advantages over VM tests:
|
||||||
|
|
||||||
|
- **Faster startup**
|
||||||
|
- **Lower resource usage**: No full kernel boot or hardware emulation overhead
|
||||||
|
|
||||||
|
To control whether a test uses containers or VMs, use the `clan.test.useContainers` option:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
clan = {
|
||||||
|
directory = ./.;
|
||||||
|
test.useContainers = true; # Use containers (default)
|
||||||
|
# test.useContainers = false; # Use VMs instead
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use VM tests instead of container tests:**
|
||||||
|
|
||||||
|
- Testing kernel features, modules, or boot processes
|
||||||
|
- Testing hardware-specific features
|
||||||
|
- When you need full system isolation
|
||||||
|
|
||||||
|
### System Requirements for Container Tests
|
||||||
|
|
||||||
|
Container tests require the **`uid-range`** system feature** in the Nix sandbox.
|
||||||
|
This feature allows Nix to allocate a range of UIDs for containers to use, enabling `systemd-nspawn` containers to run properly inside the Nix build sandbox.
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
|
||||||
|
The `uid-range` feature requires the `auto-allocate-uids` setting to be enabled in your Nix configuration.
|
||||||
|
|
||||||
|
To verify or enable it, add to your `/etc/nix/nix.conf` or NixOS configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
settings.experimental-features = [
|
||||||
|
"auto-allocate-uids"
|
||||||
|
];
|
||||||
|
|
||||||
|
nix.settings.auto-allocate-uids = true;
|
||||||
|
nix.settings.system-features = [ "uid-range" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
**Technical details:**
|
||||||
|
|
||||||
|
- Container tests set `requiredSystemFeatures = [ "uid-range" ];` in their derivation (see `lib/test/container-test-driver/driver-module.nix:98`)
|
||||||
|
- Without this feature, containers cannot properly manage user namespaces and will fail to start
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the containers.
|
||||||
|
- Early implementation and limited by features.
|
||||||
|
|
||||||
### Where to find examples for NixOS container tests
|
### Where to find examples for NixOS container tests
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ 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,6 +43,7 @@ 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,6 +63,7 @@ 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.
|
||||||
|
|||||||
42
flake.lock
generated
42
flake.lock
generated
@@ -31,11 +31,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760701190,
|
"lastModified": 1761899396,
|
||||||
"narHash": "sha256-y7UhnWlER8r776JsySqsbTUh2Txf7K30smfHlqdaIQw=",
|
"narHash": "sha256-XOpKBp6HLzzMCbzW50TEuXN35zN5WGQREC7n34DcNMM=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "3a9450b26e69dcb6f8de6e2b07b3fc1c288d85f5",
|
"rev": "6f4cf5abbe318e4cd1e879506f6eeafd83f7b998",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -51,11 +51,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760948891,
|
"lastModified": 1762040540,
|
||||||
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
|
"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
|
"rev": "0010412d62a25d959151790968765a70c436598b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -71,11 +71,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760721282,
|
"lastModified": 1762039661,
|
||||||
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
|
"narHash": "sha256-oM5BwAGE78IBLZn+AqxwH/saqwq3e926rNq5HmOulkc=",
|
||||||
"owner": "nix-darwin",
|
"owner": "nix-darwin",
|
||||||
"repo": "nix-darwin",
|
"repo": "nix-darwin",
|
||||||
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
|
"rev": "c3c8c9f2a5ed43175ac4dc030308756620e6e4e4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -99,11 +99,11 @@
|
|||||||
},
|
},
|
||||||
"nixos-facter-modules": {
|
"nixos-facter-modules": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756491981,
|
"lastModified": 1761137276,
|
||||||
"narHash": "sha256-lXyDAWPw/UngVtQfgQ8/nrubs2r+waGEYIba5UX62+k=",
|
"narHash": "sha256-4lDjGnWRBLwqKQ4UWSUq6Mvxu9r8DSqCCydodW/Jsi8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-facter-modules",
|
"repo": "nixos-facter-modules",
|
||||||
"rev": "c1b29520945d3e148cd96618c8a0d1f850965d8c",
|
"rev": "70bcd64225d167c7af9b475c4df7b5abba5c7de8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -115,10 +115,10 @@
|
|||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 315532800,
|
"lastModified": 315532800,
|
||||||
"narHash": "sha256-yDxtm0PESdgNetiJN5+MFxgubBcLDTiuSjjrJiyvsvM=",
|
"narHash": "sha256-LDT9wuUZtjPfmviCcVWif5+7j4kBI2mWaZwjNNeg4eg=",
|
||||||
"rev": "d7f52a7a640bc54c7bb414cca603835bf8dd4b10",
|
"rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre871443.d7f52a7a640b/nixexprs.tar.xz"
|
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre887438.a7fc11be66bd/nixexprs.tar.xz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
@@ -146,11 +146,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760845571,
|
"lastModified": 1760998189,
|
||||||
"narHash": "sha256-PwGzU3EOU65Ef1VvuNnVLie+l+P0g/fzf/PGUG82KbM=",
|
"narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "9c9a9798be331ed3f4b2902933d7677d0659ee61",
|
"rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -181,11 +181,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1760945191,
|
"lastModified": 1761311587,
|
||||||
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
(
|
(
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
|
debug = true;
|
||||||
clan = {
|
clan = {
|
||||||
meta.name = "clan-core";
|
meta.name = "clan-core";
|
||||||
inventory = {
|
inventory = {
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
./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,32 +39,10 @@ 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;
|
|
||||||
---
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
apply =
|
# Important: !This logic needs to be kept in sync with lib.clan function!
|
||||||
config:
|
apply = config: clan-core.lib.checkConfig config.checks 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
|
||||||
|
|||||||
19
lib/clan/checkConfig.nix
Normal file
19
lib/clan/checkConfig.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{ 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,20 +33,23 @@
|
|||||||
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
|
||||||
lib.evalModules {
|
clan-core.clanLib.checkConfig configuration.config.checks configuration
|
||||||
class = "clan";
|
|
||||||
specialArgs = {
|
|
||||||
inherit
|
|
||||||
self
|
|
||||||
;
|
|
||||||
inherit
|
|
||||||
nixpkgs
|
|
||||||
nix-darwin
|
|
||||||
;
|
|
||||||
};
|
|
||||||
modules = [
|
|
||||||
clan-core.modules.clan.default
|
|
||||||
m
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -137,6 +137,12 @@ 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,10 +16,12 @@ lib.fix (
|
|||||||
*/
|
*/
|
||||||
callLib = file: args: import file ({ inherit lib clanLib; } // args);
|
callLib = file: args: import file ({ inherit lib clanLib; } // args);
|
||||||
|
|
||||||
evalService = clanLib.callLib ./modules/inventory/distributed-service/evalService.nix { };
|
checkConfig = clanLib.callLib ./clan/checkConfig.nix { };
|
||||||
|
|
||||||
|
evalService = clanLib.callLib ./evalService.nix { };
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
# ClanLib functions
|
# ClanLib functions
|
||||||
inventory = clanLib.callLib ./modules/inventory { };
|
inventory = clanLib.callLib ./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
|
||||||
@@ -30,6 +32,8 @@ 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 { };
|
||||||
|
|
||||||
|
|||||||
@@ -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 ./clan { inherit clan-core; };
|
modules.clan.default = lib.modules.importApply ../modules/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,7 +53,12 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}).clan
|
}).clan
|
||||||
{ config.directory = rootPath; };
|
{
|
||||||
|
directory = rootPath;
|
||||||
|
self = {
|
||||||
|
inputs.nixpkgs.lib.version = "25.11";
|
||||||
|
};
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit vclan;
|
inherit vclan;
|
||||||
@@ -94,7 +99,12 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}).clan
|
}).clan
|
||||||
{ config.directory = rootPath; };
|
{
|
||||||
|
directory = rootPath;
|
||||||
|
self = {
|
||||||
|
inputs.nixpkgs.lib.version = "25.11";
|
||||||
|
};
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit vclan;
|
inherit vclan;
|
||||||
@@ -17,9 +17,9 @@ lib.evalModules {
|
|||||||
specialArgs._ctx = prefix;
|
specialArgs._ctx = prefix;
|
||||||
modules = [
|
modules = [
|
||||||
# Base module
|
# Base module
|
||||||
./service-module.nix
|
./inventory/distributed-service/service-module.nix
|
||||||
# Feature modules
|
# Feature modules
|
||||||
(lib.modules.importApply ./api-feature.nix {
|
(lib.modules.importApply ./inventory/distributed-service/api-feature.nix {
|
||||||
inherit clanLib prefix;
|
inherit clanLib prefix;
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
@@ -10,12 +10,11 @@ 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
|
||||||
@@ -78,9 +77,6 @@ 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
|
||||||
@@ -101,6 +97,36 @@ 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;
|
||||||
|
};
|
||||||
|
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
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,11 @@
|
|||||||
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 = [
|
||||||
../inventoryClass/inventory.nix
|
../../modules/inventoryClass/inventory.nix
|
||||||
];
|
];
|
||||||
_module.args = { inherit clanLib; };
|
_module.args = { inherit clanLib; };
|
||||||
};
|
};
|
||||||
@@ -28,9 +28,9 @@ in
|
|||||||
elemType = submoduleWith {
|
elemType = submoduleWith {
|
||||||
class = "clan.service";
|
class = "clan.service";
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
exports = config.exports;
|
|
||||||
directory = directory;
|
directory = directory;
|
||||||
clanLib = specialArgs.clanLib;
|
clanLib = specialArgs.clanLib;
|
||||||
|
exports = config.exports;
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
(
|
(
|
||||||
@@ -13,16 +13,18 @@ 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
|
||||||
../../../../machines
|
../../../nixosModules/machineModules
|
||||||
../../../../inventory.json
|
../../../machines
|
||||||
|
../../../inventory.json
|
||||||
|
../../../modules
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -81,6 +81,7 @@ 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;
|
||||||
@@ -158,6 +159,29 @@ 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 = ''
|
||||||
@@ -216,7 +240,7 @@ in
|
|||||||
|
|
||||||
options.extraModules = lib.mkOption {
|
options.extraModules = lib.mkOption {
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf (types.either types.deferredModule types.str);
|
type = types.listOf types.deferredModule;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -483,6 +507,9 @@ in
|
|||||||
type = 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
|
||||||
|
|
||||||
@@ -611,6 +638,9 @@ in
|
|||||||
type = 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
|
||||||
|
|
||||||
@@ -712,6 +742,9 @@ 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.
|
||||||
|
|
||||||
@@ -850,7 +883,11 @@ in
|
|||||||
instanceRes.nixosModule
|
instanceRes.nixosModule
|
||||||
]
|
]
|
||||||
++ (map (
|
++ (map (
|
||||||
s: if builtins.typeOf s == "string" then "${directory}/${s}" else s
|
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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4,63 +4,53 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib)
|
|
||||||
evalModules
|
|
||||||
;
|
|
||||||
|
|
||||||
evalInventory =
|
flakeInputsFixture = {
|
||||||
m:
|
upstream.clan.modules = {
|
||||||
(evalModules {
|
uzzi = {
|
||||||
# Static modules
|
_class = "clan.service";
|
||||||
modules = [
|
manifest = {
|
||||||
clanLib.inventory.inventoryModule
|
name = "uzzi-from-upstream";
|
||||||
{
|
|
||||||
_file = "test file";
|
|
||||||
tags.all = [ ];
|
|
||||||
tags.nixos = [ ];
|
|
||||||
tags.darwin = [ ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
modules.test = { };
|
|
||||||
}
|
|
||||||
m
|
|
||||||
];
|
|
||||||
}).config;
|
|
||||||
|
|
||||||
callInventoryAdapter =
|
|
||||||
inventoryModule:
|
|
||||||
let
|
|
||||||
inventory = evalInventory inventoryModule;
|
|
||||||
flakeInputsFixture = {
|
|
||||||
self.clan.modules = inventoryModule.modules or { };
|
|
||||||
# Example upstream module
|
|
||||||
upstream.clan.modules = {
|
|
||||||
uzzi = {
|
|
||||||
_class = "clan.service";
|
|
||||||
manifest = {
|
|
||||||
name = "uzzi-from-upstream";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
|
||||||
clanLib.inventory.mapInstances {
|
|
||||||
directory = ./.;
|
|
||||||
clanCoreModules = { };
|
|
||||||
flakeInputs = flakeInputsFixture;
|
|
||||||
inherit inventory;
|
|
||||||
exportsModule = { };
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
createTestClan =
|
||||||
|
testClan:
|
||||||
|
let
|
||||||
|
res = clanLib.clan ({
|
||||||
|
# Static / mocked
|
||||||
|
specialArgs = {
|
||||||
|
clan-core = {
|
||||||
|
clan.modules = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
self.inputs = flakeInputsFixture // {
|
||||||
|
self.clan = res.config;
|
||||||
|
};
|
||||||
|
directory = ./.;
|
||||||
|
exportsModule = { };
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
testClan
|
||||||
|
];
|
||||||
|
});
|
||||||
|
in
|
||||||
|
res;
|
||||||
|
|
||||||
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 callInventoryAdapter; };
|
settings = import ./settings.nix { inherit lib createTestClan; };
|
||||||
specialArgs = import ./specialArgs.nix { inherit lib callInventoryAdapter; };
|
specialArgs = import ./specialArgs.nix { inherit lib createTestClan; };
|
||||||
resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; };
|
resolve_module_spec = import ./import_module_spec.nix {
|
||||||
|
inherit lib createTestClan;
|
||||||
|
};
|
||||||
test_simple =
|
test_simple =
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
# 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
|
||||||
@@ -71,7 +61,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
# User config
|
# User config
|
||||||
instances."instance_foo" = {
|
inventory.instances."instance_foo" = {
|
||||||
module = {
|
module = {
|
||||||
name = "simple-module";
|
name = "simple-module";
|
||||||
};
|
};
|
||||||
@@ -81,7 +71,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.importedModulesEvaluated ? "<clan-core>-simple-module";
|
expr = res.config._services.mappedServices ? "<clan-core>-simple-module";
|
||||||
expected = true;
|
expected = true;
|
||||||
inherit res;
|
inherit res;
|
||||||
};
|
};
|
||||||
@@ -92,7 +82,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 = callInventoryAdapter {
|
res = createTestClan {
|
||||||
# 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
|
||||||
@@ -112,18 +102,19 @@ in
|
|||||||
|
|
||||||
perMachine = { }: { };
|
perMachine = { }: { };
|
||||||
};
|
};
|
||||||
|
|
||||||
# User config
|
# User config
|
||||||
instances."instance_foo" = {
|
inventory.instances."instance_foo" = {
|
||||||
module = {
|
module = {
|
||||||
name = "A";
|
name = "A";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
instances."instance_bar" = {
|
inventory.instances."instance_bar" = {
|
||||||
module = {
|
module = {
|
||||||
name = "B";
|
name = "B";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
instances."instance_baz" = {
|
inventory.instances."instance_baz" = {
|
||||||
module = {
|
module = {
|
||||||
name = "A";
|
name = "A";
|
||||||
};
|
};
|
||||||
@@ -133,16 +124,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.mapAttrs (_n: v: builtins.length v) res.grouped;
|
expr = lib.attrNames res.config._services.mappedServices;
|
||||||
expected = {
|
expected = [
|
||||||
"<clan-core>-A" = 2;
|
"<clan-core>-A"
|
||||||
"<clan-core>-B" = 1;
|
"<clan-core>-B"
|
||||||
};
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
test_creates_all_instances =
|
test_creates_all_instances =
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
# 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
|
||||||
@@ -154,22 +145,24 @@ in
|
|||||||
|
|
||||||
perMachine = { }: { };
|
perMachine = { }: { };
|
||||||
};
|
};
|
||||||
instances."instance_foo" = {
|
inventory = {
|
||||||
module = {
|
instances."instance_foo" = {
|
||||||
name = "A";
|
module = {
|
||||||
input = "self";
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
instances."instance_bar" = {
|
||||||
instances."instance_bar" = {
|
module = {
|
||||||
module = {
|
name = "A";
|
||||||
name = "A";
|
input = "self";
|
||||||
input = "self";
|
};
|
||||||
};
|
};
|
||||||
};
|
instances."instance_zaza" = {
|
||||||
instances."instance_zaza" = {
|
module = {
|
||||||
module = {
|
name = "B";
|
||||||
name = "B";
|
input = null;
|
||||||
input = null;
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -177,7 +170,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.importedModulesEvaluated.self-A.instances;
|
expr = lib.attrNames res.config._services.mappedServices.self-A.instances;
|
||||||
expected = [
|
expected = [
|
||||||
"instance_bar"
|
"instance_bar"
|
||||||
"instance_foo"
|
"instance_foo"
|
||||||
@@ -187,7 +180,7 @@ in
|
|||||||
# Membership via roles
|
# Membership via roles
|
||||||
test_add_machines_directly =
|
test_add_machines_directly =
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
# 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
|
||||||
@@ -202,38 +195,40 @@ in
|
|||||||
|
|
||||||
# perMachine = {}: {};
|
# perMachine = {}: {};
|
||||||
};
|
};
|
||||||
machines = {
|
inventory = {
|
||||||
jon = { };
|
machines = {
|
||||||
sara = { };
|
jon = { };
|
||||||
hxi = { };
|
sara = { };
|
||||||
};
|
hxi = { };
|
||||||
instances."instance_foo" = {
|
|
||||||
module = {
|
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
};
|
||||||
roles.peer.machines.jon = { };
|
instances."instance_foo" = {
|
||||||
};
|
module = {
|
||||||
instances."instance_bar" = {
|
name = "A";
|
||||||
module = {
|
input = "self";
|
||||||
name = "A";
|
};
|
||||||
input = "self";
|
roles.peer.machines.jon = { };
|
||||||
};
|
};
|
||||||
roles.peer.machines.sara = { };
|
instances."instance_bar" = {
|
||||||
};
|
module = {
|
||||||
instances."instance_zaza" = {
|
name = "A";
|
||||||
module = {
|
input = "self";
|
||||||
name = "B";
|
};
|
||||||
input = null;
|
roles.peer.machines.sara = { };
|
||||||
|
};
|
||||||
|
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.importedModulesEvaluated.self-A.result.allMachines;
|
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
|
||||||
expected = [
|
expected = [
|
||||||
"jon"
|
"jon"
|
||||||
"sara"
|
"sara"
|
||||||
@@ -243,7 +238,7 @@ in
|
|||||||
# Membership via tags
|
# Membership via tags
|
||||||
test_add_machines_via_tags =
|
test_add_machines_via_tags =
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
# 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
|
||||||
@@ -257,35 +252,37 @@ in
|
|||||||
|
|
||||||
# perMachine = {}: {};
|
# perMachine = {}: {};
|
||||||
};
|
};
|
||||||
machines = {
|
inventory = {
|
||||||
jon = {
|
machines = {
|
||||||
tags = [ "foo" ];
|
jon = {
|
||||||
|
tags = [ "foo" ];
|
||||||
|
};
|
||||||
|
sara = {
|
||||||
|
tags = [ "foo" ];
|
||||||
|
};
|
||||||
|
hxi = { };
|
||||||
};
|
};
|
||||||
sara = {
|
instances."instance_foo" = {
|
||||||
tags = [ "foo" ];
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.tags.foo = { };
|
||||||
};
|
};
|
||||||
hxi = { };
|
instances."instance_zaza" = {
|
||||||
};
|
module = {
|
||||||
instances."instance_foo" = {
|
name = "B";
|
||||||
module = {
|
input = null;
|
||||||
name = "A";
|
};
|
||||||
input = "self";
|
roles.peer.tags.all = { };
|
||||||
};
|
};
|
||||||
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.importedModulesEvaluated.self-A.result.allMachines;
|
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
|
||||||
expected = [
|
expected = [
|
||||||
"jon"
|
"jon"
|
||||||
"sara"
|
"sara"
|
||||||
@@ -293,6 +290,9 @@ 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 callInventoryAdapter; };
|
per_machine_args = import ./per_machine_args.nix { inherit lib createTestClan; };
|
||||||
per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; };
|
per_instance_args = import ./per_instance_args.nix {
|
||||||
|
inherit lib;
|
||||||
|
callInventoryAdapter = createTestClan;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{ callInventoryAdapter, ... }:
|
{ createTestClan, ... }:
|
||||||
let
|
let
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
@@ -23,10 +23,13 @@ let
|
|||||||
|
|
||||||
resolve =
|
resolve =
|
||||||
spec:
|
spec:
|
||||||
callInventoryAdapter {
|
createTestClan {
|
||||||
inherit modules machines;
|
inherit modules;
|
||||||
instances."instance_foo" = {
|
inventory = {
|
||||||
module = spec;
|
inherit machines;
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = spec;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -36,25 +39,16 @@ in
|
|||||||
(resolve {
|
(resolve {
|
||||||
name = "A";
|
name = "A";
|
||||||
input = "self";
|
input = "self";
|
||||||
}).importedModuleWithInstances.instance_foo.resolvedModule;
|
}).config._services.mappedServices.self-A.manifest.name;
|
||||||
expected = {
|
expected = "network";
|
||||||
_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";
|
||||||
}).importedModuleWithInstances.instance_foo.resolvedModule;
|
}).config._services.mappedServices.upstream-uzzi.manifest.name;
|
||||||
expected = {
|
expected = "uzzi-from-upstream";
|
||||||
_class = "clan.service";
|
|
||||||
manifest = {
|
|
||||||
name = "uzzi-from-upstream";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -58,39 +58,43 @@ let
|
|||||||
sara = { };
|
sara = { };
|
||||||
};
|
};
|
||||||
res = callInventoryAdapter {
|
res = callInventoryAdapter {
|
||||||
inherit modules machines;
|
inherit modules;
|
||||||
instances."instance_foo" = {
|
|
||||||
module = {
|
inventory = {
|
||||||
name = "A";
|
inherit machines;
|
||||||
input = "self";
|
instances."instance_foo" = {
|
||||||
|
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 = { };
|
||||||
};
|
};
|
||||||
roles.peer.machines.jon = {
|
instances."instance_bar" = {
|
||||||
settings.timeout = lib.mkForce "foo-peer-jon";
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = "bar-peer-jon";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
roles.peer = {
|
# TODO: move this into a seperate test.
|
||||||
settings.timeout = "foo-peer";
|
# 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 = { };
|
||||||
};
|
};
|
||||||
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 = { };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,9 +109,10 @@ in
|
|||||||
{
|
{
|
||||||
# settings should evaluate
|
# settings should evaluate
|
||||||
test_per_instance_arguments = {
|
test_per_instance_arguments = {
|
||||||
|
inherit res;
|
||||||
expr = {
|
expr = {
|
||||||
instanceName =
|
instanceName =
|
||||||
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
|
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
# Below we access:
|
# Below we access:
|
||||||
@@ -115,11 +120,11 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
settings =
|
settings =
|
||||||
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
|
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
|
||||||
machine =
|
machine =
|
||||||
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
|
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
|
||||||
roles =
|
roles =
|
||||||
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
|
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
instanceName = "instance_foo";
|
instanceName = "instance_foo";
|
||||||
@@ -137,6 +142,7 @@ in
|
|||||||
settings = { };
|
settings = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
settings = { };
|
||||||
};
|
};
|
||||||
peer = {
|
peer = {
|
||||||
machines = {
|
machines = {
|
||||||
@@ -146,6 +152,9 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
settings = {
|
||||||
|
timeout = "foo-peer";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
settings = {
|
settings = {
|
||||||
@@ -156,9 +165,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.importedModulesEvaluated.self-A;
|
x = res.config._services.mappedServices.self-A;
|
||||||
expr =
|
expr =
|
||||||
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
|
res.config._services.mappedServices.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, callInventoryAdapter }:
|
{ lib, createTestClan }:
|
||||||
let
|
let
|
||||||
# Authored module
|
# Authored module
|
||||||
# A minimal module looks like this
|
# A minimal module looks like this
|
||||||
@@ -39,36 +39,40 @@ let
|
|||||||
jon = { };
|
jon = { };
|
||||||
sara = { };
|
sara = { };
|
||||||
};
|
};
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
inherit modules machines;
|
inherit modules;
|
||||||
instances."instance_foo" = {
|
inventory = {
|
||||||
module = {
|
|
||||||
name = "A";
|
inherit machines;
|
||||||
input = "self";
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = lib.mkForce "foo-peer-jon";
|
||||||
|
};
|
||||||
|
roles.peer = {
|
||||||
|
settings.timeout = "foo-peer";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
roles.peer.machines.jon = {
|
instances."instance_bar" = {
|
||||||
settings.timeout = lib.mkForce "foo-peer-jon";
|
module = {
|
||||||
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = "bar-peer-jon";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
roles.peer = {
|
instances."instance_zaza" = {
|
||||||
settings.timeout = "foo-peer";
|
module = {
|
||||||
|
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
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ in
|
|||||||
inherit res;
|
inherit res;
|
||||||
expr = {
|
expr = {
|
||||||
hasMachineSettings =
|
hasMachineSettings =
|
||||||
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
|
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -88,10 +92,10 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = jon
|
# machines = jon
|
||||||
specificMachineSettings =
|
specificMachineSettings =
|
||||||
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
|
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
|
||||||
|
|
||||||
hasRoleSettings =
|
hasRoleSettings =
|
||||||
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
|
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
|
||||||
? settings;
|
? settings;
|
||||||
|
|
||||||
# settings are specific.
|
# settings are specific.
|
||||||
@@ -100,20 +104,25 @@ in
|
|||||||
# roles = peer
|
# roles = peer
|
||||||
# machines = *
|
# machines = *
|
||||||
specificRoleSettings =
|
specificRoleSettings =
|
||||||
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
|
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
|
||||||
};
|
};
|
||||||
expected = rec {
|
expected = {
|
||||||
hasMachineSettings = true;
|
hasMachineSettings = true;
|
||||||
hasRoleSettings = false;
|
hasRoleSettings = true;
|
||||||
specificMachineSettings = {
|
specificMachineSettings = {
|
||||||
timeout = "foo-peer-jon";
|
timeout = "foo-peer-jon";
|
||||||
};
|
};
|
||||||
specificRoleSettings = {
|
specificRoleSettings = {
|
||||||
machines = {
|
machines = {
|
||||||
jon = {
|
jon = {
|
||||||
settings = specificMachineSettings;
|
settings = {
|
||||||
|
timeout = "foo-peer-jon";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
settings = {
|
||||||
|
timeout = "foo-peer";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{ callInventoryAdapter, lib, ... }:
|
{ createTestClan, lib, ... }:
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
modules."A" = {
|
modules."A" = {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest = {
|
manifest = {
|
||||||
@@ -21,28 +21,31 @@ let
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
machines = {
|
inventory = {
|
||||||
jon = { };
|
|
||||||
sara = { };
|
machines = {
|
||||||
};
|
jon = { };
|
||||||
instances."instance_foo" = {
|
sara = { };
|
||||||
module = {
|
|
||||||
name = "A";
|
|
||||||
input = "self";
|
|
||||||
};
|
};
|
||||||
# Settings for both jon and sara
|
instances."instance_foo" = {
|
||||||
roles.peer.settings = {
|
module = {
|
||||||
timeout = 40;
|
name = "A";
|
||||||
|
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.servicesEval.config.mappedServices.self-A;
|
config = res.config._services.mappedServices.self-A;
|
||||||
|
|
||||||
#
|
#
|
||||||
applySettings =
|
applySettings =
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{ callInventoryAdapter, lib, ... }:
|
{ createTestClan, lib, ... }:
|
||||||
let
|
let
|
||||||
res = callInventoryAdapter {
|
res = createTestClan {
|
||||||
modules."A" = m: {
|
modules."A" = m: {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
config = {
|
config = {
|
||||||
@@ -14,19 +14,21 @@ let
|
|||||||
default = m;
|
default = m;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
machines = {
|
inventory = {
|
||||||
jon = { };
|
machines = {
|
||||||
};
|
jon = { };
|
||||||
instances."instance_foo" = {
|
};
|
||||||
module = {
|
instances."instance_foo" = {
|
||||||
name = "A";
|
module = {
|
||||||
input = "self";
|
name = "A";
|
||||||
|
input = "self";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = { };
|
||||||
};
|
};
|
||||||
roles.peer.machines.jon = { };
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
specialArgs = lib.attrNames res.servicesEval.config.mappedServices.self-A.test.specialArgs;
|
specialArgs = lib.attrNames res.config._services.mappedServices.self-A.test.specialArgs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
test_simple = {
|
test_simple = {
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{ clan-core }:
|
|
||||||
{
|
|
||||||
_class = "clan";
|
|
||||||
_module.args = {
|
|
||||||
inherit clan-core;
|
|
||||||
inherit (clan-core) clanLib;
|
|
||||||
};
|
|
||||||
imports = [
|
|
||||||
./module.nix
|
|
||||||
./interface.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{ self, lib, ... }:
|
|
||||||
{
|
|
||||||
flake.modules.clan.default = lib.modules.importApply ./default.nix { clan-core = self; };
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
# 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
|
|
||||||
;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [
|
|
||||||
./interface.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{ 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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
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"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -62,6 +62,9 @@
|
|||||||
# Core libraries
|
# Core libraries
|
||||||
(root + "/lib")
|
(root + "/lib")
|
||||||
|
|
||||||
|
# modules directory
|
||||||
|
(root + "/modules")
|
||||||
|
|
||||||
# User-provided fileset
|
# User-provided fileset
|
||||||
fileset
|
fileset
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ in
|
|||||||
description = null;
|
description = null;
|
||||||
icon = null;
|
icon = null;
|
||||||
name = "test";
|
name = "test";
|
||||||
|
tld = "clan";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ in
|
|||||||
self = {
|
self = {
|
||||||
inputs = { };
|
inputs = { };
|
||||||
};
|
};
|
||||||
directory = ../../.;
|
directory = ../.;
|
||||||
meta.name = "test-clan-core";
|
meta.name = "test-clan-core";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -123,7 +124,7 @@ in
|
|||||||
self = {
|
self = {
|
||||||
inputs = { };
|
inputs = { };
|
||||||
};
|
};
|
||||||
directory = ../../.;
|
directory = ../.;
|
||||||
meta.name = "test-clan-core";
|
meta.name = "test-clan-core";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
@@ -211,6 +212,87 @@ 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 {
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
foo-machine
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
foo-shared
|
||||||
25
lib/vars.nix
Normal file
25
lib/vars.nix
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
_: {
|
||||||
|
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";
|
||||||
|
}
|
||||||
3
lib/vars_test.nix
Normal file
3
lib/vars_test.nix
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
16
modules/clan/checks.nix
Normal file
16
modules/clan/checks.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ lib, nixpkgs, ... }:
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
---
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
25
modules/clan/default.nix
Normal file
25
modules/clan/default.nix
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
Root 'clan' Module
|
||||||
|
|
||||||
|
Defines lib.clan and flake-parts.clan options
|
||||||
|
and all common logic for the 'clan' module.
|
||||||
|
|
||||||
|
- has Class _class = "clan"
|
||||||
|
|
||||||
|
- _module.args.clan-core: reference to clan-core flake
|
||||||
|
- _module.args.clanLib: reference to lib.clan function
|
||||||
|
*/
|
||||||
|
{ clan-core }:
|
||||||
|
{
|
||||||
|
_class = "clan";
|
||||||
|
_module.args = {
|
||||||
|
inherit clan-core;
|
||||||
|
inherit (clan-core) clanLib;
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
./top-level-interface.nix
|
||||||
|
./module.nix
|
||||||
|
./distributed-services.nix
|
||||||
|
./checks.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
163
modules/clan/distributed-services.nix
Normal file
163
modules/clan/distributed-services.nix
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
clanLib,
|
||||||
|
config,
|
||||||
|
clan-core,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
# Keep a reference to top-level
|
||||||
|
clanConfig = config;
|
||||||
|
|
||||||
|
inventory = clanConfig.inventory;
|
||||||
|
flakeInputs = clanConfig.self.inputs;
|
||||||
|
clanCoreModules = clan-core.clan.modules;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
_class = "clan";
|
||||||
|
options._services = mkOption {
|
||||||
|
visible = false;
|
||||||
|
description = ''
|
||||||
|
All service instances
|
||||||
|
|
||||||
|
!!! Danger "Internal API"
|
||||||
|
Do not rely on this API yet.
|
||||||
|
|
||||||
|
- Will be renamed to just 'services' in the future.
|
||||||
|
Once the name can be claimed again.
|
||||||
|
- Structure will change.
|
||||||
|
|
||||||
|
API will be declared as public after beeing simplified.
|
||||||
|
'';
|
||||||
|
type = types.submoduleWith {
|
||||||
|
# TODO: Remove specialArgs
|
||||||
|
specialArgs = {
|
||||||
|
inherit clanLib;
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
(import ../../lib/inventory/distributed-service/all-services-wrapper.nix {
|
||||||
|
inherit (clanConfig) directory;
|
||||||
|
})
|
||||||
|
# Dependencies
|
||||||
|
{
|
||||||
|
exportsModule = clanConfig.exportsModule;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# TODO: Rename to "allServices"
|
||||||
|
# All services
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
options._allMachines = mkOption {
|
||||||
|
internal = true;
|
||||||
|
type = types.raw;
|
||||||
|
default = 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 { } ]
|
||||||
|
) [ ] config._services.mappedServices;
|
||||||
|
}) inventory.machines or { };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
clanInternals.inventoryClass.machines = config._allMachines;
|
||||||
|
# clanInternals.inventoryClass.distributedServices = config._services;
|
||||||
|
|
||||||
|
# Exports from distributed services
|
||||||
|
exports = config._services.exports;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
|
clanModule,
|
||||||
|
clanLib,
|
||||||
clan-core,
|
clan-core,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
eval = lib.evalModules {
|
eval = lib.evalModules {
|
||||||
modules = [
|
modules = [
|
||||||
clan-core.modules.clan.default
|
clanModule
|
||||||
];
|
];
|
||||||
|
specialArgs = {
|
||||||
|
self = clan-core;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
evalDocs = pkgs.nixosOptionsDoc {
|
evalDocs = pkgs.nixosOptionsDoc {
|
||||||
options = eval.options;
|
options = eval.options;
|
||||||
warningsAreErrors = false;
|
warningsAreErrors = false;
|
||||||
transformOptions = clan-core.clanLib.docs.stripStorePathsFromDeclarations;
|
transformOptions = clanLib.docs.stripStorePathsFromDeclarations;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
27
modules/clan/flake-module.nix
Normal file
27
modules/clan/flake-module.nix
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{ self, lib, ... }:
|
||||||
|
let
|
||||||
|
clanModule = lib.modules.importApply ./default.nix { clan-core = self; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
flake.modules.clan.default = clanModule;
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
jsonDocs = import ./eval-docs.nix {
|
||||||
|
clan-core = self;
|
||||||
|
inherit
|
||||||
|
pkgs
|
||||||
|
lib
|
||||||
|
clanModule
|
||||||
|
;
|
||||||
|
clanLib = self.clanLib;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
legacyPackages.clan-options = jsonDocs.optionsJSON;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ let
|
|||||||
_: machine:
|
_: machine:
|
||||||
machine.extendModules {
|
machine.extendModules {
|
||||||
modules = [
|
modules = [
|
||||||
(lib.modules.importApply ../machineModules/overridePkgs.nix {
|
(lib.modules.importApply ../../nixosModules/machineModules/overridePkgs.nix {
|
||||||
pkgs = pkgsFor.${system};
|
pkgs = pkgsFor.${system};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@@ -167,6 +167,9 @@ 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.
|
||||||
@@ -176,7 +179,7 @@ in
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(lib.modules.importApply ../machineModules/forName.nix {
|
(lib.modules.importApply ../../nixosModules/machineModules/forName.nix {
|
||||||
inherit (config.inventory) meta;
|
inherit (config.inventory) meta;
|
||||||
inherit
|
inherit
|
||||||
name
|
name
|
||||||
@@ -216,43 +219,44 @@ 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/builder/default.nix
|
../inventoryClass/default.nix
|
||||||
(lib.modules.importApply ../inventoryClass/service-list-from-inputs.nix {
|
|
||||||
inherit flakeInputs clanLib;
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
inherit inventory directory;
|
inherit
|
||||||
|
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,3 +1,28 @@
|
|||||||
|
/**
|
||||||
|
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,
|
||||||
...
|
...
|
||||||
@@ -288,7 +288,7 @@ in
|
|||||||
Global information about the clan.
|
Global information about the clan.
|
||||||
'';
|
'';
|
||||||
type = types.deferredModuleWith {
|
type = types.deferredModuleWith {
|
||||||
staticModules = [ ../inventoryClass/meta-interface.nix ];
|
staticModules = [ ../inventoryClass/meta.nix ];
|
||||||
};
|
};
|
||||||
default = { };
|
default = { };
|
||||||
};
|
};
|
||||||
5
modules/flake-module.nix
Normal file
5
modules/flake-module.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./clan/flake-module.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
156
modules/inventoryClass/default.nix
Normal file
156
modules/inventoryClass/default.nix
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
clanLib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
submodule = m: types.submoduleWith { modules = [ m ]; };
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
exposedInventory = lib.intersectAttrs {
|
||||||
|
meta = null;
|
||||||
|
machines = null;
|
||||||
|
instances = null;
|
||||||
|
tags = null;
|
||||||
|
} config.inventory;
|
||||||
|
|
||||||
|
filterAttrsRecursive' =
|
||||||
|
path: pred: set:
|
||||||
|
lib.listToAttrs (
|
||||||
|
lib.concatMap (
|
||||||
|
name:
|
||||||
|
let
|
||||||
|
v = set.${name};
|
||||||
|
loc = path ++ [ name ];
|
||||||
|
in
|
||||||
|
if pred loc v then
|
||||||
|
[
|
||||||
|
(lib.nameValuePair name (if lib.isAttrs v then filterAttrsRecursive' loc pred v else v))
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
) (lib.attrNames set)
|
||||||
|
);
|
||||||
|
|
||||||
|
filteredInventory = filterAttrsRecursive' [ ] (
|
||||||
|
# Remove extraModules from serialization,
|
||||||
|
# identified by: prefix + pathLength + name
|
||||||
|
# inventory.instances.*.roles.*.extraModules
|
||||||
|
path: _value: !(lib.length path == 5 && ((lib.last path)) == "extraModules")
|
||||||
|
) exposedInventory;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
flakeInputs = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
};
|
||||||
|
exportsModule = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
inventory = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
};
|
||||||
|
inventorySerialization = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
readOnly = true;
|
||||||
|
default = filteredInventory;
|
||||||
|
};
|
||||||
|
directory = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
relativeDirectory = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
internal = true;
|
||||||
|
description = ''
|
||||||
|
The relative directory path from the flake root to the clan directory.
|
||||||
|
Empty string if directory equals the flake root.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
machines = mkOption {
|
||||||
|
type = types.attrsOf (submodule ({
|
||||||
|
options = {
|
||||||
|
machineImports = mkOption {
|
||||||
|
type = types.listOf types.raw;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
staticModules = lib.mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
|
||||||
|
apply = moduleSet: lib.mapAttrs (inspectModule "<clan-core>") moduleSet;
|
||||||
|
};
|
||||||
|
modulesPerSource = lib.mkOption {
|
||||||
|
# { sourceName :: { moduleName :: {} }}
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
inputsWithModules = lib.filterAttrs (_inputName: v: v ? clan.modules) config.flakeInputs;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (
|
||||||
|
inputName: v: lib.mapAttrs (inspectModule inputName) v.clan.modules
|
||||||
|
) inputsWithModules;
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
templatesPerSource = lib.mkOption {
|
||||||
|
# { sourceName :: { moduleName :: {} }}
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.raw;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
inputsWithTemplates = lib.filterAttrs (_inputName: v: v ? clan.templates) config.flakeInputs;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (_inputName: v: lib.mapAttrs (_n: t: t) v.clan.templates) inputsWithTemplates;
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -115,7 +115,7 @@ in
|
|||||||
meta = lib.mkOption {
|
meta = lib.mkOption {
|
||||||
type = lib.types.submoduleWith {
|
type = lib.types.submoduleWith {
|
||||||
modules = [
|
modules = [
|
||||||
./meta-interface.nix
|
./meta.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -167,7 +167,7 @@ in
|
|||||||
'';
|
'';
|
||||||
type = types.submoduleWith {
|
type = types.submoduleWith {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit (config) machines;
|
inherit (config) machines clanLib;
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
{
|
{
|
||||||
@@ -359,7 +359,7 @@ in
|
|||||||
inherit clanLib;
|
inherit clanLib;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
(import ./roles-interface.nix { })
|
(import ./role.nix { })
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -31,6 +31,20 @@ 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,12 +44,6 @@ 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.**
|
||||||
|
|
||||||
@@ -73,15 +67,8 @@ in
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
'';
|
'';
|
||||||
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
|
|
||||||
default = [ ];
|
default = [ ];
|
||||||
type = types.listOf (
|
type = types.listOf types.raw;
|
||||||
types.oneOf [
|
|
||||||
types.str
|
|
||||||
types.path
|
|
||||||
(types.attrsOf types.anything)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,14 @@ in
|
|||||||
# Set by the flake, so it's read-only in the machine
|
# Set by the flake, so it's read-only in the machine
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
};
|
};
|
||||||
|
tld = lib.mkOption {
|
||||||
|
type = types.strMatching "[a-z]+";
|
||||||
|
description = ''
|
||||||
|
the TLD for the clan
|
||||||
|
'';
|
||||||
|
# Set by the flake, so it's read-only in the machine
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
machine = mkOption {
|
machine = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Settings of the machine.
|
Settings of the machine.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ let
|
|||||||
inputs.data-mesher.nixosModules.data-mesher
|
inputs.data-mesher.nixosModules.data-mesher
|
||||||
];
|
];
|
||||||
config = {
|
config = {
|
||||||
clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.hostPlatform.system};
|
clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Note this might jump back and worth as kernel get added or removed.
|
# Note this might jump back and worth as kernel get added or removed.
|
||||||
boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.hostPlatform pkgs.zfs) latestKernelPackage;
|
boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.zfs) latestKernelPackage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
directory,
|
directory,
|
||||||
meta,
|
meta,
|
||||||
}:
|
}:
|
||||||
|
# The following is a nixos/darwin module
|
||||||
{
|
{
|
||||||
_class,
|
_class,
|
||||||
lib,
|
lib,
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
clan.core.settings = {
|
clan.core.settings = {
|
||||||
inherit (meta) name icon;
|
inherit (meta) name icon tld;
|
||||||
inherit directory;
|
inherit directory;
|
||||||
machine = {
|
machine = {
|
||||||
inherit name;
|
inherit name;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user