Compare commits
69 Commits
enable-mor
...
push-wqqzv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01c9432cc5 | ||
|
|
f62e9db126 | ||
|
|
dcb2231332 | ||
|
|
725eeb87ae | ||
|
|
66df677fd2 | ||
|
|
f7d15215ea | ||
|
|
c25574bebd | ||
|
|
fe5796ba17 | ||
|
|
f2e89d27fe | ||
|
|
06dd2ebf8c | ||
|
|
40740860c0 | ||
|
|
89bc39869c | ||
|
|
84d0a2f2f0 | ||
|
|
1d07737989 | ||
|
|
9d386485dd | ||
|
|
ee9ae9c76d | ||
|
|
d4d4d77d2d | ||
|
|
c0ebad1cd9 | ||
|
|
86d0c95da7 | ||
|
|
0fb1b5c5ce | ||
|
|
dc0349e835 | ||
|
|
cc8a74b195 | ||
|
|
046fe0df36 | ||
|
|
3f948fdbd4 | ||
|
|
eb35e6ea21 | ||
|
|
4a0e1b3b6b | ||
|
|
1b8974d167 | ||
|
|
5e2b5fe213 | ||
|
|
74fb3abbc7 | ||
|
|
f2b04e74f1 | ||
|
|
d3ae684575 | ||
|
|
5b294e7651 | ||
|
|
40ae510075 | ||
|
|
48d910f11f | ||
|
|
f242b9a35c | ||
|
|
978822d40a | ||
|
|
fa6c3be21e | ||
|
|
be61bac9af | ||
|
|
42b58910a9 | ||
|
|
a746b10578 | ||
|
|
19341e4cb1 | ||
|
|
f4e06271ba | ||
|
|
d93fe229b3 | ||
|
|
5fc62806b1 | ||
|
|
e0be2f3435 | ||
|
|
a69b81488b | ||
|
|
b133a2407a | ||
|
|
68ae27899a | ||
|
|
b83d3ecba2 | ||
|
|
bec4317709 | ||
|
|
f37f15c482 | ||
|
|
fae8ec318d | ||
|
|
8e2005f38c | ||
|
|
94781bb358 | ||
|
|
de740cf686 | ||
|
|
064edf61ef | ||
|
|
aaf58d7be8 | ||
|
|
03f8e41291 | ||
|
|
43bd4403c6 | ||
|
|
ebee55ffdc | ||
|
|
47e9e5a8f0 | ||
|
|
d1a79653fe | ||
|
|
351ce1414a | ||
|
|
e2ccd979ed | ||
|
|
f5f3f96809 | ||
|
|
59253a9c71 | ||
|
|
aa03adc581 | ||
|
|
ffd84d50f7 | ||
|
|
679387e4ba |
@@ -160,14 +160,10 @@
|
|||||||
"flake.lock"
|
"flake.lock"
|
||||||
"flakeModules"
|
"flakeModules"
|
||||||
"inventory.json"
|
"inventory.json"
|
||||||
"lib/build-clan"
|
|
||||||
"lib/default.nix"
|
|
||||||
"lib/select.nix"
|
|
||||||
"lib/flake-module.nix"
|
|
||||||
"lib/frontmatter"
|
|
||||||
"lib/inventory"
|
|
||||||
"lib/constraints"
|
|
||||||
"nixosModules"
|
"nixosModules"
|
||||||
|
# Just include everything in 'lib'
|
||||||
|
# If anything changes in /lib that may affect everything
|
||||||
|
"lib"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
138
checks/data-mesher/default.nix
Normal file
138
checks/data-mesher/default.nix
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
(import ../lib/test-base.nix) (
|
||||||
|
{ self, lib, ... }:
|
||||||
|
let
|
||||||
|
|
||||||
|
inherit (self.lib.inventory) buildInventory;
|
||||||
|
|
||||||
|
machines = [
|
||||||
|
"signer"
|
||||||
|
"admin"
|
||||||
|
"peer"
|
||||||
|
];
|
||||||
|
|
||||||
|
serviceConfigs = buildInventory {
|
||||||
|
inventory = {
|
||||||
|
machines = lib.genAttrs machines (_: { });
|
||||||
|
services = {
|
||||||
|
data-mesher.default = {
|
||||||
|
roles.peer.machines = [ "peer" ];
|
||||||
|
roles.admin.machines = [ "admin" ];
|
||||||
|
roles.signer.machines = [ "signer" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
modules = {
|
||||||
|
data-mesher = self.clanModules.data-mesher;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
directory = ./.;
|
||||||
|
};
|
||||||
|
|
||||||
|
commonConfig =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
|
||||||
|
imports = [ self.nixosModules.clanCore ];
|
||||||
|
|
||||||
|
clan.core.settings.directory = builtins.toString ./.;
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
config.services.data-mesher.package
|
||||||
|
];
|
||||||
|
|
||||||
|
clan.core.vars.settings.publicStore = "in_repo";
|
||||||
|
clan.core.vars.settings.secretStore = "vm";
|
||||||
|
|
||||||
|
clan.data-mesher.network.interface = "eth1";
|
||||||
|
clan.data-mesher.bootstrapNodes = [
|
||||||
|
"[2001:db8:1::1]:7946" # peer1
|
||||||
|
"[2001:db8:1::2]:7946" # peer2
|
||||||
|
];
|
||||||
|
|
||||||
|
# speed up for testing
|
||||||
|
services.data-mesher.settings = {
|
||||||
|
cluster.join_interval = lib.mkForce "2s";
|
||||||
|
cluster.push_pull_interval = lib.mkForce "5s";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.settings."vmsecrets" = {
|
||||||
|
"/etc/secrets" = {
|
||||||
|
C.argument = "${./vars/secret/${config.clan.core.settings.machine.name}}";
|
||||||
|
z = {
|
||||||
|
mode = "0700";
|
||||||
|
user = "data-mesher";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
adminConfig = {
|
||||||
|
imports = serviceConfigs.machines.admin.machineImports;
|
||||||
|
|
||||||
|
config.clan.data-mesher.network.tld = "foo";
|
||||||
|
config.clan.core.settings.machine.name = "admin";
|
||||||
|
};
|
||||||
|
|
||||||
|
peerConfig = {
|
||||||
|
imports = serviceConfigs.machines.peer.machineImports;
|
||||||
|
config.clan.core.settings.machine.name = "peer";
|
||||||
|
};
|
||||||
|
|
||||||
|
signerConfig = {
|
||||||
|
imports = serviceConfigs.machines.signer.machineImports;
|
||||||
|
clan.core.settings.machine.name = "signer";
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "data-mesher";
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
peer = {
|
||||||
|
imports = [
|
||||||
|
peerConfig
|
||||||
|
commonConfig
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = {
|
||||||
|
imports = [
|
||||||
|
adminConfig
|
||||||
|
commonConfig
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
signer = {
|
||||||
|
imports = [
|
||||||
|
signerConfig
|
||||||
|
commonConfig
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO Add better test script.
|
||||||
|
testScript = ''
|
||||||
|
|
||||||
|
def resolve(node, success = {}, fail = [], timeout = 60):
|
||||||
|
for hostname, ips in success.items():
|
||||||
|
for ip in ips:
|
||||||
|
node.wait_until_succeeds(f"getent ahosts {hostname} | grep {ip}", timeout)
|
||||||
|
|
||||||
|
for hostname in fail:
|
||||||
|
node.wait_until_fails(f"getent ahosts {hostname}")
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
admin.wait_for_unit("data-mesher")
|
||||||
|
signer.wait_for_unit("data-mesher")
|
||||||
|
peer.wait_for_unit("data-mesher")
|
||||||
|
|
||||||
|
# check dns resolution
|
||||||
|
for node in [admin, signer, peer]:
|
||||||
|
resolve(node, {
|
||||||
|
"admin.foo": ["2001:db8:1::1", "192.168.1.1"],
|
||||||
|
"peer.foo": ["2001:db8:1::2", "192.168.1.2"],
|
||||||
|
"signer.foo": ["2001:db8:1::3", "192.168.1.3"]
|
||||||
|
})
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEAV/XZHv1UQEEzfD2YbJP1Q2jd1ZDG+CP5wvGf/1hcR+Q=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEAKSSUXJCftt5Vif6ek57CNKBcDRNfrWrxZUHjAIFW9HY=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEAvLD0mHQA+hf9ItlUHD0ml3i5XEArmmjwCC5rYEOmzWs=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEIFX+AzHy821hHqWLPeK3nzRuHod3FNrnPfaDoFvpz6LX
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEIMwuDntiLoC7cFFyttGDf7cQWlOXOR0q90Jz3lEiuLg+
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEIPmH2+vjYG6UOp+/g0Iqu7yZZKId5jffrfsySE36yO+D
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEINS0tSnjHPG8IfpzQAS3wzoJA+4mYM70DIpltN8O4YD7
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEA3P18+R5Gt+Jn7wYXpWNTXM5pyWn2WiOWekYCzXqWPwg=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
@@ -41,6 +41,7 @@ in
|
|||||||
borgbackup = import ./borgbackup nixosTestArgs;
|
borgbackup = import ./borgbackup nixosTestArgs;
|
||||||
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
||||||
mumble = import ./mumble nixosTestArgs;
|
mumble = import ./mumble nixosTestArgs;
|
||||||
|
data-mesher = import ./data-mesher nixosTestArgs;
|
||||||
syncthing = import ./syncthing nixosTestArgs;
|
syncthing = import ./syncthing nixosTestArgs;
|
||||||
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
||||||
postgresql = import ./postgresql nixosTestArgs;
|
postgresql = import ./postgresql nixosTestArgs;
|
||||||
|
|||||||
@@ -165,7 +165,6 @@
|
|||||||
(modulesPath + "/../tests/common/auto-format-root-device.nix")
|
(modulesPath + "/../tests/common/auto-format-root-device.nix")
|
||||||
];
|
];
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
users.users.root.openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ];
|
|
||||||
system.nixos.variant_id = "installer";
|
system.nixos.variant_id = "installer";
|
||||||
environment.systemPackages = [ pkgs.nixos-facter ];
|
environment.systemPackages = [ pkgs.nixos-facter ];
|
||||||
virtualisation.emptyDiskImages = [ 512 ];
|
virtualisation.emptyDiskImages = [ 512 ];
|
||||||
@@ -184,6 +183,12 @@
|
|||||||
"flakes"
|
"flakes"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
users.users.nonrootuser = {
|
||||||
|
isNormalUser = true;
|
||||||
|
openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ];
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
};
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
system.extraDependencies = dependencies;
|
system.extraDependencies = dependencies;
|
||||||
};
|
};
|
||||||
nodes.client = {
|
nodes.client = {
|
||||||
@@ -211,14 +216,14 @@
|
|||||||
installer.start()
|
installer.start()
|
||||||
|
|
||||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||||
client.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v root@installer hostname")
|
client.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v nonrootuser@installer hostname")
|
||||||
client.succeed("cp -r ${../..} test-flake && chmod -R +w test-flake")
|
client.succeed("cp -r ${../..} test-flake && chmod -R +w test-flake")
|
||||||
client.fail("test -f test-flake/machines/test-install-machine-without-system/hardware-configuration.nix")
|
client.fail("test -f test-flake/machines/test-install-machine-without-system/hardware-configuration.nix")
|
||||||
client.fail("test -f test-flake/machines/test-install-machine-without-system/facter.json")
|
client.fail("test -f test-flake/machines/test-install-machine-without-system/facter.json")
|
||||||
client.succeed("clan machines update-hardware-config --flake test-flake test-install-machine-without-system root@installer >&2")
|
client.succeed("clan machines update-hardware-config --flake test-flake test-install-machine-without-system nonrootuser@installer >&2")
|
||||||
client.succeed("test -f test-flake/machines/test-install-machine-without-system/facter.json")
|
client.succeed("test -f test-flake/machines/test-install-machine-without-system/facter.json")
|
||||||
client.succeed("rm test-flake/machines/test-install-machine-without-system/facter.json")
|
client.succeed("rm test-flake/machines/test-install-machine-without-system/facter.json")
|
||||||
client.succeed("clan machines install --debug --flake test-flake --yes test-install-machine-without-system --target-host root@installer --update-hardware-config nixos-facter >&2")
|
client.succeed("clan machines install --debug --flake test-flake --yes test-install-machine-without-system --target-host nonrootuser@installer --update-hardware-config nixos-facter >&2")
|
||||||
try:
|
try:
|
||||||
installer.shutdown()
|
installer.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
environment.etc."install-successful".text = "ok";
|
environment.etc."install-successful".text = "ok";
|
||||||
|
|
||||||
nixpkgs.hostPlatform = "x86_64-linux";
|
|
||||||
boot.consoleLogLevel = lib.mkForce 100;
|
boot.consoleLogLevel = lib.mkForce 100;
|
||||||
boot.kernelParams = [ "boot.shell_on_fail" ];
|
boot.kernelParams = [ "boot.shell_on_fail" ];
|
||||||
|
|
||||||
@@ -89,9 +88,9 @@
|
|||||||
let
|
let
|
||||||
dependencies = [
|
dependencies = [
|
||||||
self
|
self
|
||||||
self.nixosConfigurations.test-install-machine.config.system.build.toplevel
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.build.toplevel
|
||||||
self.nixosConfigurations.test-install-machine.config.system.build.diskoScript
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.build.diskoScript
|
||||||
self.nixosConfigurations.test-install-machine.config.system.clan.deployment.file
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine.config.system.clan.deployment.file
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.nixos-anywhere
|
pkgs.nixos-anywhere
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
# vm-test-run-test-installation> new_machine: QEMU running (pid 80)
|
# vm-test-run-test-installation> new_machine: QEMU running (pid 80)
|
||||||
# vm-test-run-test-installation> new_machine: Guest root shell did not produce any data yet...
|
# vm-test-run-test-installation> new_machine: Guest root shell did not produce any data yet...
|
||||||
# vm-test-run-test-installation> new_machine: To debug, enter the VM and run 'systemctl status backdoor.service'.
|
# vm-test-run-test-installation> new_machine: To debug, enter the VM and run 'systemctl status backdoor.service'.
|
||||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux && !pkgs.stdenv.isAarch64) {
|
checks = pkgs.lib.mkIf pkgs.stdenv.isLinux {
|
||||||
test-installation = (import ../lib/test-base.nix) {
|
test-installation = (import ../lib/test-base.nix) {
|
||||||
name = "test-installation";
|
name = "test-installation";
|
||||||
nodes.target = {
|
nodes.target = {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux && !pkgs.stdenv.isAarch64) {
|
checks = pkgs.lib.mkIf pkgs.stdenv.isLinux {
|
||||||
test-morph = (import ../lib/test-base.nix) {
|
test-morph = (import ../lib/test-base.nix) {
|
||||||
name = "morph";
|
name = "morph";
|
||||||
|
|
||||||
|
|||||||
10
clanModules/data-mesher/README.md
Normal file
10
clanModules/data-mesher/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
description = "Set up data-mesher"
|
||||||
|
categories = ["System"]
|
||||||
|
features = [ "inventory" ]
|
||||||
|
|
||||||
|
[constraints]
|
||||||
|
roles.admin.min = 1
|
||||||
|
roles.admin.max = 1
|
||||||
|
---
|
||||||
|
|
||||||
19
clanModules/data-mesher/lib.nix
Normal file
19
clanModules/data-mesher/lib.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
lib: {
|
||||||
|
|
||||||
|
machines =
|
||||||
|
config:
|
||||||
|
let
|
||||||
|
instanceNames = builtins.attrNames config.clan.inventory.services.data-mesher;
|
||||||
|
instanceName = builtins.head instanceNames;
|
||||||
|
dataMesherInstances = config.clan.inventory.services.data-mesher.${instanceName};
|
||||||
|
|
||||||
|
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
admins = dataMesherInstances.roles.admin.machines or [ ];
|
||||||
|
signers = dataMesherInstances.roles.signer.machines or [ ];
|
||||||
|
peers = dataMesherInstances.roles.peer.machines or [ ];
|
||||||
|
bootstrap = uniqueStrings (admins ++ signers);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
51
clanModules/data-mesher/roles/admin.nix
Normal file
51
clanModules/data-mesher/roles/admin.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.clan.data-mesher;
|
||||||
|
|
||||||
|
dmLib = import ../lib.nix lib;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../shared.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options.clan.data-mesher = {
|
||||||
|
network = {
|
||||||
|
tld = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = (config.networking.domain or "clan");
|
||||||
|
description = "Top level domain to use for the network";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostTTL = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "672h"; # 28 days
|
||||||
|
example = "24h";
|
||||||
|
description = "The TTL for hosts in the network, in the form of a Go time.Duration";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
services.data-mesher.initNetwork =
|
||||||
|
let
|
||||||
|
# for a given machine, read it's public key and remove any new lines
|
||||||
|
readHostKey =
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
||||||
|
in
|
||||||
|
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
enable = true;
|
||||||
|
keyPath = config.clan.core.vars.generators.data-mesher-network-key.files.private_key.path;
|
||||||
|
|
||||||
|
tld = cfg.network.tld;
|
||||||
|
hostTTL = cfg.network.hostTTL;
|
||||||
|
|
||||||
|
# admin and signer host public keys
|
||||||
|
signingKeys = builtins.map readHostKey (dmLib.machines config).bootstrap;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
5
clanModules/data-mesher/roles/peer.nix
Normal file
5
clanModules/data-mesher/roles/peer.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../shared.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
5
clanModules/data-mesher/roles/signer.nix
Normal file
5
clanModules/data-mesher/roles/signer.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../shared.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
154
clanModules/data-mesher/shared.nix
Normal file
154
clanModules/data-mesher/shared.nix
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.clan.data-mesher;
|
||||||
|
dmLib = import ./lib.nix lib;
|
||||||
|
|
||||||
|
# the default bootstrap nodes are any machines with the admin or signers role
|
||||||
|
# we iterate through those machines, determining an IP address for them based on their VPN
|
||||||
|
# currently only supports zerotier
|
||||||
|
defaultBootstrapNodes = builtins.foldl' (
|
||||||
|
urls: name:
|
||||||
|
if
|
||||||
|
builtins.pathExists "${config.clan.core.settings.directory}/machines/${name}/facts/zerotier-ip"
|
||||||
|
then
|
||||||
|
let
|
||||||
|
ip = builtins.readFile "${config.clan.core.settings.directory}/machines/${name}/facts/zerotier-ip";
|
||||||
|
in
|
||||||
|
urls ++ "${ip}:${cfg.network.port}"
|
||||||
|
else
|
||||||
|
urls
|
||||||
|
) [ ] (dmLib.machines config).bootstrap;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.clan.data-mesher = {
|
||||||
|
|
||||||
|
bootstrapNodes = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
A list of bootstrap nodes that act as an initial gateway when joining
|
||||||
|
the cluster.
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"192.168.1.1:7946"
|
||||||
|
"192.168.1.2:7946"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
network = {
|
||||||
|
|
||||||
|
interface = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
The interface over which cluster communication should be performed.
|
||||||
|
All the ip addresses associate with this interface will be part of
|
||||||
|
our host claim, including both ipv4 and ipv6.
|
||||||
|
|
||||||
|
This should be set to an internal/VPN interface.
|
||||||
|
'';
|
||||||
|
example = "tailscale0";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 7946;
|
||||||
|
description = ''
|
||||||
|
Port to listen on for cluster communication.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
|
||||||
|
services.data-mesher = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
log_level = "warn";
|
||||||
|
state_dir = "/var/lib/data-mesher";
|
||||||
|
|
||||||
|
# read network id from vars
|
||||||
|
network.id = config.clan.core.vars.generators.data-mesher-network-key.files.public_key.value;
|
||||||
|
|
||||||
|
host = {
|
||||||
|
names = [ config.networking.hostName ];
|
||||||
|
key_path = config.clan.core.vars.generators.data-mesher-host-key.files.private_key.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
cluster = {
|
||||||
|
port = cfg.network.port;
|
||||||
|
join_interval = "30s";
|
||||||
|
push_pull_interval = "30s";
|
||||||
|
|
||||||
|
interface = cfg.network.interface;
|
||||||
|
bootstrap_nodes = cfg.bootstrapNodes or defaultBootstrapNodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
http.port = 7331;
|
||||||
|
http.interface = "lo";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Generate host key.
|
||||||
|
clan.core.vars.generators.data-mesher-host-key = {
|
||||||
|
files =
|
||||||
|
let
|
||||||
|
owner = config.users.users.data-mesher.name;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
private_key = {
|
||||||
|
inherit owner;
|
||||||
|
};
|
||||||
|
public_key = {
|
||||||
|
inherit owner;
|
||||||
|
secret = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
runtimeInputs = [
|
||||||
|
config.services.data-mesher.package
|
||||||
|
];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
data-mesher generate keypair \
|
||||||
|
--public-key-path $out/public_key \
|
||||||
|
--private-key-path $out/private_key
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.vars.generators.data-mesher-network-key = {
|
||||||
|
# generated once per clan
|
||||||
|
share = true;
|
||||||
|
|
||||||
|
files =
|
||||||
|
let
|
||||||
|
owner = config.users.users.data-mesher.name;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
private_key = {
|
||||||
|
inherit owner;
|
||||||
|
};
|
||||||
|
public_key = {
|
||||||
|
inherit owner;
|
||||||
|
secret = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
runtimeInputs = [
|
||||||
|
config.services.data-mesher.package
|
||||||
|
];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
data-mesher generate keypair \
|
||||||
|
--public-key-path $out/public_key \
|
||||||
|
--private-key-path $out/private_key
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ in
|
|||||||
borgbackup = ./borgbackup;
|
borgbackup = ./borgbackup;
|
||||||
borgbackup-static = ./borgbackup-static;
|
borgbackup-static = ./borgbackup-static;
|
||||||
deltachat = ./deltachat;
|
deltachat = ./deltachat;
|
||||||
|
data-mesher = ./data-mesher;
|
||||||
disk-id = ./disk-id;
|
disk-id = ./disk-id;
|
||||||
dyndns = ./dyndns;
|
dyndns = ./dyndns;
|
||||||
ergochat = ./ergochat;
|
ergochat = ./ergochat;
|
||||||
|
|||||||
116
decisions/02-clan-api.md
Normal file
116
decisions/02-clan-api.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Clan as library
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
In the long term we envision the clan application will consist of the following user facing tools in the long term.
|
||||||
|
|
||||||
|
- `CLI`
|
||||||
|
- `TUI`
|
||||||
|
- `Desktop Application`
|
||||||
|
- `REST-API`
|
||||||
|
- `Mobile Application`
|
||||||
|
|
||||||
|
We might not be sure whether all of those will exist but the architecture should be generic such that those are possible without major changes of the underlying system.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
This leads to the conclusion that we should do `library` centric development.
|
||||||
|
With the current `clan` python code beeing a library that can be imported to create various tools ontop of it.
|
||||||
|
All **CLI** or **UI** related parts should be moved out of the main library.
|
||||||
|
|
||||||
|
*Note: The next person who wants implement any new frontend should do this first. Currently it looks like the TUI is the next one.*
|
||||||
|
|
||||||
|
Imagine roughly the following architecture:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
%% Define styles
|
||||||
|
classDef frontend fill:#f9f,stroke:#333,stroke-width:2px;
|
||||||
|
classDef backend fill:#bbf,stroke:#333,stroke-width:2px;
|
||||||
|
classDef storage fill:#ff9,stroke:#333,stroke-width:2px;
|
||||||
|
classDef testing fill:#cfc,stroke:#333,stroke-width:2px;
|
||||||
|
|
||||||
|
%% Define nodes
|
||||||
|
user(["User"]) -->|Interacts with| Frontends
|
||||||
|
|
||||||
|
subgraph "Frontends"
|
||||||
|
CLI["CLI"]:::frontend
|
||||||
|
APP["Desktop App"]:::frontend
|
||||||
|
TUI["TUI"]:::frontend
|
||||||
|
REST["REST API"]:::frontend
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Python"
|
||||||
|
API["Library <br>for interacting with clan"]:::backend
|
||||||
|
BusinessLogic["Business Logic<br>Implements actions like 'machine create'"]:::backend
|
||||||
|
STORAGE[("Persistence")]:::storage
|
||||||
|
NIX["Nix Eval & Build"]:::backend
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "CI/CD & Tests"
|
||||||
|
TEST["Feature Testing"]:::testing
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Define connections
|
||||||
|
CLI --> API
|
||||||
|
APP --> API
|
||||||
|
TUI --> API
|
||||||
|
REST --> API
|
||||||
|
|
||||||
|
TEST --> API
|
||||||
|
|
||||||
|
API --> BusinessLogic
|
||||||
|
BusinessLogic --> STORAGE
|
||||||
|
BusinessLogic --> NIX
|
||||||
|
```
|
||||||
|
|
||||||
|
With this very simple design it is ensured that all the basic features remain stable across all frontends.
|
||||||
|
In the end it is straight forward to create python library function calls in a testing framework to ensure that kind of stability.
|
||||||
|
|
||||||
|
Integration tests and smaller unit-tests should both be utilized to ensure the stability of the library.
|
||||||
|
|
||||||
|
Note: Library function don't have to be json-serializable in general.
|
||||||
|
|
||||||
|
Persistence includes but is not limited to: creating git commits, writing to inventory.json, reading and writing vars and to/from disk in general.
|
||||||
|
|
||||||
|
## Benefits / Drawbacks
|
||||||
|
|
||||||
|
- (+) Less tight coupling of frontend- / backend-teams
|
||||||
|
- (+) Consistency and inherent behavior
|
||||||
|
- (+) Performance & Scalability
|
||||||
|
- (+) Different frontends for different user groups
|
||||||
|
- (+) Documentation per library function makes it convenient to interact with the clan resources.
|
||||||
|
- (+) Testing the library ensures stability of the underlyings for all layers above.
|
||||||
|
- (-) Complexity overhead
|
||||||
|
- (-) library needs to be designed / documented
|
||||||
|
- (+) library can be well documented since it is a finite set of functions.
|
||||||
|
- (-) Error handling might be harder.
|
||||||
|
- (+) Common error reporting
|
||||||
|
- (-) different frontends need different features. The library must include them all.
|
||||||
|
- (+) All those core features must be implemented anyways.
|
||||||
|
- (+) VPN Benchmarking uses the existing library's already and works relatively well.
|
||||||
|
|
||||||
|
## Implementation considerations
|
||||||
|
|
||||||
|
Not all required details that need to change over time are possible to be pointed out ahead of time.
|
||||||
|
The goal of this document is to create a common understanding for how we like our project to be structured.
|
||||||
|
Any future commits should contribute to this goal.
|
||||||
|
|
||||||
|
Some ideas what might be needed to change:
|
||||||
|
|
||||||
|
- Having separate locations or packages for the library and the CLI.
|
||||||
|
- Rename the `clan_cli` package to `clan` and move the `cli` frontend into a subfolder or a separate package.
|
||||||
|
- Python Argparse or other cli related code should not exist in the `clan` python library.
|
||||||
|
- `__init__.py` should be very minimal. Only init the business logic models and resources. Note that all `__init__.py` files all the way up in the module tree are always executed as part of the python module import logic and thus should be as small as possible.
|
||||||
|
i.e. `from clan_cli.vars.generators import ...` executes both `clan_cli/__init__.py` and `clan_cli/vars/__init__.py` if any of those exist.
|
||||||
|
- `api` folder doesn't make sense since the python library `clan` is the api.
|
||||||
|
- Logic needed for the webui that performs json serialization and deserialization will be some `json-adapter` folder or package.
|
||||||
|
- Code for serializing dataclasses and typed dictionaries is needed for the persistence layer. (i.e. for read-write of inventory.json)
|
||||||
|
- The inventory-json is a backend resource, that is internal. Its logic includes merging, unmerging and partial updates with considering nix values and their priorities. Nobody should try to read or write to it directly.
|
||||||
|
Instead there will be library methods i.e. to add a `service` or to update/read/delete some information from it.
|
||||||
|
- Library functions should be carefully designed with suitable conventions for writing good api's in mind. (i.e: https://swagger.io/resources/articles/best-practices-in-api-design/)
|
||||||
|
|
||||||
36
devShell.nix
36
devShell.nix
@@ -1,10 +1,12 @@
|
|||||||
{ ... }:
|
{ inputs, ... }:
|
||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
{
|
{
|
||||||
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
self',
|
self',
|
||||||
config,
|
config,
|
||||||
|
system,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -24,18 +26,26 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
packages = [
|
packages =
|
||||||
select-shell
|
[
|
||||||
pkgs.nix-unit
|
select-shell
|
||||||
pkgs.tea
|
pkgs.nix-unit
|
||||||
# Better error messages than nix 2.18
|
pkgs.tea
|
||||||
pkgs.nixVersions.latest
|
# Better error messages than nix 2.18
|
||||||
self'.packages.tea-create-pr
|
pkgs.nixVersions.latest
|
||||||
self'.packages.merge-after-ci
|
self'.packages.tea-create-pr
|
||||||
self'.packages.pending-reviews
|
self'.packages.merge-after-ci
|
||||||
# treefmt with config defined in ./flake-parts/formatting.nix
|
self'.packages.pending-reviews
|
||||||
config.treefmt.build.wrapper
|
# treefmt with config defined in ./flake-parts/formatting.nix
|
||||||
];
|
config.treefmt.build.wrapper
|
||||||
|
]
|
||||||
|
# bring in data-mesher for the cli which can help with things like key generation
|
||||||
|
++ (
|
||||||
|
let
|
||||||
|
data-mesher = inputs.data-mesher.packages.${system}.data-mesher or null;
|
||||||
|
in
|
||||||
|
lib.optional (data-mesher != null) data-mesher
|
||||||
|
);
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}"
|
echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}"
|
||||||
export PRJ_ROOT=$(git rev-parse --show-toplevel)
|
export PRJ_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ nav:
|
|||||||
# This is the module overview and should stay at the top
|
# This is the module overview and should stay at the top
|
||||||
- reference/clanModules/admin.md
|
- reference/clanModules/admin.md
|
||||||
- reference/clanModules/borgbackup-static.md
|
- reference/clanModules/borgbackup-static.md
|
||||||
|
- reference/clanModules/data-mesher.md
|
||||||
- reference/clanModules/borgbackup.md
|
- reference/clanModules/borgbackup.md
|
||||||
- reference/clanModules/deltachat.md
|
- reference/clanModules/deltachat.md
|
||||||
- reference/clanModules/disk-id.md
|
- reference/clanModules/disk-id.md
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
||||||
jsonDocs = pkgs.callPackage ./get-module-docs.nix {
|
jsonDocs = pkgs.callPackage ./get-module-docs.nix {
|
||||||
inherit (self) clanModules;
|
inherit (self) clanModules;
|
||||||
evalClanModules = self.lib.evalClanModules;
|
evalClanModules = self.lib.evalClan.evalClanModules;
|
||||||
modulesRolesOptions = self.lib.evalClanModulesWithRoles self.clanModules;
|
modulesRolesOptions = self.lib.evalClan.evalClanModulesWithRoles self.clanModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Frontmatter for clanModules
|
# Frontmatter for clanModules
|
||||||
|
|||||||
48
flake.lock
generated
48
flake.lock
generated
@@ -1,5 +1,34 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"data-mesher": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": [
|
||||||
|
"flake-parts"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
],
|
||||||
|
"treefmt-nix": [
|
||||||
|
"treefmt-nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1743379277,
|
||||||
|
"narHash": "sha256-4BNv+I6hksqZeRCrEHcQygK0MV1acjA8+L2TtA11H3c=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "bf8c5448d826e047b842d6f2ac0fc698e976dda5",
|
||||||
|
"revCount": 375,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.clan.lol/clan/data-mesher"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.clan.lol/clan/data-mesher"
|
||||||
|
}
|
||||||
|
},
|
||||||
"disko": {
|
"disko": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -58,10 +87,10 @@
|
|||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 315532800,
|
"lastModified": 315532800,
|
||||||
"narHash": "sha256-+bxPXRQiQ0SsjR8syBcc8X+S8WGllNM+Qreu5Td7gnI=",
|
"narHash": "sha256-Ls4VPCGSQrm6k3FCokyonfX/sgIdZc8f5ZzqEdukBFA=",
|
||||||
"rev": "1750f3c1c89488e2ffdd47cab9d05454dddfb734",
|
"rev": "eb0e0f21f15c559d2ac7633dc81d079d1caf5f5f",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre773343.1750f3c1c894/nixexprs.tar.xz"
|
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre776128.eb0e0f21f15c/nixexprs.tar.xz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
@@ -70,6 +99,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"data-mesher": "data-mesher",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"nixos-facter-modules": "nixos-facter-modules",
|
"nixos-facter-modules": "nixos-facter-modules",
|
||||||
@@ -86,11 +116,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742700801,
|
"lastModified": 1743305778,
|
||||||
"narHash": "sha256-ZGlpUDsuBdeZeTNgoMv+aw0ByXT2J3wkYw9kJwkAS4M=",
|
"narHash": "sha256-Ux/UohNtnM5mn9SFjaHp6IZe2aAnUCzklMluNtV6zFo=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "67566fe68a8bed2a7b1175fdfb0697ed22ae8852",
|
"rev": "8e873886bbfc32163fe027b8676c75637b7da114",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -121,11 +151,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1742370146,
|
"lastModified": 1743081648,
|
||||||
"narHash": "sha256-XRE8hL4vKIQyVMDXykFh4ceo3KSpuJF3ts8GKwh5bIU=",
|
"narHash": "sha256-WRAylyYptt6OX5eCEBWyTwOEqEtD6zt33rlUkr6u3cE=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "adc195eef5da3606891cedf80c0d9ce2d3190808",
|
"rev": "29a3d7b768c70addce17af0869f6e2bd8f5be4b7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
10
flake.nix
10
flake.nix
@@ -19,6 +19,16 @@
|
|||||||
|
|
||||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||||
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
|
treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
data-mesher = {
|
||||||
|
url = "git+https://git.clan.lol/clan/data-mesher";
|
||||||
|
inputs = {
|
||||||
|
flake-parts.follows = "flake-parts";
|
||||||
|
nixpkgs.follows = "nixpkgs";
|
||||||
|
systems.follows = "systems";
|
||||||
|
treefmt-nix.follows = "treefmt-nix";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"*.clan-flake"
|
"*.clan-flake"
|
||||||
"*.code-workspace"
|
"*.code-workspace"
|
||||||
"*.pub"
|
"*.pub"
|
||||||
|
"*.priv"
|
||||||
"*.typed"
|
"*.typed"
|
||||||
"*.age"
|
"*.age"
|
||||||
"*.list"
|
"*.list"
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
# prettier messes up our mkdocs flavoured markdown
|
# prettier messes up our mkdocs flavoured markdown
|
||||||
"*.md"
|
"*.md"
|
||||||
|
|
||||||
|
"checks/data-mesher/vars/*"
|
||||||
"checks/lib/ssh/privkey"
|
"checks/lib/ssh/privkey"
|
||||||
"checks/lib/ssh/pubkey"
|
"checks/lib/ssh/pubkey"
|
||||||
"checks/matrix-synapse/synapse-registration_shared_secret"
|
"checks/matrix-synapse/synapse-registration_shared_secret"
|
||||||
|
|||||||
72
lib/README.md
Normal file
72
lib/README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# ClanLib
|
||||||
|
|
||||||
|
This folder is supposed to contain clan specific nix functions.
|
||||||
|
|
||||||
|
Such as:
|
||||||
|
|
||||||
|
- build-clan function
|
||||||
|
- select
|
||||||
|
- build-inventory function
|
||||||
|
- json-schema-converter
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
Similar to `nixpkgs/lib` this produces a recursive attribute set in a fixed-point.
|
||||||
|
Functions within lib can depend on each other to create new abstractions.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
Note: This is not consistently enforced yet.
|
||||||
|
If you start a new feature, or refactoring/touching existing ones, please help us to move towards the below illustrated.
|
||||||
|
|
||||||
|
A single feature-set/module may be organized like this:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# ↓ The final clanLib
|
||||||
|
{lib, clanLib, ...}:
|
||||||
|
# ↓ portion to add to clanLib
|
||||||
|
{
|
||||||
|
inventory.resolveTags = tags: inventory.machines; # implementation
|
||||||
|
inventory.buildMachines = x: clanLib.inventory.resolveTags x; # implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every bigger feature should live in a subfolder with the feature name.
|
||||||
|
It should contain two files:
|
||||||
|
|
||||||
|
- `impl.nix`
|
||||||
|
- `test.nix`
|
||||||
|
- Everything else may be adopted as needed.
|
||||||
|
|
||||||
|
```
|
||||||
|
Example filetree
|
||||||
|
```
|
||||||
|
```sh
|
||||||
|
.
|
||||||
|
├── default.nix
|
||||||
|
├── feature_foo
|
||||||
|
│ ├── impl.nix
|
||||||
|
│ └── test.nix
|
||||||
|
└── feature_bar
|
||||||
|
├── impl.nix
|
||||||
|
├── complex-subfeature
|
||||||
|
│ ├── impl.nix
|
||||||
|
│ └── test.nix
|
||||||
|
├── testless-subfeature # <- We immediately see that this feature is not tested on itself.
|
||||||
|
│ └── impl.nix
|
||||||
|
└── test.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# default.nix
|
||||||
|
{lib, clanLib, ...}:
|
||||||
|
{
|
||||||
|
inventory.resolveTags = import ./resolveTags { inherit lib clanLib; };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
For testing we use [nix-unit](https://github.com/nix-community/nix-unit)
|
||||||
|
|
||||||
|
TODO: define a helper that automatically hooks up `tests` in `flake.legacyPackages` and a corresponding buildable `checks` attribute
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Returns an attrset with inputs that have the attribute `clanModules`
|
|
||||||
inputsWithClanModules = lib.filterAttrs (
|
|
||||||
_name: value: builtins.hasAttr "clanModules" value
|
|
||||||
) self.inputs;
|
|
||||||
|
|
||||||
flattenedClanModules = lib.foldl' (
|
|
||||||
acc: input:
|
|
||||||
lib.mkMerge [
|
|
||||||
acc
|
|
||||||
input.clanModules
|
|
||||||
]
|
|
||||||
) { } (lib.attrValues inputsWithClanModules);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inventory.modules = flattenedClanModules;
|
|
||||||
}
|
|
||||||
@@ -43,10 +43,7 @@ in
|
|||||||
include = [
|
include = [
|
||||||
"flakeModules"
|
"flakeModules"
|
||||||
"inventory.json"
|
"inventory.json"
|
||||||
"lib/build-clan"
|
"lib"
|
||||||
"lib/default.nix"
|
|
||||||
"lib/flake-module.nix"
|
|
||||||
"lib/inventory"
|
|
||||||
"machines"
|
"machines"
|
||||||
"nixosModules"
|
"nixosModules"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -142,11 +142,13 @@ in
|
|||||||
inventoryFile = lib.mkOption { type = lib.types.raw; };
|
inventoryFile = lib.mkOption { type = lib.types.raw; };
|
||||||
# The machine 'imports' generated by the inventory per machine
|
# The machine 'imports' generated by the inventory per machine
|
||||||
inventoryClass = lib.mkOption { type = lib.types.raw; };
|
inventoryClass = lib.mkOption { type = lib.types.raw; };
|
||||||
|
# new attribute
|
||||||
|
distributedServices = lib.mkOption { type = lib.types.raw; };
|
||||||
# clan-core's modules
|
# clan-core's modules
|
||||||
clanModules = lib.mkOption { type = lib.types.raw; };
|
clanModules = lib.mkOption { type = lib.types.raw; };
|
||||||
source = lib.mkOption { type = lib.types.raw; };
|
source = lib.mkOption { type = lib.types.raw; };
|
||||||
meta = lib.mkOption { type = lib.types.raw; };
|
meta = lib.mkOption { type = lib.types.raw; };
|
||||||
lib = lib.mkOption { type = lib.types.raw; };
|
clanLib = lib.mkOption { type = lib.types.raw; };
|
||||||
all-machines-json = lib.mkOption { type = lib.types.raw; };
|
all-machines-json = lib.mkOption { type = lib.types.raw; };
|
||||||
machines = lib.mkOption { type = lib.types.raw; };
|
machines = lib.mkOption { type = lib.types.raw; };
|
||||||
machinesFunc = lib.mkOption { type = lib.types.raw; };
|
machinesFunc = lib.mkOption { type = lib.types.raw; };
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ let
|
|||||||
# Inherit the inventory assertions ?
|
# Inherit the inventory assertions ?
|
||||||
# { inherit (mergedInventory) assertions; }
|
# { inherit (mergedInventory) assertions; }
|
||||||
{ imports = inventoryClass.machines.${name}.machineImports or [ ]; }
|
{ imports = inventoryClass.machines.${name}.machineImports or [ ]; }
|
||||||
|
|
||||||
|
# Import the distribute services
|
||||||
|
{ imports = config.clanInternals.distributedServices.allMachines.${name} or [ ]; }
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
# Settings
|
# Settings
|
||||||
@@ -165,7 +168,6 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./auto-imports.nix
|
|
||||||
# Merge the inventory file
|
# Merge the inventory file
|
||||||
{
|
{
|
||||||
inventory = _: {
|
inventory = _: {
|
||||||
@@ -199,7 +201,11 @@ in
|
|||||||
clanInternals = {
|
clanInternals = {
|
||||||
moduleSchemas = clan-core.lib.modules.getModulesSchema config.inventory.modules;
|
moduleSchemas = clan-core.lib.modules.getModulesSchema config.inventory.modules;
|
||||||
inherit inventoryClass;
|
inherit inventoryClass;
|
||||||
inherit (clan-core) clanModules;
|
distributedServices = import ../distributed-service/inventory-adapter.nix {
|
||||||
|
inherit lib inventory;
|
||||||
|
flake = config.self;
|
||||||
|
};
|
||||||
|
inherit (clan-core) clanModules clanLib;
|
||||||
inherit inventoryFile;
|
inherit inventoryFile;
|
||||||
inventoryValuesPrios =
|
inventoryValuesPrios =
|
||||||
# Temporary workaround
|
# Temporary workaround
|
||||||
@@ -211,9 +217,6 @@ in
|
|||||||
templates = config.templates;
|
templates = config.templates;
|
||||||
inventory = config.inventory;
|
inventory = config.inventory;
|
||||||
meta = config.inventory.meta;
|
meta = config.inventory.meta;
|
||||||
lib = {
|
|
||||||
inherit (clan-core.lib) select;
|
|
||||||
};
|
|
||||||
|
|
||||||
source = "${clan-core}";
|
source = "${clan-core}";
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
clan-core,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
# Produces the
|
||||||
|
# 'clanLib' attribute set
|
||||||
|
# Wrapped with fix, so we can depend on other clanLib functions without passing the whole flake
|
||||||
|
lib.fix (clanLib: {
|
||||||
|
# TODO:
|
||||||
|
# SSome bad lib functions that depend on something in 'self'.
|
||||||
|
# We should reduce the dependency on 'self' aka the 'flake' object
|
||||||
|
# This makes it easier to test
|
||||||
|
# most of the time passing the whole flake is unnecessary
|
||||||
|
callLib = file: args: import file { inherit lib clanLib; } // args;
|
||||||
|
|
||||||
evalClan = import ./eval-clan-modules {
|
evalClan = import ./eval-clan-modules {
|
||||||
inherit clan-core lib;
|
inherit lib;
|
||||||
|
clan-core = self;
|
||||||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||||
};
|
};
|
||||||
in
|
buildClan = import ./build-clan {
|
||||||
{
|
inherit lib nixpkgs;
|
||||||
inherit (evalClan) evalClanModules evalClanModulesWithRoles;
|
clan-core = self;
|
||||||
buildClan = import ./build-clan { inherit lib nixpkgs clan-core; };
|
};
|
||||||
|
# ------------------------------------
|
||||||
|
# Lib functions that don't depend on 'self'
|
||||||
|
inventory = clanLib.callLib ./inventory { };
|
||||||
|
modules = clanLib.callLib ./frontmatter { };
|
||||||
facts = import ./facts.nix { inherit lib; };
|
facts = import ./facts.nix { inherit lib; };
|
||||||
inventory = import ./inventory { inherit lib clan-core; };
|
|
||||||
values = import ./values { inherit lib; };
|
values = import ./values { inherit lib; };
|
||||||
jsonschema = import ./jsonschema { inherit lib; };
|
jsonschema = import ./jsonschema { inherit lib; };
|
||||||
modules = import ./frontmatter {
|
|
||||||
inherit lib;
|
|
||||||
self = clan-core;
|
|
||||||
};
|
|
||||||
select = import ./select.nix;
|
select = import ./select.nix;
|
||||||
}
|
})
|
||||||
|
|||||||
33
lib/distributed-service/flake-module.nix
Normal file
33
lib/distributed-service/flake-module.nix
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{ self, inputs, ... }:
|
||||||
|
let
|
||||||
|
inputOverrides = builtins.concatStringsSep " " (
|
||||||
|
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.<attrName>
|
||||||
|
legacyPackages.evalTest-distributedServices = import ./tests {
|
||||||
|
inherit lib self;
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
lib-distributedServices-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||||
|
export HOME="$(realpath .)"
|
||||||
|
nix-unit --eval-store "$HOME" \
|
||||||
|
--extra-experimental-features flakes \
|
||||||
|
${inputOverrides} \
|
||||||
|
--flake ${self}#legacyPackages.${system}.evalTest-distributedServices
|
||||||
|
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
199
lib/distributed-service/inventory-adapter.nix
Normal file
199
lib/distributed-service/inventory-adapter.nix
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# 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,
|
||||||
|
# This is used to resolve the module imports from 'flake.inputs'
|
||||||
|
flake,
|
||||||
|
# The clan inventory
|
||||||
|
inventory,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# Returns the list of machine names
|
||||||
|
# { ... } -> [ string ]
|
||||||
|
resolveTags =
|
||||||
|
{
|
||||||
|
# Available InventoryMachines :: { {name} :: { tags = [ string ]; }; }
|
||||||
|
machines,
|
||||||
|
# Requested members :: { machines, tags }
|
||||||
|
# Those will be resolved against the available machines
|
||||||
|
members,
|
||||||
|
# Not needed for resolution - only for error reporting
|
||||||
|
roleName,
|
||||||
|
instanceName,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
machines =
|
||||||
|
members.machines or [ ]
|
||||||
|
++ (builtins.foldl' (
|
||||||
|
acc: tag:
|
||||||
|
let
|
||||||
|
# For error printing
|
||||||
|
availableTags = lib.foldlAttrs (
|
||||||
|
acc: _: v:
|
||||||
|
v.tags or [ ] ++ acc
|
||||||
|
) [ ] (machines);
|
||||||
|
|
||||||
|
tagMembers = builtins.attrNames (lib.filterAttrs (_n: v: builtins.elem tag v.tags or [ ]) machines);
|
||||||
|
in
|
||||||
|
if tagMembers == [ ] then
|
||||||
|
lib.warn ''
|
||||||
|
Service instance '${instanceName}': - ${roleName} tags: no machine with tag '${tag}' found.
|
||||||
|
Available tags: ${builtins.toJSON (lib.unique availableTags)}
|
||||||
|
'' acc
|
||||||
|
else
|
||||||
|
acc ++ tagMembers
|
||||||
|
) [ ] members.tags or [ ]);
|
||||||
|
};
|
||||||
|
|
||||||
|
machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags;
|
||||||
|
|
||||||
|
# map the instances into the module
|
||||||
|
importedModuleWithInstances = lib.mapAttrs (
|
||||||
|
instanceName: instance:
|
||||||
|
let
|
||||||
|
# TODO:
|
||||||
|
resolvedModuleSet =
|
||||||
|
# If the module.name is self then take the modules defined in the flake
|
||||||
|
# Otherwise its an external input which provides the modules via 'clan.modules' attribute
|
||||||
|
if instance.module.input == null then
|
||||||
|
inventory.modules
|
||||||
|
else
|
||||||
|
let
|
||||||
|
input =
|
||||||
|
flake.inputs.${instance.module.input} or (throw ''
|
||||||
|
Flake doesn't provide input with name '${instance.module.input}'
|
||||||
|
|
||||||
|
Choose one of the following inputs:
|
||||||
|
- ${
|
||||||
|
builtins.concatStringsSep "\n- " (
|
||||||
|
lib.attrNames (lib.filterAttrs (_name: input: input ? clan) flake.inputs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
To import a local module from 'inventory.modules' remove the 'input' attribute from the module definition
|
||||||
|
Remove the following line from the module definition:
|
||||||
|
|
||||||
|
...
|
||||||
|
- module.input = "${instance.module.input}"
|
||||||
|
|
||||||
|
|
||||||
|
'');
|
||||||
|
clanAttrs =
|
||||||
|
input.clan
|
||||||
|
or (throw "It seems the flake input ${instance.module.input} doesn't export any clan resources");
|
||||||
|
in
|
||||||
|
clanAttrs.modules;
|
||||||
|
|
||||||
|
resolvedModule =
|
||||||
|
resolvedModuleSet.${instance.module.name}
|
||||||
|
or (throw "flake doesn't provide clan-module with name ${instance.module.name}");
|
||||||
|
|
||||||
|
# Every instance includes machines via roles
|
||||||
|
# :: { client :: ... }
|
||||||
|
instanceRoles = lib.mapAttrs (
|
||||||
|
roleName: role:
|
||||||
|
let
|
||||||
|
resolvedMachines = 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> =
|
||||||
|
{
|
||||||
|
machines = lib.genAttrs resolvedMachines.machines (
|
||||||
|
machineName:
|
||||||
|
let
|
||||||
|
machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { };
|
||||||
|
settingsViaTags = lib.filterAttrs (
|
||||||
|
tagName: _: machineHasTag machineName tagName
|
||||||
|
) instance.roles.${roleName}.tags;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
# Maps to settings for the role.
|
||||||
|
# In other words this sets the following path of a clan.service module:
|
||||||
|
# instances.<instanceName>.roles.<roleName>.settings
|
||||||
|
settings = role.settings;
|
||||||
|
}
|
||||||
|
) instance.roles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit (instance) module;
|
||||||
|
inherit resolvedModule instanceRoles;
|
||||||
|
}
|
||||||
|
) inventory.instances;
|
||||||
|
|
||||||
|
# TODO: Eagerly check the _class of the resolved module
|
||||||
|
evals = lib.mapAttrs (
|
||||||
|
_module_ident: instances:
|
||||||
|
(lib.evalModules {
|
||||||
|
class = "clan.service";
|
||||||
|
modules =
|
||||||
|
[
|
||||||
|
./service-module.nix
|
||||||
|
# Import the resolved 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;
|
||||||
|
|
||||||
|
# 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 "self" else instance.module.input;
|
||||||
|
id = inputName + "-" + instance.module.name;
|
||||||
|
in
|
||||||
|
acc
|
||||||
|
// {
|
||||||
|
${id} = acc.${id} or [ ] ++ [
|
||||||
|
{
|
||||||
|
inherit instanceName instance;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
) { } importedModuleWithInstances;
|
||||||
|
|
||||||
|
# TODO: Return an attribute set of resources instead of a plain list of nixosModules
|
||||||
|
allMachines = lib.foldlAttrs (
|
||||||
|
acc: _name: eval:
|
||||||
|
acc
|
||||||
|
// lib.mapAttrs (
|
||||||
|
machineName: result: acc.${machineName} or [ ] ++ [ result.nixosModule ]
|
||||||
|
) eval.config.result.final
|
||||||
|
) { } evals;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit importedModuleWithInstances grouped;
|
||||||
|
inherit evals allMachines;
|
||||||
|
}
|
||||||
514
lib/distributed-service/service-module.nix
Normal file
514
lib/distributed-service/service-module.nix
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
inherit (types) attrsWith submoduleWith;
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# Remove once this gets merged upstream; performs in O(n*log(n) instead of O(n^2))
|
||||||
|
# https://github.com/NixOS/nixpkgs/pull/355616/files
|
||||||
|
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||||
|
|
||||||
|
checkInstanceRoles =
|
||||||
|
instanceName: instanceRoles:
|
||||||
|
let
|
||||||
|
unmatchedRoles = lib.filter (roleName: !lib.elem roleName (lib.attrNames config.roles)) (
|
||||||
|
lib.attrNames instanceRoles
|
||||||
|
);
|
||||||
|
in
|
||||||
|
if unmatchedRoles == [ ] then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
throw ''
|
||||||
|
inventory instance: 'instances.${instanceName}' defines the following roles:
|
||||||
|
${builtins.toJSON unmatchedRoles}
|
||||||
|
|
||||||
|
But the clan-service module '${config.manifest.name}' defines roles:
|
||||||
|
${builtins.toJSON (lib.attrNames config.roles)}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# checkInstanceSettings =
|
||||||
|
# instanceName: instanceSettings:
|
||||||
|
# let
|
||||||
|
# unmatchedRoles = 1;
|
||||||
|
# in
|
||||||
|
# unmatchedRoles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Merges the role- and machine-settings using the role interface
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- roleName: The name of the role
|
||||||
|
- instanceName: The name of the instance
|
||||||
|
- settings: The settings of the machine. Leave empty to get the role settings
|
||||||
|
|
||||||
|
Returns: evalModules result
|
||||||
|
|
||||||
|
The caller is responsible to use .config or .extendModules
|
||||||
|
*/
|
||||||
|
# TODO: evaluate against the role.settings statically and use extendModules to get the machineSettings
|
||||||
|
# Doing this might improve performance
|
||||||
|
evalMachineSettings =
|
||||||
|
{
|
||||||
|
roleName,
|
||||||
|
instanceName,
|
||||||
|
machineName ? null,
|
||||||
|
settings,
|
||||||
|
}:
|
||||||
|
lib.evalModules {
|
||||||
|
# Prefix for better error reporting
|
||||||
|
# This prints the path where the option should be defined rather than the plain path within settings
|
||||||
|
# "The option `instances.foo.roles.server.machines.test.settings.<>' was accessed but has no value defined. Try setting the option."
|
||||||
|
prefix =
|
||||||
|
[
|
||||||
|
"instances"
|
||||||
|
instanceName
|
||||||
|
"roles"
|
||||||
|
roleName
|
||||||
|
]
|
||||||
|
++ (lib.optionals (machineName != null) [
|
||||||
|
"machines"
|
||||||
|
machineName
|
||||||
|
])
|
||||||
|
++ [ "settings" ];
|
||||||
|
|
||||||
|
# This may lead to better error reporting
|
||||||
|
# And catch errors if anyone tried to import i.e. a nixosConfiguration
|
||||||
|
# Set some class: i.e "network.server.settings"
|
||||||
|
class = lib.concatStringsSep "." [
|
||||||
|
config.manifest.name
|
||||||
|
roleName
|
||||||
|
"settings"
|
||||||
|
];
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
(lib.setDefaultModuleLocation "Via clan.service module: roles.${roleName}.interface"
|
||||||
|
config.roles.${roleName}.interface
|
||||||
|
)
|
||||||
|
(lib.setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.settings"
|
||||||
|
config.instances.${instanceName}.roles.${roleName}.settings
|
||||||
|
)
|
||||||
|
settings
|
||||||
|
# Dont set the module location here
|
||||||
|
# This should already be set by the tags resolver
|
||||||
|
# config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.settings
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Makes a module extensible
|
||||||
|
returning its config
|
||||||
|
and making it extensible via '__functor' polymorphism
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
res = makeExtensibleConfig (evalModules { options.foo = mkOption { default = 42; };)
|
||||||
|
res
|
||||||
|
=>
|
||||||
|
{
|
||||||
|
foo = 42;
|
||||||
|
_functor = <function>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# This allows to override using mkDefault, mkForce, etc.
|
||||||
|
res { foo = 100; }
|
||||||
|
=>
|
||||||
|
{
|
||||||
|
foo = 100;
|
||||||
|
_functor = <function>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
makeExtensibleConfig =
|
||||||
|
f: args:
|
||||||
|
let
|
||||||
|
makeModuleExtensible =
|
||||||
|
eval:
|
||||||
|
eval.config
|
||||||
|
// {
|
||||||
|
__functor = _self: m: makeModuleExtensible (eval.extendModules { modules = lib.toList m; });
|
||||||
|
};
|
||||||
|
in
|
||||||
|
makeModuleExtensible (f args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Apply the settings to the instance
|
||||||
|
|
||||||
|
Takes a [ServiceInstance] :: { roles :: { roleName :: { machines :: { machineName :: { settings :: { ... } } } } } }
|
||||||
|
Returns the same object but evaluates the settings against the interface.
|
||||||
|
|
||||||
|
We need this because 'perMachine' shouldn't gain access the raw deferred module.
|
||||||
|
*/
|
||||||
|
applySettings =
|
||||||
|
instanceName: instance:
|
||||||
|
lib.mapAttrs (roleName: role: {
|
||||||
|
machines = lib.mapAttrs (machineName: v: {
|
||||||
|
# TODO: evaluate the settings against the interface
|
||||||
|
# settings = (evalMachineSettings { inherit roleName instanceName; inherit (v) settings; }).config;
|
||||||
|
settings = (
|
||||||
|
makeExtensibleConfig evalMachineSettings {
|
||||||
|
inherit roleName instanceName machineName;
|
||||||
|
inherit (v) settings;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}) role.machines;
|
||||||
|
# TODO: evaluate the settings against the interface
|
||||||
|
settings = (
|
||||||
|
makeExtensibleConfig evalMachineSettings {
|
||||||
|
inherit roleName instanceName;
|
||||||
|
inherit (role) settings;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}) instance.roles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
instances = mkOption {
|
||||||
|
default = throw ''
|
||||||
|
The clan service module ${config.manifest.name} doesn't define any instances.
|
||||||
|
|
||||||
|
Did you forget to create instances via 'inventory.instances' ?
|
||||||
|
'';
|
||||||
|
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "instanceName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [
|
||||||
|
(
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
# options.settings = mkOption {
|
||||||
|
# description = "settings of 'instance': ${name}";
|
||||||
|
# default = {};
|
||||||
|
# apply = v: lib.seq (checkInstanceSettings name v) v;
|
||||||
|
# };
|
||||||
|
options.roles = mkOption {
|
||||||
|
default = throw ''
|
||||||
|
Instance '${name}' of service '${config.manifest.name}' mut define members via 'roles'.
|
||||||
|
|
||||||
|
To include a machine:
|
||||||
|
'instances.${name}.roles.<role-name>.machines.<your-machine-name>' must be set.
|
||||||
|
'';
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "roleName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# instances.{instanceName}.roles.{roleName}.machines
|
||||||
|
options.machines = mkOption {
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "machineName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [
|
||||||
|
(m: {
|
||||||
|
options.settings = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
description = "Settings of '${name}-machine': ${m.name}.";
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# instances.{instanceName}.roles.{roleName}.settings
|
||||||
|
# options._settings = mkOption { };
|
||||||
|
# options._settingsViaTags = mkOption { };
|
||||||
|
# A deferred module that combines _settingsViaTags with _settings
|
||||||
|
options.settings = mkOption {
|
||||||
|
type = types.raw;
|
||||||
|
description = "Settings of 'role': ${name}";
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
apply = v: lib.seq (checkInstanceRoles name v) v;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
manifest = mkOption {
|
||||||
|
description = "Meta information about this module itself";
|
||||||
|
type = submoduleWith {
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = ''
|
||||||
|
The name of the module
|
||||||
|
|
||||||
|
Mainly used to create an error context while evaluating.
|
||||||
|
This helps backtracking which module was included; And where an error came from originally.
|
||||||
|
'';
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
roles = mkOption {
|
||||||
|
default = throw ''
|
||||||
|
Role behavior of service '${config.manifest.name}' must be defined.
|
||||||
|
A 'clan.service' module should always define its behavior via 'roles'
|
||||||
|
---
|
||||||
|
To add the role:
|
||||||
|
`roles.client = {}`
|
||||||
|
|
||||||
|
To define multiple instance behavior:
|
||||||
|
`roles.client.perInstance = { ... }: {}`
|
||||||
|
'';
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "roleName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [
|
||||||
|
(
|
||||||
|
{ name, ... }:
|
||||||
|
let
|
||||||
|
roleName = name;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.interface = mkOption {
|
||||||
|
type = types.deferredModule;
|
||||||
|
# TODO: Default to an empty module
|
||||||
|
# need to test that an the empty module can be evaluated to empty settings
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
options.perInstance = mkOption {
|
||||||
|
type = types.deferredModuleWith {
|
||||||
|
staticModules = [
|
||||||
|
# Common output format
|
||||||
|
# As described by adr
|
||||||
|
# { nixosModule, services, ... }
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
options.nixosModule = mkOption { default = { }; };
|
||||||
|
options.services = mkOption {
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "serviceName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [ ./service-module.nix ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
apply =
|
||||||
|
/**
|
||||||
|
This apply transforms the module into a function that takes arguments and returns an evaluated module
|
||||||
|
The arguments of the function are determined by its scope:
|
||||||
|
-> 'perInstance' maps over all instances and over all machines hence it takes 'instanceName' and 'machineName' as iterator arguments
|
||||||
|
*/
|
||||||
|
v: instanceName: machineName:
|
||||||
|
(lib.evalModules {
|
||||||
|
specialArgs = {
|
||||||
|
inherit instanceName;
|
||||||
|
machine = {
|
||||||
|
name = machineName;
|
||||||
|
roles = applySettings instanceName config.instances.${instanceName};
|
||||||
|
};
|
||||||
|
settings = (
|
||||||
|
makeExtensibleConfig evalMachineSettings {
|
||||||
|
inherit roleName instanceName machineName;
|
||||||
|
settings =
|
||||||
|
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.settings or { };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
modules = [ v ];
|
||||||
|
}).config;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
perMachine = mkOption {
|
||||||
|
type = types.deferredModuleWith {
|
||||||
|
staticModules = [
|
||||||
|
# Common output format
|
||||||
|
# As described by adr
|
||||||
|
# { nixosModule, services, ... }
|
||||||
|
(
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
options.nixosModule = mkOption { default = { }; };
|
||||||
|
options.services = mkOption {
|
||||||
|
type = attrsWith {
|
||||||
|
placeholder = "serviceName";
|
||||||
|
elemType = submoduleWith {
|
||||||
|
modules = [ ./service-module.nix ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
apply =
|
||||||
|
v: machineName: machineScope:
|
||||||
|
(lib.evalModules {
|
||||||
|
specialArgs = {
|
||||||
|
/**
|
||||||
|
This apply transforms the module into a function that takes arguments and returns an evaluated module
|
||||||
|
The arguments of the function are determined by its scope:
|
||||||
|
-> 'perMachine' maps over all machines of a service 'machineName' and a helper 'scope' (some aggregated attributes) as iterator arguments
|
||||||
|
The 'scope' attribute is used to collect the 'roles' of all 'instances' where the machine is part of and inject both into the specialArgs
|
||||||
|
*/
|
||||||
|
machine = {
|
||||||
|
name = machineName;
|
||||||
|
roles =
|
||||||
|
let
|
||||||
|
collectRoles =
|
||||||
|
instances:
|
||||||
|
lib.foldlAttrs (
|
||||||
|
r: _instanceName: instance:
|
||||||
|
r
|
||||||
|
++ lib.foldlAttrs (
|
||||||
|
r2: roleName: _role:
|
||||||
|
r2 ++ [ roleName ]
|
||||||
|
) [ ] instance.roles
|
||||||
|
) [ ] instances;
|
||||||
|
in
|
||||||
|
uniqueStrings (collectRoles machineScope.instances);
|
||||||
|
};
|
||||||
|
inherit (machineScope) instances;
|
||||||
|
|
||||||
|
# There are no machine settings.
|
||||||
|
# Settings are always role specific, having settings that apply to a machine globally would mean to merge all role and all instance settings into a single module.
|
||||||
|
# But that will likely cause conflicts because it is inherently wrong.
|
||||||
|
settings = throw ''
|
||||||
|
'perMachine' doesn't have a 'settings' argument.
|
||||||
|
|
||||||
|
Alternatives:
|
||||||
|
- 'instances.<instanceName>.roles.<roleName>.settings' should be used instead.
|
||||||
|
- 'instances.<instanceName>.roles.<roleName>.machines.<machineName>.settings' should be used instead.
|
||||||
|
|
||||||
|
If that is insufficient, you might also consider using 'roles.<roleName>.perInstance' instead of 'perMachine'.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [ v ];
|
||||||
|
}).config;
|
||||||
|
};
|
||||||
|
# ---
|
||||||
|
# Place the result in _module.result to mark them as "internal" and discourage usage/overrides
|
||||||
|
#
|
||||||
|
# ---
|
||||||
|
# Intermediate result by mapping over the 'roles', 'instances', and 'machines'.
|
||||||
|
# During this step the 'perMachine' and 'perInstance' are applied.
|
||||||
|
# The result-set for a single machine can then be found by collecting all 'nixosModules' recursively.
|
||||||
|
result.allRoles = mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
default = lib.mapAttrs (roleName: roleCfg: {
|
||||||
|
allInstances = lib.mapAttrs (instanceName: instanceCfg: {
|
||||||
|
allMachines = lib.mapAttrs (
|
||||||
|
machineName: _machineCfg: roleCfg.perInstance instanceName machineName
|
||||||
|
) instanceCfg.roles.${roleName}.machines or { };
|
||||||
|
}) config.instances;
|
||||||
|
}) config.roles;
|
||||||
|
};
|
||||||
|
|
||||||
|
result.allMachines = mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
collectMachinesFromInstance =
|
||||||
|
instance:
|
||||||
|
uniqueStrings (
|
||||||
|
lib.foldlAttrs (
|
||||||
|
acc: _roleName: role:
|
||||||
|
acc ++ (lib.attrNames role.machines)
|
||||||
|
) [ ] instance.roles
|
||||||
|
);
|
||||||
|
# The service machines are defined by collecting all instance machines
|
||||||
|
serviceMachines = lib.foldlAttrs (
|
||||||
|
acc: instanceName: instance:
|
||||||
|
acc
|
||||||
|
// lib.genAttrs (collectMachinesFromInstance instance) (machineName:
|
||||||
|
# Store information why this machine is part of the service
|
||||||
|
# MachineOrigin :: { instances :: [ string ]; }
|
||||||
|
{
|
||||||
|
# Helper attribute to
|
||||||
|
instances = [ instanceName ] ++ acc.${machineName}.instances or [ ];
|
||||||
|
# All roles of the machine ?
|
||||||
|
roles = lib.foldlAttrs (
|
||||||
|
acc2: roleName: role:
|
||||||
|
if builtins.elem machineName (lib.attrNames role.machines) then acc2 ++ [ roleName ] else acc2
|
||||||
|
) [ ] instance.roles;
|
||||||
|
})
|
||||||
|
) { } config.instances;
|
||||||
|
|
||||||
|
allMachines = lib.mapAttrs (_machineName: MachineOrigin: {
|
||||||
|
# Filter out instances of which the machine is not part of
|
||||||
|
instances = lib.mapAttrs (_n: v: { roles = v; }) (
|
||||||
|
lib.filterAttrs (instanceName: _: builtins.elem instanceName MachineOrigin.instances) (
|
||||||
|
# Instances with evaluated settings
|
||||||
|
lib.mapAttrs applySettings config.instances
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}) serviceMachines;
|
||||||
|
in
|
||||||
|
# allMachines;
|
||||||
|
lib.mapAttrs config.perMachine allMachines;
|
||||||
|
};
|
||||||
|
|
||||||
|
result.final = mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
default = lib.mapAttrs (
|
||||||
|
machineName: machineResult:
|
||||||
|
let
|
||||||
|
# config.result.allRoles.client.allInstances.bar.allMachines.test
|
||||||
|
# instanceResults = config.result.allRoles.client.allInstances.bar.allMachines.${machineName};
|
||||||
|
instanceResults = lib.foldlAttrs (
|
||||||
|
acc: roleName: role:
|
||||||
|
acc
|
||||||
|
++ lib.foldlAttrs (
|
||||||
|
acc: instanceName: instance:
|
||||||
|
if instance.allMachines.${machineName}.nixosModule or { } != { } then
|
||||||
|
acc
|
||||||
|
++ [
|
||||||
|
(lib.setDefaultModuleLocation
|
||||||
|
"Via instances.${instanceName}.roles.${roleName}.machines.${machineName}"
|
||||||
|
instance.allMachines.${machineName}.nixosModule
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else
|
||||||
|
acc
|
||||||
|
) [ ] role.allInstances
|
||||||
|
) [ ] config.result.allRoles;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit instanceResults;
|
||||||
|
nixosModule = {
|
||||||
|
imports = [
|
||||||
|
# For error backtracing. This module was produced by the 'perMachine' function
|
||||||
|
# TODO: check if we need this or if it leads to better errors if we pass the underlying module locations
|
||||||
|
(lib.setDefaultModuleLocation "clan.service: ${config.manifest.name} - via perMachine" machineResult.nixosModule)
|
||||||
|
] ++ instanceResults;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) config.result.allMachines;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
327
lib/distributed-service/tests/default.nix
Normal file
327
lib/distributed-service/tests/default.nix
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
evalModules
|
||||||
|
;
|
||||||
|
|
||||||
|
evalInventory =
|
||||||
|
m:
|
||||||
|
(evalModules {
|
||||||
|
# Static modules
|
||||||
|
modules = [
|
||||||
|
../../inventory/build-inventory/interface.nix
|
||||||
|
{
|
||||||
|
modules.test = { };
|
||||||
|
}
|
||||||
|
m
|
||||||
|
];
|
||||||
|
}).config;
|
||||||
|
|
||||||
|
flakeFixture = {
|
||||||
|
inputs = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
callInventoryAdapter =
|
||||||
|
inventoryModule:
|
||||||
|
import ../inventory-adapter.nix {
|
||||||
|
inherit lib;
|
||||||
|
flake = flakeFixture;
|
||||||
|
inventory = evalInventory inventoryModule;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
test_simple =
|
||||||
|
let
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
# Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."simple-module" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "netwitness";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# User config
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "simple-module";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Test that the module is mapped into the output
|
||||||
|
# We might change the attribute name in the future
|
||||||
|
expr = res.evals ? "self-simple-module";
|
||||||
|
expected = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# A module can be imported multiple times
|
||||||
|
# A module can also have multiple instances within the same module
|
||||||
|
# This mean modules must be grouped together, imported once
|
||||||
|
# All instances should be included within one evaluation to make all of them available
|
||||||
|
test_module_grouping =
|
||||||
|
let
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
# Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."A" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "A-name";
|
||||||
|
};
|
||||||
|
|
||||||
|
perMachine = { }: { };
|
||||||
|
};
|
||||||
|
modules."B" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "B-name";
|
||||||
|
};
|
||||||
|
|
||||||
|
perMachine = { }: { };
|
||||||
|
};
|
||||||
|
# User config
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_baz" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Test that the module is mapped into the output
|
||||||
|
# We might change the attribute name in the future
|
||||||
|
expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped;
|
||||||
|
expected = {
|
||||||
|
self-A = 2;
|
||||||
|
self-B = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test_creates_all_instances =
|
||||||
|
let
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
# Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."A" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "network";
|
||||||
|
};
|
||||||
|
|
||||||
|
perMachine = { }: { };
|
||||||
|
};
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Test that the module is mapped into the output
|
||||||
|
# We might change the attribute name in the future
|
||||||
|
expr = lib.attrNames res.evals.self-A.config.instances;
|
||||||
|
expected = [
|
||||||
|
"instance_bar"
|
||||||
|
"instance_foo"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Membership via roles
|
||||||
|
test_add_machines_directly =
|
||||||
|
let
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
# Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."A" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "network";
|
||||||
|
};
|
||||||
|
# Define a role without special behavior
|
||||||
|
roles.peer = { };
|
||||||
|
|
||||||
|
# perMachine = {}: {};
|
||||||
|
};
|
||||||
|
machines = {
|
||||||
|
jon = { };
|
||||||
|
sara = { };
|
||||||
|
hxi = { };
|
||||||
|
};
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = { };
|
||||||
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
roles.peer.machines.sara = { };
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Test that the module is mapped into the output
|
||||||
|
# We might change the attribute name in the future
|
||||||
|
expr = lib.attrNames res.evals.self-A.config.result.allMachines;
|
||||||
|
expected = [
|
||||||
|
"jon"
|
||||||
|
"sara"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Membership via tags
|
||||||
|
test_add_machines_via_tags =
|
||||||
|
let
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
# Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."A" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "network";
|
||||||
|
};
|
||||||
|
# Define a role without special behavior
|
||||||
|
roles.peer = { };
|
||||||
|
|
||||||
|
# perMachine = {}: {};
|
||||||
|
};
|
||||||
|
machines = {
|
||||||
|
jon = {
|
||||||
|
tags = [ "foo" ];
|
||||||
|
};
|
||||||
|
sara = {
|
||||||
|
tags = [ "foo" ];
|
||||||
|
};
|
||||||
|
hxi = { };
|
||||||
|
};
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
roles.peer.tags.foo = { };
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Test that the module is mapped into the output
|
||||||
|
# We might change the attribute name in the future
|
||||||
|
expr = lib.attrNames res.evals.self-A.config.result.allMachines;
|
||||||
|
expected = [
|
||||||
|
"jon"
|
||||||
|
"sara"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; };
|
||||||
|
# test_per_machine_receives_instances =
|
||||||
|
# let
|
||||||
|
# res = callInventoryAdapter {
|
||||||
|
# # Authored module
|
||||||
|
# # A minimal module looks like this
|
||||||
|
# # It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
# modules."A" = {
|
||||||
|
# _class = "clan.service";
|
||||||
|
# manifest = {
|
||||||
|
# name = "network";
|
||||||
|
# };
|
||||||
|
# # Define a role without special behavior
|
||||||
|
# roles.peer = { };
|
||||||
|
|
||||||
|
# perMachine =
|
||||||
|
# { instances, ... }:
|
||||||
|
# {
|
||||||
|
# nixosModule = instances;
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# machines = {
|
||||||
|
# jon = { };
|
||||||
|
# sara = { };
|
||||||
|
# };
|
||||||
|
# instances."instance_foo" = {
|
||||||
|
# module = {
|
||||||
|
# name = "A";
|
||||||
|
# };
|
||||||
|
# roles.peer.machines.jon = { };
|
||||||
|
# };
|
||||||
|
# instances."instance_bar" = {
|
||||||
|
# module = {
|
||||||
|
# name = "A";
|
||||||
|
# };
|
||||||
|
# roles.peer.machines.sara = { };
|
||||||
|
# };
|
||||||
|
# instances."instance_zaza" = {
|
||||||
|
# module = {
|
||||||
|
# name = "B";
|
||||||
|
# };
|
||||||
|
# roles.peer.tags.all = { };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# in
|
||||||
|
# {
|
||||||
|
# expr = {
|
||||||
|
# hasMachineSettings =
|
||||||
|
# res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } }
|
||||||
|
# instance_foo.roles.peer.machines.jon ? settings;
|
||||||
|
# machineSettingsEmpty =
|
||||||
|
# lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } }
|
||||||
|
# instance_foo.roles.peer.machines.jon.settings;
|
||||||
|
# hasRoleSettings =
|
||||||
|
# res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } }
|
||||||
|
# instance_foo.roles.peer ? settings;
|
||||||
|
# roleSettingsEmpty =
|
||||||
|
# lib.filterAttrs (n: _v: n != "__functor" ) res.evals.self-A.config.result.allMachines.jon.nixosModule. # { {instanceName} :: { roles :: { {roleName} :: { machines :: { {machineName} :: { settings :: {} } } } } } }
|
||||||
|
# instance_foo.roles.peer.settings;
|
||||||
|
# };
|
||||||
|
# expected = {
|
||||||
|
# hasMachineSettings = true;
|
||||||
|
# machineSettingsEmpty = {};
|
||||||
|
# hasRoleSettings = true;
|
||||||
|
# roleSettingsEmpty = {};
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
}
|
||||||
107
lib/distributed-service/tests/per_machine_args.nix
Normal file
107
lib/distributed-service/tests/per_machine_args.nix
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{ lib, callInventoryAdapter }:
|
||||||
|
|
||||||
|
let # Authored module
|
||||||
|
# A minimal module looks like this
|
||||||
|
# It isn't exactly doing anything but it's a valid module that produces an output
|
||||||
|
modules."A" = {
|
||||||
|
_class = "clan.service";
|
||||||
|
manifest = {
|
||||||
|
name = "network";
|
||||||
|
};
|
||||||
|
# Define two roles with unmergeable interfaces
|
||||||
|
# Both define some 'timeout' but with completely different types.
|
||||||
|
roles.peer.interface =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.timeout = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
roles.server.interface =
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
options.timeout = lib.mkOption {
|
||||||
|
type = lib.types.submodule;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
perMachine =
|
||||||
|
{ instances, ... }:
|
||||||
|
{
|
||||||
|
nixosModule = instances;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
machines = {
|
||||||
|
jon = { };
|
||||||
|
sara = { };
|
||||||
|
};
|
||||||
|
res = callInventoryAdapter {
|
||||||
|
inherit modules machines;
|
||||||
|
instances."instance_foo" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = lib.mkForce "foo-peer-jon";
|
||||||
|
};
|
||||||
|
roles.peer = {
|
||||||
|
settings.timeout = "foo-peer";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_bar" = {
|
||||||
|
module = {
|
||||||
|
name = "A";
|
||||||
|
};
|
||||||
|
roles.peer.machines.jon = {
|
||||||
|
settings.timeout = "bar-peer-jon";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
instances."instance_zaza" = {
|
||||||
|
module = {
|
||||||
|
name = "B";
|
||||||
|
};
|
||||||
|
roles.peer.tags.all = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
filterInternals = lib.filterAttrs (n: _v: !lib.hasPrefix "_" n);
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
# settings should evaluate
|
||||||
|
test_per_machine_receives_instance_settings = {
|
||||||
|
expr = {
|
||||||
|
hasMachineSettings =
|
||||||
|
res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.machines.jon
|
||||||
|
? settings;
|
||||||
|
|
||||||
|
# settings are specific.
|
||||||
|
# Below we access:
|
||||||
|
# instance = instance_foo
|
||||||
|
# roles = peer
|
||||||
|
# machines = jon
|
||||||
|
specificMachineSettings = filterInternals res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.machines.jon.settings;
|
||||||
|
|
||||||
|
hasRoleSettings =
|
||||||
|
res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer ? settings;
|
||||||
|
|
||||||
|
# settings are specific.
|
||||||
|
# Below we access:
|
||||||
|
# instance = instance_foo
|
||||||
|
# roles = peer
|
||||||
|
# machines = *
|
||||||
|
specificRoleSettings = filterInternals res.evals.self-A.config.result.allMachines.jon.nixosModule.instance_foo.roles.peer.settings;
|
||||||
|
};
|
||||||
|
expected = {
|
||||||
|
hasMachineSettings = true;
|
||||||
|
specificMachineSettings = {
|
||||||
|
timeout = "foo-peer-jon";
|
||||||
|
};
|
||||||
|
hasRoleSettings = true;
|
||||||
|
specificRoleSettings = {
|
||||||
|
timeout = "foo-peer";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,16 +10,20 @@ let
|
|||||||
pathExists
|
pathExists
|
||||||
;
|
;
|
||||||
in
|
in
|
||||||
{
|
rec {
|
||||||
|
# We should remove this.
|
||||||
|
# It would enforce treating at least 'lib' as a module in a whole
|
||||||
imports = filter pathExists [
|
imports = filter pathExists [
|
||||||
./jsonschema/flake-module.nix
|
./jsonschema/flake-module.nix
|
||||||
./inventory/flake-module.nix
|
./inventory/flake-module.nix
|
||||||
./build-clan/flake-module.nix
|
./build-clan/flake-module.nix
|
||||||
./values/flake-module.nix
|
./values/flake-module.nix
|
||||||
|
./distributed-service/flake-module.nix
|
||||||
];
|
];
|
||||||
flake.lib = import ./default.nix {
|
flake.clanLib = import ./default.nix {
|
||||||
inherit lib inputs;
|
inherit lib inputs self;
|
||||||
inherit (inputs) nixpkgs;
|
inherit (inputs) nixpkgs;
|
||||||
clan-core = self;
|
|
||||||
};
|
};
|
||||||
|
# TODO: remove this legacy alias
|
||||||
|
flake.lib = flake.clanLib;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{ lib, self }:
|
{ lib, clanLib }:
|
||||||
let
|
let
|
||||||
# Trim the .nix extension from a filename
|
# Trim the .nix extension from a filename
|
||||||
trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name;
|
trimExtension = name: builtins.substring 0 (builtins.stringLength name - 4) name;
|
||||||
|
|
||||||
jsonWithoutHeader = self.lib.jsonschema {
|
jsonWithoutHeader = clanLib.jsonschema {
|
||||||
includeDefaults = true;
|
includeDefaults = true;
|
||||||
header = { };
|
header = { };
|
||||||
};
|
};
|
||||||
@@ -13,7 +13,7 @@ let
|
|||||||
lib.mapAttrs (
|
lib.mapAttrs (
|
||||||
_moduleName: rolesOptions:
|
_moduleName: rolesOptions:
|
||||||
lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions
|
lib.mapAttrs (_roleName: options: jsonWithoutHeader.parseOptions options { }) rolesOptions
|
||||||
) (self.lib.evalClanModulesWithRoles modules);
|
) (clanLib.evalClan.evalClanModulesWithRoles modules);
|
||||||
|
|
||||||
evalFrontmatter =
|
evalFrontmatter =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
clan-core,
|
clanLib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -43,8 +43,7 @@ let
|
|||||||
|
|
||||||
checkService =
|
checkService =
|
||||||
modulepath: serviceName:
|
modulepath: serviceName:
|
||||||
builtins.elem "inventory"
|
builtins.elem "inventory" (clanLib.modules.getFrontmatter modulepath serviceName).features or [ ];
|
||||||
(clan-core.lib.modules.getFrontmatter modulepath serviceName).features or [ ];
|
|
||||||
|
|
||||||
compileMachine =
|
compileMachine =
|
||||||
{ machineConfig }:
|
{ machineConfig }:
|
||||||
@@ -160,7 +159,7 @@ in
|
|||||||
inherit
|
inherit
|
||||||
resolveTags
|
resolveTags
|
||||||
inventory
|
inventory
|
||||||
clan-core
|
clanLib
|
||||||
machineName
|
machineName
|
||||||
serviceConfigs
|
serviceConfigs
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
config,
|
config,
|
||||||
resolveTags,
|
resolveTags,
|
||||||
inventory,
|
inventory,
|
||||||
clan-core,
|
clanLib,
|
||||||
machineName,
|
machineName,
|
||||||
serviceConfigs,
|
serviceConfigs,
|
||||||
...
|
...
|
||||||
@@ -14,7 +14,7 @@ in
|
|||||||
{
|
{
|
||||||
# Roles resolution
|
# Roles resolution
|
||||||
# : List String
|
# : List String
|
||||||
supportedRoles = clan-core.lib.modules.getRoles inventory.modules serviceName;
|
supportedRoles = clanLib.modules.getRoles inventory.modules serviceName;
|
||||||
matchedRoles = builtins.attrNames (
|
matchedRoles = builtins.attrNames (
|
||||||
lib.filterAttrs (_: ms: builtins.elem machineName ms) config.machinesRoles
|
lib.filterAttrs (_: ms: builtins.elem machineName ms) config.machinesRoles
|
||||||
);
|
);
|
||||||
@@ -56,7 +56,7 @@ in
|
|||||||
|
|
||||||
assertions = lib.concatMapAttrs (
|
assertions = lib.concatMapAttrs (
|
||||||
instanceName: resolvedRoles:
|
instanceName: resolvedRoles:
|
||||||
clan-core.lib.modules.checkConstraints {
|
clanLib.modules.checkConstraints {
|
||||||
moduleName = serviceName;
|
moduleName = serviceName;
|
||||||
allModules = inventory.modules;
|
allModules = inventory.modules;
|
||||||
inherit resolvedRoles instanceName;
|
inherit resolvedRoles instanceName;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Generate partial NixOS configurations for every machine in the inventory
|
# Generate partial NixOS configurations for every machine in the inventory
|
||||||
# This function is responsible for generating the module configuration for every machine in the inventory.
|
# This function is responsible for generating the module configuration for every machine in the inventory.
|
||||||
{ lib, clan-core }:
|
{ lib, clanLib }:
|
||||||
let
|
let
|
||||||
/*
|
/*
|
||||||
Returns a set with NixOS configuration for every machine in the inventory.
|
Returns a set with NixOS configuration for every machine in the inventory.
|
||||||
@@ -11,7 +11,7 @@ let
|
|||||||
{ inventory, directory }:
|
{ inventory, directory }:
|
||||||
(lib.evalModules {
|
(lib.evalModules {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
inherit clan-core;
|
inherit clanLib;
|
||||||
};
|
};
|
||||||
modules = [
|
modules = [
|
||||||
./builder
|
./builder
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ in
|
|||||||
default = options;
|
default = options;
|
||||||
};
|
};
|
||||||
modules = lib.mkOption {
|
modules = lib.mkOption {
|
||||||
type = types.attrsOf types.path;
|
# Don't define the type yet
|
||||||
|
# We manually transform the value with types.deferredModule.merge later to keep them serializable
|
||||||
|
type = types.attrsOf types.raw;
|
||||||
default = { };
|
default = { };
|
||||||
defaultText = "clanModules of clan-core";
|
defaultText = "clanModules of clan-core";
|
||||||
description = ''
|
description = ''
|
||||||
@@ -275,7 +277,79 @@ in
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
instances = lib.mkOption {
|
||||||
|
# Keep as internal until all de-/serialization issues are resolved
|
||||||
|
visible = false;
|
||||||
|
internal = true;
|
||||||
|
description = "Multi host service module instances";
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
# ModuleSpec
|
||||||
|
module = lib.mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options.input = lib.mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
defaultText = "Name of the input. Default to 'null' which means the module is local";
|
||||||
|
description = ''
|
||||||
|
Name of the input. Default to 'null' which means the module is local
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
options.name = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
roles = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
# TODO: deduplicate
|
||||||
|
machines = lib.mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
# Dont transform the value with `types.deferredModule` here. We need to keep it json serializable
|
||||||
|
# TODO: We need a custom serializer for deferredModule
|
||||||
|
type = types.deferredModule;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
tags = lib.mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options.settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.deferredModule;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.deferredModule;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
apply =
|
||||||
|
v:
|
||||||
|
if v == { } then
|
||||||
|
v
|
||||||
|
else
|
||||||
|
lib.warn "Inventory.instances and related features are still under development. Please use with care." v;
|
||||||
|
};
|
||||||
services = lib.mkOption {
|
services = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Services of the inventory.
|
Services of the inventory.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{ lib, clan-core }:
|
{ lib, clanLib }:
|
||||||
{
|
{
|
||||||
inherit (import ./build-inventory { inherit lib clan-core; }) buildInventory;
|
inherit (import ./build-inventory { inherit lib clanLib; }) buildInventory;
|
||||||
interface = ./build-inventory/interface.nix;
|
interface = ./build-inventory/interface.nix;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ in
|
|||||||
self.filter {
|
self.filter {
|
||||||
include = [
|
include = [
|
||||||
"flakeModules"
|
"flakeModules"
|
||||||
"lib/default.nix"
|
"lib"
|
||||||
"lib/flake-module.nix"
|
|
||||||
"lib/inventory"
|
|
||||||
"lib/constraints"
|
|
||||||
"lib/frontmatter"
|
|
||||||
"clanModules/flake-module.nix"
|
"clanModules/flake-module.nix"
|
||||||
"clanModules/borgbackup"
|
"clanModules/borgbackup"
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
features = [ "inventory" ]
|
|
||||||
---
|
|
||||||
Description
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
_class = "clan";
|
|
||||||
perInstance = { };
|
|
||||||
perService = { };
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
let
|
let
|
||||||
inventory = (
|
inventory = (
|
||||||
import ../build-inventory {
|
import ../build-inventory {
|
||||||
|
inherit lib;
|
||||||
inherit lib clan-core;
|
clanLib = clan-core.clanLib;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
inherit (inventory) buildInventory;
|
inherit (inventory) buildInventory;
|
||||||
@@ -17,11 +17,9 @@ in
|
|||||||
A = { };
|
A = { };
|
||||||
};
|
};
|
||||||
services = {
|
services = {
|
||||||
clanModule = { };
|
|
||||||
legacyModule = { };
|
legacyModule = { };
|
||||||
};
|
};
|
||||||
modules = {
|
modules = {
|
||||||
clanModule = ./clanModule;
|
|
||||||
legacyModule = ./legacyModule;
|
legacyModule = ./legacyModule;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -30,17 +28,11 @@ in
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
expr = {
|
expr = {
|
||||||
clanModule = lib.filterAttrs (
|
|
||||||
name: _: name == "isClanModule"
|
|
||||||
) compiled.machines.A.compiledServices.clanModule;
|
|
||||||
legacyModule = lib.filterAttrs (
|
legacyModule = lib.filterAttrs (
|
||||||
name: _: name == "isClanModule"
|
name: _: name == "isClanModule"
|
||||||
) compiled.machines.A.compiledServices.legacyModule;
|
) compiled.machines.A.compiledServices.legacyModule;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
clanModule = {
|
|
||||||
isClanModule = true;
|
|
||||||
};
|
|
||||||
legacyModule = {
|
legacyModule = {
|
||||||
isClanModule = false;
|
isClanModule = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
|
||||||
clan-core,
|
clan-core,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
# Just some random stuff
|
# Just some random stuff
|
||||||
config.user.user = lib.mapAttrs clan-core.users.root;
|
options.test = lib.mapAttrs clan-core;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,7 @@ in
|
|||||||
self.filter {
|
self.filter {
|
||||||
include = [
|
include = [
|
||||||
"flakeModules"
|
"flakeModules"
|
||||||
"lib/default.nix"
|
"lib"
|
||||||
"lib/flake-module.nix"
|
|
||||||
"lib/values"
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}#legacyPackages.${system}.evalTests-values
|
}#legacyPackages.${system}.evalTests-values
|
||||||
|
|||||||
@@ -6,11 +6,12 @@
|
|||||||
{
|
{
|
||||||
config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "vm") {
|
config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "vm") {
|
||||||
fileModule = file: {
|
fileModule = file: {
|
||||||
path =
|
path = lib.mkIf (file.config.secret == true) (
|
||||||
if file.config.neededFor == "partitioning" then
|
if file.config.neededFor == "partitioning" then
|
||||||
"/run/partitioning-secrets/${file.config.generatorName}/${file.config.name}"
|
"/run/partitioning-secrets/${file.config.generatorName}/${file.config.name}"
|
||||||
else
|
else
|
||||||
"/etc/secrets/${file.config.generatorName}/${file.config.name}";
|
"/etc/secrets/${file.config.generatorName}/${file.config.name}"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
secretModule = "clan_cli.vars.secret_modules.vm";
|
secretModule = "clan_cli.vars.secret_modules.vm";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
inputs.sops-nix.nixosModules.sops
|
inputs.sops-nix.nixosModules.sops
|
||||||
inputs.nixos-facter-modules.nixosModules.facter
|
inputs.nixos-facter-modules.nixosModules.facter
|
||||||
inputs.disko.nixosModules.default
|
inputs.disko.nixosModules.default
|
||||||
|
inputs.data-mesher.nixosModules.data-mesher
|
||||||
./clanCore
|
./clanCore
|
||||||
(
|
(
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class FlakeCacheEntry:
|
|||||||
self.selector = {int(selectors[0])}
|
self.selector = {int(selectors[0])}
|
||||||
selector = int(selectors[0])
|
selector = int(selectors[0])
|
||||||
elif isinstance(selectors[0], str):
|
elif isinstance(selectors[0], str):
|
||||||
self.selector = {(selectors[0])}
|
self.selector = {selectors[0]}
|
||||||
selector = selectors[0]
|
selector = selectors[0]
|
||||||
elif isinstance(selectors[0], AllSelector):
|
elif isinstance(selectors[0], AllSelector):
|
||||||
self.selector = AllSelector()
|
self.selector = AllSelector()
|
||||||
@@ -154,7 +154,9 @@ class FlakeCacheEntry:
|
|||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def insert(
|
def insert(
|
||||||
self, value: str | float | dict[str, Any] | list[Any], selectors: list[Selector]
|
self,
|
||||||
|
value: str | float | dict[str, Any] | list[Any] | None,
|
||||||
|
selectors: list[Selector],
|
||||||
) -> None:
|
) -> None:
|
||||||
selector: Selector
|
selector: Selector
|
||||||
if selectors == []:
|
if selectors == []:
|
||||||
@@ -244,6 +246,12 @@ class FlakeCacheEntry:
|
|||||||
if self.value != value:
|
if self.value != value:
|
||||||
msg = "value mismatch in cache, something is fishy"
|
msg = "value mismatch in cache, something is fishy"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
elif value is None:
|
||||||
|
if self.value is not None:
|
||||||
|
msg = "value mismatch in cache, something is fishy"
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = f"Cannot insert value of type {type(value)} into cache"
|
msg = f"Cannot insert value of type {type(value)} into cache"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
@@ -473,7 +481,7 @@ class Flake:
|
|||||||
flake = builtins.getFlake("path:{self.store_path}?narHash={self.hash}");
|
flake = builtins.getFlake("path:{self.store_path}?narHash={self.hash}");
|
||||||
in
|
in
|
||||||
flake.inputs.nixpkgs.legacyPackages.{config["system"]}.writeText "clan-flake-select" (
|
flake.inputs.nixpkgs.legacyPackages.{config["system"]}.writeText "clan-flake-select" (
|
||||||
builtins.toJSON [ ({" ".join([f"flake.clanInternals.lib.select ''{attr}'' flake" for attr in selectors])}) ]
|
builtins.toJSON [ ({" ".join([f"flake.clanInternals.clanLib.select ''{attr}'' flake" for attr in selectors])}) ]
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
if tmp_store := nix_test_store():
|
if tmp_store := nix_test_store():
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ Service = dict[str, Any]
|
|||||||
class Inventory(TypedDict):
|
class Inventory(TypedDict):
|
||||||
machines: NotRequired[dict[str, Machine]]
|
machines: NotRequired[dict[str, Machine]]
|
||||||
meta: NotRequired[Meta]
|
meta: NotRequired[Meta]
|
||||||
modules: NotRequired[dict[str, str]]
|
modules: NotRequired[dict[str, Any]]
|
||||||
services: NotRequired[dict[str, Service]]
|
services: NotRequired[dict[str, Service]]
|
||||||
tags: NotRequired[dict[str, list[str]]]
|
tags: NotRequired[dict[str, Any]]
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ set -euo pipefail
|
|||||||
jsonSchema=$(nix build .#schemas.inventory-schema-abstract --print-out-paths)/schema.json
|
jsonSchema=$(nix build .#schemas.inventory-schema-abstract --print-out-paths)/schema.json
|
||||||
SCRIPT_DIR=$(dirname "$0")
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
nix run .#classgen -- "$jsonSchema" "../../../clan-cli/clan_cli/inventory/classes.py" --stop-at "Service"
|
nix run .#classgen -- "$jsonSchema" "../../../clan-cli/clan_cli/inventory/classes.py"
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
|
|||||||
]
|
]
|
||||||
|
|
||||||
host = machine.target_host
|
host = machine.target_host
|
||||||
|
|
||||||
|
# HACK: to make non-root user work
|
||||||
|
if host.user != "root":
|
||||||
|
config_command.insert(0, "sudo")
|
||||||
|
|
||||||
cmd = nix_shell(
|
cmd = nix_shell(
|
||||||
[
|
[
|
||||||
"nixpkgs#openssh",
|
"nixpkgs#openssh",
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ def deploy_machines(machines: list[Machine]) -> None:
|
|||||||
"--flake",
|
"--flake",
|
||||||
f"{path}#{machine.name}",
|
f"{path}#{machine.name}",
|
||||||
]
|
]
|
||||||
|
|
||||||
switch_cmd = ["nixos-rebuild", "switch", *nix_options]
|
switch_cmd = ["nixos-rebuild", "switch", *nix_options]
|
||||||
test_cmd = ["nixos-rebuild", "test", *nix_options]
|
test_cmd = ["nixos-rebuild", "test", *nix_options]
|
||||||
|
|
||||||
@@ -160,6 +161,10 @@ def deploy_machines(machines: list[Machine]) -> None:
|
|||||||
switch_cmd.extend(["--target-host", target_host.target])
|
switch_cmd.extend(["--target-host", target_host.target])
|
||||||
test_cmd.extend(["--target-host", target_host.target])
|
test_cmd.extend(["--target-host", target_host.target])
|
||||||
|
|
||||||
|
if target_host and target_host.user != "root":
|
||||||
|
switch_cmd.extend(["--use-remote-sudo"])
|
||||||
|
test_cmd.extend(["--use-remote-sudo"])
|
||||||
|
|
||||||
env = host.nix_ssh_env(None)
|
env = host.nix_ssh_env(None)
|
||||||
ret = host.run(
|
ret = host.run(
|
||||||
switch_cmd,
|
switch_cmd,
|
||||||
@@ -185,6 +190,7 @@ def deploy_machines(machines: list[Machine]) -> None:
|
|||||||
test_cmd,
|
test_cmd,
|
||||||
RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),
|
RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),
|
||||||
extra_env=env,
|
extra_env=env,
|
||||||
|
become_root=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# retry nixos-rebuild switch if the first attempt failed
|
# retry nixos-rebuild switch if the first attempt failed
|
||||||
@@ -193,6 +199,7 @@ def deploy_machines(machines: list[Machine]) -> None:
|
|||||||
switch_cmd,
|
switch_cmd,
|
||||||
RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),
|
RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),
|
||||||
extra_env=env,
|
extra_env=env,
|
||||||
|
become_root=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
with AsyncRuntime() as runtime:
|
with AsyncRuntime() as runtime:
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
import tarfile
|
import tarfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from shlex import quote
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from clan_cli.cmd import Log, RunOpts
|
from clan_cli.cmd import Log, RunOpts
|
||||||
from clan_cli.cmd import run as run_local
|
from clan_cli.cmd import run as run_local
|
||||||
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.ssh.host import Host
|
from clan_cli.ssh.host import Host
|
||||||
|
|
||||||
|
|
||||||
def upload(
|
def upload(
|
||||||
host: Host,
|
host: Host,
|
||||||
local_src: Path, # must be a directory
|
local_src: Path,
|
||||||
remote_dest: Path, # must be a directory
|
remote_dest: Path, # must be a directory
|
||||||
file_user: str = "root",
|
file_user: str = "root",
|
||||||
file_group: str = "root",
|
file_group: str = "root",
|
||||||
dir_mode: int = 0o700,
|
dir_mode: int = 0o700,
|
||||||
file_mode: int = 0o400,
|
file_mode: int = 0o400,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# Check if the remote destination is at least 3 directories deep
|
||||||
|
if len(remote_dest.parts) < 3:
|
||||||
|
msg = f"The remote destination must be at least 3 directories deep. Got: {remote_dest}. Reason: The directory will be deleted with 'rm -rf'."
|
||||||
|
raise ClanError(msg)
|
||||||
|
|
||||||
# Create the tarball from the temporary directory
|
# Create the tarball from the temporary directory
|
||||||
with TemporaryDirectory(prefix="facts-upload-") as tardir:
|
with TemporaryDirectory(prefix="facts-upload-") as tardir:
|
||||||
tar_path = Path(tardir) / "upload.tar.gz"
|
tar_path = Path(tardir) / "upload.tar.gz"
|
||||||
@@ -55,50 +62,22 @@ def upload(
|
|||||||
with local_src.open("rb") as f:
|
with local_src.open("rb") as f:
|
||||||
tar.addfile(tarinfo, f)
|
tar.addfile(tarinfo, f)
|
||||||
|
|
||||||
if local_src.is_dir():
|
sudo = ""
|
||||||
cmd = [
|
if host.user != "root":
|
||||||
*host.ssh_cmd(),
|
sudo = "sudo -- "
|
||||||
"rm",
|
|
||||||
"-r",
|
cmd = "rm -rf $0 && mkdir -m $1 -p $0 && tar -C $0 -xzf -"
|
||||||
str(remote_dest),
|
|
||||||
";",
|
|
||||||
"mkdir",
|
|
||||||
"-m",
|
|
||||||
f"{dir_mode:o}",
|
|
||||||
"-p",
|
|
||||||
str(remote_dest),
|
|
||||||
"&&",
|
|
||||||
"tar",
|
|
||||||
"-C",
|
|
||||||
str(remote_dest),
|
|
||||||
"-xzf",
|
|
||||||
"-",
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# For single file, extract to parent directory and ensure correct name
|
|
||||||
cmd = [
|
|
||||||
*host.ssh_cmd(),
|
|
||||||
"rm",
|
|
||||||
"-f",
|
|
||||||
str(remote_dest),
|
|
||||||
";",
|
|
||||||
"mkdir",
|
|
||||||
"-m",
|
|
||||||
f"{dir_mode:o}",
|
|
||||||
"-p",
|
|
||||||
str(remote_dest.parent),
|
|
||||||
"&&",
|
|
||||||
"tar",
|
|
||||||
"-C",
|
|
||||||
str(remote_dest.parent),
|
|
||||||
"-xzf",
|
|
||||||
"-",
|
|
||||||
]
|
|
||||||
|
|
||||||
# TODO accept `input` to be an IO object instead of bytes so that we don't have to read the tarfile into memory.
|
# TODO accept `input` to be an IO object instead of bytes so that we don't have to read the tarfile into memory.
|
||||||
with tar_path.open("rb") as f:
|
with tar_path.open("rb") as f:
|
||||||
run_local(
|
run_local(
|
||||||
cmd,
|
[
|
||||||
|
*host.ssh_cmd(),
|
||||||
|
"--",
|
||||||
|
f"{sudo}bash -c {quote(cmd)}",
|
||||||
|
str(remote_dest),
|
||||||
|
f"{dir_mode:o}",
|
||||||
|
],
|
||||||
RunOpts(
|
RunOpts(
|
||||||
input=f.read(),
|
input=f.read(),
|
||||||
log=Log.BOTH,
|
log=Log.BOTH,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class Generator:
|
|||||||
name: str
|
name: str
|
||||||
files: list[Var] = field(default_factory=list)
|
files: list[Var] = field(default_factory=list)
|
||||||
share: bool = False
|
share: bool = False
|
||||||
validation: str | None = None
|
|
||||||
prompts: list[Prompt] = field(default_factory=list)
|
prompts: list[Prompt] = field(default_factory=list)
|
||||||
dependencies: list[str] = field(default_factory=list)
|
dependencies: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
@@ -62,7 +61,6 @@ class Generator:
|
|||||||
name=data["name"],
|
name=data["name"],
|
||||||
share=data["share"],
|
share=data["share"],
|
||||||
files=[Var.from_json(data["name"], f) for f in data["files"].values()],
|
files=[Var.from_json(data["name"], f) for f in data["files"].values()],
|
||||||
validation=data["validationHash"],
|
|
||||||
dependencies=data["dependencies"],
|
dependencies=data["dependencies"],
|
||||||
migrate_fact=data["migrateFact"],
|
migrate_fact=data["migrateFact"],
|
||||||
prompts=[Prompt.from_json(p) for p in data["prompts"].values()],
|
prompts=[Prompt.from_json(p) for p in data["prompts"].values()],
|
||||||
@@ -76,6 +74,13 @@ class Generator:
|
|||||||
)
|
)
|
||||||
return final_script
|
return final_script
|
||||||
|
|
||||||
|
@property
|
||||||
|
def validation(self) -> str | None:
|
||||||
|
assert self._machine is not None
|
||||||
|
return self._machine.eval_nix(
|
||||||
|
f'config.clan.core.vars.generators."{self.name}".validationHash'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def bubblewrap_cmd(generator: str, tmpdir: Path) -> list[str]:
|
def bubblewrap_cmd(generator: str, tmpdir: Path) -> list[str]:
|
||||||
test_store = nix_test_store()
|
test_store = nix_test_store()
|
||||||
@@ -253,6 +258,8 @@ def execute_generator(
|
|||||||
machine.flake_dir,
|
machine.flake_dir,
|
||||||
f"Update vars via generator {generator.name} for machine {machine.name}",
|
f"Update vars via generator {generator.name} for machine {machine.name}",
|
||||||
)
|
)
|
||||||
|
if len(files_to_commit) > 0:
|
||||||
|
machine.flush_caches()
|
||||||
|
|
||||||
|
|
||||||
def _ask_prompts(
|
def _ask_prompts(
|
||||||
@@ -456,8 +463,6 @@ def generate_vars_for_machine(
|
|||||||
public_vars_store=machine.public_vars_store,
|
public_vars_store=machine.public_vars_store,
|
||||||
prompt_values=_ask_prompts(generator),
|
prompt_values=_ask_prompts(generator),
|
||||||
)
|
)
|
||||||
# flush caches to make sure the new secrets are available in evaluation
|
|
||||||
machine.flush_caches()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def ask(
|
|||||||
text = f"Enter the value for {ident}:"
|
text = f"Enter the value for {ident}:"
|
||||||
if label:
|
if label:
|
||||||
text = f"{label}"
|
text = f"{label}"
|
||||||
|
log.info(f"Prompting value for {ident}")
|
||||||
if MOCK_PROMPT_RESPONSE:
|
if MOCK_PROMPT_RESPONSE:
|
||||||
return next(MOCK_PROMPT_RESPONSE)
|
return next(MOCK_PROMPT_RESPONSE)
|
||||||
match input_type:
|
match input_type:
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ let
|
|||||||
ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs
|
ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs
|
||||||
cp -r ${../../templates} $out/clan_cli/templates
|
cp -r ${../../templates} $out/clan_cli/templates
|
||||||
|
|
||||||
${classgen}/bin/classgen ${inventory-schema-abstract}/schema.json $out/clan_cli/inventory/classes.py --stop-at "Service"
|
${classgen}/bin/classgen ${inventory-schema-abstract}/schema.json $out/clan_cli/inventory/classes.py
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Create a custom nixpkgs for use within the project
|
# Create a custom nixpkgs for use within the project
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service"
|
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py
|
||||||
|
|
||||||
python docs.py reference
|
python docs.py reference
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service"
|
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
# Retrieve python API Typescript types
|
# Retrieve python API Typescript types
|
||||||
python api.py > $out/API.json
|
python api.py > $out/API.json
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
classFile = "classes.py";
|
classFile = "classes.py";
|
||||||
};
|
};
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json b_classes.py --stop-at "Service"
|
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json b_classes.py
|
||||||
file1=$classFile
|
file1=$classFile
|
||||||
file2=b_classes.py
|
file2=b_classes.py
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,6 @@ mkShell {
|
|||||||
|
|
||||||
# Generate classes.py from inventory schema
|
# Generate classes.py from inventory schema
|
||||||
# This file is in .gitignore
|
# This file is in .gitignore
|
||||||
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py --stop-at "Service"
|
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ def sshd_config(test_root: Path) -> Iterator[SshdConfig]:
|
|||||||
)
|
)
|
||||||
config = tmpdir / "sshd_config"
|
config = tmpdir / "sshd_config"
|
||||||
config.write_text(content)
|
config.write_text(content)
|
||||||
login_shell = tmpdir / "shell"
|
bin_path = tmpdir / "bin"
|
||||||
|
login_shell = bin_path / "shell"
|
||||||
|
fake_sudo = bin_path / "sudo"
|
||||||
|
login_shell.parent.mkdir(parents=True)
|
||||||
|
|
||||||
bash = shutil.which("bash")
|
bash = shutil.which("bash")
|
||||||
path = os.environ["PATH"]
|
path = os.environ["PATH"]
|
||||||
@@ -65,19 +68,23 @@ def sshd_config(test_root: Path) -> Iterator[SshdConfig]:
|
|||||||
|
|
||||||
login_shell.write_text(
|
login_shell.write_text(
|
||||||
f"""#!{bash}
|
f"""#!{bash}
|
||||||
|
set -x
|
||||||
if [[ -f /etc/profile ]]; then
|
if [[ -f /etc/profile ]]; then
|
||||||
source /etc/profile
|
source /etc/profile
|
||||||
fi
|
fi
|
||||||
if [[ -n "$REALPATH" ]]; then
|
export PATH="{bin_path}:{path}"
|
||||||
export PATH="$REALPATH:${path}"
|
|
||||||
else
|
|
||||||
export PATH="${path}"
|
|
||||||
fi
|
|
||||||
exec {bash} -l "${{@}}"
|
exec {bash} -l "${{@}}"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
login_shell.chmod(0o755)
|
login_shell.chmod(0o755)
|
||||||
|
|
||||||
|
fake_sudo.write_text(
|
||||||
|
f"""#!{bash}
|
||||||
|
exec "${{@}}"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fake_sudo.chmod(0o755)
|
||||||
|
|
||||||
lib_path = None
|
lib_path = None
|
||||||
|
|
||||||
extension = ".so"
|
extension = ".so"
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ def test_select() -> None:
|
|||||||
assert not test_cache.is_cached(["x", "z", 1])
|
assert not test_cache.is_cached(["x", "z", 1])
|
||||||
|
|
||||||
|
|
||||||
|
def test_insert() -> None:
|
||||||
|
test_cache = FlakeCacheEntry({}, [])
|
||||||
|
# Inserting the same thing twice should succeed
|
||||||
|
test_cache.insert(None, ["nix"])
|
||||||
|
test_cache.insert(None, ["nix"])
|
||||||
|
assert test_cache.select(["nix"]) is None
|
||||||
|
|
||||||
|
|
||||||
def test_out_path() -> None:
|
def test_out_path() -> None:
|
||||||
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/bla"}}
|
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/bla"}}
|
||||||
test_cache = FlakeCacheEntry(testdict, [])
|
test_cache = FlakeCacheEntry(testdict, [])
|
||||||
|
|||||||
@@ -26,6 +26,17 @@ def test_secrets_upload(
|
|||||||
monkeypatch.chdir(str(flake.path))
|
monkeypatch.chdir(str(flake.path))
|
||||||
monkeypatch.setenv("SOPS_AGE_KEY", age_keys[0].privkey)
|
monkeypatch.setenv("SOPS_AGE_KEY", age_keys[0].privkey)
|
||||||
|
|
||||||
|
sops_dir = flake.path / "facts"
|
||||||
|
|
||||||
|
# the flake defines this path as the location where the sops key should be installed
|
||||||
|
sops_key = sops_dir / "key.txt"
|
||||||
|
sops_key2 = sops_dir / "key2.txt"
|
||||||
|
|
||||||
|
# Create old state, which should be cleaned up
|
||||||
|
sops_dir.mkdir()
|
||||||
|
sops_key.write_text("OLD STATE")
|
||||||
|
sops_key2.write_text("OLD STATE2")
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"secrets",
|
"secrets",
|
||||||
@@ -56,8 +67,6 @@ def test_secrets_upload(
|
|||||||
|
|
||||||
cli.run(["facts", "upload", "--flake", str(flake_path), "vm1"])
|
cli.run(["facts", "upload", "--flake", str(flake_path), "vm1"])
|
||||||
|
|
||||||
# the flake defines this path as the location where the sops key should be installed
|
|
||||||
sops_key = flake.path / "facts" / "key.txt"
|
|
||||||
|
|
||||||
assert sops_key.exists()
|
assert sops_key.exists()
|
||||||
assert sops_key.read_text() == age_keys[0].privkey
|
assert sops_key.read_text() == age_keys[0].privkey
|
||||||
|
assert not sops_key2.exists()
|
||||||
|
|||||||
@@ -919,3 +919,75 @@ def test_invalidation(
|
|||||||
str(machine.flake.path), machine.name, "my_generator/my_value"
|
str(machine.flake.path), machine.name, "my_generator/my_value"
|
||||||
).printable_value
|
).printable_value
|
||||||
assert value2 == value2_new
|
assert value2 == value2_new
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_dynamic_invalidation(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
flake: ClanFlake,
|
||||||
|
) -> None:
|
||||||
|
gen_prefix = "config.clan.core.vars.generators"
|
||||||
|
|
||||||
|
machine = Machine(name="my_machine", flake=Flake(str(flake.path)))
|
||||||
|
|
||||||
|
config = flake.machines[machine.name]
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
|
|
||||||
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
|
my_generator["script"] = "echo -n $RANDOM > $out/my_value"
|
||||||
|
|
||||||
|
dependent_generator = config["clan"]["core"]["vars"]["generators"][
|
||||||
|
"dependent_generator"
|
||||||
|
]
|
||||||
|
dependent_generator["files"]["my_value"]["secret"] = False
|
||||||
|
dependent_generator["dependencies"] = ["my_generator"]
|
||||||
|
dependent_generator["script"] = "echo -n $RANDOM > $out/my_value"
|
||||||
|
|
||||||
|
flake.refresh()
|
||||||
|
|
||||||
|
# this is an abuse
|
||||||
|
custom_nix = flake.path / "machines" / machine.name / "hardware-configuration.nix"
|
||||||
|
custom_nix.write_text("""
|
||||||
|
{ config, ... }: let
|
||||||
|
p = config.clan.core.vars.generators.my_generator.files.my_value.path;
|
||||||
|
in {
|
||||||
|
clan.core.vars.generators.dependent_generator.validation = if builtins.pathExists p then builtins.readFile p else null;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
flake.refresh()
|
||||||
|
machine.flush_caches()
|
||||||
|
monkeypatch.chdir(flake.path)
|
||||||
|
|
||||||
|
# before generating, dependent generator validation should be empty; see bogus hardware-configuration.nix above
|
||||||
|
# we have to avoid `*.files.value` in this initial select because the generators haven't been run yet
|
||||||
|
generators_0 = machine.eval_nix(f"{gen_prefix}.*.{{validationHash}}")
|
||||||
|
assert generators_0["dependent_generator"]["validationHash"] is None
|
||||||
|
|
||||||
|
# generate both my_generator and (the dependent) dependent_generator
|
||||||
|
cli.run(["vars", "generate", "--flake", str(flake.path), machine.name])
|
||||||
|
machine.flush_caches()
|
||||||
|
|
||||||
|
# after generating once, dependent generator validation should be set
|
||||||
|
generators_1 = machine.eval_nix(gen_prefix)
|
||||||
|
assert generators_1["dependent_generator"]["validationHash"] is not None
|
||||||
|
|
||||||
|
# after generating once, neither generator should want to run again because `clan vars generate` should have re-evaluated the dependent generator's validationHash after executing the parent generator but before executing the dependent generator
|
||||||
|
# this ensures that validation can depend on parent generators while still only requiring a single pass
|
||||||
|
cli.run(["vars", "generate", "--flake", str(flake.path), machine.name])
|
||||||
|
machine.flush_caches()
|
||||||
|
|
||||||
|
generators_2 = machine.eval_nix(gen_prefix)
|
||||||
|
assert (
|
||||||
|
generators_1["dependent_generator"]["validationHash"]
|
||||||
|
== generators_2["dependent_generator"]["validationHash"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
generators_1["my_generator"]["files"]["my_value"]["value"]
|
||||||
|
== generators_2["my_generator"]["files"]["my_value"]["value"]
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
generators_1["dependent_generator"]["files"]["my_value"]["value"]
|
||||||
|
== generators_2["dependent_generator"]["files"]["my_value"]["value"]
|
||||||
|
)
|
||||||
|
|||||||
@@ -32,8 +32,14 @@ def map_json_type(
|
|||||||
return {"str"}
|
return {"str"}
|
||||||
if json_type == "integer":
|
if json_type == "integer":
|
||||||
return {"int"}
|
return {"int"}
|
||||||
|
if json_type == "number":
|
||||||
|
return {"float"}
|
||||||
if json_type == "boolean":
|
if json_type == "boolean":
|
||||||
return {"bool"}
|
return {"bool"}
|
||||||
|
# In Python, "number" is analogous to the float type.
|
||||||
|
# https://json-schema.org/understanding-json-schema/reference/numeric#number
|
||||||
|
if json_type == "number":
|
||||||
|
return {"float"}
|
||||||
if json_type == "array":
|
if json_type == "array":
|
||||||
assert nested_types, f"Array type not found for {parent}"
|
assert nested_types, f"Array type not found for {parent}"
|
||||||
return {f"""list[{" | ".join(nested_types)}]"""}
|
return {f"""list[{" | ".join(nested_types)}]"""}
|
||||||
@@ -48,7 +54,11 @@ def map_json_type(
|
|||||||
|
|
||||||
known_classes = set()
|
known_classes = set()
|
||||||
root_class = "Inventory"
|
root_class = "Inventory"
|
||||||
stop_at = None
|
# TODO: make this configurable
|
||||||
|
# For now this only includes static top-level attributes of the inventory.
|
||||||
|
attrs = ["machines", "meta", "services"]
|
||||||
|
|
||||||
|
static: dict[str, str] = {"Service": "dict[str, Any]"}
|
||||||
|
|
||||||
|
|
||||||
def field_def_from_default_type(
|
def field_def_from_default_type(
|
||||||
@@ -187,19 +197,32 @@ def get_field_def(
|
|||||||
|
|
||||||
|
|
||||||
# Recursive function to generate dataclasses from JSON schema
|
# Recursive function to generate dataclasses from JSON schema
|
||||||
def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> str:
|
def generate_dataclass(
|
||||||
|
schema: dict[str, Any],
|
||||||
|
attr_path: list[str],
|
||||||
|
class_name: str = root_class,
|
||||||
|
) -> str:
|
||||||
properties = schema.get("properties", {})
|
properties = schema.get("properties", {})
|
||||||
|
|
||||||
required_fields = []
|
required_fields = []
|
||||||
fields_with_default = []
|
fields_with_default = []
|
||||||
nested_classes: list[str] = []
|
nested_classes: list[str] = []
|
||||||
if stop_at and class_name == stop_at:
|
|
||||||
# Skip generating classes below the stop_at property
|
# if We are at the top level, and the attribute name is in shallow
|
||||||
return f"{class_name} = dict[str, Any]"
|
# return f"{class_name} = dict[str, Any]"
|
||||||
|
if class_name in static:
|
||||||
|
return f"{class_name} = {static[class_name]}"
|
||||||
|
|
||||||
for prop, prop_info in properties.items():
|
for prop, prop_info in properties.items():
|
||||||
|
# If we are at the top level, and the attribute name is not explicitly included we only do shallow
|
||||||
field_name = prop.replace("-", "_")
|
field_name = prop.replace("-", "_")
|
||||||
|
|
||||||
|
if len(attr_path) == 0 and prop not in attrs:
|
||||||
|
field_def = f"{field_name}: NotRequired[dict[str, Any]]"
|
||||||
|
fields_with_default.append(field_def)
|
||||||
|
# breakpoint()
|
||||||
|
continue
|
||||||
|
|
||||||
prop_type = prop_info.get("type", None)
|
prop_type = prop_info.get("type", None)
|
||||||
union_variants = prop_info.get("oneOf", [])
|
union_variants = prop_info.get("oneOf", [])
|
||||||
enum_variants = prop_info.get("enum", [])
|
enum_variants = prop_info.get("enum", [])
|
||||||
@@ -237,7 +260,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
|
|||||||
|
|
||||||
if nested_class_name not in known_classes:
|
if nested_class_name not in known_classes:
|
||||||
nested_classes.append(
|
nested_classes.append(
|
||||||
generate_dataclass(inner_type, nested_class_name)
|
generate_dataclass(
|
||||||
|
inner_type, [*attr_path, prop], nested_class_name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
known_classes.add(nested_class_name)
|
known_classes.add(nested_class_name)
|
||||||
|
|
||||||
@@ -253,7 +278,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
|
|||||||
field_types = {nested_class_name}
|
field_types = {nested_class_name}
|
||||||
if nested_class_name not in known_classes:
|
if nested_class_name not in known_classes:
|
||||||
nested_classes.append(
|
nested_classes.append(
|
||||||
generate_dataclass(prop_info, nested_class_name)
|
generate_dataclass(
|
||||||
|
prop_info, [*attr_path, prop], nested_class_name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
known_classes.add(nested_class_name)
|
known_classes.add(nested_class_name)
|
||||||
else:
|
else:
|
||||||
@@ -318,6 +345,8 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
|
|||||||
)
|
)
|
||||||
required_fields.append(field_def)
|
required_fields.append(field_def)
|
||||||
|
|
||||||
|
# breakpoint()
|
||||||
|
|
||||||
fields_str = "\n ".join(required_fields + fields_with_default)
|
fields_str = "\n ".join(required_fields + fields_with_default)
|
||||||
nested_classes_str = "\n\n".join(nested_classes)
|
nested_classes_str = "\n\n".join(nested_classes)
|
||||||
|
|
||||||
@@ -332,14 +361,11 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
|
|||||||
|
|
||||||
def run_gen(args: argparse.Namespace) -> None:
|
def run_gen(args: argparse.Namespace) -> None:
|
||||||
print(f"Converting {args.input} to {args.output}")
|
print(f"Converting {args.input} to {args.output}")
|
||||||
if args.stop_at:
|
|
||||||
global stop_at
|
|
||||||
stop_at = args.stop_at
|
|
||||||
|
|
||||||
dataclass_code = ""
|
dataclass_code = ""
|
||||||
with args.input.open() as f:
|
with args.input.open() as f:
|
||||||
schema = json.load(f)
|
schema = json.load(f)
|
||||||
dataclass_code = generate_dataclass(schema)
|
dataclass_code = generate_dataclass(schema, [])
|
||||||
|
|
||||||
with args.output.open("w") as f:
|
with args.output.open("w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
|
|||||||
430
pkgs/webview-ui/app/package-lock.json
generated
430
pkgs/webview-ui/app/package-lock.json
generated
@@ -663,9 +663,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||||
"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
|
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -679,9 +679,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
|
||||||
"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
|
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -695,9 +695,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
|
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -711,9 +711,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
|
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -727,9 +727,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
|
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -743,9 +743,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
|
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -759,9 +759,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
|
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -775,9 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
|
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -791,9 +791,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
|
||||||
"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
|
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -807,9 +807,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
|
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -823,9 +823,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
|
||||||
"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
|
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -839,9 +839,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
|
||||||
"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
|
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -855,9 +855,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
|
||||||
"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
|
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -871,9 +871,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
|
||||||
"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
|
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -887,9 +887,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
|
||||||
"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
|
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -903,9 +903,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
|
||||||
"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
|
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -919,9 +919,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
|
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -935,9 +935,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
|
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -951,9 +951,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
|
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -967,9 +967,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
|
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -983,9 +983,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
|
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -999,9 +999,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
|
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1015,9 +1015,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
|
||||||
"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
|
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1031,9 +1031,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
|
||||||
"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
|
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -1047,9 +1047,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
|
||||||
"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
|
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1445,9 +1445,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz",
|
||||||
"integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==",
|
"integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1458,9 +1458,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz",
|
||||||
"integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==",
|
"integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1471,9 +1471,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz",
|
||||||
"integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==",
|
"integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1484,9 +1484,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz",
|
||||||
"integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==",
|
"integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1497,9 +1497,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz",
|
||||||
"integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==",
|
"integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1510,9 +1510,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz",
|
||||||
"integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==",
|
"integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1523,9 +1523,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz",
|
||||||
"integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==",
|
"integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1536,9 +1536,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz",
|
||||||
"integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==",
|
"integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1549,9 +1549,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==",
|
"integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1562,9 +1562,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz",
|
||||||
"integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==",
|
"integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1575,9 +1575,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==",
|
"integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -1588,9 +1588,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==",
|
"integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -1601,9 +1601,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==",
|
"integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -1614,9 +1614,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz",
|
||||||
"integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==",
|
"integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -1627,9 +1627,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==",
|
"integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -1640,9 +1640,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz",
|
||||||
"integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==",
|
"integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1653,9 +1653,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz",
|
||||||
"integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==",
|
"integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1666,9 +1666,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz",
|
||||||
"integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==",
|
"integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1679,9 +1679,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz",
|
||||||
"integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==",
|
"integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -1692,9 +1692,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz",
|
||||||
"integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==",
|
"integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2020,9 +2020,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.69.0",
|
"version": "5.71.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.69.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.71.0.tgz",
|
||||||
"integrity": "sha512-Kn410jq6vs1P8Nm+ZsRj9H+U3C0kjuEkYLxbiCyn3MDEiYor1j2DGVULqAz62SLZtUZ/e9Xt6xMXiJ3NJ65WyQ==",
|
"integrity": "sha512-p4+T7CIEe1kMhii4booWiw42nuaiYI9La/bRCNzBaj1P3PDb0dEZYDhc/7oBifKJfHYN+mtS1ynW1qsmzQW7Og==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2030,12 +2030,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/solid-query": {
|
"node_modules/@tanstack/solid-query": {
|
||||||
"version": "5.69.0",
|
"version": "5.71.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/solid-query/-/solid-query-5.69.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/solid-query/-/solid-query-5.71.0.tgz",
|
||||||
"integrity": "sha512-yWSodAKaTGLRkGHwMe0Gg6Mh11QDoyb7GgjmU8Q0G6MGeAhcPKpPgbALeoNisa8FW5XfW2hFz2vb6zePIdx6rQ==",
|
"integrity": "sha512-Gz3MyZtOda9ppPnoPcaohEqwj3ovlU1Mg1IuOr0BSIe4EzkRW8Wpn47VqMi2I2wtryNStPZ2tIU/N98w8IfO/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.69.0"
|
"@tanstack/query-core": "5.71.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2090,9 +2090,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__traverse": {
|
"node_modules/@types/babel__traverse": {
|
||||||
"version": "7.20.6",
|
"version": "7.20.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
|
||||||
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
|
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2145,9 +2145,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.13",
|
"version": "22.13.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
|
||||||
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
|
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3369,9 +3369,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.123",
|
"version": "1.5.128",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz",
|
||||||
"integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==",
|
"integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -3451,9 +3451,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.2",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
|
||||||
"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
|
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3463,31 +3463,31 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.25.1",
|
"@esbuild/aix-ppc64": "0.25.2",
|
||||||
"@esbuild/android-arm": "0.25.1",
|
"@esbuild/android-arm": "0.25.2",
|
||||||
"@esbuild/android-arm64": "0.25.1",
|
"@esbuild/android-arm64": "0.25.2",
|
||||||
"@esbuild/android-x64": "0.25.1",
|
"@esbuild/android-x64": "0.25.2",
|
||||||
"@esbuild/darwin-arm64": "0.25.1",
|
"@esbuild/darwin-arm64": "0.25.2",
|
||||||
"@esbuild/darwin-x64": "0.25.1",
|
"@esbuild/darwin-x64": "0.25.2",
|
||||||
"@esbuild/freebsd-arm64": "0.25.1",
|
"@esbuild/freebsd-arm64": "0.25.2",
|
||||||
"@esbuild/freebsd-x64": "0.25.1",
|
"@esbuild/freebsd-x64": "0.25.2",
|
||||||
"@esbuild/linux-arm": "0.25.1",
|
"@esbuild/linux-arm": "0.25.2",
|
||||||
"@esbuild/linux-arm64": "0.25.1",
|
"@esbuild/linux-arm64": "0.25.2",
|
||||||
"@esbuild/linux-ia32": "0.25.1",
|
"@esbuild/linux-ia32": "0.25.2",
|
||||||
"@esbuild/linux-loong64": "0.25.1",
|
"@esbuild/linux-loong64": "0.25.2",
|
||||||
"@esbuild/linux-mips64el": "0.25.1",
|
"@esbuild/linux-mips64el": "0.25.2",
|
||||||
"@esbuild/linux-ppc64": "0.25.1",
|
"@esbuild/linux-ppc64": "0.25.2",
|
||||||
"@esbuild/linux-riscv64": "0.25.1",
|
"@esbuild/linux-riscv64": "0.25.2",
|
||||||
"@esbuild/linux-s390x": "0.25.1",
|
"@esbuild/linux-s390x": "0.25.2",
|
||||||
"@esbuild/linux-x64": "0.25.1",
|
"@esbuild/linux-x64": "0.25.2",
|
||||||
"@esbuild/netbsd-arm64": "0.25.1",
|
"@esbuild/netbsd-arm64": "0.25.2",
|
||||||
"@esbuild/netbsd-x64": "0.25.1",
|
"@esbuild/netbsd-x64": "0.25.2",
|
||||||
"@esbuild/openbsd-arm64": "0.25.1",
|
"@esbuild/openbsd-arm64": "0.25.2",
|
||||||
"@esbuild/openbsd-x64": "0.25.1",
|
"@esbuild/openbsd-x64": "0.25.2",
|
||||||
"@esbuild/sunos-x64": "0.25.1",
|
"@esbuild/sunos-x64": "0.25.2",
|
||||||
"@esbuild/win32-arm64": "0.25.1",
|
"@esbuild/win32-arm64": "0.25.2",
|
||||||
"@esbuild/win32-ia32": "0.25.1",
|
"@esbuild/win32-ia32": "0.25.2",
|
||||||
"@esbuild/win32-x64": "0.25.1"
|
"@esbuild/win32-x64": "0.25.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@@ -5315,9 +5315,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nwsapi": {
|
"node_modules/nwsapi": {
|
||||||
"version": "2.2.19",
|
"version": "2.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.19.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
|
||||||
"integrity": "sha512-94bcyI3RsqiZufXjkr3ltkI86iEl+I7uiHVDtcq9wJUTwYQJ5odHDeSzkkrRzi80jJ8MaeZgqKjH1bAWAFw9bA==",
|
"integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -5508,9 +5508,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5902,12 +5902,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.37.0",
|
"version": "4.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz",
|
||||||
"integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==",
|
"integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.6"
|
"@types/estree": "1.0.7"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
@@ -5917,35 +5917,29 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.37.0",
|
"@rollup/rollup-android-arm-eabi": "4.38.0",
|
||||||
"@rollup/rollup-android-arm64": "4.37.0",
|
"@rollup/rollup-android-arm64": "4.38.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.37.0",
|
"@rollup/rollup-darwin-arm64": "4.38.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.37.0",
|
"@rollup/rollup-darwin-x64": "4.38.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.37.0",
|
"@rollup/rollup-freebsd-arm64": "4.38.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.37.0",
|
"@rollup/rollup-freebsd-x64": "4.38.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.37.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.38.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.37.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.38.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.37.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.37.0",
|
"@rollup/rollup-linux-arm64-musl": "4.38.0",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.37.0",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.37.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.37.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.37.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.38.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.37.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.37.0",
|
"@rollup/rollup-linux-x64-gnu": "4.38.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.37.0",
|
"@rollup/rollup-linux-x64-musl": "4.38.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.37.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.38.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.37.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.38.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.37.0",
|
"@rollup/rollup-win32-x64-msvc": "4.38.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@types/estree": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
|
||||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/rrweb-cssom": {
|
"node_modules/rrweb-cssom": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||||
@@ -7429,9 +7423,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
||||||
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export async function get_iwd_service(base_path: string, machine_name: string) {
|
|||||||
if (r.status == "error") {
|
if (r.status == "error") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// @FIXME: Clean this up once we implement the feature
|
||||||
|
// @ts-expect-error: This doesn't check currently
|
||||||
const inventory: Inventory = r.data;
|
const inventory: Inventory = r.data;
|
||||||
|
|
||||||
const instance_key = instance_name(machine_name);
|
const instance_key = instance_name(machine_name);
|
||||||
|
|||||||
Reference in New Issue
Block a user