Compare commits
1 Commits
push-tnkqq
...
docs-frame
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be4cd657c9 |
@@ -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
|
||||
@@ -8,6 +8,6 @@ jobs:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: nix run --print-build-logs .#deploy-docs
|
||||
- run: nix run .#deploy-docs
|
||||
env:
|
||||
SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }}
|
||||
|
||||
4
CONTRIBUTING.md
Normal file
4
CONTRIBUTING.md
Normal 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
|
||||
@@ -2,6 +2,7 @@
|
||||
self,
|
||||
lib,
|
||||
inputs,
|
||||
privateInputs ? { },
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -18,19 +19,28 @@ let
|
||||
nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { };
|
||||
in
|
||||
{
|
||||
imports = 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
|
||||
# clan core submodule tests
|
||||
../nixosModules/clanCore/machine-id/tests/flake-module.nix
|
||||
../nixosModules/clanCore/postgresql/tests/flake-module.nix
|
||||
../nixosModules/clanCore/state-version/tests/flake-module.nix
|
||||
];
|
||||
imports =
|
||||
let
|
||||
clanCoreModulesDir = ../nixosModules/clanCore;
|
||||
getClanCoreTestModules =
|
||||
let
|
||||
moduleNames = attrNames (builtins.readDir clanCoreModulesDir);
|
||||
testPaths = map (
|
||||
moduleName: clanCoreModulesDir + "/${moduleName}/tests/flake-module.nix"
|
||||
) moduleNames;
|
||||
in
|
||||
filter pathExists testPaths;
|
||||
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" ] (
|
||||
system:
|
||||
let
|
||||
@@ -110,7 +120,7 @@ in
|
||||
) (self.darwinConfigurations or { })
|
||||
// lib.mapAttrs' (n: lib.nameValuePair "package-${n}") (
|
||||
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
|
||||
packagesToBuild
|
||||
)
|
||||
@@ -128,7 +138,7 @@ in
|
||||
// flakeOutputs
|
||||
// {
|
||||
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
|
||||
cp ${../flake.lock} $out/flake.lock
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
fileSystems."/".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;
|
||||
|
||||
imports = [ self.nixosModules.test-flash-machine ];
|
||||
@@ -26,24 +28,10 @@
|
||||
{
|
||||
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 { };
|
||||
|
||||
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 =
|
||||
@@ -56,15 +44,13 @@
|
||||
dependencies = [
|
||||
pkgs.disko
|
||||
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.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.diskoScript
|
||||
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);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
@@ -88,7 +74,7 @@
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = lib.mkForce 3;
|
||||
flake-registry = "";
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
@@ -97,10 +83,10 @@
|
||||
};
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
|
||||
|
||||
# Some distros like to automount disks with spaces
|
||||
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
|
||||
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
|
||||
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; };
|
||||
};
|
||||
|
||||
10
checks/installation/facter-report.nix
Normal file
10
checks/installation/facter-report.nix
Normal 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};
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
config,
|
||||
self,
|
||||
lib,
|
||||
privateInputs,
|
||||
|
||||
...
|
||||
}:
|
||||
{
|
||||
@@ -14,37 +14,26 @@
|
||||
# you can get a new one by adding
|
||||
# client.fail("cat test-flake/machines/test-install-machine/facter.json >&2")
|
||||
# to the installation test.
|
||||
clan.machines = {
|
||||
test-install-machine-without-system = {
|
||||
clan.machines.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";
|
||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||
|
||||
imports = [
|
||||
self.nixosModules.test-install-machine-without-system
|
||||
];
|
||||
imports = [ 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 = {
|
||||
test-install-machine-without-system =
|
||||
@@ -160,12 +149,13 @@
|
||||
closureInfo = pkgs.closureInfo {
|
||||
rootPaths = [
|
||||
privateInputs.clan-core-for-checks
|
||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.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.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
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.bash.drvPath
|
||||
pkgs.buildPackages.xorg.lndir
|
||||
(import ./facter-report.nix pkgs.hostPlatform.system)
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
};
|
||||
@@ -215,7 +205,7 @@
|
||||
# Prepare test flake and Nix store
|
||||
flake_dir = prepare_test_flake(
|
||||
temp_dir,
|
||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
||||
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||
"${closureInfo}"
|
||||
)
|
||||
|
||||
@@ -226,22 +216,6 @@
|
||||
"${../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
|
||||
clan_cmd = [
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
@@ -296,7 +270,7 @@
|
||||
# Prepare test flake and Nix store
|
||||
flake_dir = prepare_test_flake(
|
||||
temp_dir,
|
||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
||||
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||
"${closureInfo}"
|
||||
)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ let
|
||||
networking.useNetworkd = true;
|
||||
services.openssh.enable = true;
|
||||
services.openssh.settings.UseDns = false;
|
||||
services.openssh.settings.PasswordAuthentication = false;
|
||||
system.nixos.variant_id = "installer";
|
||||
environment.systemPackages = [
|
||||
pkgs.nixos-facter
|
||||
@@ -146,11 +147,28 @@ let
|
||||
];
|
||||
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
|
||||
{
|
||||
inherit
|
||||
target
|
||||
baseTestMachine
|
||||
nixosTestLib
|
||||
closureInfo
|
||||
;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.stdenvNoCC
|
||||
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);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
|
||||
@@ -29,34 +29,32 @@ nixosLib.runTest (
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import subprocess
|
||||
import tempfile
|
||||
from nixos_test_lib.nix_setup import setup_nix_in_nix
|
||||
from nixos_test_lib.nix_setup import setup_nix_in_nix # type: ignore[import-untyped]
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
setup_nix_in_nix(temp_dir, None) # No closure info for this test
|
||||
setup_nix_in_nix(None) # No closure info for this test
|
||||
|
||||
start_all()
|
||||
admin1.wait_for_unit("multi-user.target")
|
||||
peer1.wait_for_unit("multi-user.target")
|
||||
start_all()
|
||||
admin1.wait_for_unit("multi-user.target")
|
||||
peer1.wait_for_unit("multi-user.target")
|
||||
|
||||
# peer1 should have the 'hello' file
|
||||
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
||||
# peer1 should have the 'hello' file
|
||||
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}")
|
||||
# Check that the file is owned by 'nobody'
|
||||
assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}"
|
||||
# Check that the file is in the 'users' group
|
||||
assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}"
|
||||
# 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}"
|
||||
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'
|
||||
assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}"
|
||||
# Check that the file is in the 'users' group
|
||||
assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}"
|
||||
# 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}"
|
||||
|
||||
# Run clan command
|
||||
result = subprocess.run(
|
||||
["${
|
||||
clan-core.packages.${hostPkgs.system}.clan-cli
|
||||
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
|
||||
check=True
|
||||
)
|
||||
# Run clan command
|
||||
result = subprocess.run(
|
||||
["${
|
||||
clan-core.packages.${hostPkgs.system}.clan-cli
|
||||
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
|
||||
check=True
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
modules.new-service = {
|
||||
_class = "clan.service";
|
||||
manifest.name = "new-service";
|
||||
roles.peer = {
|
||||
description = "A peer that uses the new-service to generate some files.";
|
||||
};
|
||||
roles.peer = { };
|
||||
perMachine = {
|
||||
nixosModule = {
|
||||
# This should be generated by:
|
||||
|
||||
@@ -34,9 +34,7 @@ nixosLib.runTest (
|
||||
modules.new-service = {
|
||||
_class = "clan.service";
|
||||
manifest.name = "new-service";
|
||||
roles.peer = {
|
||||
description = "A peer that uses the new-service to generate some files.";
|
||||
};
|
||||
roles.peer = { };
|
||||
perMachine = {
|
||||
nixosModule = {
|
||||
# This should be generated by:
|
||||
|
||||
@@ -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
|
||||
fileSystems = {
|
||||
"/" = {
|
||||
@@ -115,13 +106,13 @@
|
||||
let
|
||||
closureInfo = pkgs.closureInfo {
|
||||
rootPaths = [
|
||||
self.packages.${pkgs.hostPlatform.system}.clan-cli
|
||||
self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks
|
||||
self.packages.${pkgs.system}.clan-cli
|
||||
self.checks.${pkgs.system}.clan-core-for-checks
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.bash.drvPath
|
||||
pkgs.buildPackages.xorg.lndir
|
||||
pkgs.bubblewrap
|
||||
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
};
|
||||
@@ -132,7 +123,7 @@
|
||||
imports = [ self.nixosModules.test-update-machine ];
|
||||
};
|
||||
extraPythonPackages = _p: [
|
||||
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib
|
||||
self.legacyPackages.${pkgs.system}.nixosTestLib
|
||||
];
|
||||
|
||||
testScript = ''
|
||||
@@ -154,7 +145,7 @@
|
||||
# Prepare test flake and Nix store
|
||||
flake_dir = prepare_test_flake(
|
||||
temp_dir,
|
||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
||||
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||
"${closureInfo}"
|
||||
)
|
||||
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
|
||||
@@ -221,13 +212,12 @@
|
||||
[
|
||||
"${pkgs.nix}/bin/nix",
|
||||
"copy",
|
||||
"--from",
|
||||
f"{temp_dir}/store",
|
||||
"--to",
|
||||
"ssh://root@192.168.1.1",
|
||||
"--no-check-sigs",
|
||||
f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}",
|
||||
f"${self.packages.${pkgs.system}.clan-cli}",
|
||||
"--extra-experimental-features", "nix-command flakes",
|
||||
"--from", f"{os.environ["TMPDIR"]}/store"
|
||||
],
|
||||
check=True,
|
||||
env={
|
||||
@@ -242,7 +232,7 @@
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
f"root@192.168.1.1",
|
||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan",
|
||||
"${self.packages.${pkgs.system}.clan-cli}/bin/clan",
|
||||
"machines",
|
||||
"update",
|
||||
"--debug",
|
||||
@@ -270,7 +260,7 @@
|
||||
|
||||
# Run clan update command
|
||||
subprocess.run([
|
||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
"machines",
|
||||
"update",
|
||||
"--debug",
|
||||
@@ -297,7 +287,7 @@
|
||||
|
||||
# Run clan update command with --build-host
|
||||
subprocess.run([
|
||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
"machines",
|
||||
"update",
|
||||
"--debug",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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" ];
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the admin service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
options = {
|
||||
allowedKeys = lib.mkOption {
|
||||
default = { };
|
||||
|
||||
@@ -9,7 +9,7 @@ inventory.instances = {
|
||||
};
|
||||
roles.client.machines."jon".settings = {
|
||||
destinations."storagebox" = {
|
||||
repo = "username@hostname:/./borgbackup";
|
||||
repo = "username@$hostname:/./borgbackup";
|
||||
rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# TODO: a client can only be in one instance, add constraint
|
||||
|
||||
roles.server = {
|
||||
description = "A borgbackup server that stores the backups of clients.";
|
||||
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -54,7 +54,7 @@
|
||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machineName)) ];
|
||||
# };
|
||||
# }) machinesWithKey;
|
||||
}) (roles.client.machines or { });
|
||||
}) roles.client.machines;
|
||||
in
|
||||
hosts;
|
||||
};
|
||||
@@ -62,7 +62,6 @@
|
||||
};
|
||||
|
||||
roles.client = {
|
||||
description = "A borgbackup client that backs up to all borgbackup server roles.";
|
||||
interface =
|
||||
{
|
||||
lib,
|
||||
@@ -188,7 +187,7 @@
|
||||
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
|
||||
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
||||
};
|
||||
}) (builtins.attrNames (roles.server.machines or { }));
|
||||
}) (builtins.attrNames roles.server.machines);
|
||||
in
|
||||
(builtins.listToAttrs destinations);
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.ca = {
|
||||
description = "A certificate authority that issues and signs certificates for other machines.";
|
||||
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -184,7 +184,6 @@
|
||||
|
||||
# Empty role, so we can add non-ca machins to the instance to trust the CA
|
||||
roles.default = {
|
||||
description = "A machine that trusts the CA and can get certificates issued by it.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -45,15 +45,13 @@ inventory = {
|
||||
# Add the default role to all machines, including `client`
|
||||
roles.default.tags.all = { };
|
||||
|
||||
# DNS server queries to http://<name>.foo are resolved here
|
||||
# DNS server
|
||||
roles.server.machines."dnsserver".settings = {
|
||||
ip = "192.168.1.2";
|
||||
tld = "foo";
|
||||
};
|
||||
|
||||
# First service
|
||||
# Registers http://one.foo will resolve to 192.168.1.3
|
||||
# underlying service runs on server01
|
||||
roles.default.machines."server01".settings = {
|
||||
ip = "192.168.1.3";
|
||||
services = [ "one" ];
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.server = {
|
||||
description = "A DNS server that resolves services in the clan network.";
|
||||
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -103,7 +103,6 @@
|
||||
};
|
||||
|
||||
roles.default = {
|
||||
description = "A machine that registers the 'server' role as resolver and registers services under the configured TLD in the resolver.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
dns =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.nettools ];
|
||||
environment.systemPackages = [ pkgs.net-tools ];
|
||||
};
|
||||
|
||||
client =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.nettools ];
|
||||
environment.systemPackages = [ pkgs.net-tools ];
|
||||
};
|
||||
|
||||
server01 = {
|
||||
|
||||
@@ -101,7 +101,6 @@ in
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.admin = {
|
||||
description = "A data-mesher admin node that bootstraps the network and can sign new nodes into the network.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -178,7 +177,6 @@ in
|
||||
};
|
||||
|
||||
roles.signer = {
|
||||
description = "A data-mesher signer node that can sign new nodes into the network.";
|
||||
interface = sharedInterface;
|
||||
perInstance =
|
||||
{
|
||||
@@ -210,7 +208,6 @@ in
|
||||
};
|
||||
|
||||
roles.peer = {
|
||||
description = "A data-mesher peer node that connects to the network.";
|
||||
interface = sharedInterface;
|
||||
perInstance =
|
||||
{
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the dyndns service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -2,34 +2,31 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the emergency-access service";
|
||||
perInstance = {
|
||||
nixosModule =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
boot.initrd.systemd.emergencyAccess =
|
||||
config.clan.core.vars.generators.emergency-access.files.password-hash.value;
|
||||
roles.default.perInstance = {
|
||||
nixosModule =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
boot.initrd.systemd.emergencyAccess =
|
||||
config.clan.core.vars.generators.emergency-access.files.password-hash.value;
|
||||
|
||||
clan.core.vars.generators.emergency-access = {
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.mkpasswd
|
||||
pkgs.xkcdpass
|
||||
];
|
||||
files.password.deploy = false;
|
||||
files.password-hash.secret = false;
|
||||
clan.core.vars.generators.emergency-access = {
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.mkpasswd
|
||||
pkgs.xkcdpass
|
||||
];
|
||||
files.password.deploy = false;
|
||||
files.password-hash.secret = false;
|
||||
|
||||
script = ''
|
||||
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password
|
||||
mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash
|
||||
'';
|
||||
};
|
||||
script = ''
|
||||
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password
|
||||
mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
manifest.categories = [ "System" ];
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the garage service";
|
||||
|
||||
perInstance.nixosModule =
|
||||
{
|
||||
config,
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
# defined in this file directly (e.g. the "morning" role) or split up into a
|
||||
# separate file (e.g. the "evening" role)
|
||||
roles.morning = {
|
||||
description = "A morning greeting machine";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -68,7 +67,6 @@
|
||||
# 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
|
||||
roles.evening = {
|
||||
description = "An evening greeting machine";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -6,7 +6,5 @@
|
||||
manifest.categories = [ "Utility" ];
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the importer service";
|
||||
};
|
||||
roles.default = { };
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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 = [
|
||||
"System"
|
||||
"Network"
|
||||
];
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the internet service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the localbackup service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
manifest.categories = [ "Social" ];
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the matrix-synapse service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.telegraf = {
|
||||
description = "Placeholder role to apply the telegraf monitoring agent";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.allowAllInterfaces = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.bool;
|
||||
default = null;
|
||||
description = "Deprecated. Has no effect.";
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "If true, Telegraf will listen on all interfaces. Otherwise, it will only listen on the interfaces specified in `interfaces`";
|
||||
};
|
||||
|
||||
options.interfaces = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||
default = null;
|
||||
description = "Deprecated. Has no effect.";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "zt+" ];
|
||||
description = "List of interfaces to expose the metrics to";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
{
|
||||
lib,
|
||||
self,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -10,15 +8,11 @@ in
|
||||
clan.modules.monitoring = module;
|
||||
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{ ... }:
|
||||
{
|
||||
clan.nixosTests.monitoring = {
|
||||
imports = [
|
||||
(lib.modules.importApply ./tests/vm/default.nix {
|
||||
inherit (self) packages;
|
||||
inherit pkgs;
|
||||
})
|
||||
];
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules.monitoring = module;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,54 +11,34 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
jsonpath = "/tmp/telegraf.json";
|
||||
auth_user = "prometheus";
|
||||
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
|
||||
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" = {
|
||||
|
||||
files.password.restartUnits = [ "telegraf.service" ];
|
||||
files.password-env.restartUnits = [ "telegraf.service" ];
|
||||
files.miniserve-auth.restartUnits = [ "telegraf.service" ];
|
||||
|
||||
dependencies = [ "telegraf-certs" ];
|
||||
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.xkcdpass
|
||||
@@ -77,37 +57,11 @@
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "telegraf.service" ];
|
||||
requires = [ "telegraf.service" ];
|
||||
wants = [ "telegraf.service" ];
|
||||
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";
|
||||
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 = {
|
||||
@@ -115,7 +69,6 @@
|
||||
environmentFiles = [
|
||||
(builtins.toString config.clan.core.vars.generators.telegraf.files.password-env.path)
|
||||
];
|
||||
|
||||
extraConfig = {
|
||||
agent.interval = "60s";
|
||||
inputs = {
|
||||
@@ -151,12 +104,10 @@
|
||||
metric_version = 2;
|
||||
basic_username = "${auth_user}";
|
||||
basic_password = "$${BASIC_AUTH_PWD}";
|
||||
tls_cert = "$${CRT_PATH}";
|
||||
tls_key = "$${KEY_PATH}";
|
||||
};
|
||||
|
||||
outputs.file = {
|
||||
files = [ "/run/telegraf-www/telegraf.json" ];
|
||||
files = [ jsonpath ];
|
||||
data_format = "json";
|
||||
json_timestamp_units = "1s";
|
||||
};
|
||||
|
||||
@@ -1,95 +1,24 @@
|
||||
{ ... }:
|
||||
{
|
||||
name = "monitoring";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
inventory = {
|
||||
machines.peer1 = {
|
||||
deploy.targetHost = "[2001:db8:1::1]";
|
||||
};
|
||||
machines.peer1 = { };
|
||||
|
||||
instances."test" = {
|
||||
module.name = "monitoring";
|
||||
module.input = "self";
|
||||
|
||||
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 =
|
||||
{ nodes, ... }:
|
||||
{ ... }:
|
||||
''
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import ssl
|
||||
import json
|
||||
import shlex
|
||||
import urllib.request
|
||||
from base64 import b64encode
|
||||
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"
|
||||
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../users/admin
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
25.11
|
||||
@@ -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-----
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -2,14 +2,13 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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 = [
|
||||
"System"
|
||||
"Network"
|
||||
];
|
||||
|
||||
roles.peer = {
|
||||
description = "A peer in the mycelium network";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
];
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the packages service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
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.
|
||||
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.
|
||||
|
||||
`sshd` also generates SSH certificates for both servers and clients allowing for
|
||||
certificate-based authentication for SSH.
|
||||
`sshd` also generates SSH certificates for both servers and clients allowing for certificate-based authentication for SSH.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
```nix
|
||||
{
|
||||
inventory.instances = {
|
||||
@@ -22,6 +18,7 @@ authentication.
|
||||
roles.server.tags.all = { };
|
||||
roles.client.tags.all = { };
|
||||
};
|
||||
|
||||
# Also generate RSA host keys for all servers
|
||||
sshd-with-rsa = {
|
||||
module = {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
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 =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -39,6 +38,7 @@
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||
share = true;
|
||||
files.id_ed25519.deploy = false;
|
||||
@@ -64,12 +64,11 @@
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
description = "Runs sshd with persistent host keys and (if certificate.searchDomains is set) a CA‑signed host certificate for <machine>.<domain>, enabling TOFU‑less verification by clients that trust the CA.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options = {
|
||||
hostKeys.rsa.enable = lib.mkEnableOption "generating a RSA host key";
|
||||
hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
|
||||
|
||||
certificate = {
|
||||
searchDomains = lib.mkOption {
|
||||
@@ -97,7 +96,9 @@
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
clan.core.vars.generators = {
|
||||
|
||||
openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||
share = true;
|
||||
files.id_ed25519.deploy = false;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.peer = {
|
||||
description = "A peer in the syncthing cluster that syncs files with other peers.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
pkgs.syncthing
|
||||
];
|
||||
script = ''
|
||||
syncthing generate --config "$out"
|
||||
export TMPDIR=/tmp
|
||||
TEMPORARY=$(mktemp -d)
|
||||
syncthing generate --config "$out" --data "$TEMPORARY"
|
||||
mv "$out"/key.pem "$out"/key
|
||||
mv "$out"/cert.pem "$out"/cert
|
||||
cat "$out"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$out"/id
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
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 = [
|
||||
"System"
|
||||
"Network"
|
||||
];
|
||||
|
||||
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 =
|
||||
{
|
||||
...
|
||||
@@ -35,7 +31,6 @@
|
||||
};
|
||||
|
||||
roles.server = {
|
||||
description = "Sets up a Tor onion service for the machine, thus making it reachable over Tor.";
|
||||
# interface =
|
||||
# { lib, ... }:
|
||||
# {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the trusted-nix-caches service";
|
||||
|
||||
perInstance =
|
||||
{ ... }:
|
||||
{
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the user service";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -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"; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -9,11 +9,8 @@ in
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "wifi";
|
||||
manifest.description = "Pre configure wifi networks to connect to";
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.default = {
|
||||
description = "Placeholder role to apply the wifi service";
|
||||
interface = {
|
||||
options.networks = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
@@ -45,18 +42,7 @@ in
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
example = {
|
||||
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.
|
||||
'';
|
||||
description = "Wifi networks to predefine";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -146,7 +146,6 @@ in
|
||||
|
||||
# Peer options and configuration
|
||||
roles.peer = {
|
||||
description = "A peer that connects to one or more controllers.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -262,7 +261,6 @@ in
|
||||
|
||||
# Controller options and configuration
|
||||
roles.controller = {
|
||||
description = "A controller that routes peer traffic. Must be publicly reachable.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
@@ -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 = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
@@ -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 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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}")
|
||||
'';
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1p8trv2dmpanl3gnzj294c4t5uysu7d6rfjncp5lmn6redyda8fns6p7kca",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age107mprppm3r9u7f26e6t5mhtdny0h5ugfmfjy8kac2tw9nrh9a3ksex0xca",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../users/admin
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../users/admin
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
25.11
|
||||
@@ -1 +0,0 @@
|
||||
200:fa3b:ad0e:6821:9a51:3ad5:62a4:9ab1
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer1
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:oLWxmCqCLyKCdN90eScYC4eMsY5mcpXexk03P+KfQX9hCUg7iOSv+BTujvAFuJA0z2Pgg7B/L0noYSImE3208gDC2exdZme+szpNwxD6oh1bBx5XuYGc/TDzEl7IdPrf1piuqCkWgD6wNb9aFVA9NB/CrNaszY8=,iv:pU8OGFwtqkxy0+iyhWaOnWdSVvaxYBUgnQwv89CTfKc=,tag:V/GVEvjEpeaf3Tl3XCVL7w==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1p8trv2dmpanl3gnzj294c4t5uysu7d6rfjncp5lmn6redyda8fns6p7kca",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBidXZxMVg2bkFCekFza3FV\naGVJTjRSSnQrVkgyTVU4Vk9YZFR2Nk1Xcnl3ClNTZnJsR2xZc3hGRFA0NGp2L2Uy\nU2w0N1ZON0xQRXRKL0NId1J3dWNSeXcKLS0tIFlKNk5walJrM2FGM0ozYTBUNVN1\nNHd0N3g2VWFjTUZtbHExdmsvSjJQUGMKDHgRMyTf4CuoIRdsfl6TmPLgcjxHdR4N\nzNmnSmT6QXJr4gBQ7e/3zpMdq4sKzyAOjJPkQra8nJ/KvhpFwXkdtg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1L3hBYnNTdEhTK1M0TEUx\nM1BFb3IwWDZPc2tkN0JUNXRPWkhEWHpXaFFNCkFlZ3Ixb1hhVWMvUmpKdCtjbW5h\nTVpLSkdxUXI4SVE3MW5LZkFVSmM3dDgKLS0tICtKWGYzbFQ1VGQydktHSDVadVQy\nM3JTSGc0NWVHWGl4akV2VUNNVjVvT00KKmbkGJ2KclTBb7NI0MamWZnlWMaXscws\nYO261RHc/j4s3KdbWATklh2KTicKpgta0mh294Y+hKYLGgBpnXjtOw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-17T07:12:51Z",
|
||||
"mac": "ENC[AES256_GCM,data:YG4k+PvsF0NVfjqKY/MBhCOwn9Kcn5orq8aD41X3NEPBT+wU5RlN0XqXhH2nkZ52ZHdCN84Ulbd4guwBcJcUtGvxqOpfE4SJ9RFE+SjYw+2+S9wlArQx5R5Zd+Sr+Rgtaip3moENbxETxHZazbEK3u5o0aXKrZvoXH2xgPRYUOg=,iv:lDnNELLpqykrsmoORMW7p6gF43C2QvPwf0PZep2pEo8=,tag:CZNOBx3Aeoq5WeDbANqFVw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1,3 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAguIpeMvvMtdilU6tsqdBClci2YUgBKEUPQ5ID55zOVY=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -1 +0,0 @@
|
||||
25.11
|
||||
@@ -1 +0,0 @@
|
||||
200:3975:b69f:fd15:f39a:bf24:f644:432b
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/machines/peer2
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:Ik+mzD41SwfGZrqGi5oy0u8ZS8qCKgV34aohIt7J/11S3ayhKKN8451cYPltGg+2i/+2DWRSkqYZsctgIahvthnvb30r91XHRUCtE3oT8iPOKwEuW2xnZBL2dK0qHrKFGn8B/o3Bl/+4cWuR5Jt9QHlN4oF3o50=,iv:a9mSIr7TT9im19P5XKiDGL71hMLfQE8IB6NV8WABZ+s=,tag:dVQ0HZdpbkcmFA37Il+VIw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age107mprppm3r9u7f26e6t5mhtdny0h5ugfmfjy8kac2tw9nrh9a3ksex0xca",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3WEtncHIrd2YrbHduUm4y\nYkh1U0NERDR0MzRNQXZ0elNMZzl0ZmJPMlVVCnJ1UC9uNnk0dC9JVzdReUxLZmdR\nMGIwSjBKUW8yQmQ1R3dqU29ZM2czMEUKLS0tIFpMM3l6YnVFRUtLOFRvSTBXRXU0\nQUJWNk5obzBINS90dWwybXlXdEZFZjgKrZtViJwgPpKT+Bhx6ymEhf6QmnMmZ6Jf\n3kIxzDWxGbxrDTrrdXm33gYs9bRvtXsTACRT3TRAKIwSjIV/ycCUBA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArY1A4blFZNjdTU3lGUFA3\nb1hscVhLVmtuQnJMS3VHZVVEOXBDRktXNVhjCkdLZEFFZXhubFJrZTU3RjRDN1lh\nVzgyd3E3a0NGTGh1N2lPQWZBVG9BaW8KLS0tIFN1eEhpMk5mUkNaL29MS2NlWDdP\nVXFtb3c0Y25MT3NLK0N2RStkY1R0MGsKLv8yOSl2iQx39JRJA/e0dOtIz7ZND0+o\nzmqxapD6pxbj8RGswj0szLrxaveWT6Vkmmy/a4fz35uw3xuF7uiJJA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-17T07:13:24Z",
|
||||
"mac": "ENC[AES256_GCM,data:KCBHfavyg1U/Z1e3rr0jdHivUa5/rl42N/K6c0BR47srdYqcpAS+SWW6tH7kPI3EO+P7TgXVoGlmcXaKgrlRpe37eNzdAT6PJYm7rhe6rm2UgZ1yw/NFqA11p/i3dDbTzYHaHMNGGGg9t/BdT1tD0zCICfptGQnSSBx/BEmT3ak=,iv:GOk8/z1mLIVKxFX4ayt9HvA90gv0Af1KqcDbdJRUYu4=,tag:hJpOBFRESQXmE7iMqDqO6g==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -1,3 +0,0 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEA40UksAF1BjKgbYTd3motmeXa/belhNOoRfaaPQTXy8E=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -2,12 +2,11 @@
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/zerotier";
|
||||
manifest.description = "Zerotier Mesh VPN Service for secure P2P networking between machines";
|
||||
manifest.description = "Configuration of the secure and efficient Zerotier VPN";
|
||||
manifest.categories = [ "Utility" ];
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.peer = {
|
||||
description = "A peer that connects to your private Zerotier network.";
|
||||
perInstance =
|
||||
{
|
||||
instanceName,
|
||||
@@ -52,7 +51,6 @@
|
||||
};
|
||||
|
||||
roles.moon = {
|
||||
description = "A moon acts as a relay node to connect other nodes in the zerotier network that are not publicly reachable. Each moon must be publicly reachable.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
@@ -103,7 +101,6 @@
|
||||
};
|
||||
|
||||
roles.controller = {
|
||||
description = "Manages network membership and is responsible for admitting new peers to your Zerotier network.";
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
73
devFlake/flake.lock
generated
73
devFlake/flake.lock
generated
@@ -3,42 +3,21 @@
|
||||
"clan-core-for-checks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1760368011,
|
||||
"narHash": "sha256-mLK2nwbfklfOGIVAKVNDwGyYz8mPh4fzsAqSK3BlCiI=",
|
||||
"ref": "clan-25.05",
|
||||
"rev": "1b3c129aa9741d99b27810652ca888b3fbfc3a11",
|
||||
"lastModified": 1756166884,
|
||||
"narHash": "sha256-skg4rwpbCjhpLlrv/Pndd43FoEgrJz98WARtGLhCSzo=",
|
||||
"ref": "main",
|
||||
"rev": "f7414d7e6e58709af27b6fe16eb530278e81eaaf",
|
||||
"shallow": true,
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/clan-core"
|
||||
},
|
||||
"original": {
|
||||
"ref": "clan-25.05",
|
||||
"ref": "main",
|
||||
"shallow": true,
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/clan-core"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"test-fixtures",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1741352980,
|
||||
"narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": [
|
||||
@@ -105,16 +84,16 @@
|
||||
},
|
||||
"nixpkgs-dev": {
|
||||
"locked": {
|
||||
"lastModified": 1760309387,
|
||||
"narHash": "sha256-e0lvQ7+B1Y8zjykYHAj9tBv10ggLqK0nmxwvMU3J0Eo=",
|
||||
"lastModified": 1758012326,
|
||||
"narHash": "sha256-5xX26DjtxxFAw4IyZATzUs2UYghdmcpyZ93whojp828=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6cd95994a9c8f7c6f8c1f1161be94119afdcb305",
|
||||
"rev": "1bc4de0728f2eb1602fc5cce4122f2e999bc9d35",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05-small",
|
||||
"ref": "nixos-unstable-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -128,11 +107,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1758662783,
|
||||
"narHash": "sha256-igrxT+/MnmcftPOHEb+XDwAMq3Xg1Xy7kVYQaHhPlAg=",
|
||||
"lastModified": 1757885130,
|
||||
"narHash": "sha256-56CMb5W/pgjKLh0bx2ekhn5rde/YmgR63HAqrY9/BCw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "7d4c0fc4ffe3bd64e5630417162e9e04e64b27a4",
|
||||
"rev": "fae3c59a646e00c4b1d359c50b27458a0713d2fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -148,7 +127,6 @@
|
||||
"nixpkgs-dev": "nixpkgs-dev",
|
||||
"nuschtos": "nuschtos",
|
||||
"systems": "systems_2",
|
||||
"test-fixtures": "test-fixtures",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
@@ -182,37 +160,16 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"test-fixtures": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-dev"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742806412,
|
||||
"narHash": "sha256-ZoAN0/sHEHY+ymJnkdkBAuZ/6sc66RYR4xMHpLf7++E=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "4a2bc56d886578124b05060d3fb7eddc38c019f8",
|
||||
"revCount": 2,
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/test-fixtures"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/test-fixtures"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760120816,
|
||||
"narHash": "sha256-gq9rdocpmRZCwLS5vsHozwB6b5nrOBDNc2kkEaTXHfg=",
|
||||
"lastModified": 1756662192,
|
||||
"narHash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "761ae7aff00907b607125b2f57338b74177697ed",
|
||||
"rev": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "private dev inputs";
|
||||
|
||||
# Dev dependencies
|
||||
inputs.nixpkgs-dev.url = "github:NixOS/nixpkgs/nixos-25.05-small";
|
||||
inputs.nixpkgs-dev.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.flake-utils.inputs.systems.follows = "systems";
|
||||
@@ -15,11 +15,8 @@
|
||||
|
||||
inputs.systems.url = "github:nix-systems/default";
|
||||
|
||||
inputs.clan-core-for-checks.url = "git+https://git.clan.lol/clan/clan-core?ref=clan-25.05&shallow=1";
|
||||
inputs.clan-core-for-checks.url = "git+https://git.clan.lol/clan/clan-core?ref=main&shallow=1";
|
||||
inputs.clan-core-for-checks.flake = false;
|
||||
|
||||
inputs.test-fixtures.url = "git+https://git.clan.lol/clan/test-fixtures";
|
||||
inputs.test-fixtures.inputs.nixpkgs.follows = "nixpkgs-dev";
|
||||
|
||||
outputs = inputs: inputs;
|
||||
}
|
||||
|
||||
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
/site/reference
|
||||
/site/services/official
|
||||
/site/static
|
||||
/site/option-search
|
||||
/site/options
|
||||
/site/openapi.json
|
||||
!/site/static/extra.css
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Contributing to Clan
|
||||
|
||||
|
||||
**Continuous Integration (CI)**: Each pull request gets automatically tested by gitea. If any errors are detected, it will block pull requests until they're resolved.
|
||||
|
||||
**Dependency Management**: We use the [Nix package manager](https://nixos.org/) to manage dependencies and ensure reproducibility, making your development process more robust.
|
||||
@@ -9,27 +10,25 @@
|
||||
- Linux
|
||||
- macOS
|
||||
|
||||
## Getting Started with the Development Environment
|
||||
# Getting Started with the Development Environment
|
||||
|
||||
Let's get your development environment up and running:
|
||||
|
||||
1. **Install Nix Package Manager**:
|
||||
|
||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
2. **Install direnv**:
|
||||
1. **Install direnv**:
|
||||
|
||||
- To automatically setup a devshell on entering the directory
|
||||
```bash
|
||||
nix profile install nixpkgs#nix-direnv-flakes nixpkgs#direnv
|
||||
```
|
||||
|
||||
```bash
|
||||
nix profile install nixpkgs#nix-direnv-flakes nixpkgs#direnv
|
||||
```
|
||||
|
||||
3. **Add direnv to your shell**:
|
||||
1. **Add direnv to your shell**:
|
||||
|
||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
||||
@@ -38,43 +37,34 @@ Let's get your development environment up and running:
|
||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||
```
|
||||
|
||||
4. **Allow the devshell**
|
||||
1. **Allow the devshell**
|
||||
- Go to `clan-core/pkgs/clan-cli` and do a `direnv allow` to setup the necessary development environment to execute the `clan` command
|
||||
|
||||
5. **Create a Gitea Account**
|
||||
|
||||
1. **Create a Gitea Account**:
|
||||
- Register an account on https://git.clan.lol
|
||||
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||
- Clone the repository and navigate to it
|
||||
- Add a new remote called upstream
|
||||
|
||||
```bash
|
||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||
```
|
||||
|
||||
7. **Allow .envrc**
|
||||
- Add a new remote called upstream:
|
||||
```bash
|
||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||
```
|
||||
1. **Allow .envrc**:
|
||||
|
||||
- When you enter the directory, you'll receive an error message like this:
|
||||
|
||||
```bash
|
||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||
```
|
||||
|
||||
```bash
|
||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||
```
|
||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||
|
||||
8. **(Optional) Install Git Hooks**
|
||||
|
||||
1. **(Optional) Install Git Hooks**:
|
||||
- To syntax check your code you can run:
|
||||
|
||||
```bash
|
||||
nix fmt
|
||||
```
|
||||
|
||||
```bash
|
||||
nix fmt
|
||||
```
|
||||
- To make this automatic install the git hooks
|
||||
|
||||
```bash
|
||||
./scripts/pre-commit
|
||||
```
|
||||
```bash
|
||||
./scripts/pre-commit
|
||||
```
|
||||
|
||||
## Related Projects
|
||||
|
||||
@@ -83,7 +73,7 @@ Let's get your development environment up and running:
|
||||
- **Nixos Anywhere**: [nixos-anywhere](https://github.com/nix-community/nixos-anywhere)
|
||||
- **Disko**: [disko](https://github.com/nix-community/disko)
|
||||
|
||||
### Override related projects for local development
|
||||
## Fixing Bugs or Adding Features in Clan-CLI
|
||||
|
||||
If you have a bug fix or feature that involves a related project, clone the relevant repository and replace its invocation in your local setup.
|
||||
|
||||
@@ -112,10 +102,10 @@ run(
|
||||
|
||||
```
|
||||
|
||||
The `<path_to_local_src>` doesn't need to be a local path, it can be any valid [flakeref](https://nix.dev/manual/nix/2.26/command-ref/new-cli/nix3-flake.html#flake-references).
|
||||
The <path_to_local_src> doesn't need to be a local path, it can be any valid [flakeref](https://nix.dev/manual/nix/2.26/command-ref/new-cli/nix3-flake.html#flake-references).
|
||||
And thus can point to test already opened PRs for example.
|
||||
|
||||
## Standards
|
||||
# Standards
|
||||
|
||||
- Every new module name should be in kebab-case.
|
||||
- Every fact definition, where possible should be in kebab-case.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Serve documentation locally
|
||||
|
||||
```
|
||||
nix develop .#docs -c mkdocs serve
|
||||
$ nix develop .#docs -c mkdocs serve
|
||||
```
|
||||
|
||||
41
docs/main.py
Normal file
41
docs/main.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
def define_env(env: Any) -> None:
|
||||
static_dir = "/static/"
|
||||
video_dir = "https://clan.lol/" + "videos/"
|
||||
asciinema_dir = static_dir + "asciinema-player/"
|
||||
|
||||
@env.macro
|
||||
def video(name: str) -> str:
|
||||
return f"""<video loop muted autoplay id="{name}">
|
||||
<source src={video_dir + name} type="video/webm">
|
||||
Your browser does not support the video tag.
|
||||
</video>"""
|
||||
|
||||
@env.macro
|
||||
def asciinema(name: str) -> str:
|
||||
return f"""<div id="{name}">
|
||||
<script>
|
||||
// Function to load the script and then create the Asciinema player
|
||||
function loadAsciinemaPlayer() {{
|
||||
var script = document.createElement('script');
|
||||
script.src = "{asciinema_dir}/asciinema-player.min.js";
|
||||
script.onload = function() {{
|
||||
AsciinemaPlayer.create('{video_dir + name}', document.getElementById("{name}"), {{
|
||||
loop: true,
|
||||
autoPlay: true,
|
||||
controls: false,
|
||||
speed: 1.5,
|
||||
theme: "solarized-light"
|
||||
}});
|
||||
}};
|
||||
document.head.appendChild(script);
|
||||
}}
|
||||
|
||||
// Load the Asciinema player script
|
||||
loadAsciinemaPlayer();
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{asciinema_dir}/asciinema-player.css" />
|
||||
</div>"""
|
||||
146
docs/mkdocs.yml
146
docs/mkdocs.yml
@@ -47,27 +47,30 @@ exclude_docs: |
|
||||
nav:
|
||||
- Getting Started:
|
||||
- Overview: index.md
|
||||
- getting-started/creating-your-first-clan.md
|
||||
- getting-started/add-machines.md
|
||||
- getting-started/add-users.md
|
||||
- getting-started/add-services.md
|
||||
- Creating Your First Clan: guides/getting-started/index.md
|
||||
- Add Machines: guides/getting-started/add-machines.md
|
||||
- Add User: guides/getting-started/add-user.md
|
||||
- Add Services: guides/getting-started/add-services.md
|
||||
- Deploy to Physical Machine:
|
||||
- getting-started/deploy-to-physical-machine/flash-installer.md
|
||||
- getting-started/deploy-to-physical-machine/install-machine.md
|
||||
- getting-started/deploy-to-virtual-machine.md
|
||||
- getting-started/configure-disk.md
|
||||
- getting-started/update-machines.md
|
||||
- getting-started/continuous-integration.md
|
||||
- Convert existing NixOS configurations: getting-started/convert-existing-NixOS-configuration.md
|
||||
- Create USB Installer: guides/getting-started/create-installer.md
|
||||
- Deploy Physical Machine: guides/getting-started/hardware-report-physical.md
|
||||
- Deploy to Virtual Machine: guides/getting-started/hardware-report-virtual.md
|
||||
- Configure Disk Config: guides/getting-started/choose-disk.md
|
||||
- Update Machine: guides/getting-started/update.md
|
||||
- Continuous Integration: guides/getting-started/flake-check.md
|
||||
- Convert Existing NixOS Config: guides/getting-started/convert-flake.md
|
||||
- Guides:
|
||||
- Inventory:
|
||||
- Introduction to Inventory: guides/inventory/inventory.md
|
||||
- File Autoincludes: guides/inventory/autoincludes.md
|
||||
- Services:
|
||||
- Introduction to Services: guides/services/introduction-to-services.md
|
||||
|
||||
- Clan Services:
|
||||
- Inventory Guide: guides/inventory/clanServices.md
|
||||
- Author Your Own Service: guides/services/community.md
|
||||
- Internal Services with SSL: guides/internal-ssl-services.md
|
||||
- Vars:
|
||||
- Introduction to Vars: guides/vars/vars-overview.md
|
||||
- Minimal Example: guides/vars/vars-backend.md
|
||||
- Diving deeper: guides/vars/vars-concepts.md
|
||||
- Introduction to Vars: guides/vars/vars-overview.md
|
||||
- Minimal Example: guides/vars/vars-backend.md
|
||||
- Diving deeper: guides/vars/vars-concepts.md
|
||||
@@ -76,40 +79,47 @@ nav:
|
||||
- Sops Backend:
|
||||
- Yubikeys & Age Plugins: guides/vars/sops/age-plugins.md
|
||||
- Managing Users (OLD): guides/secrets.md
|
||||
- Backups:
|
||||
- Introduction to Backups: guides/backups/backup-intro.md
|
||||
- Minimal Example: guides/backups/minimal-example.md
|
||||
- Digging Deeper: guides/backups/digging-deeper.md
|
||||
- Advanced Example: guides/backups/advanced-example.md
|
||||
- Networking:
|
||||
- Introduction to Networking: guides/networking/networking.md
|
||||
- Zerotier VPN: guides/networking/mesh-vpn.md
|
||||
- Nixpkgs Flake Input: guides/nixpkgs-flake-input/index.md
|
||||
- Disko Templates:
|
||||
- Community Disko Templates: guides/disko-templates/community.md
|
||||
- Backups:
|
||||
- Introduction to Backups: guides/backups.md
|
||||
- Flake-parts: guides/flake-parts.md
|
||||
- NixOS Rebuild: guides/nixos-rebuild.md
|
||||
- macOS: guides/macos.md
|
||||
- NixOs Rebuild: guides/nixos-rebuild.md
|
||||
- macOS:
|
||||
- Managing macOS Machines: guides/macos.md
|
||||
# Should be part of the respective sections above
|
||||
# machines, disko, clan
|
||||
- Templates:
|
||||
- Introduction to Templates: concepts/templates.md
|
||||
- Community Disko Templates: guides/disko-templates/community.md
|
||||
- Templates: concepts/templates.md
|
||||
- Migrations:
|
||||
- clan modules to clan services: guides/migrations/migrate-inventory-services.md
|
||||
- Facts to Vars: guides/migrations/migration-facts-vars.md
|
||||
- clan modules --> clan services: guides/migrations/migrate-inventory-services.md
|
||||
- Facts --> Vars: guides/migrations/migration-facts-vars.md
|
||||
- Disk id: guides/migrations/disk-id.md
|
||||
- Disk Encryption: guides/disk-encryption.md
|
||||
- Disable Secure Boot: guides/secure-boot.md
|
||||
- Contributing:
|
||||
- guides/contributing/CONTRIBUTING.md
|
||||
- guides/contributing/debugging.md
|
||||
- guides/contributing/testing.md
|
||||
- Hacking: guides/contributing/CONTRIBUTING.md
|
||||
- Advanced Debugging: guides/contributing/debugging.md
|
||||
- Testing: guides/contributing/testing.md
|
||||
|
||||
- Reference:
|
||||
- Overview: reference/index.md
|
||||
- Options:
|
||||
- reference/options/clan.md
|
||||
- reference/options/clan_inventory.md
|
||||
- reference/options/clan_service.md
|
||||
- Clan Service API: reference/clanServices/clan-service-author-interface.md
|
||||
|
||||
- clan.core (Machine Options):
|
||||
- Overview: reference/clan.core/index.md
|
||||
- reference/clan.core/backups.md
|
||||
- reference/clan.core/deployment.md
|
||||
- reference/clan.core/facts.md
|
||||
- reference/clan.core/networking.md
|
||||
- reference/clan.core/postgresql.md
|
||||
- reference/clan.core/settings.md
|
||||
- reference/clan.core/sops.md
|
||||
- reference/clan.core/state.md
|
||||
- reference/clan.core/vars.md
|
||||
- Clan Service API: reference/clanServices/clan-service-author-interface.md
|
||||
|
||||
- clan.core (Machine Options):
|
||||
- Overview: reference/clan.core/index.md
|
||||
@@ -143,44 +153,42 @@ nav:
|
||||
- HTTP API: api.md
|
||||
|
||||
- Decisions:
|
||||
- decisions/Architecture-decisions.md
|
||||
- decisions/01-Clan-Modules.md
|
||||
- decisions/02-clan-as-library.md
|
||||
- decisions/03-adr-numbering-process.md
|
||||
- decisions/04-fetching-nix-from-python.md
|
||||
- decisions/05-deployment-parameters.md
|
||||
- decisions/template.md
|
||||
- Architecture Decisions: decisions/README.md
|
||||
- 01-clanModules: decisions/01-ClanModules.md
|
||||
- 02-clan-api: decisions/02-clan-api.md
|
||||
- 03-adr-numbering-process: decisions/03-adr-numbering-process.md
|
||||
- 04-fetching-nix-from-python: decisions/04-fetching-nix-from-python.md
|
||||
- 05-deployment-parameters: decisions/05-deployment-parameters.md
|
||||
- Template: decisions/_template.md
|
||||
- Glossary: reference/glossary.md
|
||||
- Services:
|
||||
- services/definition.md
|
||||
# Generated list from the list of official services
|
||||
- Introduction to ClanServices: reference/clanServices/index.md
|
||||
- Official:
|
||||
- services/official/admin.md
|
||||
- services/official/borgbackup.md
|
||||
- services/official/certificates.md
|
||||
- services/official/coredns.md
|
||||
- services/official/data-mesher.md
|
||||
- services/official/dyndns.md
|
||||
- services/official/emergency-access.md
|
||||
- services/official/garage.md
|
||||
- services/official/hello-world.md
|
||||
- services/official/importer.md
|
||||
- services/official/localbackup.md
|
||||
- services/official/matrix-synapse.md
|
||||
- services/official/mycelium.md
|
||||
- services/official/monitoring.md
|
||||
- services/official/packages.md
|
||||
- services/official/sshd.md
|
||||
- services/official/syncthing.md
|
||||
- services/official/trusted-nix-caches.md
|
||||
- services/official/users.md
|
||||
- services/official/wifi.md
|
||||
- services/official/wireguard.md
|
||||
- services/official/yggdrasil.md
|
||||
- services/official/zerotier.md
|
||||
- services/community.md
|
||||
- reference/clanServices/admin.md
|
||||
- reference/clanServices/borgbackup.md
|
||||
- reference/clanServices/certificates.md
|
||||
- reference/clanServices/coredns.md
|
||||
- reference/clanServices/data-mesher.md
|
||||
- reference/clanServices/dyndns.md
|
||||
- reference/clanServices/emergency-access.md
|
||||
- reference/clanServices/garage.md
|
||||
- reference/clanServices/hello-world.md
|
||||
- reference/clanServices/importer.md
|
||||
- reference/clanServices/localbackup.md
|
||||
- reference/clanServices/matrix-synapse.md
|
||||
- reference/clanServices/mycelium.md
|
||||
- reference/clanServices/monitoring.md
|
||||
- reference/clanServices/packages.md
|
||||
- reference/clanServices/sshd.md
|
||||
- reference/clanServices/syncthing.md
|
||||
- reference/clanServices/trusted-nix-caches.md
|
||||
- reference/clanServices/users.md
|
||||
- reference/clanServices/wifi.md
|
||||
- reference/clanServices/wireguard.md
|
||||
- reference/clanServices/zerotier.md
|
||||
- Community: community/services/index.md
|
||||
|
||||
- Search Clan Options: "/option-search"
|
||||
- Clan Options: "/options"
|
||||
|
||||
docs_dir: site
|
||||
site_dir: out
|
||||
@@ -234,7 +242,7 @@ extra:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/clan-lol/clan-core
|
||||
- icon: fontawesome/solid/rss
|
||||
link: https://clan.lol/feed.xml
|
||||
link: /feed_rss_created.xml
|
||||
|
||||
plugins:
|
||||
- search
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
module-docs,
|
||||
clan-cli-docs,
|
||||
clan-lib-openapi,
|
||||
asciinema-player-js,
|
||||
asciinema-player-css,
|
||||
roboto,
|
||||
fira-code,
|
||||
option-search,
|
||||
docs-options,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -42,18 +44,22 @@ pkgs.stdenv.mkDerivation {
|
||||
pushd docs
|
||||
|
||||
mkdir -p ./site/reference/cli
|
||||
cp -af ${module-docs}/services/* ./site/services/
|
||||
cp -af ${module-docs}/reference/* ./site/reference/
|
||||
cp -af ${module-docs}/* ./site/reference/
|
||||
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
||||
|
||||
mkdir -p ./site/reference/internal
|
||||
cp -af ${clan-lib-openapi} ./site/openapi.json
|
||||
|
||||
chmod -R +w ./site
|
||||
chmod -R +w ./site/reference
|
||||
echo "Generated API documentation in './site/reference/' "
|
||||
|
||||
rm -rf ./site/option-search
|
||||
cp -r ${option-search} ./site/option-search
|
||||
chmod -R +w ./site/option-search
|
||||
rm -rf ./site/options
|
||||
cp -r ${docs-options} ./site/options
|
||||
chmod -R +w ./site/options
|
||||
|
||||
mkdir -p ./site/static/asciinema-player
|
||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||
|
||||
# Link to fonts
|
||||
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{ inputs, ... }:
|
||||
{ inputs, self, ... }:
|
||||
{
|
||||
imports = [
|
||||
./options/flake-module.nix
|
||||
];
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
@@ -7,14 +10,85 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# Simply evaluated options (JSON)
|
||||
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
||||
jsonDocs = pkgs.callPackage ./get-module-docs.nix {
|
||||
inherit (self) clanModules;
|
||||
clan-core = self;
|
||||
inherit pkgs;
|
||||
};
|
||||
|
||||
# clan service options
|
||||
clanModulesViaService = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesViaService);
|
||||
|
||||
# Simply evaluated options (JSON)
|
||||
renderOptions =
|
||||
pkgs.runCommand "render-options"
|
||||
{
|
||||
# TODO: ruff does not splice properly in nativeBuildInputs
|
||||
depsBuildBuild = [ pkgs.ruff ];
|
||||
nativeBuildInputs = [
|
||||
pkgs.python3
|
||||
pkgs.mypy
|
||||
self'.packages.clan-cli
|
||||
];
|
||||
}
|
||||
''
|
||||
install -D -m755 ${./render_options}/__init__.py $out/bin/render-options
|
||||
patchShebangs --build $out/bin/render-options
|
||||
|
||||
ruff format --check --diff $out/bin/render-options
|
||||
ruff check --line-length 88 $out/bin/render-options
|
||||
mypy --strict $out/bin/render-options
|
||||
'';
|
||||
|
||||
asciinema-player-js = pkgs.fetchurl {
|
||||
url = "https://github.com/asciinema/asciinema-player/releases/download/v3.7.0/asciinema-player.min.js";
|
||||
sha256 = "sha256-Ymco/+FinDr5YOrV72ehclpp4amrczjo5EU3jfr/zxs=";
|
||||
};
|
||||
asciinema-player-css = pkgs.fetchurl {
|
||||
url = "https://github.com/asciinema/asciinema-player/releases/download/v3.7.0/asciinema-player.css";
|
||||
sha256 = "sha256-GZMeZFFGvP5GMqqh516mjJKfQaiJ6bL38bSYOXkaohc=";
|
||||
};
|
||||
|
||||
module-docs =
|
||||
pkgs.runCommand "rendered"
|
||||
{
|
||||
buildInputs = [
|
||||
pkgs.python3
|
||||
self'.packages.clan-cli
|
||||
];
|
||||
}
|
||||
''
|
||||
export CLAN_CORE_PATH=${
|
||||
inputs.nixpkgs.lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = ../../clanModules;
|
||||
}
|
||||
}
|
||||
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
||||
|
||||
# A file that contains the links to all clanModule docs
|
||||
export CLAN_MODULES_VIA_SERVICE=${clanModulesViaService}
|
||||
export CLAN_SERVICE_INTERFACE=${self'.legacyPackages.clan-service-module-interface}/share/doc/nixos/options.json
|
||||
|
||||
mkdir $out
|
||||
|
||||
# The python script will place mkDocs files in the output directory
|
||||
exec python3 ${renderOptions}/bin/render-options
|
||||
'';
|
||||
in
|
||||
{
|
||||
legacyPackages = {
|
||||
inherit
|
||||
jsonDocs
|
||||
clanModulesViaService
|
||||
;
|
||||
};
|
||||
devShells.docs = self'.packages.docs.overrideAttrs (_old: {
|
||||
nativeBuildInputs = [
|
||||
# Run: htmlproofer --disable-external
|
||||
pkgs.html-proofer
|
||||
]
|
||||
++ self'.devShells.default.nativeBuildInputs
|
||||
++ self'.packages.docs.nativeBuildInputs;
|
||||
nativeBuildInputs =
|
||||
self'.devShells.default.nativeBuildInputs ++ self'.packages.docs.nativeBuildInputs;
|
||||
shellHook = ''
|
||||
${self'.devShells.default.shellHook}
|
||||
git_root=$(git rev-parse --show-toplevel)
|
||||
@@ -26,27 +100,17 @@
|
||||
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||
inherit (self'.packages)
|
||||
clan-cli-docs
|
||||
option-search
|
||||
docs-options
|
||||
inventory-api-docs
|
||||
clan-lib-openapi
|
||||
module-docs
|
||||
;
|
||||
inherit (inputs) nixpkgs;
|
||||
inherit module-docs;
|
||||
inherit asciinema-player-js;
|
||||
inherit asciinema-player-css;
|
||||
};
|
||||
deploy-docs = pkgs.callPackage ./deploy-docs.nix { inherit (config.packages) docs; };
|
||||
inherit module-docs;
|
||||
};
|
||||
checks.docs-integrity =
|
||||
pkgs.runCommand "docs-integrity"
|
||||
{
|
||||
nativeBuildInputs = [ pkgs.html-proofer ];
|
||||
LANG = "C.UTF-8";
|
||||
}
|
||||
''
|
||||
# External links should be avoided in the docs, because they often break
|
||||
# and we cannot statically control them. Thus we disable checking them
|
||||
htmlproofer --disable-external ${self'.packages.docs}
|
||||
|
||||
touch $out
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
serviceModules = self.clan.modules;
|
||||
|
||||
baseHref = "/option-search/";
|
||||
baseHref = "/options/";
|
||||
|
||||
getRoles =
|
||||
module:
|
||||
@@ -118,7 +118,7 @@
|
||||
_file = "docs flake-module";
|
||||
imports = [
|
||||
{ _module.args = { inherit clanLib; }; }
|
||||
(import ../../lib/modules/inventoryClass/roles-interface.nix {
|
||||
(import ../../../lib/modules/inventoryClass/roles-interface.nix {
|
||||
nestedSettingsOption = mkOption {
|
||||
type = types.raw;
|
||||
description = ''
|
||||
@@ -201,7 +201,7 @@
|
||||
# };
|
||||
|
||||
packages = {
|
||||
option-search =
|
||||
docs-options =
|
||||
if privateInputs ? nuschtos then
|
||||
privateInputs.nuschtos.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch {
|
||||
inherit baseHref;
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -35,13 +34,11 @@ from typing import Any
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.services.modules import (
|
||||
CategoryInfo,
|
||||
ModuleManifest,
|
||||
)
|
||||
|
||||
# Get environment variables
|
||||
CLAN_CORE_PATH = Path(os.environ["CLAN_CORE_PATH"])
|
||||
CLAN_CORE_DOCS = Path(os.environ["CLAN_CORE_DOCS"])
|
||||
CLAN_OPTIONS_PATH = Path(os.environ["CLAN_OPTIONS_PATH"])
|
||||
|
||||
# Options how to author clan.modules
|
||||
# perInstance, perMachine, ...
|
||||
@@ -105,7 +102,7 @@ def render_option(
|
||||
read_only = option.get("readOnly")
|
||||
|
||||
res = f"""
|
||||
{"#" * level} {sanitize(name) if short_head is None else sanitize(short_head)} {"{: #" + sanitize_anchor(name) + "}" if level > 1 else ""}
|
||||
{"#" * level} {sanitize(name) if short_head is None else sanitize(short_head)}
|
||||
|
||||
"""
|
||||
|
||||
@@ -132,14 +129,12 @@ def render_option(
|
||||
"""
|
||||
example = option.get("example", {}).get("text")
|
||||
if example:
|
||||
example_indented = join_lines_with_indentation(example.split("\n"))
|
||||
res += f"""
|
||||
|
||||
???+ example
|
||||
```nix title="example"
|
||||
{example}
|
||||
```
|
||||
|
||||
```nix
|
||||
{example_indented}
|
||||
```
|
||||
"""
|
||||
if option.get("relatedPackages"):
|
||||
res += f"""
|
||||
@@ -156,7 +151,7 @@ def render_option(
|
||||
source_path = source_path.split(",")[0]
|
||||
|
||||
res += f"""
|
||||
:simple-git: Declared in: [{name}]({source_path})
|
||||
📃 Declared in: [{name}]({source_path})
|
||||
"""
|
||||
res += "\n\n"
|
||||
|
||||
@@ -226,7 +221,7 @@ def produce_clan_core_docs() -> None:
|
||||
indexfile = f"{module_name}/index.md"
|
||||
core_outputs[indexfile] = module_header(module_name) + clan_core_descr
|
||||
|
||||
core_outputs[indexfile] += """!!! info "Submodules"\n"""
|
||||
core_outputs[indexfile] += """:::note[Submodules]"\n"""
|
||||
|
||||
for submodule_name, split_options in split.items():
|
||||
root = options_to_tree(split_options)
|
||||
@@ -235,7 +230,7 @@ def produce_clan_core_docs() -> None:
|
||||
if module_type is not None and "submodule" not in module_type:
|
||||
continue
|
||||
core_outputs[indexfile] += (
|
||||
f" - [{submodule_name}](../../reference/clan.core/{submodule_name}.md)\n"
|
||||
f" - [{submodule_name}](./{submodule_name}.md)\n"
|
||||
)
|
||||
|
||||
core_outputs[indexfile] += options_head
|
||||
@@ -273,10 +268,8 @@ def produce_clan_core_docs() -> None:
|
||||
core_outputs[outfile] += output
|
||||
|
||||
for outfile, output in core_outputs.items():
|
||||
(Path(OUT) / "reference" / outfile).parent.mkdir(
|
||||
parents=True, exist_ok=True
|
||||
)
|
||||
with (Path(OUT) / "reference" / outfile).open("w") as of:
|
||||
(Path(OUT) / outfile).parent.mkdir(parents=True, exist_ok=True)
|
||||
with (Path(OUT) / outfile).open("w") as of:
|
||||
of.write(output)
|
||||
|
||||
|
||||
@@ -309,6 +302,33 @@ def produce_clan_service_docs() -> None:
|
||||
msg = f"Environment variables are not set correctly: $out={OUT}"
|
||||
raise ClanError(msg)
|
||||
|
||||
indexfile = Path(OUT) / "clanServices/index.md"
|
||||
indexfile.parent.mkdir(
|
||||
parents=True,
|
||||
exist_ok=True,
|
||||
)
|
||||
index = "# Clan Services\n\n"
|
||||
index += """
|
||||
**`clanServices`** are modular building blocks that simplify the configuration and orchestration of multi-host services.
|
||||
|
||||
Each `clanService`:
|
||||
|
||||
* Is a module of class **`clan.service`**
|
||||
* Can define **roles** (e.g., `client`, `server`)
|
||||
* Uses **`inventory.instances`** to configure where and how it is deployed
|
||||
|
||||
:::note
|
||||
`clanServices` are part of Clan's next-generation service model and are intended to replace `clanModules`.
|
||||
|
||||
See [Migration Guide](../../guides/migrations/migrate-inventory-services.md) for help on migrating.
|
||||
:::
|
||||
|
||||
Learn how to use `clanServices` in practice in the [Using clanServices guide](../../guides/inventory/clanServices.md).
|
||||
"""
|
||||
|
||||
with indexfile.open("w") as of:
|
||||
of.write(index)
|
||||
|
||||
with Path(CLAN_MODULES_VIA_SERVICE).open() as f3:
|
||||
service_links: dict[str, dict[str, dict[str, Any]]] = json.load(f3)
|
||||
|
||||
@@ -317,15 +337,20 @@ def produce_clan_service_docs() -> None:
|
||||
if module_name in ["internet", "tor"]:
|
||||
continue
|
||||
|
||||
output = f"# {module_name}\n\n"
|
||||
output = f"""---
|
||||
title: {module_name}
|
||||
description: {module_info["manifest"]["description"]}
|
||||
---
|
||||
|
||||
"""
|
||||
# output += f"`clan.modules.{module_name}`\n"
|
||||
output += f"*{module_info['manifest']['description']}*\n"
|
||||
|
||||
# output += "## Categories\n\n"
|
||||
output += render_categories(
|
||||
module_info["manifest"]["categories"],
|
||||
ModuleManifest.categories_info(),
|
||||
)
|
||||
# output += render_categories(
|
||||
# module_info["manifest"]["categories"],
|
||||
# ModuleManifest.categories_info(),
|
||||
# )
|
||||
|
||||
output += f"{module_info['manifest']['readme']}\n"
|
||||
|
||||
@@ -344,7 +369,7 @@ def produce_clan_service_docs() -> None:
|
||||
replace_prefix=f"clan.{module_name}",
|
||||
)
|
||||
|
||||
outfile = Path(OUT) / "services/official" / f"{module_name}.md"
|
||||
outfile = Path(OUT) / f"clanServices/{module_name}.md"
|
||||
outfile.parent.mkdir(
|
||||
parents=True,
|
||||
exist_ok=True,
|
||||
@@ -395,10 +420,11 @@ This document describes the structure and configurable attributes of a `clan.ser
|
||||
|
||||
Typically needed by module authors to define roles, behavior and metadata for distributed services.
|
||||
|
||||
!!! Note
|
||||
This is not a user-facing documentation, but rather meant as a reference for *module authors*
|
||||
:::note
|
||||
This is not a user-facing documentation, but rather meant as a reference for *module authors*
|
||||
|
||||
See: [clanService Authoring Guide](../../guides/services/community.md)
|
||||
See: [clanService Authoring Guide](../../guides/services/community.md)
|
||||
:::
|
||||
"""
|
||||
# Inventory options are already included under the clan attribute
|
||||
# We just omitted them in the clan docs, because we want a separate output for the inventory model
|
||||
@@ -413,91 +439,7 @@ Typically needed by module authors to define roles, behavior and metadata for di
|
||||
# for option in options_tree.suboptions:
|
||||
output += options_docs_from_tree(options_tree, init_level=2)
|
||||
|
||||
outfile = Path(OUT) / "reference/options" / "clan_service.md"
|
||||
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Path.open(outfile, "w") as of:
|
||||
of.write(output)
|
||||
|
||||
|
||||
def produce_inventory_docs() -> None:
|
||||
if not CLAN_OPTIONS_PATH:
|
||||
msg = f"Environment variables are not set correctly: CLAN_OPTIONS_PATH={CLAN_OPTIONS_PATH}. Expected a path to the optionsJSON"
|
||||
raise ClanError(msg)
|
||||
|
||||
if not OUT:
|
||||
msg = f"Environment variables are not set correctly: $out={OUT}"
|
||||
raise ClanError(msg)
|
||||
|
||||
output = """# Inventory Submodule
|
||||
This provides an overview of the available options of the `inventory` model.
|
||||
|
||||
It can be set via the `inventory` attribute of the [`clan`](../../reference/options/clan_inventory.md) function, or via the [`clan.inventory`](../../reference/options/clan_inventory.md) attribute of flake-parts.
|
||||
|
||||
"""
|
||||
# Inventory options are already included under the clan attribute
|
||||
# We just omitted them in the clan docs, because we want a separate output for the inventory model
|
||||
with Path(CLAN_OPTIONS_PATH).open() as f:
|
||||
options: dict[str, dict[str, Any]] = json.load(f)
|
||||
|
||||
clan_root_option = options_to_tree(options)
|
||||
# Find the inventory options
|
||||
inventory_opt: None | Option = None
|
||||
for opt in clan_root_option.suboptions:
|
||||
if opt.name == "inventory":
|
||||
inventory_opt = opt
|
||||
break
|
||||
|
||||
if not inventory_opt:
|
||||
print("No inventory options found.")
|
||||
sys.exit(1)
|
||||
# Render the inventory options
|
||||
# This for loop excludes the root node
|
||||
for option in inventory_opt.suboptions:
|
||||
output += options_docs_from_tree(option, init_level=2)
|
||||
|
||||
outfile = Path(OUT) / "reference/options" / "clan_inventory.md"
|
||||
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Path.open(outfile, "w") as of:
|
||||
of.write(output)
|
||||
|
||||
|
||||
def produce_clan_options_docs() -> None:
|
||||
if not CLAN_OPTIONS_PATH:
|
||||
msg = f"Environment variables are not set correctly: CLAN_OPTIONS_PATH={CLAN_OPTIONS_PATH}. Expected a path to the optionsJSON"
|
||||
raise ClanError(msg)
|
||||
|
||||
if not OUT:
|
||||
msg = f"Environment variables are not set correctly: $out={OUT}"
|
||||
raise ClanError(msg)
|
||||
|
||||
output = """# Clan Options
|
||||
This provides an overview of the available options
|
||||
|
||||
Those can be set via [`clan-core.lib.clan`](../../reference/options/clan.md) function,
|
||||
or via the [`clan`](../../reference/options/clan.md) attribute of flake-parts.
|
||||
|
||||
"""
|
||||
# Inventory options are already included under the clan attribute
|
||||
# We just omitted them in the clan docs, because we want a separate output for the inventory model
|
||||
with Path(CLAN_OPTIONS_PATH).open() as f:
|
||||
options: dict[str, dict[str, Any]] = json.load(f)
|
||||
|
||||
clan_root_option = options_to_tree(options)
|
||||
# Render the inventory options
|
||||
# This for loop excludes the root node
|
||||
# Exclude inventory options
|
||||
for option in clan_root_option.suboptions:
|
||||
if "inventory" in option.name:
|
||||
output += """## Inventory
|
||||
|
||||
Attribute: `inventory`
|
||||
|
||||
See: [Inventory Submodule](../../reference/options/clan_inventory.md)
|
||||
"""
|
||||
continue
|
||||
output += options_docs_from_tree(option, init_level=2)
|
||||
|
||||
outfile = Path(OUT) / "reference/options" / "clan.md"
|
||||
outfile = Path(OUT) / "clanServices/clan-service-author-interface.md"
|
||||
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Path.open(outfile, "w") as of:
|
||||
of.write(output)
|
||||
@@ -617,7 +559,6 @@ def options_docs_from_tree(
|
||||
|
||||
if __name__ == "__main__":
|
||||
produce_clan_core_docs()
|
||||
produce_inventory_docs()
|
||||
produce_clan_options_docs()
|
||||
|
||||
produce_clan_service_author_docs()
|
||||
produce_clan_service_docs()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user