Compare commits
1 Commits
ke-qa-nixp
...
revers-upd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e17d9ec0f |
@@ -8,6 +8,6 @@ jobs:
|
|||||||
runs-on: nix
|
runs-on: nix
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: nix run --print-build-logs .#deploy-docs
|
- run: nix run .#deploy-docs
|
||||||
env:
|
env:
|
||||||
SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }}
|
SSH_HOMEPAGE_KEY: ${{ secrets.SSH_HOMEPAGE_KEY }}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -52,5 +52,3 @@ pkgs/clan-app/ui/.fonts
|
|||||||
*.gif
|
*.gif
|
||||||
*.mp4
|
*.mp4
|
||||||
*.mkv
|
*.mkv
|
||||||
|
|
||||||
.jj
|
|
||||||
|
|||||||
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
|
||||||
@@ -30,7 +30,7 @@ In the Clan ecosystem, security is paramount. Learn how to handle secrets effect
|
|||||||
|
|
||||||
The Clan project thrives on community contributions. We welcome everyone to contribute and collaborate:
|
The Clan project thrives on community contributions. We welcome everyone to contribute and collaborate:
|
||||||
|
|
||||||
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/guides/contributing/CONTRIBUTING/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
||||||
|
|
||||||
## Join the revolution
|
## Join the revolution
|
||||||
|
|
||||||
|
|||||||
6
checks/clan-core-for-checks.nix
Normal file
6
checks/clan-core-for-checks.nix
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{ fetchgit }:
|
||||||
|
fetchgit {
|
||||||
|
url = "https://git.clan.lol/clan/clan-core.git";
|
||||||
|
rev = "5d884cecc2585a29b6a3596681839d081b4de192";
|
||||||
|
sha256 = "09is1afmncamavb2q88qac37vmsijxzsy1iz1vr6gsyjq2rixaxc";
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ let
|
|||||||
elem
|
elem
|
||||||
filter
|
filter
|
||||||
filterAttrs
|
filterAttrs
|
||||||
|
flip
|
||||||
genAttrs
|
genAttrs
|
||||||
hasPrefix
|
hasPrefix
|
||||||
pathExists
|
pathExists
|
||||||
@@ -44,7 +45,7 @@ in
|
|||||||
flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] (
|
flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
checks = filterAttrs (
|
checks = flip filterAttrs self.checks.${system} (
|
||||||
name: _check:
|
name: _check:
|
||||||
!(hasPrefix "nixos-test-" name)
|
!(hasPrefix "nixos-test-" name)
|
||||||
&& !(hasPrefix "nixos-" name)
|
&& !(hasPrefix "nixos-" name)
|
||||||
@@ -56,7 +57,7 @@ in
|
|||||||
"clan-core-for-checks"
|
"clan-core-for-checks"
|
||||||
"clan-deps"
|
"clan-deps"
|
||||||
])
|
])
|
||||||
) self.checks.${system};
|
);
|
||||||
in
|
in
|
||||||
inputs.nixpkgs.legacyPackages.${system}.runCommand "fast-flake-checks-${system}"
|
inputs.nixpkgs.legacyPackages.${system}.runCommand "fast-flake-checks-${system}"
|
||||||
{ passthru.checks = checks; }
|
{ passthru.checks = checks; }
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||||
|
|
||||||
|
# We need to use `mkForce` because we inherit from `test-install-machine`
|
||||||
|
# which currently hardcodes `nixpkgs.hostPlatform`
|
||||||
nixpkgs.hostPlatform = lib.mkForce system;
|
nixpkgs.hostPlatform = lib.mkForce system;
|
||||||
|
|
||||||
imports = [ self.nixosModules.test-flash-machine ];
|
imports = [ self.nixosModules.test-flash-machine ];
|
||||||
@@ -26,24 +28,10 @@
|
|||||||
{
|
{
|
||||||
imports = [ self.nixosModules.test-install-machine-without-system ];
|
imports = [ self.nixosModules.test-install-machine-without-system ];
|
||||||
|
|
||||||
# We don't want our system to define any `vars` generators as these can't
|
|
||||||
# be generated as the flake is inside `/nix/store`.
|
|
||||||
clan.core.settings.state-version.enable = false;
|
|
||||||
clan.core.vars.generators.test = lib.mkForce { };
|
clan.core.vars.generators.test = lib.mkForce { };
|
||||||
|
|
||||||
disko.devices.disk.main.preCreateHook = lib.mkForce "";
|
disko.devices.disk.main.preCreateHook = lib.mkForce "";
|
||||||
|
|
||||||
# Every option here should match the options set through `clan flash write`
|
|
||||||
# if you get a mass rebuild on the disko derivation, this means you need to
|
|
||||||
# adjust something here. Also make sure that the injected json in clan flash write
|
|
||||||
# is up to date.
|
|
||||||
i18n.defaultLocale = "de_DE.UTF-8";
|
|
||||||
console.keyMap = "de";
|
|
||||||
services.xserver.xkb.layout = "de";
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target\n"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
perSystem =
|
perSystem =
|
||||||
@@ -56,11 +44,8 @@
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
pkgs.disko
|
pkgs.disko
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
pkgs.glibcLocales
|
|
||||||
pkgs.kbd.out
|
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
|
||||||
pkgs.bubblewrap
|
|
||||||
|
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
||||||
@@ -88,7 +73,7 @@
|
|||||||
substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
hashed-mirrors = null;
|
hashed-mirrors = null;
|
||||||
connect-timeout = lib.mkForce 3;
|
connect-timeout = lib.mkForce 3;
|
||||||
flake-registry = "";
|
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
experimental-features = [
|
experimental-features = [
|
||||||
"nix-command"
|
"nix-command"
|
||||||
"flakes"
|
"flakes"
|
||||||
@@ -97,10 +82,10 @@
|
|||||||
};
|
};
|
||||||
testScript = ''
|
testScript = ''
|
||||||
start_all()
|
start_all()
|
||||||
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
|
|
||||||
# Some distros like to automount disks with spaces
|
# Some distros like to automount disks with spaces
|
||||||
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
|
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
|
||||||
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
|
machine.succeed("clan flash write --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
|
||||||
'';
|
'';
|
||||||
} { inherit pkgs self; };
|
} { inherit pkgs self; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
self,
|
self,
|
||||||
lib,
|
lib,
|
||||||
privateInputs,
|
privateInputs,
|
||||||
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -14,38 +14,31 @@
|
|||||||
# you can get a new one by adding
|
# you can get a new one by adding
|
||||||
# client.fail("cat test-flake/machines/test-install-machine/facter.json >&2")
|
# client.fail("cat test-flake/machines/test-install-machine/facter.json >&2")
|
||||||
# to the installation test.
|
# to the installation test.
|
||||||
clan.machines = {
|
clan.machines.test-install-machine-without-system = {
|
||||||
test-install-machine-without-system = {
|
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
||||||
|
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||||
|
|
||||||
|
imports = [ self.nixosModules.test-install-machine-without-system ];
|
||||||
|
};
|
||||||
|
clan.machines.test-install-machine-with-system =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
# https://git.clan.lol/clan/test-fixtures
|
||||||
|
facter.reportPath = builtins.fetchurl {
|
||||||
|
url = "https://git.clan.lol/clan/test-fixtures/raw/commit/4a2bc56d886578124b05060d3fb7eddc38c019f8/nixos-vm-facter-json/${pkgs.hostPlatform.system}.json";
|
||||||
|
sha256 =
|
||||||
|
{
|
||||||
|
aarch64-linux = "sha256:1rlfymk03rmfkm2qgrc8l5kj5i20srx79n1y1h4nzlpwaz0j7hh2";
|
||||||
|
x86_64-linux = "sha256:16myh0ll2gdwsiwkjw5ba4dl23ppwbsanxx214863j7nvzx42pws";
|
||||||
|
}
|
||||||
|
.${pkgs.hostPlatform.system};
|
||||||
|
};
|
||||||
|
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||||
|
|
||||||
imports = [
|
imports = [ self.nixosModules.test-install-machine-without-system ];
|
||||||
self.nixosModules.test-install-machine-without-system
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
// (lib.listToAttrs (
|
|
||||||
lib.map (
|
|
||||||
system:
|
|
||||||
lib.nameValuePair "test-install-machine-${system}" {
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.test-install-machine-without-system
|
|
||||||
(
|
|
||||||
if privateInputs ? test-fixtures then
|
|
||||||
{
|
|
||||||
facter.reportPath = privateInputs.test-fixtures + /nixos-vm-facter-json/${system}.json;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ nixpkgs.hostPlatform = system; }
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
|
||||||
}
|
|
||||||
) (lib.filter (lib.hasSuffix "linux") config.systems)
|
|
||||||
));
|
|
||||||
|
|
||||||
flake.nixosModules = {
|
flake.nixosModules = {
|
||||||
test-install-machine-without-system =
|
test-install-machine-without-system =
|
||||||
{ lib, modulesPath, ... }:
|
{ lib, modulesPath, ... }:
|
||||||
@@ -160,9 +153,9 @@
|
|||||||
closureInfo = pkgs.closureInfo {
|
closureInfo = pkgs.closureInfo {
|
||||||
rootPaths = [
|
rootPaths = [
|
||||||
privateInputs.clan-core-for-checks
|
privateInputs.clan-core-for-checks
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.initialRamdisk
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
|
||||||
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
@@ -215,7 +208,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -226,22 +219,6 @@
|
|||||||
"${../assets/ssh/privkey}"
|
"${../assets/ssh/privkey}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run clan install from host using port forwarding
|
|
||||||
clan_cmd = [
|
|
||||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
|
||||||
"machines",
|
|
||||||
"init-hardware-config",
|
|
||||||
"--debug",
|
|
||||||
"--flake", str(flake_dir),
|
|
||||||
"--yes", "test-install-machine-without-system",
|
|
||||||
"--host-key-check", "none",
|
|
||||||
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
|
|
||||||
"-i", ssh_conn.ssh_key,
|
|
||||||
"--option", "store", os.environ['CLAN_TEST_STORE']
|
|
||||||
]
|
|
||||||
subprocess.run(clan_cmd, check=True)
|
|
||||||
|
|
||||||
|
|
||||||
# Run clan install from host using port forwarding
|
# Run clan install from host using port forwarding
|
||||||
clan_cmd = [
|
clan_cmd = [
|
||||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||||
@@ -296,7 +273,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -147,11 +147,28 @@ let
|
|||||||
];
|
];
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Common closure info
|
||||||
|
closureInfo = pkgs.closureInfo {
|
||||||
|
rootPaths = [
|
||||||
|
self.checks.x86_64-linux.clan-core-for-checks
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.clan.deployment.file
|
||||||
|
pkgs.stdenv.drvPath
|
||||||
|
pkgs.bash.drvPath
|
||||||
|
pkgs.buildPackages.xorg.lndir
|
||||||
|
]
|
||||||
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit
|
inherit
|
||||||
target
|
target
|
||||||
baseTestMachine
|
baseTestMachine
|
||||||
nixosTestLib
|
nixosTestLib
|
||||||
|
closureInfo
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,34 +29,32 @@ nixosLib.runTest (
|
|||||||
{ nodes, ... }:
|
{ nodes, ... }:
|
||||||
''
|
''
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
from nixos_test_lib.nix_setup import setup_nix_in_nix # type: ignore[import-untyped]
|
||||||
from nixos_test_lib.nix_setup import setup_nix_in_nix
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
setup_nix_in_nix(None) # No closure info for this test
|
||||||
setup_nix_in_nix(temp_dir, None) # No closure info for this test
|
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
admin1.wait_for_unit("multi-user.target")
|
admin1.wait_for_unit("multi-user.target")
|
||||||
peer1.wait_for_unit("multi-user.target")
|
peer1.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
# peer1 should have the 'hello' file
|
# peer1 should have the 'hello' file
|
||||||
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
||||||
|
|
||||||
ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}")
|
ls_out = peer1.succeed("ls -la ${nodes.peer1.clan.core.vars.generators.new-service.files.a-secret.path}")
|
||||||
# Check that the file is owned by 'nobody'
|
# Check that the file is owned by 'nobody'
|
||||||
assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}"
|
assert "nobody" in ls_out, f"File is not owned by 'nobody': {ls_out}"
|
||||||
# Check that the file is in the 'users' group
|
# Check that the file is in the 'users' group
|
||||||
assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}"
|
assert "users" in ls_out, f"File is not in the 'users' group: {ls_out}"
|
||||||
# Check that the file is in the '0644' mode
|
# Check that the file is in the '0644' mode
|
||||||
assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}"
|
assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}"
|
||||||
|
|
||||||
# Run clan command
|
# Run clan command
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["${
|
["${
|
||||||
clan-core.packages.${hostPkgs.system}.clan-cli
|
clan-core.packages.${hostPkgs.system}.clan-cli
|
||||||
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
|
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
|
||||||
check=True
|
check=True
|
||||||
)
|
)
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,9 +27,7 @@
|
|||||||
modules.new-service = {
|
modules.new-service = {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "new-service";
|
manifest.name = "new-service";
|
||||||
roles.peer = {
|
roles.peer = { };
|
||||||
description = "A peer that uses the new-service to generate some files.";
|
|
||||||
};
|
|
||||||
perMachine = {
|
perMachine = {
|
||||||
nixosModule = {
|
nixosModule = {
|
||||||
# This should be generated by:
|
# This should be generated by:
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ nixosLib.runTest (
|
|||||||
modules.new-service = {
|
modules.new-service = {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "new-service";
|
manifest.name = "new-service";
|
||||||
roles.peer = {
|
roles.peer = { };
|
||||||
description = "A peer that uses the new-service to generate some files.";
|
|
||||||
};
|
|
||||||
perMachine = {
|
perMachine = {
|
||||||
nixosModule = {
|
nixosModule = {
|
||||||
# This should be generated by:
|
# This should be generated by:
|
||||||
|
|||||||
@@ -67,15 +67,6 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
nix.settings = {
|
|
||||||
flake-registry = "";
|
|
||||||
# required for setting the `flake-registry`
|
|
||||||
experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Define the mounts that exist in the container to prevent them from being stopped
|
# Define the mounts that exist in the container to prevent them from being stopped
|
||||||
fileSystems = {
|
fileSystems = {
|
||||||
"/" = {
|
"/" = {
|
||||||
@@ -115,13 +106,12 @@
|
|||||||
let
|
let
|
||||||
closureInfo = pkgs.closureInfo {
|
closureInfo = pkgs.closureInfo {
|
||||||
rootPaths = [
|
rootPaths = [
|
||||||
self.packages.${pkgs.hostPlatform.system}.clan-cli
|
self.packages.${pkgs.system}.clan-cli
|
||||||
self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks
|
self.checks.${pkgs.system}.clan-core-for-checks
|
||||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
pkgs.bubblewrap
|
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
};
|
};
|
||||||
@@ -132,7 +122,7 @@
|
|||||||
imports = [ self.nixosModules.test-update-machine ];
|
imports = [ self.nixosModules.test-update-machine ];
|
||||||
};
|
};
|
||||||
extraPythonPackages = _p: [
|
extraPythonPackages = _p: [
|
||||||
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib
|
self.legacyPackages.${pkgs.system}.nixosTestLib
|
||||||
];
|
];
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
@@ -154,7 +144,7 @@
|
|||||||
# Prepare test flake and Nix store
|
# Prepare test flake and Nix store
|
||||||
flake_dir = prepare_test_flake(
|
flake_dir = prepare_test_flake(
|
||||||
temp_dir,
|
temp_dir,
|
||||||
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
|
"${self.checks.x86_64-linux.clan-core-for-checks}",
|
||||||
"${closureInfo}"
|
"${closureInfo}"
|
||||||
)
|
)
|
||||||
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
|
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
|
||||||
@@ -221,13 +211,12 @@
|
|||||||
[
|
[
|
||||||
"${pkgs.nix}/bin/nix",
|
"${pkgs.nix}/bin/nix",
|
||||||
"copy",
|
"copy",
|
||||||
"--from",
|
|
||||||
f"{temp_dir}/store",
|
|
||||||
"--to",
|
"--to",
|
||||||
"ssh://root@192.168.1.1",
|
"ssh://root@192.168.1.1",
|
||||||
"--no-check-sigs",
|
"--no-check-sigs",
|
||||||
f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}",
|
f"${self.packages.${pkgs.system}.clan-cli}",
|
||||||
"--extra-experimental-features", "nix-command flakes",
|
"--extra-experimental-features", "nix-command flakes",
|
||||||
|
"--from", f"{os.environ["TMPDIR"]}/store"
|
||||||
],
|
],
|
||||||
check=True,
|
check=True,
|
||||||
env={
|
env={
|
||||||
@@ -242,7 +231,7 @@
|
|||||||
"-o", "UserKnownHostsFile=/dev/null",
|
"-o", "UserKnownHostsFile=/dev/null",
|
||||||
"-o", "StrictHostKeyChecking=no",
|
"-o", "StrictHostKeyChecking=no",
|
||||||
f"root@192.168.1.1",
|
f"root@192.168.1.1",
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan",
|
"${self.packages.${pkgs.system}.clan-cli}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
@@ -270,7 +259,7 @@
|
|||||||
|
|
||||||
# Run clan update command
|
# Run clan update command
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
@@ -297,7 +286,7 @@
|
|||||||
|
|
||||||
# Run clan update command with --build-host
|
# Run clan update command with --build-host
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
|
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||||
"machines",
|
"machines",
|
||||||
"update",
|
"update",
|
||||||
"--debug",
|
"--debug",
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
|
{ ... }:
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/admin";
|
manifest.name = "clan-core/admin";
|
||||||
manifest.description = "Adds a root user with ssh access";
|
manifest.description = "Convenient Administration for the Clan App";
|
||||||
manifest.categories = [ "Utility" ];
|
manifest.categories = [ "Utility" ];
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the admin service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
allowedKeys = lib.mkOption {
|
allowedKeys = lib.mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ inventory.instances = {
|
|||||||
borgbackup = {
|
borgbackup = {
|
||||||
module = {
|
module = {
|
||||||
name = "borgbackup";
|
name = "borgbackup";
|
||||||
input = "clan-core";
|
input = "clan";
|
||||||
};
|
};
|
||||||
roles.client.machines."jon".settings = {
|
roles.client.machines."jon".settings = {
|
||||||
destinations."storagebox" = {
|
destinations."storagebox" = {
|
||||||
repo = "username@hostname:/./borgbackup";
|
repo = "username@$hostname:/./borgbackup";
|
||||||
rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh'';
|
rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# TODO: a client can only be in one instance, add constraint
|
# TODO: a client can only be in one instance, add constraint
|
||||||
|
|
||||||
roles.server = {
|
roles.server = {
|
||||||
description = "A borgbackup server that stores the backups of clients.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machineName)) ];
|
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machineName)) ];
|
||||||
# };
|
# };
|
||||||
# }) machinesWithKey;
|
# }) machinesWithKey;
|
||||||
}) (roles.client.machines or { });
|
}) roles.client.machines;
|
||||||
in
|
in
|
||||||
hosts;
|
hosts;
|
||||||
};
|
};
|
||||||
@@ -62,7 +62,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
roles.client = {
|
roles.client = {
|
||||||
description = "A borgbackup client that backs up to all borgbackup server roles.";
|
|
||||||
interface =
|
interface =
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
@@ -188,7 +187,7 @@
|
|||||||
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
|
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
|
||||||
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
||||||
};
|
};
|
||||||
}) (builtins.attrNames (roles.server.machines or { }));
|
}) (builtins.attrNames roles.server.machines);
|
||||||
in
|
in
|
||||||
(builtins.listToAttrs destinations);
|
(builtins.listToAttrs destinations);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
This service sets up a certificate authority (CA) that can issue certificates to
|
|
||||||
other machines in your clan. For this the `ca` role is used.
|
|
||||||
It additionally provides a `default` role, that can be applied to all machines
|
|
||||||
in your clan and will make sure they trust your CA.
|
|
||||||
|
|
||||||
## Example Usage
|
|
||||||
|
|
||||||
The following configuration would add a CA for the top level domain `.foo`. If
|
|
||||||
the machine `server` now hosts a webservice at `https://something.foo`, it will
|
|
||||||
get a certificate from `ca` which is valid inside your clan. The machine
|
|
||||||
`client` will trust this certificate if it makes a request to
|
|
||||||
`https://something.foo`.
|
|
||||||
|
|
||||||
This clan service can be combined with the `coredns` service for easy to deploy,
|
|
||||||
SSL secured clan-internal service hosting.
|
|
||||||
|
|
||||||
```nix
|
|
||||||
inventory = {
|
|
||||||
machines.ca = { };
|
|
||||||
machines.client = { };
|
|
||||||
machines.server = { };
|
|
||||||
|
|
||||||
instances."certificates" = {
|
|
||||||
module.name = "certificates";
|
|
||||||
module.input = "self";
|
|
||||||
|
|
||||||
roles.ca.machines.ca.settings.tlds = [ "foo" ];
|
|
||||||
roles.default.machines.client = { };
|
|
||||||
roles.default.machines.server = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
_class = "clan.service";
|
|
||||||
manifest.name = "certificates";
|
|
||||||
manifest.description = "Sets up a PKI certificate chain using step-ca";
|
|
||||||
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, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
options.acmeEmail = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "none@none.tld";
|
|
||||||
description = ''
|
|
||||||
Email address for account creation and correspondence from the CA.
|
|
||||||
It is recommended to use the same email for all certs to avoid account
|
|
||||||
creation limits.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
options.tlds = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
description = "Top level domain for this CA. Certificates will be issued and trusted for *.<tld>";
|
|
||||||
};
|
|
||||||
|
|
||||||
options.expire = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
description = "When the certificate should expire.";
|
|
||||||
default = "8760h";
|
|
||||||
example = "8760h";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
perInstance =
|
|
||||||
{ settings, ... }:
|
|
||||||
{
|
|
||||||
nixosModule =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
domains = map (tld: "ca.${tld}") settings.tlds;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
security.acme.defaults.email = settings.acmeEmail;
|
|
||||||
security.acme = {
|
|
||||||
certs = builtins.listToAttrs (
|
|
||||||
map (domain: {
|
|
||||||
name = domain;
|
|
||||||
value = {
|
|
||||||
server = "https://${domain}:1443/acme/acme/directory";
|
|
||||||
};
|
|
||||||
}) domains
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
virtualHosts = builtins.listToAttrs (
|
|
||||||
map (domain: {
|
|
||||||
name = domain;
|
|
||||||
value = {
|
|
||||||
addSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/".proxyPass = "https://localhost:1443";
|
|
||||||
locations."= /ca.crt".alias =
|
|
||||||
config.clan.core.vars.generators.step-intermediate-cert.files."intermediate.crt".path;
|
|
||||||
};
|
|
||||||
}) domains
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators = {
|
|
||||||
|
|
||||||
# Intermediate key generator
|
|
||||||
"step-intermediate-key" = {
|
|
||||||
files."intermediate.key" = {
|
|
||||||
secret = true;
|
|
||||||
deploy = true;
|
|
||||||
owner = "step-ca";
|
|
||||||
group = "step-ca";
|
|
||||||
};
|
|
||||||
runtimeInputs = [ pkgs.step-cli ];
|
|
||||||
script = ''
|
|
||||||
step crypto keypair --kty EC --curve P-256 --no-password --insecure $out/intermediate.pub $out/intermediate.key
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Intermediate certificate generator
|
|
||||||
"step-intermediate-cert" = {
|
|
||||||
files."intermediate.crt".secret = false;
|
|
||||||
dependencies = [
|
|
||||||
"step-ca"
|
|
||||||
"step-intermediate-key"
|
|
||||||
];
|
|
||||||
runtimeInputs = [ pkgs.step-cli ];
|
|
||||||
script = ''
|
|
||||||
# Create intermediate certificate
|
|
||||||
step certificate create \
|
|
||||||
--ca $in/step-ca/ca.crt \
|
|
||||||
--ca-key $in/step-ca/ca.key \
|
|
||||||
--ca-password-file /dev/null \
|
|
||||||
--key $in/step-intermediate-key/intermediate.key \
|
|
||||||
--template ${pkgs.writeText "intermediate.tmpl" ''
|
|
||||||
{
|
|
||||||
"subject": {{ toJson .Subject }},
|
|
||||||
"keyUsage": ["certSign", "crlSign"],
|
|
||||||
"basicConstraints": {
|
|
||||||
"isCA": true,
|
|
||||||
"maxPathLen": 0
|
|
||||||
},
|
|
||||||
"nameConstraints": {
|
|
||||||
"critical": true,
|
|
||||||
"permittedDNSDomains": [${
|
|
||||||
(lib.strings.concatStringsSep "," (map (tld: ''"${tld}"'') settings.tlds))
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
''} ${lib.optionalString (settings.expire != null) "--not-after ${settings.expire}"} \
|
|
||||||
--not-before=-12h \
|
|
||||||
--no-password --insecure \
|
|
||||||
"Clan Intermediate CA" \
|
|
||||||
$out/intermediate.crt
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.step-ca = {
|
|
||||||
enable = true;
|
|
||||||
intermediatePasswordFile = "/dev/null";
|
|
||||||
address = "0.0.0.0";
|
|
||||||
port = 1443;
|
|
||||||
settings = {
|
|
||||||
root = config.clan.core.vars.generators.step-ca.files."ca.crt".path;
|
|
||||||
crt = config.clan.core.vars.generators.step-intermediate-cert.files."intermediate.crt".path;
|
|
||||||
key = config.clan.core.vars.generators.step-intermediate-key.files."intermediate.key".path;
|
|
||||||
dnsNames = domains;
|
|
||||||
logger.format = "text";
|
|
||||||
db = {
|
|
||||||
type = "badger";
|
|
||||||
dataSource = "/var/lib/step-ca/db";
|
|
||||||
};
|
|
||||||
authority = {
|
|
||||||
provisioners = [
|
|
||||||
{
|
|
||||||
type = "ACME";
|
|
||||||
name = "acme";
|
|
||||||
forceCN = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
claims = {
|
|
||||||
maxTLSCertDuration = "2160h";
|
|
||||||
defaultTLSCertDuration = "2160h";
|
|
||||||
};
|
|
||||||
backdate = "1m0s";
|
|
||||||
};
|
|
||||||
tls = {
|
|
||||||
cipherSuites = [
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
|
|
||||||
];
|
|
||||||
minVersion = 1.2;
|
|
||||||
maxVersion = 1.3;
|
|
||||||
renegotiation = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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, ... }:
|
|
||||||
{
|
|
||||||
options.acmeEmail = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "none@none.tld";
|
|
||||||
description = ''
|
|
||||||
Email address for account creation and correspondence from the CA.
|
|
||||||
It is recommended to use the same email for all certs to avoid account
|
|
||||||
creation limits.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
perInstance =
|
|
||||||
{ settings, ... }:
|
|
||||||
{
|
|
||||||
nixosModule.security.acme.defaults.email = settings.acmeEmail;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# All machines (independent of role) will trust the CA
|
|
||||||
perMachine.nixosModule =
|
|
||||||
{ pkgs, config, ... }:
|
|
||||||
{
|
|
||||||
# Root CA generator
|
|
||||||
clan.core.vars.generators = {
|
|
||||||
"step-ca" = {
|
|
||||||
share = true;
|
|
||||||
files."ca.key" = {
|
|
||||||
secret = true;
|
|
||||||
deploy = false;
|
|
||||||
};
|
|
||||||
files."ca.crt".secret = false;
|
|
||||||
runtimeInputs = [ pkgs.step-cli ];
|
|
||||||
script = ''
|
|
||||||
step certificate create --template ${pkgs.writeText "root.tmpl" ''
|
|
||||||
{
|
|
||||||
"subject": {{ toJson .Subject }},
|
|
||||||
"issuer": {{ toJson .Subject }},
|
|
||||||
"keyUsage": ["certSign", "crlSign"],
|
|
||||||
"basicConstraints": {
|
|
||||||
"isCA": true,
|
|
||||||
"maxPathLen": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
''} "Clan Root CA" $out/ca.crt $out/ca.key \
|
|
||||||
--kty EC --curve P-256 \
|
|
||||||
--not-after=8760h \
|
|
||||||
--not-before=-12h \
|
|
||||||
--no-password --insecure
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
security.pki.certificateFiles = [ config.clan.core.vars.generators."step-ca".files."ca.crt".path ];
|
|
||||||
environment.systemPackages = [ pkgs.openssl ];
|
|
||||||
security.acme.acceptTerms = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
module = ./default.nix;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
clan.modules.certificates = module;
|
|
||||||
perSystem =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
clan.nixosTests.certificates = {
|
|
||||||
imports = [ ./tests/vm/default.nix ];
|
|
||||||
clan.modules.certificates = module;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
{
|
|
||||||
name = "certificates";
|
|
||||||
|
|
||||||
clan = {
|
|
||||||
directory = ./.;
|
|
||||||
inventory = {
|
|
||||||
|
|
||||||
machines.ca = { }; # 192.168.1.1
|
|
||||||
machines.client = { }; # 192.168.1.2
|
|
||||||
machines.server = { }; # 192.168.1.3
|
|
||||||
|
|
||||||
instances."certificates" = {
|
|
||||||
module.name = "certificates";
|
|
||||||
module.input = "self";
|
|
||||||
|
|
||||||
roles.ca.machines.ca.settings.tlds = [ "foo" ];
|
|
||||||
roles.default.machines.client = { };
|
|
||||||
roles.default.machines.server = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
nodes =
|
|
||||||
let
|
|
||||||
hostConfig = ''
|
|
||||||
192.168.1.1 ca.foo
|
|
||||||
192.168.1.3 test.foo
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
|
|
||||||
client.networking.extraHosts = hostConfig;
|
|
||||||
ca.networking.extraHosts = hostConfig;
|
|
||||||
|
|
||||||
server = {
|
|
||||||
|
|
||||||
networking.extraHosts = hostConfig;
|
|
||||||
|
|
||||||
# TODO: Could this be set automatically?
|
|
||||||
# I would like to get this information from the coredns module, but we
|
|
||||||
# cannot model dependencies yet
|
|
||||||
security.acme.certs."test.foo".server = "https://ca.foo/acme/acme/directory";
|
|
||||||
|
|
||||||
# Host a simple service on 'server', with SSL provided via our CA. 'client'
|
|
||||||
# should be able to curl it via https and accept the certificates
|
|
||||||
# presented
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts."test.foo" = {
|
|
||||||
enableACME = true;
|
|
||||||
forceSSL = true;
|
|
||||||
locations."/" = {
|
|
||||||
return = "200 'test server response'";
|
|
||||||
extraConfig = "add_header Content-Type text/plain;";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
time.sleep(3)
|
|
||||||
ca.succeed("systemctl restart acme-order-renew-ca.foo.service ")
|
|
||||||
|
|
||||||
time.sleep(3)
|
|
||||||
server.succeed("systemctl restart acme-test.foo.service")
|
|
||||||
|
|
||||||
# It takes a while for the correct certs to appear (before that self-signed
|
|
||||||
# are presented by nginx) so we wait for a bit.
|
|
||||||
client.wait_until_succeeds("curl -v https://test.foo")
|
|
||||||
|
|
||||||
# Show certificate information for debugging
|
|
||||||
client.succeed("openssl s_client -connect test.foo:443 -servername test.foo </dev/null 2>/dev/null | openssl x509 -text -noout 1>&2")
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"publickey": "age1yd2cden7jav8x4nzx2fwze2fsa5j0qm2m3t7zum765z3u4gj433q7dqj43",
|
|
||||||
"type": "age"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"publickey": "age1js225d8jc507sgcg0fdfv2x3xv3asm4ds5c6s4hp37nq8spxu95sc5x3ce",
|
|
||||||
"type": "age"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"publickey": "age1nwuh8lc604mnz5r8ku8zswyswnwv02excw237c0cmtlejp7xfp8sdrcwfa",
|
|
||||||
"type": "age"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:6+XilULKRuWtAZ6B8Lj9UqCfi1T6dmqrDqBNXqS4SvBwM1bIWiL6juaT1Q7ByOexzID7tY740gmQBqTey54uLydh8mW0m4ZtUqw=,iv:9kscsrMPBGkutTnxrc5nrc7tQXpzLxw+929pUDKqTu0=,tag:753uIjm8ZRs0xsjiejEY8g==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1d3kycldZRXhmR0FqTXJp\nWWU0MDBYNmxxbFE5M2xKYm5KWnQ0MXBHNEM4CjN4RFFVcFlkd3pjTFVDQ3Vackdj\nVTVhMWoxdFpsWHp5S1p4L05kYk5LUkkKLS0tIENtZFZZTjY2amFVQmZLZFplQzBC\nZm1vWFI4MXR1ZHIxTTQ5VXdSYUhvOTQKte0bKjXQ0xA8FrpuChjDUvjVqp97D8kT\n3tVh6scdjxW48VSBZP1GRmqcMqCdj75GvJTbWeNEV4PDBW7GI0UW+Q==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-09-02T08:42:39Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:AftMorrH7qX5ctVu5evYHn5h9pC4Mmm2VYaAV8Hy0PKTc777jNsL6DrxFVV3NVqtecpwrzZFWKgzukcdcRJe4veVeBrusmoZYtifH0AWZTEVpVlr2UXYYxCDmNZt1WHfVUo40bT//X6QM0ye6a/2Y1jYPbMbryQNcGmnpk9PDvU=,iv:5nk+d8hzA05LQp7ZHRbIgiENg2Ha6J6YzyducM6zcNU=,tag:dy1hqWVzMu/+fSK57h9ZCA==,type:str]",
|
|
||||||
"unencrypted_suffix": "_unencrypted",
|
|
||||||
"version": "3.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../users/admin
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:jdTuGQUYvT1yXei1RHKsOCsABmMlkcLuziHDVhA7NequZeNu0fSbrJTXQDCHsDGhlYRcjU5EsEDT750xdleXuD3Gs9zWvPVobI4=,iv:YVow3K1j6fzRF9bRfIEpuOkO/nRpku/UQxWNGC+UJQQ=,tag:cNLM5R7uu6QpwPB9K6MYzg==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvOVF2WXRSL0NpQzFZR01I\nNU85TGcyQmVDazN1dmpuRFVTZEg5NDRKTGhrCk1IVjFSU1V6WHBVRnFWcHkyVERr\nTjFKbW1mQ2FWOWhjN2VPamMxVEQ5VkkKLS0tIENVUGlhanhuWGtDKzBzRmk2dE4v\nMXZBRXNMa3IrOTZTNHRUWVE3UXEwSWMK2cBLoL/H/Vxd/klVrqVLdX9Mww5j7gw/\nEWc5/hN+km6XoW+DiJxVG4qaJ7qqld6u5ZnKgJT+2h9CfjA04I2akg==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-09-02T08:42:51Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:zOBQVM2Ydu4v0+Fw3p3cEU+5+7eKaadV0tKro1JVOxclG1Vs6Myq57nw2eWf5JxIl0ulL+FavPKY26qOQ3aqcGOT3PMRlCda9z+0oSn9Im9bE/DzAGmoH/bp76kFkgTTOCZTMUoqJ+UJqv0qy1BH/92sSSKmYshEX6d1vr5ISrw=,iv:i9ZW4sLxOCan4UokHlySVr1CW39nCTusG4DmEPj/gIw=,tag:iZBDPHDkE3Vt5mFcFu1TPQ==,type:str]",
|
|
||||||
"unencrypted_suffix": "_unencrypted",
|
|
||||||
"version": "3.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../users/admin
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:5CJuHcxJMXZJ8GqAeG3BrbWtT1kade4kxgJsn1cRpmr1UgN0ZVYnluPEiBscClNSOzcc6vcrBpfTI3dj1tASKTLP58M+GDBFQDo=,iv:gsK7XqBGkYCoqAvyFlIXuJ27PKSbTmy7f6cgTmT2gow=,tag:qG5KejkBvy9ytfhGXa/Mnw==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxbzVqYkplTzJKN1pwS3VM\naFFIK2VsR3lYUVExYW9ieERBL0tlcFZtVzJRCkpiLzdmWmFlOUZ5QUJ4WkhXZ2tQ\nZm92YXBCV0RpYnIydUdEVTRiamI4bjAKLS0tIG93a2htS1hFcjBOeVFnNCtQTHVr\na2FPYjVGbWtORjJVWXE5bndPU1RWcXMKikMEB7X+kb7OtiyqXn3HRpLYkCdoayDh\n7cjGnplk17q25/lRNHM4JVS5isFfuftCl01enESqkvgq+cwuFwa9DQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-09-02T08:42:59Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:xybV2D0xukZnH2OwRpIugPnS7LN9AbgGKwFioPJc1FQWx9TxMUVDwgMN6V5WrhWkXgF2zP4krtDYpEz4Vq+LbOjcnTUteuCc+7pMHubuRuip7j+M32MH1kuf4bVZuXbCfvm7brGxe83FzjoioLqzA8g/X6Q1q7/ErkNeFjluC3Q=,iv:QEW3EUKSRZY3fbXlP7z+SffWkQeXwMAa5K8RQW7NvPE=,tag:DhFxY7xr7H1Wbd527swD0Q==,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,12 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIBsDCCAVegAwIBAgIQbT1Ivm+uwyf0HNkJfan2BTAKBggqhkjOPQQDAjAXMRUw
|
|
||||||
EwYDVQQDEwxDbGFuIFJvb3QgQ0EwHhcNMjUwOTAxMjA0MzAzWhcNMjYwOTAyMDg0
|
|
||||||
MzAzWjAfMR0wGwYDVQQDExRDbGFuIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49
|
|
||||||
AgEGCCqGSM49AwEHA0IABDXCNrUIotju9P1U6JxLV43sOxLlRphQJS4dM+lvjTZc
|
|
||||||
aQ+HwQg0AHVlQNRwS3JqKrJJtJVyKbZklh6eFaDPoj6jfTB7MA4GA1UdDwEB/wQE
|
|
||||||
AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRKHaccHgP2ccSWVBWN
|
|
||||||
zGoDdTg7aTAfBgNVHSMEGDAWgBSfsnz4phMJx9su/kgeF/FbZQCBgzAVBgNVHR4B
|
|
||||||
Af8ECzAJoAcwBYIDZm9vMAoGCCqGSM49BAMCA0cAMEQCICiUDk1zGNzpS/iVKLfW
|
|
||||||
zUGaCagpn2mCx4xAXQM9UranAiAn68nVYGWjkzhU31wyCAupxOjw7Bt96XXqIAz9
|
|
||||||
hLLtMA==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/machines/ca
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:Auonh9fa7jSkld1Zyxw74x5ydj6Xc+0SOgiqumVETNCfner9K96Rmv1PkREuHNGWPsnzyEM3pRT8ijvu3QoKvy9QPCCewyT07Wqe4G74+bk1iMeAHsV3To6kHs6M8OISvE+CmG0+hlLmdfRSabTzyWPLHbOjvFTEEuA5G7xiryacSYOE++eeEHdn+oUDh/IMTcfLjCGMjsXFikx1Hb+ofeRTlCg47+0w4MXVvQkOzQB5V2C694jZXvZ19jd/ioqr8YASz2xatGvqwW6cpZxqOWyZJ0UAj/6yFk6tZWifqVB3wgU=,iv:ITFCrDkeWl4GWCebVq15ei9QmkOLDwUIYojKZ2TU6JU=,tag:8k4iYbCIusUykY79H86WUQ==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsT25UbjJTQ2tzbnQyUm9p\neWx1UlZIeVpocnBqUCt0YnFlN2FOU25Lb0hNCmdXUUsyalRTbHRRQ0NLSGc1YllV\nUXRwaENhaXU1WmdnVDE0UWprUUUyeDAKLS0tIHV3dHU3aG5JclM0V3FadzN0SU14\ndFptbEJUNXQ4QVlqbkJ1TjAvdDQwSGsKcKPWUjhK7wzIpdIdksMShF2fpLdDTUBS\nZiU7P1T+3psxad9qhapvU0JrAY+9veFaYVEHha2aN/XKs8HqUcTp3A==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"recipient": "age1yd2cden7jav8x4nzx2fwze2fsa5j0qm2m3t7zum765z3u4gj433q7dqj43",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjZFVteVZwVGVmRE9NT3hG\nNGMyS3FSaXluM1FpeUp6SDVMUEpwYzg5SmdvCkRPU0QyU1JicGNkdlMyQWVkT0k3\nL2YrbDhWeGk4WFhxcUFmTmhZQ0pEQncKLS0tIG85Ui9rKzBJQ2VkMFBUQTMvSTlu\nbm8rZ09Wa24rQkNvTTNtYTZBN3MrZlkK7cjNhlUKZdOrRq/nKUsbUQgNTzX8jO+0\nzADpz6WCMvsJ15xazc10BGh03OtdMWl5tcoWMaZ71HWtI9Gip5DH0w==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-09-02T08:42:42Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:9xlO5Yis8DG/y8GjvP63NltD4xEL7zqdHL2cQE8gAoh/ZamAmK5ZL0ld80mB3eIYEPKZYvmUYI4Lkrge2ZdqyDoubrW+eJ3dxn9+StxA9FzXYwUE0t+bbsNJfOOp/kDojf060qLGsu0kAGKd2ca4WiDccR0Cieky335C7Zzhi/Q=,iv:bWQ4wr0CJHSN+6ipUbkYTDWZJyFQjDKszfpVX9EEUsY=,tag:kADIFgJBEGCvr5fPbbdEDA==,type:str]",
|
|
||||||
"unencrypted_suffix": "_unencrypted",
|
|
||||||
"version": "3.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../../sops/users/admin
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
25.11
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
25.11
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIBcTCCARigAwIBAgIRAIix99+AE7Y+uyiLGaRHEhUwCgYIKoZIzj0EAwIwFzEV
|
|
||||||
MBMGA1UEAxMMQ2xhbiBSb290IENBMB4XDTI1MDkwMTIwNDI1N1oXDTI2MDkwMjA4
|
|
||||||
NDI1N1owFzEVMBMGA1UEAxMMQ2xhbiBSb290IENBMFkwEwYHKoZIzj0CAQYIKoZI
|
|
||||||
zj0DAQcDQgAEk7nn9kzxI+xkRmNMlxD+7T78UqV3aqus0foJh6uu1CHC+XaebMcw
|
|
||||||
JN95nAe3oYA3yZG6Mnq9nCxsYha4EhzGYqNFMEMwDgYDVR0PAQH/BAQDAgEGMBIG
|
|
||||||
A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFJ+yfPimEwnH2y7+SB4X8VtlAIGD
|
|
||||||
MAoGCCqGSM49BAMCA0cAMEQCIBId/CcbT5MPFL90xa+XQz+gVTdRwsu6Bg7ehMso
|
|
||||||
Bj0oAiBjSlttd5yeuZGXBm+O0Gl+WdKV60QlrWutNewXFS4UpQ==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"data": "ENC[AES256_GCM,data:PnEXteU3I7U0OKgE+oR3xjHdLWYTpJjM/jlzxtGU0uP2pUBuQv3LxtEz+cP0ZsafHLNq2iNJ7xpUEE0g4d3M296S56oSocK3fREWBiJFiaC7SAEUiil1l3UCwHn7LzmdEmn8Kq7T+FK89wwqtVWIASLo2gZC/yHE5eEanEATTchGLSNiHJRzZ8n0Ekm8EFUA6czOqA5nPQHaSmeLzu1g80lSSi1ICly6dJksa6DVucwOyVFYFEeq8Dfyc1eyP8L1ee0D7QFYBMduYOXTKPtNnyDmdaQMj7cMMvE7fn04idIiAqw=,iv:nvLmAfFk2GXnnUy+Afr648R60Ou13eu9UKykkiA8Y+4=,tag:lTTAxfG0EDCU6u7xlW6xSQ==,type:str]",
|
|
||||||
"sops": {
|
|
||||||
"age": [
|
|
||||||
{
|
|
||||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
|
||||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEMjNWUm5NbktQeTRWRjJE\nWWFZc2Rsa3I5aitPSno1WnhORENNcng5OHprCjNUQVhBVHFBcWFjaW5UdmxKTnZw\nQlI4MDk5Wkp0RElCeWgzZ2dFQkF2dkkKLS0tIDVreTkydnJ0RDdHSHlQeVV6bGlP\nTmpJOVBSb2dkVS9TZG5SRmFjdnQ1b3cKQ5XvwH1jD4XPVs5RzOotBDq8kiE6S5k2\nDBv6ugjsM5qV7/oGP9H69aSB4jKPZjEn3yiNw++Oorc8uXd5kSGh7w==\n-----END AGE ENCRYPTED FILE-----\n"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lastmodified": "2025-09-02T08:43:00Z",
|
|
||||||
"mac": "ENC[AES256_GCM,data:3jFf66UyZUWEtPdPu809LCS3K/Hc6zbnluystl3eXS+KGI+dCoYmN9hQruRNBRxf6jli2RIlArmmEPBDQVt67gG/qugTdT12krWnYAZ78iocmOnkf44fWxn/pqVnn4JYpjEYRgy8ueGDnUkwvpGWVZpcXw5659YeDQuYOJ2mq0U=,iv:3k7fBPrABdLItQ2Z+Mx8Nx0eIEKo93zG/23K+Q5Hl3I=,tag:aehAObdx//DEjbKlOeM7iQ==,type:str]",
|
|
||||||
"unencrypted_suffix": "_unencrypted",
|
|
||||||
"version": "3.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../../../../sops/users/admin
|
|
||||||
@@ -8,7 +8,7 @@ The service consists of two roles:
|
|||||||
- A `server` role: This is the DNS-server that will be queried when trying to
|
- A `server` role: This is the DNS-server that will be queried when trying to
|
||||||
resolve clan-internal services. It defines the top-level domain.
|
resolve clan-internal services. It defines the top-level domain.
|
||||||
- A `default` role: This does two things. First, it sets up the nameservers so
|
- A `default` role: This does two things. First, it sets up the nameservers so
|
||||||
that clan-internal queries are resolved via the `server` machine, while
|
thatclan-internal queries are resolved via the `server` machine, while
|
||||||
external queries are resolved as normal via DHCP. Second, it allows exposing
|
external queries are resolved as normal via DHCP. Second, it allows exposing
|
||||||
services (see example below).
|
services (see example below).
|
||||||
|
|
||||||
@@ -45,15 +45,13 @@ inventory = {
|
|||||||
# Add the default role to all machines, including `client`
|
# Add the default role to all machines, including `client`
|
||||||
roles.default.tags.all = { };
|
roles.default.tags.all = { };
|
||||||
|
|
||||||
# DNS server queries to http://<name>.foo are resolved here
|
# DNS server
|
||||||
roles.server.machines."dnsserver".settings = {
|
roles.server.machines."dnsserver".settings = {
|
||||||
ip = "192.168.1.2";
|
ip = "192.168.1.2";
|
||||||
tld = "foo";
|
tld = "foo";
|
||||||
};
|
};
|
||||||
|
|
||||||
# First service
|
# First service
|
||||||
# Registers http://one.foo will resolve to 192.168.1.3
|
|
||||||
# underlying service runs on server01
|
|
||||||
roles.default.machines."server01".settings = {
|
roles.default.machines."server01".settings = {
|
||||||
ip = "192.168.1.3";
|
ip = "192.168.1.3";
|
||||||
services = [ "one" ];
|
services = [ "one" ];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "coredns";
|
manifest.name = "coredns";
|
||||||
@@ -8,7 +7,7 @@
|
|||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.server = {
|
roles.server = {
|
||||||
description = "A DNS server that resolves services in the clan network.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -26,12 +25,6 @@
|
|||||||
# TODO: Set a default
|
# TODO: Set a default
|
||||||
description = "IP for the DNS to listen on";
|
description = "IP for the DNS to listen on";
|
||||||
};
|
};
|
||||||
|
|
||||||
options.dnsPort = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 1053;
|
|
||||||
description = "Port of the clan-internal DNS server";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
perInstance =
|
perInstance =
|
||||||
@@ -49,8 +42,8 @@
|
|||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ settings.dnsPort ];
|
networking.firewall.allowedTCPPorts = [ 53 ];
|
||||||
networking.firewall.allowedUDPPorts = [ settings.dnsPort ];
|
networking.firewall.allowedUDPPorts = [ 53 ];
|
||||||
|
|
||||||
services.coredns =
|
services.coredns =
|
||||||
let
|
let
|
||||||
@@ -81,29 +74,22 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
config =
|
config = ''
|
||||||
|
. {
|
||||||
|
forward . 1.1.1.1
|
||||||
|
cache 30
|
||||||
|
}
|
||||||
|
|
||||||
let
|
${settings.tld} {
|
||||||
dnsPort = builtins.toString settings.dnsPort;
|
file ${zonefile}
|
||||||
in
|
}
|
||||||
|
'';
|
||||||
''
|
|
||||||
.:${dnsPort} {
|
|
||||||
forward . 1.1.1.1
|
|
||||||
cache 30
|
|
||||||
}
|
|
||||||
|
|
||||||
${settings.tld}:${dnsPort} {
|
|
||||||
file ${zonefile}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "A machine that registers the 'server' role as resolver and registers services under the configured TLD in the resolver.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -121,16 +107,10 @@
|
|||||||
# TODO: Set a default
|
# TODO: Set a default
|
||||||
description = "IP on which the services will listen";
|
description = "IP on which the services will listen";
|
||||||
};
|
};
|
||||||
|
|
||||||
options.dnsPort = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 1053;
|
|
||||||
description = "Port of the clan-internal DNS server";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
perInstance =
|
perInstance =
|
||||||
{ roles, settings, ... }:
|
{ roles, ... }:
|
||||||
{
|
{
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
@@ -167,7 +147,7 @@
|
|||||||
];
|
];
|
||||||
stub-zone = map (m: {
|
stub-zone = map (m: {
|
||||||
name = "${roles.server.machines.${m}.settings.tld}.";
|
name = "${roles.server.machines.${m}.settings.tld}.";
|
||||||
stub-addr = "${roles.server.machines.${m}.settings.ip}@${builtins.toString settings.dnsPort}";
|
stub-addr = "${roles.server.machines.${m}.settings.ip}";
|
||||||
}) (lib.attrNames roles.server.machines);
|
}) (lib.attrNames roles.server.machines);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -95,15 +95,18 @@
|
|||||||
for m in machines:
|
for m in machines:
|
||||||
m.wait_for_unit("network-online.target")
|
m.wait_for_unit("network-online.target")
|
||||||
|
|
||||||
|
# import time
|
||||||
|
# time.sleep(2333333)
|
||||||
|
|
||||||
# This should work, but is borken in tests i think? Instead we dig directly
|
# This should work, but is borken in tests i think? Instead we dig directly
|
||||||
|
|
||||||
# client.succeed("curl -k -v http://one.foo")
|
# client.succeed("curl -k -v http://one.foo")
|
||||||
# client.succeed("curl -k -v http://two.foo")
|
# client.succeed("curl -k -v http://two.foo")
|
||||||
|
|
||||||
answer = client.succeed("dig @192.168.1.2 -p 1053 one.foo")
|
answer = client.succeed("dig @192.168.1.2 one.foo")
|
||||||
assert "192.168.1.3" in answer, "IP not found"
|
assert "192.168.1.3" in answer, "IP not found"
|
||||||
|
|
||||||
answer = client.succeed("dig @192.168.1.2 -p 1053 two.foo")
|
answer = client.succeed("dig @192.168.1.2 two.foo")
|
||||||
assert "192.168.1.4" in answer, "IP not found"
|
assert "192.168.1.4" in answer, "IP not found"
|
||||||
|
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ in
|
|||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.admin = {
|
roles.admin = {
|
||||||
description = "A data-mesher admin node that bootstraps the network and can sign new nodes into the network.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -178,7 +177,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
roles.signer = {
|
roles.signer = {
|
||||||
description = "A data-mesher signer node that can sign new nodes into the network.";
|
|
||||||
interface = sharedInterface;
|
interface = sharedInterface;
|
||||||
perInstance =
|
perInstance =
|
||||||
{
|
{
|
||||||
@@ -210,7 +208,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
roles.peer = {
|
roles.peer = {
|
||||||
description = "A data-mesher peer node that connects to the network.";
|
|
||||||
interface = sharedInterface;
|
interface = sharedInterface;
|
||||||
perInstance =
|
perInstance =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/dyndns";
|
manifest.name = "clan-core/dyndns";
|
||||||
manifest.description = "A dynamic DNS service to auto update domain IPs";
|
manifest.description = "A dynamic DNS service to update domain IPs";
|
||||||
manifest.categories = [ "Network" ];
|
manifest.categories = [ "Network" ];
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the dyndns service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -2,34 +2,31 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/emergency-access";
|
manifest.name = "clan-core/emergency-access";
|
||||||
manifest.description = "Set recovery password for emergency access to machine to debug boot issues";
|
manifest.description = "Set recovery password for emergency access to machine";
|
||||||
manifest.categories = [ "System" ];
|
manifest.categories = [ "System" ];
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.default = {
|
roles.default.perInstance = {
|
||||||
description = "Placeholder role to apply the emergency-access service";
|
nixosModule =
|
||||||
perInstance = {
|
{ config, pkgs, ... }:
|
||||||
nixosModule =
|
{
|
||||||
{ config, pkgs, ... }:
|
boot.initrd.systemd.emergencyAccess =
|
||||||
{
|
config.clan.core.vars.generators.emergency-access.files.password-hash.value;
|
||||||
boot.initrd.systemd.emergencyAccess =
|
|
||||||
config.clan.core.vars.generators.emergency-access.files.password-hash.value;
|
|
||||||
|
|
||||||
clan.core.vars.generators.emergency-access = {
|
clan.core.vars.generators.emergency-access = {
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
pkgs.coreutils
|
pkgs.coreutils
|
||||||
pkgs.mkpasswd
|
pkgs.mkpasswd
|
||||||
pkgs.xkcdpass
|
pkgs.xkcdpass
|
||||||
];
|
];
|
||||||
files.password.deploy = false;
|
files.password.deploy = false;
|
||||||
files.password-hash.secret = false;
|
files.password-hash.secret = false;
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password
|
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > $out/password
|
||||||
mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash
|
mkpasswd -s -m sha-512 < $out/password | tr -d "\n" > $out/password-hash
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
emergency-access = ./default.nix;
|
emergency-access = lib.modules.importApply ./default.nix { };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
manifest.categories = [ "System" ];
|
manifest.categories = [ "System" ];
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the garage service";
|
|
||||||
perInstance.nixosModule =
|
perInstance.nixosModule =
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# The test for this module in ./tests/vm/default.nix shows an example of how
|
# The test for this module in ./tests/vm/default.nix shows an example of how
|
||||||
# the service is used.
|
# the service is used.
|
||||||
|
|
||||||
|
{ packages }:
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
# defined in this file directly (e.g. the "morning" role) or split up into a
|
# defined in this file directly (e.g. the "morning" role) or split up into a
|
||||||
# separate file (e.g. the "evening" role)
|
# separate file (e.g. the "evening" role)
|
||||||
roles.morning = {
|
roles.morning = {
|
||||||
description = "A morning greeting machine";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -34,17 +34,20 @@
|
|||||||
settings,
|
settings,
|
||||||
|
|
||||||
# The name of this instance of the service
|
# The name of this instance of the service
|
||||||
|
instanceName,
|
||||||
|
|
||||||
# The current machine
|
# The current machine
|
||||||
|
machine,
|
||||||
|
|
||||||
# All roles of this service, with their assigned machines
|
# All roles of this service, with their assigned machines
|
||||||
|
roles,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
# Analog to 'perSystem' of flake-parts.
|
# Analog to 'perSystem' of flake-parts.
|
||||||
# For every instance of this service we will add a nixosModule to a morning-machine
|
# For every instance of this service we will add a nixosModule to a morning-machine
|
||||||
nixosModule =
|
nixosModule =
|
||||||
{ ... }:
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
# Interaction examples what you could do here:
|
# Interaction examples what you could do here:
|
||||||
# - Get some settings of this machine
|
# - Get some settings of this machine
|
||||||
@@ -68,7 +71,6 @@
|
|||||||
# the interface here, so we can see all settings of the service in one place,
|
# the interface here, so we can see all settings of the service in one place,
|
||||||
# but you can also move it to the respective file
|
# but you can also move it to the respective file
|
||||||
roles.evening = {
|
roles.evening = {
|
||||||
description = "An evening greeting machine";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix {
|
||||||
|
inherit (self) packages;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -6,7 +6,5 @@
|
|||||||
manifest.categories = [ "Utility" ];
|
manifest.categories = [ "Utility" ];
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.default = {
|
roles.default = { };
|
||||||
description = "Placeholder role to apply the importer service";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
importer = ./default.nix;
|
importer = lib.modules.importApply ./default.nix { };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/internet";
|
manifest.name = "clan-core/internet";
|
||||||
manifest.description = "Part of the clan networking abstraction to define how to reach machines from outside the clan network over the internet, if defined has the highest priority";
|
manifest.description = "direct access (or via ssh jumphost) to machines";
|
||||||
manifest.categories = [
|
manifest.categories = [
|
||||||
"System"
|
"System"
|
||||||
"Network"
|
"Network"
|
||||||
];
|
];
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the internet service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -32,6 +31,7 @@
|
|||||||
{
|
{
|
||||||
roles,
|
roles,
|
||||||
lib,
|
lib,
|
||||||
|
settings,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "localbackup";
|
manifest.name = "localbackup";
|
||||||
manifest.description = "Automatically backups current machine to local directory or a mounted drive.";
|
manifest.description = "Automatically backups current machine to local directory.";
|
||||||
manifest.categories = [ "System" ];
|
manifest.categories = [ "System" ];
|
||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the localbackup service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules.localbackup = module;
|
clan.modules.localbackup = module;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
manifest.categories = [ "Social" ];
|
manifest.categories = [ "Social" ];
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the matrix-synapse service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -145,7 +144,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// lib.mapAttrs' (
|
// lib.mapAttrs' (
|
||||||
_name: user:
|
name: user:
|
||||||
lib.nameValuePair "matrix-password-${user.name}" {
|
lib.nameValuePair "matrix-password-${user.name}" {
|
||||||
files."matrix-password-${user.name}" = { };
|
files."matrix-password-${user.name}" = { };
|
||||||
migrateFact = "matrix-password-${user.name}";
|
migrateFact = "matrix-password-${user.name}";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules.matrix-synapse = module;
|
clan.modules.matrix-synapse = module;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{ packages }:
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
@@ -6,20 +7,19 @@
|
|||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.telegraf = {
|
roles.telegraf = {
|
||||||
description = "Placeholder role to apply the telegraf monitoring agent";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
options.allowAllInterfaces = lib.mkOption {
|
options.allowAllInterfaces = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = null;
|
default = false;
|
||||||
description = "Deprecated. Has no effect.";
|
description = "If true, Telegraf will listen on all interfaces. Otherwise, it will only listen on the interfaces specified in `interfaces`";
|
||||||
};
|
};
|
||||||
|
|
||||||
options.interfaces = lib.mkOption {
|
options.interfaces = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
type = lib.types.listOf lib.types.str;
|
||||||
default = null;
|
default = [ "zt+" ];
|
||||||
description = "Deprecated. Has no effect.";
|
description = "List of interfaces to expose the metrics to";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
{
|
{
|
||||||
lib,
|
|
||||||
self,
|
self,
|
||||||
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix {
|
||||||
|
inherit (self) packages;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules.monitoring = module;
|
clan.modules.monitoring = module;
|
||||||
|
|
||||||
perSystem =
|
perSystem =
|
||||||
{ pkgs, ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
clan.nixosTests.monitoring = {
|
clan.nixosTests.monitoring = {
|
||||||
imports = [
|
imports = [ ./tests/vm/default.nix ];
|
||||||
(lib.modules.importApply ./tests/vm/default.nix {
|
|
||||||
inherit (self) packages;
|
|
||||||
inherit pkgs;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
clan.modules.monitoring = module;
|
clan.modules.monitoring = module;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,54 +11,34 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
jsonpath = "/tmp/telegraf.json";
|
||||||
auth_user = "prometheus";
|
auth_user = "prometheus";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
warnings =
|
|
||||||
lib.optionals (settings.allowAllInterfaces != null) [
|
|
||||||
"monitoring.settings.allowAllInterfaces is deprecated and and has no effect. Please remove it from your inventory."
|
|
||||||
"The monitoring service will now always listen on all interfaces over https."
|
|
||||||
]
|
|
||||||
++ (lib.optionals (settings.interfaces != null) [
|
|
||||||
"monitoring.settings.interfaces is deprecated and and has no effect. Please remove it from your inventory."
|
|
||||||
"The monitoring service will now always listen on all interfaces over https."
|
|
||||||
]);
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.interfaces = lib.mkIf (settings.allowAllInterfaces == false) (
|
||||||
|
builtins.listToAttrs (
|
||||||
|
map (name: {
|
||||||
|
inherit name;
|
||||||
|
value.allowedTCPPorts = [
|
||||||
|
9273
|
||||||
|
9990
|
||||||
|
];
|
||||||
|
}) settings.interfaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = lib.mkIf (settings.allowAllInterfaces == true) [
|
||||||
9273
|
9273
|
||||||
9990
|
9990
|
||||||
];
|
];
|
||||||
|
|
||||||
clan.core.vars.generators."telegraf-certs" = {
|
|
||||||
files.crt = {
|
|
||||||
restartUnits = [ "telegraf.service" ];
|
|
||||||
deploy = true;
|
|
||||||
secret = false;
|
|
||||||
};
|
|
||||||
files.key = {
|
|
||||||
mode = "0600";
|
|
||||||
restartUnits = [ "telegraf.service" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
openssl req -x509 -nodes -newkey rsa:4096 \
|
|
||||||
-keyout "$out"/key \
|
|
||||||
-out "$out"/crt \
|
|
||||||
-subj "/C=US/ST=CA/L=San Francisco/O=Example Corp/OU=IT/CN=example.com"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators."telegraf" = {
|
clan.core.vars.generators."telegraf" = {
|
||||||
|
|
||||||
files.password.restartUnits = [ "telegraf.service" ];
|
files.password.restartUnits = [ "telegraf.service" ];
|
||||||
files.password-env.restartUnits = [ "telegraf.service" ];
|
files.password-env.restartUnits = [ "telegraf.service" ];
|
||||||
files.miniserve-auth.restartUnits = [ "telegraf.service" ];
|
files.miniserve-auth.restartUnits = [ "telegraf.service" ];
|
||||||
|
|
||||||
dependencies = [ "telegraf-certs" ];
|
|
||||||
|
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
pkgs.coreutils
|
pkgs.coreutils
|
||||||
pkgs.xkcdpass
|
pkgs.xkcdpass
|
||||||
@@ -76,38 +56,7 @@
|
|||||||
systemd.services.telegraf-json = {
|
systemd.services.telegraf-json = {
|
||||||
enable = true;
|
enable = true;
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "telegraf.service" ];
|
script = "${pkgs.miniserve}/bin/miniserve -p 9990 ${jsonpath} --auth-file ${config.clan.core.vars.generators.telegraf.files.miniserve-auth.path}";
|
||||||
requires = [ "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"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.telegraf = {
|
services.telegraf = {
|
||||||
@@ -115,7 +64,6 @@
|
|||||||
environmentFiles = [
|
environmentFiles = [
|
||||||
(builtins.toString config.clan.core.vars.generators.telegraf.files.password-env.path)
|
(builtins.toString config.clan.core.vars.generators.telegraf.files.password-env.path)
|
||||||
];
|
];
|
||||||
|
|
||||||
extraConfig = {
|
extraConfig = {
|
||||||
agent.interval = "60s";
|
agent.interval = "60s";
|
||||||
inputs = {
|
inputs = {
|
||||||
@@ -151,12 +99,10 @@
|
|||||||
metric_version = 2;
|
metric_version = 2;
|
||||||
basic_username = "${auth_user}";
|
basic_username = "${auth_user}";
|
||||||
basic_password = "$${BASIC_AUTH_PWD}";
|
basic_password = "$${BASIC_AUTH_PWD}";
|
||||||
tls_cert = "$${CRT_PATH}";
|
|
||||||
tls_key = "$${KEY_PATH}";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs.file = {
|
outputs.file = {
|
||||||
files = [ "/run/telegraf-www/telegraf.json" ];
|
files = [ jsonpath ];
|
||||||
data_format = "json";
|
data_format = "json";
|
||||||
json_timestamp_units = "1s";
|
json_timestamp_units = "1s";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,95 +1,24 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
{
|
||||||
name = "monitoring";
|
name = "monitoring";
|
||||||
|
|
||||||
clan = {
|
clan = {
|
||||||
directory = ./.;
|
directory = ./.;
|
||||||
inventory = {
|
inventory = {
|
||||||
machines.peer1 = {
|
machines.peer1 = { };
|
||||||
deploy.targetHost = "[2001:db8:1::1]";
|
|
||||||
};
|
|
||||||
|
|
||||||
instances."test" = {
|
instances."test" = {
|
||||||
module.name = "monitoring";
|
module.name = "monitoring";
|
||||||
module.input = "self";
|
module.input = "self";
|
||||||
|
|
||||||
roles.telegraf.machines.peer1 = { };
|
roles.telegraf.machines.peer1 = { };
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes = {
|
|
||||||
peer1 =
|
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
services.telegraf.extraConfig = {
|
|
||||||
agent.interval = lib.mkForce "1s";
|
|
||||||
outputs.prometheus_client = {
|
|
||||||
# BUG: We have to disable basic auth here because the prometheus_client
|
|
||||||
# output plugin will otherwise deadlock Telegraf on startup.
|
|
||||||
basic_password = lib.mkForce "";
|
|
||||||
basic_username = lib.mkForce "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# !!! ANY CHANGES HERE MUST BE REFLECTED IN:
|
|
||||||
# clan_lib/metrics/telegraf.py::get_metrics
|
|
||||||
testScript =
|
testScript =
|
||||||
{ nodes, ... }:
|
{ ... }:
|
||||||
''
|
''
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
import ssl
|
|
||||||
import json
|
|
||||||
import shlex
|
|
||||||
import urllib.request
|
|
||||||
from base64 import b64encode
|
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
peer1.wait_for_unit("network-online.target")
|
|
||||||
peer1.wait_for_unit("telegraf.service")
|
|
||||||
peer1.wait_for_unit("telegraf-json.service")
|
|
||||||
|
|
||||||
# Fetch the basic auth password from the secret file
|
|
||||||
password = peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.telegraf.files.password.path}").strip()
|
|
||||||
credentials = f"prometheus:{password}"
|
|
||||||
|
|
||||||
print("Using credentials:", credentials)
|
|
||||||
peer1.succeed(f"curl -k -u {credentials} https://localhost:9990/telegraf.json")
|
|
||||||
peer1.succeed(f"curl -k -u {credentials} https://localhost:9273/metrics")
|
|
||||||
|
|
||||||
cert_path = "${nodes.peer1.clan.core.vars.generators.telegraf-certs.files.crt.path}"
|
|
||||||
url = "https://192.168.1.1:9990/telegraf.json" # HTTPS required
|
|
||||||
|
|
||||||
print("Waiting for /var/run/telegraf-www/telegraf.json to be bigger then 200 bytes")
|
|
||||||
peer1.wait_until_succeeds(f"test \"$(stat -c%s /var/run/telegraf-www/telegraf.json)\" -ge 200", timeout=30)
|
|
||||||
|
|
||||||
encoded_credentials = b64encode(credentials.encode("utf-8")).decode("utf-8")
|
|
||||||
headers = {"Authorization": f"Basic {encoded_credentials}"}
|
|
||||||
req = urllib.request.Request(url, headers=headers) # noqa: S310
|
|
||||||
|
|
||||||
# Trust the provided CA/server certificate
|
|
||||||
context = ssl.create_default_context(cafile=cert_path)
|
|
||||||
context.check_hostname = False
|
|
||||||
context.verify_mode = ssl.CERT_REQUIRED
|
|
||||||
|
|
||||||
found_system = False
|
|
||||||
with urllib.request.urlopen(req, context=context, timeout=5) as response:
|
|
||||||
for raw_line in response:
|
|
||||||
line_str = raw_line.decode("utf-8").strip()
|
|
||||||
if not line_str:
|
|
||||||
continue
|
|
||||||
obj = json.loads(line_str)
|
|
||||||
if obj.get("name") == "nixos_systems":
|
|
||||||
found_system = True
|
|
||||||
print("Found nixos_systems metric in json output")
|
|
||||||
break
|
|
||||||
|
|
||||||
assert found_system, "nixos_systems metric not found in json output"
|
|
||||||
|
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-----
|
|
||||||
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
|
|
||||||
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";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/mycelium";
|
manifest.name = "clan-core/mycelium";
|
||||||
manifest.description = "End-2-end encrypted P2P IPv6 overlay network";
|
manifest.description = "End-2-end encrypted IPv6 overlay network";
|
||||||
manifest.categories = [
|
manifest.categories = [
|
||||||
"System"
|
"System"
|
||||||
"Network"
|
"Network"
|
||||||
];
|
];
|
||||||
|
|
||||||
roles.peer = {
|
roles.peer = {
|
||||||
description = "A peer in the mycelium network";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
roles.default = {
|
roles.default = {
|
||||||
description = "Placeholder role to apply the packages service";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -1,91 +1,36 @@
|
|||||||
# Clan service: sshd
|
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.
|
||||||
What it does
|
|
||||||
- Generates and persists SSH host keys via `vars`.
|
|
||||||
- Optionally issues CA‑signed host certificates for servers.
|
|
||||||
- Installs the `server` CA public key into `clients` `known_hosts` for TOFU‑less verification.
|
|
||||||
|
|
||||||
|
`sshd` also generates SSH certificates for both servers and clients allowing for certificate-based authentication for SSH.
|
||||||
|
|
||||||
When to use it
|
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.
|
||||||
- Zero‑TOFU SSH for dynamic fleets: admins/CI can connect to frequently rebuilt hosts (e.g., server-1.example.com) without prompts or per‑host `known_hosts` churn.
|
|
||||||
|
|
||||||
Roles
|
## Usage
|
||||||
- Server: runs sshd, presents a CA‑signed host certificate for `<machine>.<domain>`.
|
|
||||||
- Client: trusts the CA for the given domains to verify servers’ certificates.
|
|
||||||
Tip: assign both roles to a machine if it should both present a cert and verify others.
|
|
||||||
|
|
||||||
Quick start (with host certificates)
|
|
||||||
Useful if you never want to get a prompt about trusting the ssh fingerprint.
|
|
||||||
```nix
|
|
||||||
{
|
|
||||||
inventory.instances = {
|
|
||||||
sshd-with-certs = {
|
|
||||||
module = { name = "sshd"; input = "clan-core"; };
|
|
||||||
# Servers present certificates for <machine>.example.com
|
|
||||||
roles.server.tags.all = { };
|
|
||||||
roles.server.settings = {
|
|
||||||
certificate.searchDomains = [ "example.com" ];
|
|
||||||
# Optional: also add RSA host keys
|
|
||||||
# hostKeys.rsa.enable = true;
|
|
||||||
};
|
|
||||||
# Clients trust the CA for *.example.com
|
|
||||||
roles.client.tags.all = { };
|
|
||||||
roles.client.settings = {
|
|
||||||
certificate.searchDomains = [ "example.com" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Basic: only add persistent host keys (ed25519), no certificates
|
|
||||||
Useful if you want to get an ssh "trust this server" prompt once and then never again.
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
inventory.instances = {
|
inventory.instances = {
|
||||||
|
# By default this service only generates ed25519 host keys
|
||||||
sshd-basic = {
|
sshd-basic = {
|
||||||
module = {
|
module = {
|
||||||
name = "sshd";
|
name = "sshd";
|
||||||
input = "clan-core";
|
input = "clan-core";
|
||||||
};
|
};
|
||||||
roles.server.tags.all = { };
|
roles.server.tags.all = { };
|
||||||
|
roles.client.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example: selective trust per environment
|
# Also generate RSA host keys for all servers
|
||||||
Admins should trust only production; CI should trust prod and staging. Servers are reachable under both domains.
|
sshd-with-rsa = {
|
||||||
```nix
|
module = {
|
||||||
{
|
name = "sshd";
|
||||||
inventory.instances = {
|
input = "clan-core";
|
||||||
sshd-env-scoped = {
|
};
|
||||||
module = { name = "sshd"; input = "clan-core"; };
|
|
||||||
|
|
||||||
# Servers present certs for both prod and staging FQDNs
|
|
||||||
roles.server.tags.all = { };
|
roles.server.tags.all = { };
|
||||||
roles.server.settings = {
|
roles.server.settings = {
|
||||||
certificate.searchDomains = [ "prod.example.com" "staging.example.com" ];
|
hostKeys.rsa.enable = true;
|
||||||
};
|
|
||||||
|
|
||||||
# Admin laptop: trust prod only
|
|
||||||
roles.client.machines."admin-laptop".settings = {
|
|
||||||
certificate.searchDomains = [ "prod.example.com" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# CI runner: trust prod and staging
|
|
||||||
roles.client.machines."ci-runner-1".settings = {
|
|
||||||
certificate.searchDomains = [ "prod.example.com" "staging.example.com" ];
|
|
||||||
};
|
};
|
||||||
|
roles.client.tags.all = { };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Admin -> server1.prod.example.com: zero‑TOFU (verified via cert).
|
|
||||||
- Admin -> server1.staging.example.com: falls back to TOFU (or is blocked by policy).
|
|
||||||
- CI -> either prod or staging: zero‑TOFU for both.
|
|
||||||
Note: server and client searchDomains don’t have to be identical; they only need to overlap for the hostnames you actually use.
|
|
||||||
|
|
||||||
Notes
|
|
||||||
- Connect using a name that matches a cert principal (e.g., `server1.example.com`); wildcards are not allowed inside the certificate.
|
|
||||||
- CA private key stays in `vars` (not deployed); only the CA public key is distributed.
|
|
||||||
- Logins still require your user SSH keys on the server (passwords are disabled).
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.client = {
|
roles.client = {
|
||||||
description = "Installs the SSH CA public key into known_hosts for the configured domains, so this machine can verify servers’ host certificates without TOFU prompts.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
@@ -39,6 +38,7 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|
||||||
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||||
share = true;
|
share = true;
|
||||||
files.id_ed25519.deploy = false;
|
files.id_ed25519.deploy = false;
|
||||||
@@ -64,12 +64,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
roles.server = {
|
roles.server = {
|
||||||
description = "Runs sshd with persistent host keys and (if certificate.searchDomains is set) a CA‑signed host certificate for <machine>.<domain>, enabling TOFU‑less verification by clients that trust the CA.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
hostKeys.rsa.enable = lib.mkEnableOption "generating a RSA host key";
|
hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
|
||||||
|
|
||||||
certificate = {
|
certificate = {
|
||||||
searchDomains = lib.mkOption {
|
searchDomains = lib.mkOption {
|
||||||
@@ -97,7 +96,9 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|
||||||
clan.core.vars.generators = {
|
clan.core.vars.generators = {
|
||||||
|
|
||||||
openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
|
||||||
share = true;
|
share = true;
|
||||||
files.id_ed25519.deploy = false;
|
files.id_ed25519.deploy = false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now the folder `~/syncthing/documents` will be shared and kept in sync with all your machines.
|
Now the folder `~/syncthing/documents` will be shared with all your machines.
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
manifest.readme = builtins.readFile ./README.md;
|
manifest.readme = builtins.readFile ./README.md;
|
||||||
|
|
||||||
roles.peer = {
|
roles.peer = {
|
||||||
description = "A peer in the syncthing cluster that syncs files with other peers.";
|
|
||||||
interface =
|
interface =
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix {
|
||||||
|
inherit (self) packages;
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -11,9 +11,7 @@
|
|||||||
pkgs.syncthing
|
pkgs.syncthing
|
||||||
];
|
];
|
||||||
script = ''
|
script = ''
|
||||||
export TMPDIR=/tmp
|
syncthing generate --config "$out"
|
||||||
TEMPORARY=$(mktemp -d)
|
|
||||||
syncthing generate --config "$out" --data "$TEMPORARY"
|
|
||||||
mv "$out"/key.pem "$out"/key
|
mv "$out"/key.pem "$out"/key
|
||||||
mv "$out"/cert.pem "$out"/cert
|
mv "$out"/cert.pem "$out"/cert
|
||||||
cat "$out"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$out"/id
|
cat "$out"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$out"/id
|
||||||
|
|||||||
@@ -2,17 +2,13 @@
|
|||||||
{
|
{
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
manifest.name = "clan-core/tor";
|
manifest.name = "clan-core/tor";
|
||||||
manifest.description = "Part of the clan networking abstraction to define how to reach machines through the Tor network, if used has the lowest priority";
|
manifest.description = "Onion routing, use Hidden services to connect your machines";
|
||||||
manifest.categories = [
|
manifest.categories = [
|
||||||
"System"
|
"System"
|
||||||
"Network"
|
"Network"
|
||||||
];
|
];
|
||||||
|
|
||||||
roles.client = {
|
roles.client = {
|
||||||
description = ''
|
|
||||||
Enables a continuosly running Tor proxy on the machine, allowing access to other machines via the Tor network.
|
|
||||||
If not enabled, a Tor proxy will be started automatically when required.
|
|
||||||
'';
|
|
||||||
perInstance =
|
perInstance =
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
@@ -35,7 +31,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
roles.server = {
|
roles.server = {
|
||||||
description = "Sets up a Tor onion service for the machine, thus making it reachable over Tor.";
|
|
||||||
# interface =
|
# interface =
|
||||||
# { lib, ... }:
|
# { lib, ... }:
|
||||||
# {
|
# {
|
||||||
@@ -63,7 +58,7 @@
|
|||||||
priority = lib.mkDefault 10;
|
priority = lib.mkDefault 10;
|
||||||
# TODO add user space network support to clan-cli
|
# TODO add user space network support to clan-cli
|
||||||
module = "clan_lib.network.tor";
|
module = "clan_lib.network.tor";
|
||||||
peers = lib.mapAttrs (name: _machine: {
|
peers = lib.mapAttrs (name: machine: {
|
||||||
host.var = {
|
host.var = {
|
||||||
machine = name;
|
machine = name;
|
||||||
generator = "tor_${instanceName}";
|
generator = "tor_${instanceName}";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
module = ./default.nix;
|
module = lib.modules.importApply ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan.modules = {
|
clan.modules = {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ inventory.instances = {
|
|||||||
clan-cache = {
|
clan-cache = {
|
||||||
module = {
|
module = {
|
||||||
name = "trusted-nix-caches";
|
name = "trusted-nix-caches";
|
||||||
input = "clan-core";
|
input = "clan";
|
||||||
};
|
};
|
||||||
roles.default.machines.draper = { };
|
roles.default.machines.draper = { };
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user