Compare commits

..

1 Commits

Author SHA1 Message Date
Qubasa
da84b911b0 fix pnpm build error on zfs 2025-09-10 14:02:48 +02:00
442 changed files with 5915 additions and 10422 deletions

View File

@@ -1,12 +0,0 @@
## Description of the change
<!-- Brief summary of the change if not already clear from the title -->
## Checklist
- [ ] Updated Documentation
- [ ] Added tests
- [ ] Doesn't affect backwards compatibility - or check the next points
- [ ] Add the breaking change and migration details to docs/release-notes.md
- !!! Review from another person is required *BEFORE* merge !!!
- [ ] Add introduction of major feature to docs/release-notes.md

View File

@@ -8,6 +8,6 @@ jobs:
runs-on: nix runs-on: nix
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: nix run --print-build-logs .#deploy-docs - run: nix run .#deploy-docs
env: env:
SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }} SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }}

2
.gitignore vendored
View File

@@ -52,5 +52,3 @@ pkgs/clan-app/ui/.fonts
*.gif *.gif
*.mp4 *.mp4
*.mkv *.mkv
.jj

4
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,4 @@
# Contributing to Clan
<!-- Local file: docs/CONTRIBUTING.md -->
Go to the Contributing guide at https://docs.clan.lol/guides/contributing/CONTRIBUTING

View File

@@ -30,7 +30,7 @@ In the Clan ecosystem, security is paramount. Learn how to handle secrets effect
The Clan project thrives on community contributions. We welcome everyone to contribute and collaborate: The Clan project thrives on community contributions. We welcome everyone to contribute and collaborate:
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/guides/contributing/CONTRIBUTING/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->. - **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
## Join the revolution ## Join the revolution

View File

@@ -2,6 +2,7 @@
self, self,
lib, lib,
inputs, inputs,
privateInputs ? { },
... ...
}: }:
let let
@@ -11,6 +12,7 @@ let
elem elem
filter filter
filterAttrs filterAttrs
flip
genAttrs genAttrs
hasPrefix hasPrefix
pathExists pathExists
@@ -18,23 +20,32 @@ let
nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { }; nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { };
in in
{ {
imports = filter pathExists [ imports =
./devshell/flake-module.nix let
./flash/flake-module.nix clanCoreModulesDir = ../nixosModules/clanCore;
./installation/flake-module.nix getClanCoreTestModules =
./update/flake-module.nix let
./morph/flake-module.nix moduleNames = attrNames (builtins.readDir clanCoreModulesDir);
./nixos-documentation/flake-module.nix testPaths = map (
./dont-depend-on-repo-root.nix moduleName: clanCoreModulesDir + "/${moduleName}/tests/flake-module.nix"
# clan core submodule tests ) moduleNames;
../nixosModules/clanCore/machine-id/tests/flake-module.nix in
../nixosModules/clanCore/postgresql/tests/flake-module.nix filter pathExists testPaths;
../nixosModules/clanCore/state-version/tests/flake-module.nix in
]; getClanCoreTestModules
++ filter pathExists [
./devshell/flake-module.nix
./flash/flake-module.nix
./installation/flake-module.nix
./update/flake-module.nix
./morph/flake-module.nix
./nixos-documentation/flake-module.nix
./dont-depend-on-repo-root.nix
];
flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] ( flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] (
system: system:
let let
checks = filterAttrs ( checks = flip filterAttrs self.checks.${system} (
name: _check: name: _check:
!(hasPrefix "nixos-test-" name) !(hasPrefix "nixos-test-" name)
&& !(hasPrefix "nixos-" name) && !(hasPrefix "nixos-" name)
@@ -46,7 +57,7 @@ in
"clan-core-for-checks" "clan-core-for-checks"
"clan-deps" "clan-deps"
]) ])
) self.checks.${system}; );
in in
inputs.nixpkgs.legacyPackages.${system}.runCommand "fast-flake-checks-${system}" inputs.nixpkgs.legacyPackages.${system}.runCommand "fast-flake-checks-${system}"
{ passthru.checks = checks; } { passthru.checks = checks; }
@@ -110,7 +121,7 @@ in
) (self.darwinConfigurations or { }) ) (self.darwinConfigurations or { })
// lib.mapAttrs' (n: lib.nameValuePair "package-${n}") ( // lib.mapAttrs' (n: lib.nameValuePair "package-${n}") (
if system == "aarch64-darwin" then if system == "aarch64-darwin" then
lib.filterAttrs (n: _: n != "docs" && n != "deploy-docs" && n != "option-search") packagesToBuild lib.filterAttrs (n: _: n != "docs" && n != "deploy-docs" && n != "docs-options") packagesToBuild
else else
packagesToBuild packagesToBuild
) )
@@ -128,7 +139,7 @@ in
// flakeOutputs // flakeOutputs
// { // {
clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } '' clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } ''
cp -r ${self} $out cp -r ${privateInputs.clan-core-for-checks} $out
chmod -R +w $out chmod -R +w $out
cp ${../flake.lock} $out/flake.lock cp ${../flake.lock} $out/flake.lock

View File

@@ -13,6 +13,8 @@
fileSystems."/".device = lib.mkDefault "/dev/vda"; fileSystems."/".device = lib.mkDefault "/dev/vda";
boot.loader.grub.device = lib.mkDefault "/dev/vda"; boot.loader.grub.device = lib.mkDefault "/dev/vda";
# We need to use `mkForce` because we inherit from `test-install-machine`
# which currently hardcodes `nixpkgs.hostPlatform`
nixpkgs.hostPlatform = lib.mkForce system; nixpkgs.hostPlatform = lib.mkForce system;
imports = [ self.nixosModules.test-flash-machine ]; imports = [ self.nixosModules.test-flash-machine ];
@@ -26,24 +28,10 @@
{ {
imports = [ self.nixosModules.test-install-machine-without-system ]; imports = [ self.nixosModules.test-install-machine-without-system ];
# We don't want our system to define any `vars` generators as these can't
# be generated as the flake is inside `/nix/store`.
clan.core.settings.state-version.enable = false;
clan.core.vars.generators.test = lib.mkForce { }; clan.core.vars.generators.test = lib.mkForce { };
disko.devices.disk.main.preCreateHook = lib.mkForce ""; disko.devices.disk.main.preCreateHook = lib.mkForce "";
# Every option here should match the options set through `clan flash write`
# if you get a mass rebuild on the disko derivation, this means you need to
# adjust something here. Also make sure that the injected json in clan flash write
# is up to date.
i18n.defaultLocale = "de_DE.UTF-8";
console.keyMap = "de";
services.xserver.xkb.layout = "de";
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target\n"
];
}; };
}; };
perSystem = perSystem =
@@ -56,15 +44,13 @@
dependencies = [ dependencies = [
pkgs.disko pkgs.disko
pkgs.buildPackages.xorg.lndir pkgs.buildPackages.xorg.lndir
pkgs.glibcLocales
pkgs.kbd.out
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
pkgs.bubblewrap
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
] ]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
@@ -88,7 +74,7 @@
substituters = lib.mkForce [ ]; substituters = lib.mkForce [ ];
hashed-mirrors = null; hashed-mirrors = null;
connect-timeout = lib.mkForce 3; connect-timeout = lib.mkForce 3;
flake-registry = ""; flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
experimental-features = [ experimental-features = [
"nix-command" "nix-command"
"flakes" "flakes"
@@ -97,10 +83,10 @@
}; };
testScript = '' testScript = ''
start_all() start_all()
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
# Some distros like to automount disks with spaces # Some distros like to automount disks with spaces
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"') machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}") machine.succeed("clan flash write --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
''; '';
} { inherit pkgs self; }; } { inherit pkgs self; };
}; };

View File

@@ -0,0 +1,10 @@
system:
builtins.fetchurl {
url = "https://git.clan.lol/clan/test-fixtures/raw/commit/4a2bc56d886578124b05060d3fb7eddc38c019f8/nixos-vm-facter-json/${system}.json";
sha256 =
{
aarch64-linux = "sha256:1rlfymk03rmfkm2qgrc8l5kj5i20srx79n1y1h4nzlpwaz0j7hh2";
x86_64-linux = "sha256:16myh0ll2gdwsiwkjw5ba4dl23ppwbsanxx214863j7nvzx42pws";
}
.${system};
}

View File

@@ -1,8 +1,8 @@
{ {
config,
self, self,
lib, lib,
privateInputs, privateInputs,
... ...
}: }:
{ {
@@ -14,37 +14,26 @@
# you can get a new one by adding # you can get a new one by adding
# client.fail("cat test-flake/machines/test-install-machine/facter.json >&2") # client.fail("cat test-flake/machines/test-install-machine/facter.json >&2")
# to the installation test. # to the installation test.
clan.machines = { clan.machines.test-install-machine-without-system = {
test-install-machine-without-system = { fileSystems."/".device = lib.mkDefault "/dev/vda";
boot.loader.grub.device = lib.mkDefault "/dev/vda";
imports = [
self.nixosModules.test-install-machine-without-system
];
};
clan.machines.test-install-machine-with-system =
{ pkgs, ... }:
{
# https://git.clan.lol/clan/test-fixtures
facter.reportPath = import ./facter-report.nix pkgs.hostPlatform.system;
fileSystems."/".device = lib.mkDefault "/dev/vda"; fileSystems."/".device = lib.mkDefault "/dev/vda";
boot.loader.grub.device = lib.mkDefault "/dev/vda"; boot.loader.grub.device = lib.mkDefault "/dev/vda";
imports = [ imports = [ self.nixosModules.test-install-machine-without-system ];
self.nixosModules.test-install-machine-without-system
];
}; };
}
// (lib.listToAttrs (
lib.map (
system:
lib.nameValuePair "test-install-machine-${system}" {
imports = [
self.nixosModules.test-install-machine-without-system
(
if privateInputs ? test-fixtures then
{
facter.reportPath = privateInputs.test-fixtures + /nixos-vm-facter-json/${system}.json;
}
else
{ nixpkgs.hostPlatform = system; }
)
];
fileSystems."/".device = lib.mkDefault "/dev/vda";
boot.loader.grub.device = lib.mkDefault "/dev/vda";
}
) (lib.filter (lib.hasSuffix "linux") config.systems)
));
flake.nixosModules = { flake.nixosModules = {
test-install-machine-without-system = test-install-machine-without-system =
@@ -160,12 +149,13 @@
closureInfo = pkgs.closureInfo { closureInfo = pkgs.closureInfo {
rootPaths = [ rootPaths = [
privateInputs.clan-core-for-checks privateInputs.clan-core-for-checks
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.initialRamdisk self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
pkgs.stdenv.drvPath pkgs.stdenv.drvPath
pkgs.bash.drvPath pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir pkgs.buildPackages.xorg.lndir
(import ./facter-report.nix pkgs.hostPlatform.system)
] ]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
}; };
@@ -215,7 +205,7 @@
# Prepare test flake and Nix store # Prepare test flake and Nix store
flake_dir = prepare_test_flake( flake_dir = prepare_test_flake(
temp_dir, temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", "${self.checks.x86_64-linux.clan-core-for-checks}",
"${closureInfo}" "${closureInfo}"
) )
@@ -226,22 +216,6 @@
"${../assets/ssh/privkey}" "${../assets/ssh/privkey}"
) )
# Run clan install from host using port forwarding
clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines",
"init-hardware-config",
"--debug",
"--flake", str(flake_dir),
"--yes", "test-install-machine-without-system",
"--host-key-check", "none",
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE']
]
subprocess.run(clan_cmd, check=True)
# Run clan install from host using port forwarding # Run clan install from host using port forwarding
clan_cmd = [ clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan", "${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
@@ -296,7 +270,7 @@
# Prepare test flake and Nix store # Prepare test flake and Nix store
flake_dir = prepare_test_flake( flake_dir = prepare_test_flake(
temp_dir, temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", "${self.checks.x86_64-linux.clan-core-for-checks}",
"${closureInfo}" "${closureInfo}"
) )

View File

@@ -15,6 +15,7 @@ let
networking.useNetworkd = true; networking.useNetworkd = true;
services.openssh.enable = true; services.openssh.enable = true;
services.openssh.settings.UseDns = false; services.openssh.settings.UseDns = false;
services.openssh.settings.PasswordAuthentication = false;
system.nixos.variant_id = "installer"; system.nixos.variant_id = "installer";
environment.systemPackages = [ environment.systemPackages = [
pkgs.nixos-facter pkgs.nixos-facter
@@ -146,11 +147,28 @@ let
]; ];
doCheck = false; doCheck = false;
}; };
# Common closure info
closureInfo = pkgs.closureInfo {
rootPaths = [
self.checks.x86_64-linux.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.clan.deployment.file
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir
]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
};
in in
{ {
inherit inherit
target target
baseTestMachine baseTestMachine
nixosTestLib nixosTestLib
closureInfo
; ;
} }

View File

@@ -35,6 +35,7 @@
pkgs.stdenv.drvPath pkgs.stdenv.drvPath
pkgs.stdenvNoCC pkgs.stdenvNoCC
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
] ]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; }; closureInfo = pkgs.closureInfo { rootPaths = dependencies; };

View File

@@ -29,34 +29,32 @@ nixosLib.runTest (
{ nodes, ... }: { nodes, ... }:
'' ''
import subprocess import subprocess
import tempfile from nixos_test_lib.nix_setup import setup_nix_in_nix # type: ignore[import-untyped]
from nixos_test_lib.nix_setup import setup_nix_in_nix
with tempfile.TemporaryDirectory() as temp_dir: setup_nix_in_nix(None) # No closure info for this test
setup_nix_in_nix(temp_dir, None) # No closure info for this test
start_all() start_all()
admin1.wait_for_unit("multi-user.target") admin1.wait_for_unit("multi-user.target")
peer1.wait_for_unit("multi-user.target") peer1.wait_for_unit("multi-user.target")
# peer1 should have the 'hello' file # peer1 should have the 'hello' file
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}") peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}") ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}")
# Check that the file is owned by 'nobody' # Check that the file is owned by 'nobody'
assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}" assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}"
# Check that the file is in the 'users' group # Check that the file is in the 'users' group
assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}" assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}"
# Check that the file is in the '0644' mode # Check that the file is in the '0644' mode
assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}" assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}"
# Run clan command # Run clan command
result = subprocess.run( result = subprocess.run(
["${ ["${
clan-core.packages.${hostPkgs.system}.clan-cli clan-core.packages.${hostPkgs.system}.clan-cli
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"], }/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
check=True check=True
) )
''; '';
} }
) )

View File

@@ -27,9 +27,7 @@
modules.new-service = { modules.new-service = {
_class = "clan.service"; _class = "clan.service";
manifest.name = "new-service"; manifest.name = "new-service";
roles.peer = { roles.peer = { };
description = "A peer that uses the new-service to generate some files.";
};
perMachine = { perMachine = {
nixosModule = { nixosModule = {
# This should be generated by: # This should be generated by:

View File

@@ -34,9 +34,7 @@ nixosLib.runTest (
modules.new-service = { modules.new-service = {
_class = "clan.service"; _class = "clan.service";
manifest.name = "new-service"; manifest.name = "new-service";
roles.peer = { roles.peer = { };
description = "A peer that uses the new-service to generate some files.";
};
perMachine = { perMachine = {
nixosModule = { nixosModule = {
# This should be generated by: # This should be generated by:

View File

@@ -67,15 +67,6 @@
]; ];
}; };
nix.settings = {
flake-registry = "";
# required for setting the `flake-registry`
experimental-features = [
"nix-command"
"flakes"
];
};
# Define the mounts that exist in the container to prevent them from being stopped # Define the mounts that exist in the container to prevent them from being stopped
fileSystems = { fileSystems = {
"/" = { "/" = {
@@ -115,13 +106,13 @@
let let
closureInfo = pkgs.closureInfo { closureInfo = pkgs.closureInfo {
rootPaths = [ rootPaths = [
self.packages.${pkgs.hostPlatform.system}.clan-cli self.packages.${pkgs.system}.clan-cli
self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks self.checks.${pkgs.system}.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
pkgs.stdenv.drvPath pkgs.stdenv.drvPath
pkgs.bash.drvPath pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir pkgs.buildPackages.xorg.lndir
pkgs.bubblewrap (import ../installation/facter-report.nix pkgs.hostPlatform.system)
] ]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs); ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
}; };
@@ -132,7 +123,7 @@
imports = [ self.nixosModules.test-update-machine ]; imports = [ self.nixosModules.test-update-machine ];
}; };
extraPythonPackages = _p: [ extraPythonPackages = _p: [
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib self.legacyPackages.${pkgs.system}.nixosTestLib
]; ];
testScript = '' testScript = ''
@@ -154,7 +145,7 @@
# Prepare test flake and Nix store # Prepare test flake and Nix store
flake_dir = prepare_test_flake( flake_dir = prepare_test_flake(
temp_dir, temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}", "${self.checks.x86_64-linux.clan-core-for-checks}",
"${closureInfo}" "${closureInfo}"
) )
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists (flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
@@ -221,13 +212,12 @@
[ [
"${pkgs.nix}/bin/nix", "${pkgs.nix}/bin/nix",
"copy", "copy",
"--from",
f"{temp_dir}/store",
"--to", "--to",
"ssh://root@192.168.1.1", "ssh://root@192.168.1.1",
"--no-check-sigs", "--no-check-sigs",
f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}", f"${self.packages.${pkgs.system}.clan-cli}",
"--extra-experimental-features", "nix-command flakes", "--extra-experimental-features", "nix-command flakes",
"--from", f"{os.environ["TMPDIR"]}/store"
], ],
check=True, check=True,
env={ env={
@@ -242,7 +232,7 @@
"-o", "UserKnownHostsFile=/dev/null", "-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no", "-o", "StrictHostKeyChecking=no",
f"root@192.168.1.1", f"root@192.168.1.1",
"${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan", "${self.packages.${pkgs.system}.clan-cli}/bin/clan",
"machines", "machines",
"update", "update",
"--debug", "--debug",
@@ -270,7 +260,7 @@
# Run clan update command # Run clan update command
subprocess.run([ subprocess.run([
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan", "${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines", "machines",
"update", "update",
"--debug", "--debug",
@@ -297,7 +287,7 @@
# Run clan update command with --build-host # Run clan update command with --build-host
subprocess.run([ subprocess.run([
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan", "${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines", "machines",
"update", "update",
"--debug", "--debug",

View File

@@ -1,14 +1,15 @@
{ ... }:
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/admin"; manifest.name = "clan-core/admin";
manifest.description = "Adds a root user with ssh access"; manifest.description = "Convenient Administration for the Clan App";
manifest.categories = [ "Utility" ]; manifest.categories = [ "Utility" ];
roles.default = { roles.default = {
description = "Placeholder role to apply the admin service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
options = { options = {
allowedKeys = lib.mkOption { allowedKeys = lib.mkOption {
default = { }; default = { };

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -9,7 +9,7 @@ inventory.instances = {
}; };
roles.client.machines."jon".settings = { roles.client.machines."jon".settings = {
destinations."storagebox" = { destinations."storagebox" = {
repo = "username@hostname:/./borgbackup"; repo = "username@$hostname:/./borgbackup";
rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh''; rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh'';
}; };
}; };

View File

@@ -9,7 +9,7 @@
# TODO: a client can only be in one instance, add constraint # TODO: a client can only be in one instance, add constraint
roles.server = { roles.server = {
description = "A borgbackup server that stores the backups of clients.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -54,7 +54,7 @@
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machineName)) ]; authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machineName)) ];
# }; # };
# }) machinesWithKey; # }) machinesWithKey;
}) (roles.client.machines or { }); }) roles.client.machines;
in in
hosts; hosts;
}; };
@@ -62,7 +62,6 @@
}; };
roles.client = { roles.client = {
description = "A borgbackup client that backs up to all borgbackup server roles.";
interface = interface =
{ {
lib, lib,
@@ -188,7 +187,7 @@
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes"; } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
}; };
}) (builtins.attrNames (roles.server.machines or { })); }) (builtins.attrNames roles.server.machines);
in in
(builtins.listToAttrs destinations); (builtins.listToAttrs destinations);

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -2,12 +2,12 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "certificates"; manifest.name = "certificates";
manifest.description = "Sets up a PKI certificate chain using step-ca"; manifest.description = "Sets up a certificates internal to your Clan";
manifest.categories = [ "Network" ]; manifest.categories = [ "Network" ];
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.ca = { roles.ca = {
description = "A certificate authority that issues and signs certificates for other machines.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -184,7 +184,6 @@
# Empty role, so we can add non-ca machins to the instance to trust the CA # Empty role, so we can add non-ca machins to the instance to trust the CA
roles.default = { roles.default = {
description = "A machine that trusts the CA and can get certificates issued by it.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,8 +1,12 @@
{ {
self,
lib,
... ...
}: }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in in
{ {
clan.modules.certificates = module; clan.modules.certificates = module;

View File

@@ -8,7 +8,7 @@ The service consists of two roles:
- A `server` role: This is the DNS-server that will be queried when trying to - A `server` role: This is the DNS-server that will be queried when trying to
resolve clan-internal services. It defines the top-level domain. resolve clan-internal services. It defines the top-level domain.
- A `default` role: This does two things. First, it sets up the nameservers so - A `default` role: This does two things. First, it sets up the nameservers so
that clan-internal queries are resolved via the `server` machine, while thatclan-internal queries are resolved via the `server` machine, while
external queries are resolved as normal via DHCP. Second, it allows exposing external queries are resolved as normal via DHCP. Second, it allows exposing
services (see example below). services (see example below).
@@ -45,15 +45,13 @@ inventory = {
# Add the default role to all machines, including `client` # Add the default role to all machines, including `client`
roles.default.tags.all = { }; roles.default.tags.all = { };
# DNS server queries to http://<name>.foo are resolved here # DNS server
roles.server.machines."dnsserver".settings = { roles.server.machines."dnsserver".settings = {
ip = "192.168.1.2"; ip = "192.168.1.2";
tld = "foo"; tld = "foo";
}; };
# First service # First service
# Registers http://one.foo will resolve to 192.168.1.3
# underlying service runs on server01
roles.default.machines."server01".settings = { roles.default.machines."server01".settings = {
ip = "192.168.1.3"; ip = "192.168.1.3";
services = [ "one" ]; services = [ "one" ];

View File

@@ -8,7 +8,7 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.server = { roles.server = {
description = "A DNS server that resolves services in the clan network.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -103,7 +103,6 @@
}; };
roles.default = { roles.default = {
description = "A machine that registers the 'server' role as resolver and registers services under the configured TLD in the resolver.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -50,13 +50,13 @@
dns = dns =
{ pkgs, ... }: { pkgs, ... }:
{ {
environment.systemPackages = [ pkgs.nettools ]; environment.systemPackages = [ pkgs.net-tools ];
}; };
client = client =
{ pkgs, ... }: { pkgs, ... }:
{ {
environment.systemPackages = [ pkgs.nettools ]; environment.systemPackages = [ pkgs.net-tools ];
}; };
server01 = { server01 = {

View File

@@ -101,7 +101,6 @@ in
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.admin = { roles.admin = {
description = "A data-mesher admin node that bootstraps the network and can sign new nodes into the network.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -178,7 +177,6 @@ in
}; };
roles.signer = { roles.signer = {
description = "A data-mesher signer node that can sign new nodes into the network.";
interface = sharedInterface; interface = sharedInterface;
perInstance = perInstance =
{ {
@@ -210,7 +208,6 @@ in
}; };
roles.peer = { roles.peer = {
description = "A data-mesher peer node that connects to the network.";
interface = sharedInterface; interface = sharedInterface;
perInstance = perInstance =
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -2,12 +2,11 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/dyndns"; manifest.name = "clan-core/dyndns";
manifest.description = "A dynamic DNS service to auto update domain IPs"; manifest.description = "A dynamic DNS service to update domain IPs";
manifest.categories = [ "Network" ]; manifest.categories = [ "Network" ];
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = {
description = "Placeholder role to apply the dyndns service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -2,34 +2,31 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/emergency-access"; manifest.name = "clan-core/emergency-access";
manifest.description = "Set recovery password for emergency access to machine to debug boot issues"; manifest.description = "Set recovery password for emergency access to machine";
manifest.categories = [ "System" ]; manifest.categories = [ "System" ];
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default.perInstance = {
description = "Placeholder role to apply the emergency-access service"; nixosModule =
perInstance = { { config, pkgs, ... }:
nixosModule = {
{ config, pkgs, ... }: boot.initrd.systemd.emergencyAccess =
{ config.clan.core.vars.generators.emergency-access.files.password-hash.value;
boot.initrd.systemd.emergencyAccess =
config.clan.core.vars.generators.emergency-access.files.password-hash.value;
clan.core.vars.generators.emergency-access = { clan.core.vars.generators.emergency-access = {
runtimeInputs = [ runtimeInputs = [
pkgs.coreutils pkgs.coreutils
pkgs.mkpasswd pkgs.mkpasswd
pkgs.xkcdpass pkgs.xkcdpass
]; ];
files.password.deploy = false; files.password.deploy = false;
files.password-hash.secret = false; files.password-hash.secret = false;
script = '' script = ''
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password
mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash
''; '';
};
}; };
}; };
}; };
} }

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
{ {
clan.modules = { clan.modules = {
emergency-access = ./default.nix; emergency-access = lib.modules.importApply ./default.nix { };
}; };
} }

View File

@@ -6,7 +6,7 @@
manifest.categories = [ "System" ]; manifest.categories = [ "System" ];
roles.default = { roles.default = {
description = "Placeholder role to apply the garage service";
perInstance.nixosModule = perInstance.nixosModule =
{ {
config, config,

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -4,6 +4,7 @@
# The test for this module in ./tests/vm/default.nix shows an example of how # The test for this module in ./tests/vm/default.nix shows an example of how
# the service is used. # the service is used.
{ packages }:
{ ... }: { ... }:
{ {
_class = "clan.service"; _class = "clan.service";
@@ -14,7 +15,6 @@
# defined in this file directly (e.g. the "morning" role) or split up into a # defined in this file directly (e.g. the "morning" role) or split up into a
# separate file (e.g. the "evening" role) # separate file (e.g. the "evening" role)
roles.morning = { roles.morning = {
description = "A morning greeting machine";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -34,17 +34,20 @@
settings, settings,
# The name of this instance of the service # The name of this instance of the service
instanceName,
# The current machine # The current machine
machine,
# All roles of this service, with their assigned machines # All roles of this service, with their assigned machines
roles,
... ...
}: }:
{ {
# Analog to 'perSystem' of flake-parts. # Analog to 'perSystem' of flake-parts.
# For every instance of this service we will add a nixosModule to a morning-machine # For every instance of this service we will add a nixosModule to a morning-machine
nixosModule = nixosModule =
{ ... }: { config, ... }:
{ {
# Interaction examples what you could do here: # Interaction examples what you could do here:
# - Get some settings of this machine # - Get some settings of this machine
@@ -68,7 +71,6 @@
# the interface here, so we can see all settings of the service in one place, # the interface here, so we can see all settings of the service in one place,
# but you can also move it to the respective file # but you can also move it to the respective file
roles.evening = { roles.evening = {
description = "An evening greeting machine";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -5,7 +5,9 @@
... ...
}: }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -6,7 +6,5 @@
manifest.categories = [ "Utility" ]; manifest.categories = [ "Utility" ];
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = { };
description = "Placeholder role to apply the importer service";
};
} }

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
{ {
clan.modules = { clan.modules = {
importer = ./default.nix; importer = lib.modules.importApply ./default.nix { };
}; };
} }

View File

@@ -2,13 +2,12 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/internet"; manifest.name = "clan-core/internet";
manifest.description = "Part of the clan networking abstraction to define how to reach machines from outside the clan network over the internet, if defined has the highest priority"; manifest.description = "direct access (or via ssh jumphost) to machines";
manifest.categories = [ manifest.categories = [
"System" "System"
"Network" "Network"
]; ];
roles.default = { roles.default = {
description = "Placeholder role to apply the internet service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -32,6 +31,7 @@
{ {
roles, roles,
lib, lib,
settings,
... ...
}: }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -2,12 +2,11 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "localbackup"; manifest.name = "localbackup";
manifest.description = "Automatically backups current machine to local directory or a mounted drive."; manifest.description = "Automatically backups current machine to local directory.";
manifest.categories = [ "System" ]; manifest.categories = [ "System" ];
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = {
description = "Placeholder role to apply the localbackup service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules.localbackup = module; clan.modules.localbackup = module;

View File

@@ -6,7 +6,6 @@
manifest.categories = [ "Social" ]; manifest.categories = [ "Social" ];
roles.default = { roles.default = {
description = "Placeholder role to apply the matrix-synapse service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -145,7 +144,7 @@
}; };
} }
// lib.mapAttrs' ( // lib.mapAttrs' (
_name: user: name: user:
lib.nameValuePair "matrix-password-${user.name}" { lib.nameValuePair "matrix-password-${user.name}" {
files."matrix-password-${user.name}" = { }; files."matrix-password-${user.name}" = { };
migrateFact = "matrix-password-${user.name}"; migrateFact = "matrix-password-${user.name}";

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules.matrix-synapse = module; clan.modules.matrix-synapse = module;

View File

@@ -1,3 +1,4 @@
{ packages }:
{ ... }: { ... }:
{ {
_class = "clan.service"; _class = "clan.service";
@@ -6,20 +7,19 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.telegraf = { roles.telegraf = {
description = "Placeholder role to apply the telegraf monitoring agent";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
options.allowAllInterfaces = lib.mkOption { options.allowAllInterfaces = lib.mkOption {
type = lib.types.nullOr lib.types.bool; type = lib.types.bool;
default = null; default = false;
description = "Deprecated. Has no effect."; description = "If true, Telegraf will listen on all interfaces. Otherwise, it will only listen on the interfaces specified in `interfaces`";
}; };
options.interfaces = lib.mkOption { options.interfaces = lib.mkOption {
type = lib.types.nullOr (lib.types.listOf lib.types.str); type = lib.types.listOf lib.types.str;
default = null; default = [ "zt+" ];
description = "Deprecated. Has no effect."; description = "List of interfaces to expose the metrics to";
}; };
}; };
}; };

View File

@@ -1,24 +1,22 @@
{ {
lib,
self, self,
lib,
... ...
}: }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in in
{ {
clan.modules.monitoring = module; clan.modules.monitoring = module;
perSystem = perSystem =
{ pkgs, ... }: { ... }:
{ {
clan.nixosTests.monitoring = { clan.nixosTests.monitoring = {
imports = [ imports = [ ./tests/vm/default.nix ];
(lib.modules.importApply ./tests/vm/default.nix {
inherit (self) packages;
inherit pkgs;
})
];
clan.modules.monitoring = module; clan.modules.monitoring = module;
}; };
}; };

View File

@@ -11,54 +11,34 @@
... ...
}: }:
let let
jsonpath = "/tmp/telegraf.json";
auth_user = "prometheus"; auth_user = "prometheus";
in in
{ {
warnings =
lib.optionals (settings.allowAllInterfaces != null) [
"monitoring.settings.allowAllInterfaces is deprecated and and has no effect. Please remove it from your inventory."
"The monitoring service will now always listen on all interfaces over https."
]
++ (lib.optionals (settings.interfaces != null) [
"monitoring.settings.interfaces is deprecated and and has no effect. Please remove it from your inventory."
"The monitoring service will now always listen on all interfaces over https."
]);
networking.firewall.allowedTCPPorts = [ networking.firewall.interfaces = lib.mkIf (settings.allowAllInterfaces == false) (
builtins.listToAttrs (
map (name: {
inherit name;
value.allowedTCPPorts = [
9273
9990
];
}) settings.interfaces
)
);
networking.firewall.allowedTCPPorts = lib.mkIf (settings.allowAllInterfaces == true) [
9273 9273
9990 9990
]; ];
clan.core.vars.generators."telegraf-certs" = {
files.crt = {
restartUnits = [ "telegraf.service" ];
deploy = true;
secret = false;
};
files.key = {
mode = "0600";
restartUnits = [ "telegraf.service" ];
};
runtimeInputs = [
pkgs.openssl
];
script = ''
openssl req -x509 -nodes -newkey rsa:4096 \
-keyout "$out"/key \
-out "$out"/crt \
-subj "/C=US/ST=CA/L=San Francisco/O=Example Corp/OU=IT/CN=example.com"
'';
};
clan.core.vars.generators."telegraf" = { clan.core.vars.generators."telegraf" = {
files.password.restartUnits = [ "telegraf.service" ]; files.password.restartUnits = [ "telegraf.service" ];
files.password-env.restartUnits = [ "telegraf.service" ]; files.password-env.restartUnits = [ "telegraf.service" ];
files.miniserve-auth.restartUnits = [ "telegraf.service" ]; files.miniserve-auth.restartUnits = [ "telegraf.service" ];
dependencies = [ "telegraf-certs" ];
runtimeInputs = [ runtimeInputs = [
pkgs.coreutils pkgs.coreutils
pkgs.xkcdpass pkgs.xkcdpass
@@ -77,37 +57,11 @@
enable = true; enable = true;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "telegraf.service" ]; after = [ "telegraf.service" ];
requires = [ "telegraf.service" ]; wants = [ "telegraf.service" ];
serviceConfig = { serviceConfig = {
LoadCredential = [
"auth_file_path:${config.clan.core.vars.generators.telegraf.files.miniserve-auth.path}"
"telegraf_crt_path:${config.clan.core.vars.generators.telegraf-certs.files.crt.path}"
"telegraf_key_path:${config.clan.core.vars.generators.telegraf-certs.files.key.path}"
];
Environment = [
"AUTH_FILE_PATH=%d/auth_file_path"
"CRT_PATH=%d/telegraf_crt_path"
"KEY_PATH=%d/telegraf_key_path"
];
Restart = "on-failure"; Restart = "on-failure";
User = "telegraf";
Group = "telegraf";
RuntimeDirectory = "telegraf-www";
};
script = "${pkgs.miniserve}/bin/miniserve -p 9990 /run/telegraf-www --auth-file \"$AUTH_FILE_PATH\" --tls-cert \"$CRT_PATH\" --tls-key \"$KEY_PATH\"";
};
systemd.services.telegraf = {
serviceConfig = {
LoadCredential = [
"telegraf_crt_path:${config.clan.core.vars.generators.telegraf-certs.files.crt.path}"
"telegraf_key_path:${config.clan.core.vars.generators.telegraf-certs.files.key.path}"
];
Environment = [
"CRT_PATH=%d/telegraf_crt_path"
"KEY_PATH=%d/telegraf_key_path"
];
}; };
script = "${pkgs.miniserve}/bin/miniserve -p 9990 ${jsonpath} --auth-file ${config.clan.core.vars.generators.telegraf.files.miniserve-auth.path}";
}; };
services.telegraf = { services.telegraf = {
@@ -115,7 +69,6 @@
environmentFiles = [ environmentFiles = [
(builtins.toString config.clan.core.vars.generators.telegraf.files.password-env.path) (builtins.toString config.clan.core.vars.generators.telegraf.files.password-env.path)
]; ];
extraConfig = { extraConfig = {
agent.interval = "60s"; agent.interval = "60s";
inputs = { inputs = {
@@ -151,12 +104,10 @@
metric_version = 2; metric_version = 2;
basic_username = "${auth_user}"; basic_username = "${auth_user}";
basic_password = "$${BASIC_AUTH_PWD}"; basic_password = "$${BASIC_AUTH_PWD}";
tls_cert = "$${CRT_PATH}";
tls_key = "$${KEY_PATH}";
}; };
outputs.file = { outputs.file = {
files = [ "/run/telegraf-www/telegraf.json" ]; files = [ jsonpath ];
data_format = "json"; data_format = "json";
json_timestamp_units = "1s"; json_timestamp_units = "1s";
}; };

View File

@@ -1,95 +1,24 @@
{ ... }:
{ {
name = "monitoring"; name = "monitoring";
clan = { clan = {
directory = ./.; directory = ./.;
inventory = { inventory = {
machines.peer1 = { machines.peer1 = { };
deploy.targetHost = "[2001:db8:1::1]";
};
instances."test" = { instances."test" = {
module.name = "monitoring"; module.name = "monitoring";
module.input = "self"; module.input = "self";
roles.telegraf.machines.peer1 = { }; roles.telegraf.machines.peer1 = { };
}; };
}; };
}; };
nodes = {
peer1 =
{ lib, ... }:
{
services.telegraf.extraConfig = {
agent.interval = lib.mkForce "1s";
outputs.prometheus_client = {
# BUG: We have to disable basic auth here because the prometheus_client
# output plugin will otherwise deadlock Telegraf on startup.
basic_password = lib.mkForce "";
basic_username = lib.mkForce "";
};
};
};
};
# !!! ANY CHANGES HERE MUST BE REFLECTED IN:
# clan_lib/metrics/telegraf.py::get_metrics
testScript = testScript =
{ nodes, ... }: { ... }:
'' ''
import time
import os
import sys
import subprocess
import ssl
import json
import shlex
import urllib.request
from base64 import b64encode
start_all() start_all()
peer1.wait_for_unit("network-online.target")
peer1.wait_for_unit("telegraf.service")
peer1.wait_for_unit("telegraf-json.service")
# Fetch the basic auth password from the secret file
password = peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.telegraf.files.password.path}").strip()
credentials = f"prometheus:{password}"
print("Using credentials:", credentials)
peer1.succeed(f"curl -k -u {credentials} https://localhost:9990/telegraf.json")
peer1.succeed(f"curl -k -u {credentials} https://localhost:9273/metrics")
cert_path = "${nodes.peer1.clan.core.vars.generators.telegraf-certs.files.crt.path}"
url = "https://192.168.1.1:9990/telegraf.json" # HTTPS required
print("Waiting for /var/run/telegraf-www/telegraf.json to be bigger then 200 bytes")
peer1.wait_until_succeeds(f"test \"$(stat -c%s /var/run/telegraf-www/telegraf.json)\" -ge 200", timeout=30)
encoded_credentials = b64encode(credentials.encode("utf-8")).decode("utf-8")
headers = {"Authorization": f"Basic {encoded_credentials}"}
req = urllib.request.Request(url, headers=headers) # noqa: S310
# Trust the provided CA/server certificate
context = ssl.create_default_context(cafile=cert_path)
context.check_hostname = False
context.verify_mode = ssl.CERT_REQUIRED
found_system = False
with urllib.request.urlopen(req, context=context, timeout=5) as response:
for raw_line in response:
line_str = raw_line.decode("utf-8").strip()
if not line_str:
continue
obj = json.loads(line_str)
if obj.get("name") == "nixos_systems":
found_system = True
print("Found nixos_systems metric in json output")
break
assert found_system, "nixos_systems metric not found in json output"
''; '';
} }

View File

@@ -1,6 +0,0 @@
[
{
"publickey": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"type": "age"
}
]

View File

@@ -1,15 +0,0 @@
{
"data": "ENC[AES256_GCM,data:ACFpRJRDIgVPurZwHYW0J1MnvyuiRGnXMeQj1nb9rDAIqHbZzZk8+E0Nu1+EdXwk78ziP6tHR1GQP2ILTtpLME4lXXRVjouW5Eo=,iv:ctR1HENO3XGIq1/gzYi47nateYzsSK317EKn92ptqDI=,tag:q1yuk/ZMx3nuORkiT/XXqg==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvMUtabnp3V0dzNFFYRzk0\nd0ZJbUtDMXRPRGxpRjhYR1MyQzdJYWdJTUFrCjBNV0pPTTlIOHBBbzlEQkFzVy92\ndENxcDdIZlNDSm1oZTNveUtIeVc3MXcKLS0tIGtocENjMFNYT0s1LzhYNy92QU5G\nREVEdjErb0xPSE1yb0g5bGlackh6bEUKwxBoDteD7+JfnlFF71CHx4oEdV/TFYcF\n3JPYUbTWAIyMtUu/CLbX+Pn9Mv+McrEIqhwT7TWL/YbELKVadX/k5Q==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:37Z",
"mac": "ENC[AES256_GCM,data:4631iJmioJ2vZ2PTFbdEJu7UqtyQbp43XBlgEbFAviGZdugb3weVI24rJ8m1Rdnxq8uciEeiX6YHBhURdWQY4JNm2wTGnjz7e2PwQ8FCwOmxCcIQPpdKKsziq/M4HArgD66eUxIWfTt1yJfHgBcUuuANbrbH8MirllT+hJTBhqE=,iv:rM8a/MpKbK7DlqjuR4BG77XDHLK11Q+E2rzZLDJalhk=,tag:bbGMn4anXrLHg4eLA0/CXA==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../users/admin

View File

@@ -1,4 +0,0 @@
{
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"type": "age"
}

View File

@@ -1,33 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFuTCCA6GgAwIBAgIUMXnA00bMrYvYSq0PjU5/HhXTpmcwDQYJKoZIhvcNAQEL
BQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
bmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxCzAJBgNVBAsMAklUMRQwEgYD
VQQDDAtleGFtcGxlLmNvbTAeFw0yNTA5MTgxNDMzMzZaFw0yNTEwMTgxNDMzMzZa
MGwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5j
aXNjbzEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMQswCQYDVQQLDAJJVDEUMBIGA1UE
AwwLZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7
sdy27E/XMAyKrgeFcXY70R/vX0gx6EcZlWGp2vZSUVAfW1ni/Vq/LVC02sxGEGwv
10+42yP2yghi89doKo8oCoLsbVu+Pi+TmRsgAijy4jN8pHqbn9/Vk8M8utLa1u4z
VonSIx9pzCYd2+IIdwVuWoyPAAnK/JIKS3n0A8KWkZ/1lq6YDl2whj8iY4YF2Ekg
M0SWhquLZiaApAs7STTYvcP7iLfL4U6cH65dRAbwWMpMErPuLf/CedkXiSUp8Zqx
YIXXE5lf7wqt7tM6k6BHic9FEzAo1HnBWBXV5eB5fs1lX9M1VPmx43XINCfzKwxE
xODtIBrmvj+qOp6/ihBsu3LlOoOikxmL+T9Wgvf7fOuFC4BgmX85mGUV+EMZCDoJ
44jlwFF8wgrfG/ZawkP+opNsQLsdOm9DbAdWpx5+JYdgWBahjxuH4z2eIiBmMKgj
puqDgXdZzcERiYtOEEn0p0tvIkVLO3Tm2GjtHbmg1yF2nwsZjupGfcOGTVX4Zi5x
ZCs7vYgBtZy96kNAuyZcFl8eBUr/oVg//i3Zc9Vnw/UJryB7I6dvj228hlrSz0Ve
pGoeZXbcCzRv8NX2V0V1VTtrblSA3w5WRxVzK7UAVetPZ4dlJX+eyx3x2wiC3TiW
ZYH8haFubQqr1h9oXFHgDE5xYZKr51T3SRGfpn6KvQIDAQABo1MwUTAdBgNVHQ4E
FgQUJHOErJYWaGdla1XhxWha4XBKFYgwHwYDVR0jBBgwFoAUJHOErJYWaGdla1Xh
xWha4XBKFYgwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXqcg
DW6qzFccR+JTqNR5HBOneB07LxaUqfBTAzU5GTRljY3mVpnTa6vVvXlStChqdmwU
JJdRhWzTpzE4K92l4UKiYKy486PT1ff34aPLPX5BB9OzL4dgvC3gO0MYDJ84AFZl
6BN/MRTinioG+s14SsxmgcUTl+HXsxt75r3WKjXvqECqhONLPXEXDJ6TVmfb2yd5
X9cE6HLS2IXqfvs0EdXmQhSQVS7AlUQWZPDeoBTDUA1tT6ZKCcG0BuHEFnHxg4Yg
W9xp/wMJCEly+9eNJYZYzyK1AHRGnTMRCSifTJEybwI4A35v68FyRLfAC0lM2qVL
yQIGjj55+r4yGCK7bySSKjs59LLLxi6Px3S61OxAYq9KMT65nBLK9JAPFyTnikw9
q/xW208lL+kcRtG+ARo5ycx5QUjWdsHn7TCnqxnDhHznwSV4KGbJFaGQZTtgfcz0
g5a1GwxqHjEZ9IWiN38f2l4kpLLybKhwVQMYeG000s7rDa5hgjbh13qtQN6vUvI6
VozzZPnFcR1Rsa8RR9njDugxbVwlJQfGkoMiMZwNGgXnZRC2XaI6SCyPwqTPBuVP
ZR1eWv4qwsIGKJzJYcdChb5dimlTuVSfZmONpnrOP/4mhQLyaWr3XLqxxP3mIXsz
k1PNWTkgLsXO8DNkCudxcvPElXfmaw6zwaLrZys=
-----END CERTIFICATE-----

View File

@@ -1 +0,0 @@
../../../../../../sops/machines/peer1

View File

@@ -1,19 +0,0 @@
{
"data": "ENC[AES256_GCM,data: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,iv:q3j8EAokyyxiszf+wyRqxEr2igaD1bX7YnFx/NbsGg8=,tag:HKKYWRJEUwW2/TxL+5dSng==,type:str]",
"sops": {
"age": [
{
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaeXRjU214aWk5ajl1aW9E\naGJlb1ViaVRmMTBHdkFDQUNDZS94WFZiNUNvCllmWTJBck9hR3U3V09VWDZwQ2xI\nd3ZEQnBIUG5ZSTVIdS8rQ2FMYVhyNk0KLS0tIEE1UG8rSzFyU01sVXhGVHpoaE9i\nSis4Qi9tMGFqbTNMTDZUVk1ZdXkrM28Km4VkfaOsZ69ckjvrg+os43H/O1IoWHzC\nt4LqZRz1Tk7/d1aLWavSPPjVYrCOMZeNBqGbQpGfjjuXrafClRNQdQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R1RHTGViTnRLVVkyM3J0\nbm96cGVPTlo4NXBNL0g1eEVSNG9DUkgwVFRBCmRKVTlMRmV3Tmg2RTZIclBlWlcr\ndzI5MUxhcllzbE1IMDNxa08zVkpITmsKLS0tIG01Y2dyQkY3UmRudFk2d0p6bThn\nemlaWnZoS3p4VHhMTFFwTm9VN0ttYzQKVbLFgtK6NIRIiryWHeeOPD45iwUds4QD\n7b8xYYoxlo+DETggxK6Vz3IdT/BSK5bFtgAxl864b5gW+Aw4c6AO5w==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:37Z",
"mac": "ENC[AES256_GCM,data:XKCnd0QrAlOCECSeSvbLYHMLbmUh4fMRnLaTb5ARoP4Zc9joWGsCaRZxokc2/sG4BXA/6pkbQXHyIOudKbcBpVjjvs9E+6Mnzt53nfRoH/iOkYPbN2EO49okVZJXW0M1rlBxrxvGuiDlz2p2p6L7neKLy4EB482pYea5+dUr2Yw=,iv:oj/MkZCfkvCmAb79uzEvKwEAm1bKtWhS4rPRAWSgRgw=,tag:h5TPPILXkhJplnDT2Gqtfw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../../../../sops/users/admin

View File

@@ -1 +0,0 @@
../../../../../../sops/machines/peer1

View File

@@ -1,19 +0,0 @@
{
"data": "ENC[AES256_GCM,data:Q0Vn7J0nERccBYT8HZxHF0Zi5qxmMu40n0H1c+L2SCRF6vRLdURxXKDwvh8xtTU=,iv:ucExjoYDFYy19GsBbNNldJRPBSpT+L+x4PrwTG+m2K8=,tag:/Quupyy/nnUNZsDudEMmNA==,type:str]",
"sops": {
"age": [
{
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWWo5OEJ5N1RTR0xMaDhL\nQnlUV2RrRXIzM01OemhQWjVkd3FNZjRhR2dzCi9IeE56b3VZTkNkdW9DMzVia3Zx\nbklxWmFpenRjdEIrc0ZDTGdmSTAxRTQKLS0tIHZJdjdYUzhhY0YzQjRqS0psZmpI\nVHJpUjNZNHRpc2ZWSml1TVNNejhiT28K8TTP/J+XspXZ7TVYj9YaBhEodPIXjojB\nRLqAIgJXRaK4NCLukC6l0IMii6w5J/512RnO2ZBTGhKfbdLfyLOFqg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrZVc5b0FhbzNXcG1zUDlD\neEVWcWpSRkRCMkxBTHdBM3dCbjVpR3FBa0VjCitlTmx4eUJOMHlaU0dFZEhpK3ZD\nZzlMQXVuZWpnaUNmQW9kOGtOaGVDMU0KLS0tIFNlUi9LSzF0UEJCSVBiRlRSNFQz\nNHhMbmNlRXd4ZEJQWVcvTWdCRWEzMUkKls7RbmNOdPDx8z15F+7qay9qIWx6jNsN\nTahT+GgbG29t1aGQCb0yEzKuUyAp39maxxSWToPsfCgJSYJ8RYiUng==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:39Z",
"mac": "ENC[AES256_GCM,data:g+9/fRiqom2+W28ZpiF+oBj9V6ieq5Xz3sRz3GyzvHoLr6yw51JvpG2QuYNYANW0WCiUjFDkU0qPj/9gLHcuX52nc+gNaTzznb1QGPg7WCGSQI7xaMzyYsPxHpg/BOdj5CL8GyLiOWstD1ch0kc3bJmyu68sJUs04uGtHAADzsE=,iv:oASrYaZarEPDu0R3hd/jMazLgwG5r//hIdMyU/tN15o=,tag:o1fgf5oy+rlWXg88FN5Nfw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../../../../sops/users/admin

View File

@@ -1 +0,0 @@
../../../../../../sops/machines/peer1

View File

@@ -1,19 +0,0 @@
{
"data": "ENC[AES256_GCM,data:4NIUEK05kEQAKjR8F9mU3M/XvtZXw+X6CejVI0usMcb4WzagNz7XTVDhLWXZ9St5Ev0Y,iv:bD2+rDLMoWSqUAIZRJof0wRrJVya1xwZUTIJBdCs98I=,tag:g2s4byFHTzwU3ikcBGMElA==,type:str]",
"sops": {
"age": [
{
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQeVh2M2tqSGlOVkpzNlhU\nd0pMd1R0c0tQWnZzdXViWmtxcjl1Wk1Ka0FNCnBUUWJVbjlyR1hSNGpXNWlPRHJB\nNnMzN3BMQ2NDamFBMlhHbVdJUEZ6cjQKLS0tIEJjWmI0ZDl1NXgrSW9uc0R0LzAr\neEwwOC9DdDg2RTJHQ0M3QTFlcVBaSE0K2Du4NguefdEyY1gS6OuVdO3gHga4omcR\n8B+K1wUfIQbArxZLawPxrj7WNDoW5d4mF9fA3MeV1DFyc4KwtYZmUw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvWkdBakVrMVR4RU8xdDlF\nRDkvL0Mrb3ltazhIMjRLZDVlSTVlaFY2ODBBCnlQM2s0SGEvZjFDN3dGWDhIN0dK\nenhQbjZ1ZS9QZzg5SE5XazZXS3dFSkkKLS0tIHJhKzhadGpjTXd4L3hOQkhpR0Fy\nYzhTN2dxVSt3OE5uZFpuWmVlYW4vd1kKwHOxP0C5mLcm4oIT/sGQtUsdsmu3LSN0\nSola5+N+IrAZ+HKnuZlDLZ5JmJSc5j/YhGNn7KR1xhkhfGSS1e3UZw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:39Z",
"mac": "ENC[AES256_GCM,data:ehbrYqTJcsBKGHUB25JHFnKXrJ6z3LkcElZ89xVr4XxLet+odbhsjIoP2FCcxex7PlXcegMduhHBpXwNGUbX+IUNAXTxlWA9CLDmYhWuS2WLiEVXrS11NE03/zUyHdVx/C38dbIPrWD9iaYSrAiuOyfqDTh9k/Bn7vehLTtadoE=,iv:Nk2WVuJydi5tfsb1Mib4A6NocBCDp9QoIbSadq3bIDI=,tag:IaoyfCv3SkmtemXMR9XnkA==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../../../../sops/users/admin

View File

@@ -1 +0,0 @@
../../../../../../sops/machines/peer1

View File

@@ -1,19 +0,0 @@
{
"data": "ENC[AES256_GCM,data:0BmP+NwG/NGe6R5yU55/MdPEQ8E5u+VXWtvstHc4GpDtmBY=,iv:vo8XBcN7KcYjiyKvvp+XDOdP9yR9B7wJi0XlaiCdVbk=,tag:brK9ntAPSuOvw/C+oDo51g==,type:str]",
"sops": {
"age": [
{
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4Tk1INGtybUVlejlNNlZE\nVms3TkdRVVF1T0E4TmV3NmxvYWVEL2U3WVhNCjJIaHhBcWVlMEYxRjg5bzJpTWdJ\neUhaRTNRTmtlTW0zUXQxTVZEMkQ2MFEKLS0tIFNGWDI4b2FXTE8xQ2xqb0cyK3FI\ncktHWnE5c1ZSVFpmQU1HZmU2VVB1QmcK/s1fVmwpMMg4BYkkAJzSY7hVQWae1F7g\nmfH8EGlr74mifWUNEbd49/K13nl8atQx6bcau83JIEQR+yyihuY4Jw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsL2FXVytUUVZnVU90bG5L\nYURiYjgwN3RuTldWMGl4clpUWmxkeUsrVzM0CkhKZFgwWHl4dWhNSWRQRXVPNDR6\na3hHNmp2RG9YNDhNM2MyV2FuOGY2UlUKLS0tIFpNU2tNOHdhRDRTdHhYWVh2NGZa\nU3J3S0hpclZzWGIwTlFyczdNZkZSZTAKXCZrLaIOVq90ejoKMaRiK0xNw8WOPcnm\nz2uxProEYvQhY8k29mhCFX5HCN0tGn1XTtHeDL7uHuKuFsnSG/fgYQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:39Z",
"mac": "ENC[AES256_GCM,data:QkGJKj/H+MI9Mr9Up5NDUToSddY5eTz47egc2+IatfxR8RebKJ2/mYaeLV26vPdmY60bIac4N/nZkoa6IVBhkHHMvsEHsx3nD6Lro9Wf/pWP8Zddzr90LF5p2+wusq25JutKQiPKOb2gmrcagmSsH/7V/UqI/my3PMeKmw6irhw=,iv:hOtHF/cDFdNfvqCKRhJsOwAHEiQmCPjENzsg23sKG+Q=,tag:K7qG9b4fQD0VbAV8OYp3vw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../../../../sops/users/admin

View File

@@ -2,14 +2,13 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/mycelium"; manifest.name = "clan-core/mycelium";
manifest.description = "End-2-end encrypted P2P IPv6 overlay network"; manifest.description = "End-2-end encrypted IPv6 overlay network";
manifest.categories = [ manifest.categories = [
"System" "System"
"Network" "Network"
]; ];
roles.peer = { roles.peer = {
description = "A peer in the mycelium network";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -8,7 +8,6 @@
]; ];
roles.default = { roles.default = {
description = "Placeholder role to apply the packages service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -1,15 +1,11 @@
The `sshd` Clan service manages SSH to make it easy to securely access your The `sshd` Clan service manages SSH to make it easy to securely access your machines over the internet. The service uses `vars` to store the SSH host keys for each machine to ensure they remain stable across deployments.
machines over the internet. The service uses `vars` to store the SSH host keys
for each machine to ensure they remain stable across deployments.
`sshd` also generates SSH certificates for both servers and clients allowing for `sshd` also generates SSH certificates for both servers and clients allowing for certificate-based authentication for SSH.
certificate-based authentication for SSH.
The service also disables password-based authentication over SSH, to access your The service also disables password-based authentication over SSH, to access your machines you'll need to use public key authentication or certificate-based authentication.
machines you'll need to use public key authentication or certificate-based
authentication.
## Usage ## Usage
```nix ```nix
{ {
inventory.instances = { inventory.instances = {
@@ -22,6 +18,7 @@ authentication.
roles.server.tags.all = { }; roles.server.tags.all = { };
roles.client.tags.all = { }; roles.client.tags.all = { };
}; };
# Also generate RSA host keys for all servers # Also generate RSA host keys for all servers
sshd-with-rsa = { sshd-with-rsa = {
module = { module = {

View File

@@ -10,7 +10,6 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.client = { roles.client = {
description = "Installs the SSH CA public key into known_hosts for the configured domains, so this machine can verify servers host certificates without TOFU prompts.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -39,6 +38,7 @@
... ...
}: }:
{ {
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) { clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
share = true; share = true;
files.id_ed25519.deploy = false; files.id_ed25519.deploy = false;
@@ -64,12 +64,11 @@
}; };
roles.server = { roles.server = {
description = "Runs sshd with persistent host keys and (if certificate.searchDomains is set) a CAsigned host certificate for <machine>.<domain>, enabling TOFUless verification by clients that trust the CA.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
options = { options = {
hostKeys.rsa.enable = lib.mkEnableOption "generating a RSA host key"; hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
certificate = { certificate = {
searchDomains = lib.mkOption { searchDomains = lib.mkOption {
@@ -97,7 +96,9 @@
... ...
}: }:
{ {
clan.core.vars.generators = { clan.core.vars.generators = {
openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) { openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
share = true; share = true;
files.id_ed25519.deploy = false; files.id_ed25519.deploy = false;

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -13,7 +13,7 @@
} }
``` ```
Now the folder `~/syncthing/documents` will be shared and kept in sync with all your machines. Now the folder `~/syncthing/documents` will be shared with all your machines.
## Documentation ## Documentation

View File

@@ -11,7 +11,6 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.peer = { roles.peer = {
description = "A peer in the syncthing cluster that syncs files with other peers.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -5,7 +5,9 @@
... ...
}: }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -2,17 +2,13 @@
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "clan-core/tor"; manifest.name = "clan-core/tor";
manifest.description = "Part of the clan networking abstraction to define how to reach machines through the Tor network, if used has the lowest priority"; manifest.description = "Onion routing, use Hidden services to connect your machines";
manifest.categories = [ manifest.categories = [
"System" "System"
"Network" "Network"
]; ];
roles.client = { roles.client = {
description = ''
Enables a continuosly running Tor proxy on the machine, allowing access to other machines via the Tor network.
If not enabled, a Tor proxy will be started automatically when required.
'';
perInstance = perInstance =
{ {
... ...
@@ -35,7 +31,6 @@
}; };
roles.server = { roles.server = {
description = "Sets up a Tor onion service for the machine, thus making it reachable over Tor.";
# interface = # interface =
# { lib, ... }: # { lib, ... }:
# { # {
@@ -63,7 +58,7 @@
priority = lib.mkDefault 10; priority = lib.mkDefault 10;
# TODO add user space network support to clan-cli # TODO add user space network support to clan-cli
module = "clan_lib.network.tor"; module = "clan_lib.network.tor";
peers = lib.mapAttrs (name: _machine: { peers = lib.mapAttrs (name: machine: {
host.var = { host.var = {
machine = name; machine = name;
generator = "tor_${instanceName}"; generator = "tor_${instanceName}";

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules = { clan.modules = {

View File

@@ -7,7 +7,7 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = {
description = "Placeholder role to apply the trusted-nix-caches service";
perInstance = perInstance =
{ ... }: { ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules.trusted-nix-caches = module; clan.modules.trusted-nix-caches = module;

View File

@@ -10,7 +10,6 @@
manifest.readme = builtins.readFile ./README.md; manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = {
description = "Placeholder role to apply the user service";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules.users = module; clan.modules.users = module;

View File

@@ -1,21 +0,0 @@
This module allows you to pre-configure WiFi networks for automatic connection.
Each attribute in `settings.network` serves as an internal identifier, not the actual SSID.
After defining your networks, you will be prompted for the SSID and password for each one.
This module leverages NetworkManager for managing connections.
```nix
instances = {
wifi = {
module.name = "wifi";
module.input = "clan-core";
roles.default = {
machines."jon" = {
settings.networks.home = { };
settings.networks.work = { keyMgmt = "wpa-eap"; };
};
};
};
};
```

View File

@@ -1,3 +1,4 @@
{ packages }:
{ lib, ... }: { lib, ... }:
let let
inherit (lib) inherit (lib)
@@ -9,11 +10,8 @@ in
{ {
_class = "clan.service"; _class = "clan.service";
manifest.name = "wifi"; manifest.name = "wifi";
manifest.description = "Pre configure wifi networks to connect to";
manifest.readme = builtins.readFile ./README.md;
roles.default = { roles.default = {
description = "Placeholder role to apply the wifi service";
interface = { interface = {
options.networks = lib.mkOption { options.networks = lib.mkOption {
type = lib.types.attrsOf ( type = lib.types.attrsOf (
@@ -31,32 +29,12 @@ in
default = true; default = true;
description = "Automatically try to join this wifi network"; description = "Automatically try to join this wifi network";
}; };
keyMgmt = lib.mkOption {
type = lib.types.str;
default = "wpa-psk";
description = ''
Key management used for the connection.
One of "none" (WEP or no password protection), "ieee8021x" (Dynamic WEP), "owe" (Opportunistic Wireless Encryption), "wpa-psk" (WPA2 + WPA3 personal),
"sae" (WPA3 personal only), "wpa-eap" (WPA2 + WPA3 enterprise) or "wpa-eap-suite-b-192" (WPA3 enterprise only).
'';
};
}; };
} }
) )
); );
default = { }; default = { };
example = { description = "Wifi networks to predefine";
home = { };
guest = {
autoConnect = false;
keyMgmt = "wpa-eap";
};
};
description = ''
List of wifi networks to configure for connection.
Each attribute name is an internal identifier (not the SSID).
For each network, you will be prompted to enter the SSID and password as secrets.
'';
}; };
}; };
@@ -72,7 +50,7 @@ in
ssid_path = ssid_path =
network_name: config.clan.core.vars.generators."wifi.${network_name}".files.network-name.path; network_name: config.clan.core.vars.generators."wifi.${network_name}".files.network-name.path;
secret_generator = name: _value: { secret_generator = name: value: {
name = "wifi.${name}"; name = "wifi.${name}";
value = { value = {
prompts.network-name.type = "line"; prompts.network-name.type = "line";
@@ -102,7 +80,7 @@ in
wifi.mode = "infrastructure"; wifi.mode = "infrastructure";
wifi.ssid = "$ssid_${name}"; wifi.ssid = "$ssid_${name}";
wifi-security.psk = "$pw_${name}"; wifi-security.psk = "$pw_${name}";
wifi-security.key-mgmt = networkCfg.keyMgmt; wifi-security.key-mgmt = "wpa-psk";
} }
); );

View File

@@ -1,8 +1,12 @@
{ {
self,
lib,
... ...
}: }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in in
{ {
clan.modules.wifi = module; clan.modules.wifi = module;

View File

@@ -146,7 +146,6 @@ in
# Peer options and configuration # Peer options and configuration
roles.peer = { roles.peer = {
description = "A peer that connects to one or more controllers.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {
@@ -262,7 +261,6 @@ in
# Controller options and configuration # Controller options and configuration
roles.controller = { roles.controller = {
description = "A controller that routes peer traffic. Must be publicly reachable.";
interface = interface =
{ lib, ... }: { lib, ... }:
{ {

View File

@@ -1,6 +1,6 @@
{ ... }: { lib, ... }:
let let
module = ./default.nix; module = lib.modules.importApply ./default.nix { };
in in
{ {
clan.modules.wireguard = module; clan.modules.wireguard = module;

View File

@@ -1,33 +0,0 @@
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across
your clan.
Yggdrasil is designed to be a future-proof and decentralised alternative to
the structured routing protocols commonly used today on the internet. Inside
your clan, it will allow you to reach all of your machines.
## Example Usage
While you can specify statically configured peers for each host, yggdrasil does
auto-discovery of local peers.
```nix
inventory = {
machines = {
peer1 = { };
peer2 = { };
};
instances = {
yggdrasil = {
# Deploy on all machines
roles.default.tags.all = { };
# Or individual hosts
roles.default.machines.peer1 = { };
roles.default.machines.peer2 = { };
};
};
};
```

View File

@@ -1,126 +0,0 @@
{ ... }:
{
_class = "clan.service";
manifest.name = "clan-core/yggdrasil";
manifest.description = "Yggdrasil encrypted IPv6 routing overlay network";
roles.default = {
description = "Placeholder role to apply the yggdrasil service";
interface =
{ lib, ... }:
{
options.extraMulticastInterfaces = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [ ];
description = ''
Additional interfaces to use for Multicast. See
https://yggdrasil-network.github.io/configurationref.html#multicastinterfaces
for reference.
'';
example = [
{
Regex = "(wg).*";
Beacon = true;
Listen = true;
Port = 5400;
Priority = 1020;
}
];
};
options.peers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Static peers to configure for this host.
If not set, local peers will be auto-discovered
'';
example = [
"tcp://192.168.1.1:6443"
"quic://192.168.1.1:6443"
"tls://192.168.1.1:6443"
"ws://192.168.1.1:6443"
];
};
};
perInstance =
{ settings, ... }:
{
nixosModule =
{
config,
pkgs,
...
}:
{
clan.core.vars.generators.yggdrasil = {
files.privateKey = { };
files.publicKey.secret = false;
files.address.secret = false;
runtimeInputs = with pkgs; [
yggdrasil
jq
openssl
];
script = ''
# Generate private key
openssl genpkey -algorithm Ed25519 -out $out/privateKey
# Generate corresponding public key
openssl pkey -in $out/privateKey -pubout -out $out/publicKey
# Derive IPv6 address from key
echo "{\"PrivateKeyPath\": \"$out/privateKey\"}" | yggdrasil -useconf -address | tr -d '\n' > $out/address
'';
};
systemd.services.yggdrasil.serviceConfig.BindReadOnlyPaths = [
"%d/key:/key"
];
systemd.services.yggdrasil.serviceConfig.LoadCredential =
"key:${config.clan.core.vars.generators.yggdrasil.files.privateKey.path}";
services.yggdrasil = {
enable = true;
openMulticastPort = true;
# We don't need this option, because we persist our keys with
# vars by ourselves. This option creates an unnecesary additional
# systemd service to save/load the keys and should be removed
# from the NixOS module entirely, as it can be replaced by the
# (at the time of writing undocumented) PrivateKeyPath= setting.
# See https://github.com/NixOS/nixpkgs/pull/440910#issuecomment-3301835895 for details.
persistentKeys = false;
settings = {
PrivateKeyPath = "/key";
IfName = "ygg";
Peers = settings.peers;
MulticastInterfaces = [
# Ethernet is preferred over WIFI
{
Regex = "(eth|en).*";
Beacon = true;
Listen = true;
Port = 5400;
Priority = 1024;
}
{
Regex = "(wl).*";
Beacon = true;
Listen = true;
Port = 5400;
Priority = 1025;
}
]
++ settings.extraMulticastInterfaces;
};
};
networking.firewall.allowedTCPPorts = [ 5400 ];
};
};
};
}

View File

@@ -1,24 +0,0 @@
{
self,
lib,
...
}:
let
module = lib.modules.importApply ./default.nix {
inherit (self) packages;
};
in
{
clan.modules = {
yggdrasil = module;
};
perSystem =
{ ... }:
{
clan.nixosTests.yggdrasil = {
imports = [ ./tests/vm/default.nix ];
clan.modules.yggdrasil = module;
};
};
}

View File

@@ -1,67 +0,0 @@
{
name = "yggdrasil";
clan = {
test.useContainers = false;
directory = ./.;
inventory = {
machines.peer1 = { };
machines.peer2 = { };
instances."yggdrasil" = {
module.name = "yggdrasil";
module.input = "self";
# Assign the roles to the two machines
roles.default.machines.peer1 = { };
roles.default.machines.peer2 = { };
};
};
};
testScript = ''
start_all()
# Wait for both machines to be ready
peer1.wait_for_unit("multi-user.target")
peer2.wait_for_unit("multi-user.target")
# Check that yggdrasil service is running on both machines
peer1.wait_for_unit("yggdrasil")
peer2.wait_for_unit("yggdrasil")
peer1.succeed("systemctl is-active yggdrasil")
peer2.succeed("systemctl is-active yggdrasil")
# Check that both machines have yggdrasil network interfaces
peer1.wait_until_succeeds("ip link show | grep -E 'ygg'", 30)
peer2.wait_until_succeeds("ip link show | grep -E 'ygg'", 30)
# Get yggdrasil IPv6 addresses from both machines
peer1_ygg_ip = peer1.succeed("yggdrasilctl -json getself | jq -r '.address'").strip()
peer2_ygg_ip = peer2.succeed("yggdrasilctl -json getself | jq -r '.address'").strip()
# Compare runtime addresses with saved addresses from vars
expected_peer1_ip = "${builtins.readFile ./vars/per-machine/peer1/yggdrasil/address/value}"
expected_peer2_ip = "${builtins.readFile ./vars/per-machine/peer2/yggdrasil/address/value}"
print(f"peer1 yggdrasil IP: {peer1_ygg_ip}")
print(f"peer2 yggdrasil IP: {peer2_ygg_ip}")
print(f"peer1 expected IP: {expected_peer1_ip}")
print(f"peer2 expected IP: {expected_peer2_ip}")
# Verify that runtime addresses match expected addresses
assert peer1_ygg_ip == expected_peer1_ip, f"peer1 runtime IP {peer1_ygg_ip} != expected IP {expected_peer1_ip}"
assert peer2_ygg_ip == expected_peer2_ip, f"peer2 runtime IP {peer2_ygg_ip} != expected IP {expected_peer2_ip}"
# Wait a bit for the yggdrasil network to establish connectivity
import time
time.sleep(10)
# Test connectivity: peer1 should be able to ping peer2 via yggdrasil
peer1.succeed(f"ping -6 -c 3 {peer2_ygg_ip}")
# Test connectivity: peer2 should be able to ping peer1 via yggdrasil
peer2.succeed(f"ping -6 -c 3 {peer1_ygg_ip}")
'';
}

View File

@@ -1,6 +0,0 @@
[
{
"publickey": "age1p8trv2dmpanl3gnzj294c4t5uysu7d6rfjncp5lmn6redyda8fns6p7kca",
"type": "age"
}
]

View File

@@ -1,6 +0,0 @@
[
{
"publickey": "age107mprppm3r9u7f26e6t5mhtdny0h5ugfmfjy8kac2tw9nrh9a3ksex0xca",
"type": "age"
}
]

View File

@@ -1,15 +0,0 @@
{
"data": "ENC[AES256_GCM,data:jDEog7FFXl28Le3rh5VTiY0DFmLhIy2ZccFjuYWx+OQrKNEqTLI1fzaeMWIcgu6ln6wfGUk640d3IhmrF45MVZiJGkpkOU8UFx0=,iv:4oGaoxhFQwr9OQfdLL7y1N/gJo/uGkTPG/xicVprIAQ=,tag:Smu0/P2bQB66w+0J2Bjlxw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpQ2hib2Mrb0plWUVwNWU5\nWmpxNlduaUVJckhuQlhQbUJpanloWGFLelJ3CjJJMlBGbGRTWEhGUHh2VVkzUzNa\nL3FGVkF3R3JJT051UTg4UlkwOHRNanMKLS0tIDVWcHU4NmFMUWp3STFTYmg5YmNp\nVzd1Uzg2Wkp5QnJ3V1Qyb2lwSXdBRDgK/V5lgw2TePhUC9ngW53ZapIMkcwPvJus\ns0jUYkStHXjsvEiN7BG8cG7/vRbLD8CnKXnmieM20mT6o7GHGfhHMg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-17T07:12:51Z",
"mac": "ENC[AES256_GCM,data:gy/1NFmpFz/tdhgU/Vr+xg46DUjy9ZbrAtCBnIxclwZLJ/fneBpblv8TFgdysY4Ay6jp1S/TOc8eyr+KLHMqcBlje09wd1ac/Y3ee6GccXitB+/c5ayuXX/ShVCdicsr/9COw7vfndAQPU8XIz6tdy0dbL7jgVTyViZW/P5CXEU=,iv:BQ/INwTTCshl5BVnJbVzHW8rwafERS6bKh2JAJsMv9s=,tag:QhsbjeEBivbl8fQLHjiKtQ==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../users/admin

View File

@@ -1,15 +0,0 @@
{
"data": "ENC[AES256_GCM,data:1rVgSwg2qPHuXUOQCgOunaNYiBbsh99dZ2y0BV4TxzACwdb3lb6/XnLeDenLELOpKruZQoNJax/NziRr+VHzmh/TlQhNgTkS71A=,iv:Wi5/cFOETb1rhAYeyzkpppzSSm+S+8cCQYc7zkp74FY=,tag:JQHFZJwYMQH4jUqSw6Ld8Q==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGV3ZLZmNrejlvTFF6NDZW\nYmljS0VXQUtCR3IzMG9tMksvSllVVkIxVTEwCi9Fd1dBbmFlYmF2cE1raVJoS3RR\nWmxQY3RwanRZUE5aN1Q2SzhJOFU1elEKLS0tIG9RMElDMEo3TFJjU0RvU3FMQk12\nT1pNc1VjeUliejk3YmJ6d29zUU15aDQKuZ62Q/ywLrpyu1jB34OCPKQEDd150qH6\nHzyw+MasUlzKNs0ZrALwfhnCKiNb/Pq0Lu660Dx5/sFxI/TAqC7NGg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-17T07:13:24Z",
"mac": "ENC[AES256_GCM,data:N7mmiEZxinOgWdd7QcZBAumnWaApjlQVww4EzAQ1/JH5i8r8CIfPh/7lGMQntlJj5ob+UgrS96nl6XKdvs3Bt7z34zPq7KV3c0mSmclEctRfcZiG4F+rZ0QIMIRJjq7xJL/M9WupSn8Lgms7qHJMdJyHdDkw47bmXz3MIw9c9zo=,iv:ZYPoo5jTIGnZ1HcAWlr26gloVhSjfhwbO/xH5YCbgF0=,tag:UKMVMGEfqyfo04cIkuKD0A==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -1 +0,0 @@
../../../users/admin

View File

@@ -1,4 +0,0 @@
{
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"type": "age"
}

View File

@@ -1 +0,0 @@
200:fa3b:ad0e:6821:9a51:3ad5:62a4:9ab1

Some files were not shown because too many files have changed in this diff Show More