Compare commits
127 Commits
fix/combob
...
flake-comp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d74b9c06e1 | ||
|
|
f248cc91ad | ||
|
|
e2cb75784c | ||
|
|
a8d48b22f8 | ||
|
|
c0f2bcf751 | ||
|
|
20c23fa64b | ||
|
|
23573e16c4 | ||
|
|
eaee4e8cad | ||
|
|
10e43a8884 | ||
|
|
dc1cd03717 | ||
|
|
a71a5880c1 | ||
|
|
6b137f21de | ||
|
|
fbc14bf20f | ||
|
|
2f2f3b6898 | ||
|
|
3ae0f37bcb | ||
|
|
e49d432542 | ||
|
|
76955533cf | ||
|
|
d0ebc75135 | ||
|
|
40503306d1 | ||
|
|
da99407e74 | ||
|
|
915178765b | ||
|
|
518de45d41 | ||
|
|
7d23189c1c | ||
|
|
eec55f73a2 | ||
|
|
484d274c3c | ||
|
|
a4b20f9167 | ||
|
|
dc7291c62b | ||
|
|
a814a44bc6 | ||
|
|
86a6177126 | ||
|
|
4536a5b4f5 | ||
|
|
a9cfda9acb | ||
|
|
b9f60218d7 | ||
|
|
f69e28a133 | ||
|
|
1968230c28 | ||
|
|
9cad074732 | ||
|
|
4859a9ab7c | ||
|
|
b53ecdc89d | ||
|
|
19603e1a1c | ||
|
|
7d20f3a33b | ||
|
|
fa03c190f8 | ||
|
|
65101ad55a | ||
|
|
e5db3e269b | ||
|
|
073750e4c5 | ||
|
|
8bafbcb295 | ||
|
|
dbef6ced77 | ||
|
|
65e7f9e6ca | ||
|
|
e1062ed97c | ||
|
|
2eb1a56d8f | ||
|
|
0f499fc651 | ||
|
|
bcb7a1aa60 | ||
|
|
273c83ec27 | ||
|
|
c74d7857da | ||
|
|
11405966c6 | ||
|
|
220839598d | ||
|
|
44dcfa7844 | ||
|
|
98f685f3ca | ||
|
|
9e43285ba8 | ||
|
|
c0bc0417a6 | ||
|
|
c90b69d499 | ||
|
|
0240acdf3e | ||
|
|
92726ecebc | ||
|
|
b8e9546762 | ||
|
|
2039f034b1 | ||
|
|
0a329f43a8 | ||
|
|
bde0a2845c | ||
|
|
af3c6282c9 | ||
|
|
73ab4d2a6e | ||
|
|
cc269c4f58 | ||
|
|
20021a92ea | ||
|
|
7b54e9b033 | ||
|
|
7971eceb74 | ||
|
|
49a5763f69 | ||
|
|
10694e58c8 | ||
|
|
0d919c4fce | ||
|
|
8cccf757a8 | ||
|
|
80c8cc8628 | ||
|
|
ab63f0d7a4 | ||
|
|
06e0461ec9 | ||
|
|
60ba00dd8f | ||
|
|
90ef55f040 | ||
|
|
de81a5d810 | ||
|
|
3fe65f1f12 | ||
|
|
6bb998f9dd | ||
|
|
af7ce9b8ed | ||
|
|
b74193514d | ||
|
|
c33fd4e504 | ||
|
|
65f3cb562a | ||
|
|
355ff648d7 | ||
|
|
f314eb04d6 | ||
|
|
ebe206cdc0 | ||
|
|
2a138d3248 | ||
|
|
77810b1d4f | ||
|
|
77c840c9ba | ||
|
|
9df7e6df1e | ||
|
|
a5e51f658d | ||
|
|
98d5b3651b | ||
|
|
713a1a550e | ||
|
|
d51d656391 | ||
|
|
0f79af697e | ||
|
|
0119fc06ca | ||
|
|
5361261bd5 | ||
|
|
86e7bcc389 | ||
|
|
79281aba90 | ||
|
|
dade91c292 | ||
|
|
d285a0e716 | ||
|
|
a97128db17 | ||
|
|
ff7b49be5f | ||
|
|
0b816a2672 | ||
|
|
e6ec331da0 | ||
|
|
0b05b0b1ec | ||
|
|
efd9beba15 | ||
|
|
dc03a9183f | ||
|
|
ab3158ca07 | ||
|
|
75a1f7b67f | ||
|
|
d453720a57 | ||
|
|
a4331cc109 | ||
|
|
434ce1af49 | ||
|
|
488ee1ae63 | ||
|
|
fc2e619046 | ||
|
|
cf6c3604ca | ||
|
|
a3ea62caba | ||
|
|
e2e4837b29 | ||
|
|
96fc3d409a | ||
|
|
392f244361 | ||
|
|
d2529704d5 | ||
|
|
62a3503987 | ||
|
|
c39aa89e29 |
@@ -19,11 +19,10 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies =
|
||||
[
|
||||
pkgs.stdenv.drvPath
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
|
||||
dependencies = [
|
||||
pkgs.stdenv.drvPath
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
@@ -154,13 +153,12 @@
|
||||
nixos-test-backups = self.clanLib.test.containerTest {
|
||||
name = "nixos-test-backups";
|
||||
nodes.machine = {
|
||||
imports =
|
||||
[
|
||||
self.nixosModules.clanCore
|
||||
# Some custom overrides for the backup tests
|
||||
self.nixosModules.test-backup
|
||||
]
|
||||
++
|
||||
imports = [
|
||||
self.nixosModules.clanCore
|
||||
# Some custom overrides for the backup tests
|
||||
self.nixosModules.test-backup
|
||||
]
|
||||
++
|
||||
# import the inventory generated nixosModules
|
||||
self.clan.clanInternals.inventoryClass.machines.test-backup.machineImports;
|
||||
clan.core.settings.directory = ./.;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{ fetchgit }:
|
||||
fetchgit {
|
||||
url = "https://git.clan.lol/clan/clan-core.git";
|
||||
rev = "ba8a80eccf091fc7f99aef3895e31617d3813d20";
|
||||
sha256 = "189srg4mc5y3prapm8day0x0wpibbqc72hrnl61agsmiq7cfmbkd";
|
||||
rev = "d0ebc75135b125fd509558c7680fa2459af91195";
|
||||
sha256 = "1k9wpy661dhwas7z05jkn8157pgmr408dn67zd9px9iphmrf7bry";
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
|
||||
@@ -158,7 +158,8 @@
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.bash.drvPath
|
||||
pkgs.buildPackages.xorg.lndir
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
};
|
||||
in
|
||||
pkgs.lib.mkIf (pkgs.stdenv.isLinux && !pkgs.stdenv.isAarch64) {
|
||||
|
||||
@@ -159,7 +159,8 @@ let
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.bash.drvPath
|
||||
pkgs.buildPackages.xorg.lndir
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.stdenvNoCC
|
||||
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
services.openssh.enable = true;
|
||||
services.openssh.settings.PasswordAuthentication = false;
|
||||
users.users.root.openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
|
||||
services.openssh.knownHosts.localhost.publicKeyFile = ../assets/ssh/pubkey;
|
||||
services.openssh.hostKeys = [
|
||||
{
|
||||
path = ../assets/ssh/privkey;
|
||||
type = "ed25519";
|
||||
}
|
||||
];
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
|
||||
boot.consoleLogLevel = lib.mkForce 100;
|
||||
@@ -99,12 +106,14 @@
|
||||
let
|
||||
closureInfo = pkgs.closureInfo {
|
||||
rootPaths = [
|
||||
self.checks.x86_64-linux.clan-core-for-checks
|
||||
self.packages.${pkgs.system}.clan-cli
|
||||
self.checks.${pkgs.system}.clan-core-for-checks
|
||||
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
|
||||
pkgs.stdenv.drvPath
|
||||
pkgs.bash.drvPath
|
||||
pkgs.buildPackages.xorg.lndir
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
};
|
||||
in
|
||||
self.clanLib.test.containerTest {
|
||||
@@ -150,15 +159,7 @@
|
||||
# Update the machine configuration to add a new file
|
||||
machine_config_path = os.path.join(flake_dir, "machines", "test-update-machine", "configuration.nix")
|
||||
os.makedirs(os.path.dirname(machine_config_path), exist_ok=True)
|
||||
|
||||
with open(machine_config_path, "w") as f:
|
||||
f.write("""
|
||||
{
|
||||
environment.etc."update-successful".text = "ok";
|
||||
}
|
||||
""")
|
||||
|
||||
# Run clan update command
|
||||
# Note: update command doesn't accept -i flag, SSH key must be in ssh-agent
|
||||
# Start ssh-agent and add the key
|
||||
agent_output = subprocess.check_output(["${pkgs.openssh}/bin/ssh-agent", "-s"], text=True)
|
||||
@@ -167,11 +168,95 @@
|
||||
os.environ["SSH_AUTH_SOCK"] = line.split("=", 1)[1].split(";")[0]
|
||||
elif line.startswith("SSH_AGENT_PID="):
|
||||
os.environ["SSH_AGENT_PID"] = line.split("=", 1)[1].split(";")[0]
|
||||
|
||||
|
||||
# Add the SSH key to the agent
|
||||
subprocess.run(["${pkgs.openssh}/bin/ssh-add", ssh_conn.ssh_key], check=True)
|
||||
|
||||
|
||||
##############
|
||||
print("TEST: update with --build-host local")
|
||||
with open(machine_config_path, "w") as f:
|
||||
f.write("""
|
||||
{
|
||||
environment.etc."update-build-local-successful".text = "ok";
|
||||
}
|
||||
""")
|
||||
|
||||
# rsync the flake into the container
|
||||
os.environ["PATH"] = f"{os.environ['PATH']}:${pkgs.openssh}/bin"
|
||||
subprocess.run(
|
||||
[
|
||||
"${pkgs.rsync}/bin/rsync",
|
||||
"-a",
|
||||
"--delete",
|
||||
"-e",
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no",
|
||||
f"{str(flake_dir)}/",
|
||||
f"root@192.168.1.1:/flake",
|
||||
],
|
||||
check=True
|
||||
)
|
||||
|
||||
# allow machine to ssh into itself
|
||||
subprocess.run([
|
||||
"ssh",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
f"root@192.168.1.1",
|
||||
"mkdir -p /root/.ssh && chmod 700 /root/.ssh && echo \"$(cat \"${../assets/ssh/privkey}\")\" > /root/.ssh/id_ed25519 && chmod 600 /root/.ssh/id_ed25519",
|
||||
], check=True)
|
||||
|
||||
# install the clan-cli package into the container's Nix store
|
||||
subprocess.run(
|
||||
[
|
||||
"${pkgs.nix}/bin/nix",
|
||||
"copy",
|
||||
"--to",
|
||||
"ssh://root@192.168.1.1",
|
||||
"--no-check-sigs",
|
||||
f"${self.packages.${pkgs.system}.clan-cli}",
|
||||
"--extra-experimental-features", "nix-command flakes",
|
||||
"--from", f"{os.environ["TMPDIR"]}/store"
|
||||
],
|
||||
check=True,
|
||||
env={
|
||||
**os.environ,
|
||||
"NIX_SSHOPTS": "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no",
|
||||
},
|
||||
)
|
||||
|
||||
# Run ssh on the host to run the clan update command via --build-host local
|
||||
subprocess.run([
|
||||
"ssh",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
f"root@192.168.1.1",
|
||||
"${self.packages.${pkgs.system}.clan-cli}/bin/clan",
|
||||
"machines",
|
||||
"update",
|
||||
"--debug",
|
||||
"--flake", "/flake",
|
||||
"--host-key-check", "none",
|
||||
"--upload-inputs", # Use local store instead of fetching from network
|
||||
"--build-host", "localhost",
|
||||
"test-update-machine",
|
||||
"--target-host", f"root@localhost",
|
||||
], check=True)
|
||||
|
||||
# Verify the update was successful
|
||||
machine.succeed("test -f /etc/update-build-local-successful")
|
||||
|
||||
|
||||
##############
|
||||
print("TEST: update with --target-host")
|
||||
|
||||
with open(machine_config_path, "w") as f:
|
||||
f.write("""
|
||||
{
|
||||
environment.etc."target-host-update-successful".text = "ok";
|
||||
}
|
||||
""")
|
||||
|
||||
# Run clan update command
|
||||
subprocess.run([
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
@@ -180,16 +265,18 @@
|
||||
"--debug",
|
||||
"--flake", flake_dir,
|
||||
"--host-key-check", "none",
|
||||
"--fetch-local", # Use local store instead of fetching from network
|
||||
"--upload-inputs", # Use local store instead of fetching from network
|
||||
"test-update-machine",
|
||||
"--target-host", f"root@192.168.1.1:{ssh_conn.host_port}",
|
||||
], check=True)
|
||||
|
||||
# Verify the update was successful
|
||||
machine.succeed("test -f /etc/update-successful")
|
||||
machine.succeed("test -f /etc/target-host-update-successful")
|
||||
|
||||
# Test update with --build-host
|
||||
# Update configuration again to test build-host functionality
|
||||
|
||||
##############
|
||||
print("TEST: update with --build-host")
|
||||
# Update configuration again
|
||||
with open(machine_config_path, "w") as f:
|
||||
f.write("""
|
||||
{
|
||||
@@ -205,24 +292,7 @@
|
||||
"--debug",
|
||||
"--flake", flake_dir,
|
||||
"--host-key-check", "none",
|
||||
"--fetch-local", # Use local store instead of fetching from network
|
||||
"--build-host", f"root@192.168.1.1:{ssh_conn.host_port}",
|
||||
"test-update-machine",
|
||||
"--target-host", f"root@192.168.1.1:{ssh_conn.host_port}",
|
||||
], check=True)
|
||||
|
||||
# Verify the second update was successful
|
||||
machine.succeed("test -f /etc/build-host-update-successful")
|
||||
|
||||
# Run clan update command with --build-host
|
||||
subprocess.run([
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
"machines",
|
||||
"update",
|
||||
"--debug",
|
||||
"--flake", flake_dir,
|
||||
"--host-key-check", "none",
|
||||
"--fetch-local", # Use local store instead of fetching from network
|
||||
"--upload-inputs", # Use local store instead of fetching from network
|
||||
"--build-host", f"root@192.168.1.1:{ssh_conn.host_port}",
|
||||
"test-update-machine",
|
||||
"--target-host", f"root@192.168.1.1:{ssh_conn.host_port}",
|
||||
|
||||
@@ -112,125 +112,124 @@ in
|
||||
'';
|
||||
in
|
||||
lib.mkIf (cfg.targets != { }) {
|
||||
environment.systemPackages =
|
||||
[
|
||||
(pkgs.writeShellScriptBin "localbackup-create" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsnapshot
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
${lib.concatMapStringsSep "\n" (target: ''
|
||||
${mountHook target}
|
||||
echo "Creating backup '${target.name}'"
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "localbackup-create" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsnapshot
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
${lib.concatMapStringsSep "\n" (target: ''
|
||||
${mountHook target}
|
||||
echo "Creating backup '${target.name}'"
|
||||
|
||||
${lib.optionalString (target.preBackupHook != null) ''
|
||||
(
|
||||
${target.preBackupHook}
|
||||
)
|
||||
''}
|
||||
|
||||
declare -A preCommandErrors
|
||||
${lib.concatMapStringsSep "\n" (
|
||||
state:
|
||||
lib.optionalString (state.preBackupCommand != null) ''
|
||||
echo "Running pre-backup command for ${state.name}"
|
||||
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
||||
preCommandErrors["${state.name}"]=1
|
||||
fi
|
||||
''
|
||||
) (builtins.attrValues config.clan.core.state)}
|
||||
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
|
||||
'') (builtins.attrValues cfg.targets)}'')
|
||||
(pkgs.writeShellScriptBin "localbackup-list" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.jq
|
||||
pkgs.findutils
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
(${
|
||||
lib.concatMapStringsSep "\n" (target: ''
|
||||
(
|
||||
${mountHook target}
|
||||
find ${lib.escapeShellArg target.directory} -mindepth 1 -maxdepth 1 -name "snapshot.*" -print0 -type d \
|
||||
| jq -Rs 'split("\u0000") | .[] | select(. != "") | { "name": ("${target.name}::" + .)}'
|
||||
)
|
||||
'') (builtins.attrValues cfg.targets)
|
||||
}) | jq -s .
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "localbackup-restore" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsync
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
pkgs.gawk
|
||||
]
|
||||
}
|
||||
if [[ "''${NAME:-}" == "" ]]; then
|
||||
echo "No backup name given via NAME environment variable"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "''${FOLDERS:-}" == "" ]]; then
|
||||
echo "No folders given via FOLDERS environment variable"
|
||||
exit 1
|
||||
fi
|
||||
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
||||
backupname=''${NAME#$name::}
|
||||
|
||||
if command -v localbackup-mount-$name; then
|
||||
localbackup-mount-$name
|
||||
fi
|
||||
if command -v localbackup-unmount-$name; then
|
||||
trap "localbackup-unmount-$name" EXIT
|
||||
fi
|
||||
|
||||
if [[ ! -d $backupname ]]; then
|
||||
echo "No backup found $backupname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
|
||||
for folder in "''${FOLDER[@]}"; do
|
||||
mkdir -p "$folder"
|
||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
||||
done
|
||||
'')
|
||||
]
|
||||
++ (lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-mount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preMountHook != null) target.preMountHook}
|
||||
${lib.optionalString (target.mountpoint != null) ''
|
||||
if ! ${pkgs.util-linux}/bin/mountpoint -q ${lib.escapeShellArg target.mountpoint}; then
|
||||
${pkgs.util-linux}/bin/mount -o X-mount.mkdir ${lib.escapeShellArg target.mountpoint}
|
||||
fi
|
||||
${lib.optionalString (target.preBackupHook != null) ''
|
||||
(
|
||||
${target.preBackupHook}
|
||||
)
|
||||
''}
|
||||
${lib.optionalString (target.postMountHook != null) target.postMountHook}
|
||||
''
|
||||
) cfg.targets)
|
||||
++ lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-unmount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preUnmountHook != null) target.preUnmountHook}
|
||||
${lib.optionalString (
|
||||
target.mountpoint != null
|
||||
) "${pkgs.util-linux}/bin/umount ${lib.escapeShellArg target.mountpoint}"}
|
||||
${lib.optionalString (target.postUnmountHook != null) target.postUnmountHook}
|
||||
''
|
||||
) cfg.targets;
|
||||
|
||||
declare -A preCommandErrors
|
||||
${lib.concatMapStringsSep "\n" (
|
||||
state:
|
||||
lib.optionalString (state.preBackupCommand != null) ''
|
||||
echo "Running pre-backup command for ${state.name}"
|
||||
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
||||
preCommandErrors["${state.name}"]=1
|
||||
fi
|
||||
''
|
||||
) (builtins.attrValues config.clan.core.state)}
|
||||
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
|
||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
|
||||
'') (builtins.attrValues cfg.targets)}'')
|
||||
(pkgs.writeShellScriptBin "localbackup-list" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.jq
|
||||
pkgs.findutils
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
]
|
||||
}
|
||||
(${
|
||||
lib.concatMapStringsSep "\n" (target: ''
|
||||
(
|
||||
${mountHook target}
|
||||
find ${lib.escapeShellArg target.directory} -mindepth 1 -maxdepth 1 -name "snapshot.*" -print0 -type d \
|
||||
| jq -Rs 'split("\u0000") | .[] | select(. != "") | { "name": ("${target.name}::" + .)}'
|
||||
)
|
||||
'') (builtins.attrValues cfg.targets)
|
||||
}) | jq -s .
|
||||
'')
|
||||
(pkgs.writeShellScriptBin "localbackup-restore" ''
|
||||
set -efu -o pipefail
|
||||
export PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.rsync
|
||||
pkgs.coreutils
|
||||
pkgs.util-linux
|
||||
pkgs.gawk
|
||||
]
|
||||
}
|
||||
if [[ "''${NAME:-}" == "" ]]; then
|
||||
echo "No backup name given via NAME environment variable"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "''${FOLDERS:-}" == "" ]]; then
|
||||
echo "No folders given via FOLDERS environment variable"
|
||||
exit 1
|
||||
fi
|
||||
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
||||
backupname=''${NAME#$name::}
|
||||
|
||||
if command -v localbackup-mount-$name; then
|
||||
localbackup-mount-$name
|
||||
fi
|
||||
if command -v localbackup-unmount-$name; then
|
||||
trap "localbackup-unmount-$name" EXIT
|
||||
fi
|
||||
|
||||
if [[ ! -d $backupname ]]; then
|
||||
echo "No backup found $backupname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
|
||||
for folder in "''${FOLDER[@]}"; do
|
||||
mkdir -p "$folder"
|
||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
||||
done
|
||||
'')
|
||||
]
|
||||
++ (lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-mount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preMountHook != null) target.preMountHook}
|
||||
${lib.optionalString (target.mountpoint != null) ''
|
||||
if ! ${pkgs.util-linux}/bin/mountpoint -q ${lib.escapeShellArg target.mountpoint}; then
|
||||
${pkgs.util-linux}/bin/mount -o X-mount.mkdir ${lib.escapeShellArg target.mountpoint}
|
||||
fi
|
||||
''}
|
||||
${lib.optionalString (target.postMountHook != null) target.postMountHook}
|
||||
''
|
||||
) cfg.targets)
|
||||
++ lib.mapAttrsToList (
|
||||
name: target:
|
||||
pkgs.writeShellScriptBin ("localbackup-unmount-" + name) ''
|
||||
set -efu -o pipefail
|
||||
${lib.optionalString (target.preUnmountHook != null) target.preUnmountHook}
|
||||
${lib.optionalString (
|
||||
target.mountpoint != null
|
||||
) "${pkgs.util-linux}/bin/umount ${lib.escapeShellArg target.mountpoint}"}
|
||||
${lib.optionalString (target.postUnmountHook != null) target.postUnmountHook}
|
||||
''
|
||||
) cfg.targets;
|
||||
|
||||
clan.core.backups.providers.localbackup = {
|
||||
# TODO list needs to run locally or on the remote machine
|
||||
|
||||
@@ -116,47 +116,45 @@ in
|
||||
};
|
||||
clan.core.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
|
||||
|
||||
clan.core.vars.generators =
|
||||
{
|
||||
"matrix-synapse" = {
|
||||
files."synapse-registration_shared_secret" = { };
|
||||
runtimeInputs = with pkgs; [
|
||||
coreutils
|
||||
pwgen
|
||||
];
|
||||
migrateFact = "matrix-synapse";
|
||||
script = ''
|
||||
echo -n "$(pwgen -s 32 1)" > "$out"/synapse-registration_shared_secret
|
||||
'';
|
||||
};
|
||||
clan.core.vars.generators = {
|
||||
"matrix-synapse" = {
|
||||
files."synapse-registration_shared_secret" = { };
|
||||
runtimeInputs = with pkgs; [
|
||||
coreutils
|
||||
pwgen
|
||||
];
|
||||
migrateFact = "matrix-synapse";
|
||||
script = ''
|
||||
echo -n "$(pwgen -s 32 1)" > "$out"/synapse-registration_shared_secret
|
||||
'';
|
||||
};
|
||||
}
|
||||
// lib.mapAttrs' (
|
||||
name: user:
|
||||
lib.nameValuePair "matrix-password-${user.name}" {
|
||||
files."matrix-password-${user.name}" = { };
|
||||
migrateFact = "matrix-password-${user.name}";
|
||||
runtimeInputs = with pkgs; [ xkcdpass ];
|
||||
script = ''
|
||||
xkcdpass -n 4 -d - > "$out"/${lib.escapeShellArg "matrix-password-${user.name}"}
|
||||
'';
|
||||
}
|
||||
// lib.mapAttrs' (
|
||||
name: user:
|
||||
lib.nameValuePair "matrix-password-${user.name}" {
|
||||
files."matrix-password-${user.name}" = { };
|
||||
migrateFact = "matrix-password-${user.name}";
|
||||
runtimeInputs = with pkgs; [ xkcdpass ];
|
||||
script = ''
|
||||
xkcdpass -n 4 -d - > "$out"/${lib.escapeShellArg "matrix-password-${user.name}"}
|
||||
'';
|
||||
}
|
||||
) cfg.users;
|
||||
) cfg.users;
|
||||
|
||||
systemd.services.matrix-synapse =
|
||||
let
|
||||
usersScript =
|
||||
''
|
||||
while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do
|
||||
if ! kill -0 "$MAINPID"; then exit 1; fi
|
||||
sleep 1;
|
||||
done
|
||||
''
|
||||
+ lib.concatMapStringsSep "\n" (user: ''
|
||||
# only create user if it doesn't exist
|
||||
/run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${
|
||||
config.clan.core.vars.generators."matrix-password-${user.name}".files."matrix-password-${user.name}".path
|
||||
} --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"}
|
||||
'') (lib.attrValues cfg.users);
|
||||
usersScript = ''
|
||||
while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do
|
||||
if ! kill -0 "$MAINPID"; then exit 1; fi
|
||||
sleep 1;
|
||||
done
|
||||
''
|
||||
+ lib.concatMapStringsSep "\n" (user: ''
|
||||
# only create user if it doesn't exist
|
||||
/run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${
|
||||
config.clan.core.vars.generators."matrix-password-${user.name}".files."matrix-password-${user.name}".path
|
||||
} --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"}
|
||||
'') (lib.attrValues cfg.users);
|
||||
in
|
||||
{
|
||||
path = [ pkgs.curl ];
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
config.clan.core.vars.generators.root-password.files.password-hash.path;
|
||||
|
||||
clan.core.vars.generators.root-password = {
|
||||
files.password-hash =
|
||||
{
|
||||
neededFor = "users";
|
||||
}
|
||||
// (lib.optionalAttrs (_class == "nixos") {
|
||||
restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
|
||||
});
|
||||
files.password-hash = {
|
||||
neededFor = "users";
|
||||
}
|
||||
// (lib.optionalAttrs (_class == "nixos") {
|
||||
restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
|
||||
});
|
||||
files.password = {
|
||||
deploy = false;
|
||||
};
|
||||
|
||||
@@ -32,17 +32,16 @@ in
|
||||
cfg.certificate.searchDomains != [ ]
|
||||
) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
||||
|
||||
hostKeys =
|
||||
[
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional cfg.hostKeys.rsa.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
hostKeys = [
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional cfg.hostKeys.rsa.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
};
|
||||
|
||||
clan.core.vars.generators.openssh = {
|
||||
@@ -62,7 +61,8 @@ in
|
||||
hostNames = [
|
||||
"localhost"
|
||||
config.networking.hostName
|
||||
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
]
|
||||
++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
---
|
||||
description = "Statically configure syncthing peers through clan"
|
||||
description = "DEPRECATED: Statically configure syncthing peers through clan"
|
||||
---
|
||||
|
||||
# ⚠️ DEPRECATED
|
||||
|
||||
This module has been migrated to the new clanServices system.
|
||||
|
||||
Please use the new syncthing service instead:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.syncthing = {
|
||||
instances.default = {
|
||||
roles.peer.machines = {
|
||||
machine1 = { };
|
||||
machine2 = { };
|
||||
machine3 = {
|
||||
excludeMachines = [ "machine4" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The new service provides the same functionality with better integration into clan's inventory system.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# DEPRECATED: This module has been migrated to clanServices/syncthing
|
||||
# Please use the syncthing service instead: services.syncthing.instances.default.roles.peer.machines = { ... };
|
||||
let
|
||||
dir = config.clan.core.settings.directory;
|
||||
machineVarDir = "${dir}/vars/per-machine/";
|
||||
@@ -32,14 +34,15 @@ let
|
||||
value = {
|
||||
name = machine;
|
||||
id = (lib.removeSuffix "\n" (builtins.readFile (syncthingPublicKeyPath machine)));
|
||||
addresses =
|
||||
[ "dynamic" ]
|
||||
++ (
|
||||
if (lib.elem machine networkIpMachines) then
|
||||
[ "tcp://[${(lib.removeSuffix "\n" (builtins.readFile (zerotierIpMachinePath machine)))}]:22000" ]
|
||||
else
|
||||
[ ]
|
||||
);
|
||||
addresses = [
|
||||
"dynamic"
|
||||
]
|
||||
++ (
|
||||
if (lib.elem machine networkIpMachines) then
|
||||
[ "tcp://[${(lib.removeSuffix "\n" (builtins.readFile (zerotierIpMachinePath machine)))}]:22000" ]
|
||||
else
|
||||
[ ]
|
||||
);
|
||||
};
|
||||
}) syncthingPublicKeyMachines;
|
||||
in
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
prompts.password.type = "hidden";
|
||||
prompts.password.persist = true;
|
||||
prompts.password.description = "You can autogenerate a password, if you leave this prompt blank.";
|
||||
prompts.password.description = "Leave empty to generate automatically";
|
||||
|
||||
script = ''
|
||||
prompt_value="$(cat "$prompts"/password)"
|
||||
|
||||
@@ -21,17 +21,16 @@ in
|
||||
settings.certificateSearchDomains != [ ]
|
||||
) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
||||
|
||||
hostKeys =
|
||||
[
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional settings.rsaHostKey.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
hostKeys = [
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional settings.rsaHostKey.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
};
|
||||
|
||||
clan.core.vars.generators.openssh = {
|
||||
@@ -51,7 +50,8 @@ in
|
||||
hostNames = [
|
||||
"localhost"
|
||||
config.networking.hostName
|
||||
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
]
|
||||
++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
settings,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
services.data-mesher.initNetwork =
|
||||
let
|
||||
# for a given machine, read it's public key and remove any new lines
|
||||
readHostKey =
|
||||
machine:
|
||||
let
|
||||
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
||||
in
|
||||
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
||||
in
|
||||
{
|
||||
enable = true;
|
||||
keyPath = config.clan.core.vars.generators.data-mesher-network-key.files.private_key.path;
|
||||
|
||||
tld = settings.network.tld;
|
||||
hostTTL = settings.network.hostTTL;
|
||||
|
||||
# admin and signer host public keys
|
||||
signingKeys = builtins.map readHostKey (builtins.attrNames settings.bootstrapNodes);
|
||||
};
|
||||
}
|
||||
@@ -5,31 +5,15 @@ let
|
||||
{
|
||||
options = {
|
||||
bootstrapNodes = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
|
||||
# the default bootstrap nodes are any machines with the admin or signers role
|
||||
# we iterate through those machines, determining an IP address for them based on their VPN
|
||||
# currently only supports zerotier
|
||||
# default = builtins.foldl' (
|
||||
# urls: name:
|
||||
# let
|
||||
# ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
|
||||
# in
|
||||
# if builtins.pathExists ipPath then
|
||||
# let
|
||||
# ip = builtins.readFile ipPath;
|
||||
# in
|
||||
# urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ]
|
||||
# else
|
||||
# urls
|
||||
# ) [ ] (dmLib.machines config).bootstrap;
|
||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||
description = ''
|
||||
A list of bootstrap nodes that act as an initial gateway when joining
|
||||
the cluster.
|
||||
'';
|
||||
example = {
|
||||
"node1" = "192.168.1.1:7946";
|
||||
"node2" = "192.168.1.2:7946";
|
||||
};
|
||||
example = [
|
||||
"192.168.1.1:7946"
|
||||
"192.168.1.2:7946"
|
||||
];
|
||||
};
|
||||
|
||||
network = {
|
||||
@@ -55,6 +39,59 @@ let
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mkBootstrapNodes =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
roles,
|
||||
settings,
|
||||
}:
|
||||
lib.mkDefault (
|
||||
builtins.foldl' (
|
||||
urls: name:
|
||||
let
|
||||
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
|
||||
in
|
||||
if builtins.pathExists ipPath then
|
||||
let
|
||||
ip = builtins.readFile ipPath;
|
||||
in
|
||||
urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ]
|
||||
else
|
||||
urls
|
||||
) [ ] (builtins.attrNames ((roles.admin.machines or { }) // (roles.signer.machines or { })))
|
||||
);
|
||||
|
||||
mkDmService = dmSettings: config: {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
|
||||
settings = {
|
||||
log_level = "warn";
|
||||
state_dir = "/var/lib/data-mesher";
|
||||
|
||||
# read network id from vars
|
||||
network.id = config.clan.core.vars.generators.data-mesher-network-key.files.public_key.value;
|
||||
|
||||
host = {
|
||||
names = [ config.networking.hostName ];
|
||||
key_path = config.clan.core.vars.generators.data-mesher-host-key.files.private_key.path;
|
||||
};
|
||||
|
||||
cluster = {
|
||||
port = dmSettings.network.port;
|
||||
join_interval = "30s";
|
||||
push_pull_interval = "30s";
|
||||
interface = dmSettings.network.interface;
|
||||
bootstrap_nodes = dmSettings.bootstrapNodes;
|
||||
};
|
||||
|
||||
http.port = 7331;
|
||||
http.interface = "lo";
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
_class = "clan.service";
|
||||
@@ -67,11 +104,9 @@ in
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
|
||||
imports = [ sharedInterface ];
|
||||
|
||||
options = {
|
||||
|
||||
network = {
|
||||
tld = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
@@ -89,54 +124,117 @@ in
|
||||
};
|
||||
};
|
||||
perInstance =
|
||||
{ settings, roles, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
imports = [
|
||||
./admin.nix
|
||||
./shared.nix
|
||||
];
|
||||
_module.args = { inherit settings roles; };
|
||||
};
|
||||
extendSettings,
|
||||
roles,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
nixosModule =
|
||||
{ config, ... }:
|
||||
let
|
||||
settings = extendSettings {
|
||||
bootstrapNodes = mkBootstrapNodes {
|
||||
inherit
|
||||
config
|
||||
lib
|
||||
roles
|
||||
settings
|
||||
;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./shared.nix ];
|
||||
|
||||
services.data-mesher = (mkDmService settings config) // {
|
||||
initNetwork =
|
||||
let
|
||||
# for a given machine, read it's public key and remove any new lines
|
||||
readHostKey =
|
||||
machine:
|
||||
let
|
||||
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
||||
in
|
||||
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
||||
in
|
||||
{
|
||||
enable = true;
|
||||
keyPath = config.clan.core.vars.generators.data-mesher-network-key.files.private_key.path;
|
||||
|
||||
tld = settings.network.tld;
|
||||
hostTTL = settings.network.hostTTL;
|
||||
|
||||
# admin and signer host public keys
|
||||
signingKeys = builtins.map readHostKey (
|
||||
builtins.attrNames ((roles.admin.machines or { }) // (roles.signer.machines or { }))
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
roles.signer = {
|
||||
interface =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ sharedInterface ];
|
||||
};
|
||||
interface = sharedInterface;
|
||||
perInstance =
|
||||
{ settings, roles, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
imports = [
|
||||
./signer.nix
|
||||
./shared.nix
|
||||
];
|
||||
_module.args = { inherit settings roles; };
|
||||
};
|
||||
extendSettings,
|
||||
lib,
|
||||
roles,
|
||||
...
|
||||
}:
|
||||
{
|
||||
nixosModule =
|
||||
{ config, ... }:
|
||||
let
|
||||
settings = extendSettings {
|
||||
bootstrapNodes = mkBootstrapNodes {
|
||||
inherit
|
||||
config
|
||||
lib
|
||||
roles
|
||||
settings
|
||||
;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./shared.nix ];
|
||||
services.data-mesher = (mkDmService settings config);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
roles.peer = {
|
||||
interface =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ sharedInterface ];
|
||||
};
|
||||
interface = sharedInterface;
|
||||
perInstance =
|
||||
{ settings, roles, ... }:
|
||||
{
|
||||
nixosModule = {
|
||||
imports = [
|
||||
./peer.nix
|
||||
./shared.nix
|
||||
];
|
||||
_module.args = { inherit settings roles; };
|
||||
};
|
||||
extendSettings,
|
||||
lib,
|
||||
roles,
|
||||
...
|
||||
}:
|
||||
{
|
||||
nixosModule =
|
||||
{ config, ... }:
|
||||
let
|
||||
settings = extendSettings {
|
||||
bootstrapNodes = mkBootstrapNodes {
|
||||
inherit
|
||||
config
|
||||
lib
|
||||
roles
|
||||
settings
|
||||
;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./shared.nix ];
|
||||
services.data-mesher = (mkDmService settings config);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
{
|
||||
config,
|
||||
settings,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
services.data-mesher = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
|
||||
settings = {
|
||||
log_level = "warn";
|
||||
state_dir = "/var/lib/data-mesher";
|
||||
|
||||
# read network id from vars
|
||||
network.id = config.clan.core.vars.generators.data-mesher-network-key.files.public_key.value;
|
||||
|
||||
host = {
|
||||
names = [ config.networking.hostName ];
|
||||
key_path = config.clan.core.vars.generators.data-mesher-host-key.files.private_key.path;
|
||||
};
|
||||
|
||||
cluster = {
|
||||
port = settings.network.port;
|
||||
join_interval = "30s";
|
||||
push_pull_interval = "30s";
|
||||
interface = settings.network.interface;
|
||||
bootstrap_nodes = (builtins.attrValues settings.bootstrapNodes);
|
||||
};
|
||||
|
||||
http.port = 7331;
|
||||
http.interface = "lo";
|
||||
};
|
||||
};
|
||||
|
||||
# Generate host key.
|
||||
clan.core.vars.generators.data-mesher-host-key = {
|
||||
files =
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
instances = {
|
||||
data-mesher =
|
||||
let
|
||||
bootstrapNodes = {
|
||||
admin = "[2001:db8:1::1]:7946";
|
||||
peer = "[2001:db8:1::2]:7946";
|
||||
# signer = "2001:db8:1::3:7946";
|
||||
};
|
||||
bootstrapNodes = [
|
||||
"[2001:db8:1::1]:7946" # admin
|
||||
"[2001:db8:1::2]:7946" # peer
|
||||
# "2001:db8:1::3:7946" #signer
|
||||
];
|
||||
in
|
||||
{
|
||||
roles.peer.machines.peer.settings = {
|
||||
|
||||
@@ -184,24 +184,24 @@
|
||||
settings.certificate.searchDomains != [ ]
|
||||
) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
||||
|
||||
hostKeys =
|
||||
[
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional settings.hostKeys.rsa.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
hostKeys = [
|
||||
{
|
||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional settings.hostKeys.rsa.enable {
|
||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
};
|
||||
|
||||
programs.ssh.knownHosts.clan-sshd-self-ed25519 = {
|
||||
hostNames = [
|
||||
"localhost"
|
||||
config.networking.hostName
|
||||
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
]
|
||||
++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
||||
};
|
||||
};
|
||||
|
||||
20
clanServices/syncthing/README.md
Normal file
20
clanServices/syncthing/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Usage
|
||||
|
||||
```nix
|
||||
{
|
||||
instances.syncthing = {
|
||||
roles.peer.tags.all = { };
|
||||
roles.peer.settings.folders = {
|
||||
documents = {
|
||||
path = "~/syncthing/documents";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now the folder `~/syncthing/documents` will be shared with all your machines.
|
||||
|
||||
|
||||
## Documentation
|
||||
Extensive documentation is available on the [Syncthing](https://docs.syncthing.net/) website.
|
||||
243
clanServices/syncthing/default.nix
Normal file
243
clanServices/syncthing/default.nix
Normal file
@@ -0,0 +1,243 @@
|
||||
{ ... }:
|
||||
{
|
||||
_class = "clan.service";
|
||||
manifest.name = "clan-core/syncthing";
|
||||
manifest.description = "Syncthing is a continuous file synchronization program with automatic peer discovery";
|
||||
manifest.categories = [
|
||||
"Utility"
|
||||
"System"
|
||||
"Network"
|
||||
];
|
||||
manifest.readme = builtins.readFile ./README.md;
|
||||
|
||||
roles.peer = {
|
||||
interface =
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.openDefaultPorts = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to open the default syncthing ports in the firewall.
|
||||
'';
|
||||
};
|
||||
|
||||
options.folders = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Path to the folder to sync";
|
||||
};
|
||||
devices = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "List of device names to share this folder with. Empty list means all peers and extraDevices.";
|
||||
};
|
||||
ignorePerms = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Ignore permission changes";
|
||||
};
|
||||
rescanIntervalS = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 3600;
|
||||
description = "Rescan interval in seconds";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"sendreceive"
|
||||
"sendonly"
|
||||
"receiveonly"
|
||||
];
|
||||
default = "sendreceive";
|
||||
description = "Folder type";
|
||||
};
|
||||
versioning = lib.mkOption {
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"external"
|
||||
"simple"
|
||||
"staggered"
|
||||
"trashcan"
|
||||
];
|
||||
description = "Versioning type";
|
||||
};
|
||||
params = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Versioning parameters";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
description = "Versioning configuration";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = "Folders to synchronize between all peers";
|
||||
};
|
||||
|
||||
options.extraDevices = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
id = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Device ID of the external syncthing device";
|
||||
example = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2";
|
||||
};
|
||||
addresses = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "dynamic" ];
|
||||
description = "List of addresses for the device";
|
||||
example = [
|
||||
"dynamic"
|
||||
"tcp://192.168.1.100:22000"
|
||||
];
|
||||
};
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Human readable name for the device";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
description = "External syncthing devices not managed by clan (e.g., mobile phones)";
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
phone = {
|
||||
id = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2";
|
||||
name = "My Phone";
|
||||
addresses = [ "dynamic" ];
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
perInstance =
|
||||
{
|
||||
settings,
|
||||
roles,
|
||||
...
|
||||
}:
|
||||
{
|
||||
nixosModule =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
allPeerMachines = lib.attrNames roles.peer.machines;
|
||||
|
||||
readMachineVar =
|
||||
machine: varPath: default:
|
||||
let
|
||||
fullPath = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/${varPath}";
|
||||
in
|
||||
if builtins.pathExists fullPath then
|
||||
lib.removeSuffix "\n" (builtins.readFile fullPath)
|
||||
else
|
||||
default;
|
||||
|
||||
peerDevices = lib.listToAttrs (
|
||||
lib.forEach allPeerMachines (machine: {
|
||||
name = machine;
|
||||
value = {
|
||||
name = machine;
|
||||
id = readMachineVar machine "syncthing/id/value" "";
|
||||
addresses = [
|
||||
"dynamic"
|
||||
]
|
||||
++
|
||||
lib.optional (readMachineVar machine "zerotier/zerotier-ip/value" null != null)
|
||||
"tcp://[${readMachineVar machine "zerotier/zerotier-ip/value" ""}]:22000";
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
extraDevicesConfig = lib.mapAttrs (deviceName: deviceConfig: {
|
||||
inherit (deviceConfig) id addresses;
|
||||
name = if deviceConfig.name != "" then deviceConfig.name else deviceName;
|
||||
}) settings.extraDevices;
|
||||
|
||||
allDevices = peerDevices // extraDevicesConfig;
|
||||
|
||||
validDevices = lib.filterAttrs (_: device: device.id != "") allDevices;
|
||||
|
||||
syncthingFolders = lib.mapAttrs (
|
||||
_folderName: folderConfig:
|
||||
let
|
||||
targetDevices =
|
||||
if folderConfig.devices == [ ] then lib.attrNames validDevices else folderConfig.devices;
|
||||
in
|
||||
folderConfig
|
||||
// {
|
||||
devices = targetDevices;
|
||||
}
|
||||
) settings.folders;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./vars.nix
|
||||
];
|
||||
|
||||
config = lib.mkMerge [
|
||||
{
|
||||
services.syncthing = {
|
||||
enable = true;
|
||||
configDir = "/var/lib/syncthing";
|
||||
group = "syncthing";
|
||||
|
||||
key = lib.mkDefault config.clan.core.vars.generators.syncthing.files.key.path or null;
|
||||
cert = lib.mkDefault config.clan.core.vars.generators.syncthing.files.cert.path or null;
|
||||
|
||||
settings = {
|
||||
devices = validDevices;
|
||||
folders = syncthingFolders;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# Conditionally open firewall ports
|
||||
(lib.mkIf settings.openDefaultPorts {
|
||||
services.syncthing.openDefaultPorts = true;
|
||||
# Syncthing ports: 8384 for remote access to GUI
|
||||
# 22000 TCP and/or UDP for sync traffic
|
||||
# 21027/UDP for discovery
|
||||
# source: https://docs.syncthing.net/users/firewall.html
|
||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [
|
||||
8384
|
||||
22000
|
||||
];
|
||||
networking.firewall.interfaces."zt+".allowedUDPPorts = [
|
||||
22000
|
||||
21027
|
||||
];
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
perMachine = _: {
|
||||
nixosModule =
|
||||
{ lib, ... }:
|
||||
{
|
||||
# Activates inotify compatibility on syncthing
|
||||
# use mkOverride 900 here as it otherwise would collide with the default of the
|
||||
# upstream nixos xserver.nix
|
||||
boot.kernel.sysctl."fs.inotify.max_user_watches" = lib.mkOverride 900 524288;
|
||||
};
|
||||
};
|
||||
}
|
||||
54
clanServices/syncthing/flake-module.nix
Normal file
54
clanServices/syncthing/flake-module.nix
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
self,
|
||||
lib,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
module = lib.modules.importApply ./default.nix {
|
||||
inherit (self) packages;
|
||||
};
|
||||
in
|
||||
{
|
||||
clan.modules = {
|
||||
syncthing = module;
|
||||
};
|
||||
perSystem =
|
||||
let
|
||||
unit-test-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
inherit module;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# The zerotier service being tested
|
||||
../../clanServices/syncthing
|
||||
# Required modules
|
||||
../../nixosModules/clanCore
|
||||
# Dependencies like clan-cli
|
||||
../../pkgs/clan-cli
|
||||
];
|
||||
testName = "syncthing";
|
||||
tests = ./tests/eval-tests.nix;
|
||||
testArgs = { };
|
||||
}
|
||||
);
|
||||
in
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
unit-test-module
|
||||
];
|
||||
/**
|
||||
1. Prepare the test vars
|
||||
nix run .#generate-test-vars -- clanServices/syncthing/tests/vm syncthing-service
|
||||
|
||||
2. To run the test
|
||||
nix build .#checks.x86_64-linux.syncthing-service
|
||||
*/
|
||||
clan.nixosTests.syncthing-service = {
|
||||
imports = [ ./tests/vm/default.nix ];
|
||||
|
||||
clan.modules.syncthing-service = module;
|
||||
};
|
||||
};
|
||||
}
|
||||
62
clanServices/syncthing/tests/eval-tests.nix
Normal file
62
clanServices/syncthing/tests/eval-tests.nix
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
module,
|
||||
clanLib,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
testFlake =
|
||||
(clanLib.clan {
|
||||
self = { };
|
||||
directory = ./vm;
|
||||
|
||||
machines.machine1 = {
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
};
|
||||
machines.machine2 = {
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
};
|
||||
machines.machine3 = {
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
};
|
||||
machines.machine4 = {
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
};
|
||||
|
||||
modules.syncthing = module;
|
||||
|
||||
inventory.instances = {
|
||||
default = {
|
||||
module.name = "syncthing";
|
||||
module.input = "self";
|
||||
|
||||
roles.peer.tags.all = { };
|
||||
roles.peer.settings.extraDevices.phone = {
|
||||
id = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2";
|
||||
};
|
||||
};
|
||||
};
|
||||
}).config;
|
||||
in
|
||||
{
|
||||
test_machine1_peers = {
|
||||
expr = {
|
||||
devices = lib.attrNames testFlake.nixosConfigurations.machine1.config.services.syncthing.settings.devices;
|
||||
machine4_ID =
|
||||
testFlake.nixosConfigurations.machine1.config.services.syncthing.settings.devices.machine1.id;
|
||||
externalPhoneId =
|
||||
testFlake.nixosConfigurations.machine1.config.services.syncthing.settings.devices.phone.id;
|
||||
};
|
||||
expected = {
|
||||
devices = [
|
||||
"machine1"
|
||||
"machine2"
|
||||
"machine3"
|
||||
"machine4"
|
||||
"phone"
|
||||
];
|
||||
machine4_ID = "LJOGYGS-RQPWIHV-HD4B3GK-JZPVPK6-VI3IAY5-CWQWIXK-NJSQMFH-KXHOHA4";
|
||||
externalPhoneId = "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2";
|
||||
};
|
||||
};
|
||||
}
|
||||
95
clanServices/syncthing/tests/vm/default.nix
Normal file
95
clanServices/syncthing/tests/vm/default.nix
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
name = "service-syncthing-service";
|
||||
|
||||
clan = {
|
||||
directory = ./.;
|
||||
test.useContainers = true;
|
||||
inventory = {
|
||||
machines.machine1 = { };
|
||||
machines.machine2 = { };
|
||||
machines.machine3 = { };
|
||||
machines.machine4 = { };
|
||||
|
||||
instances.default = {
|
||||
module.name = "syncthing-service";
|
||||
module.input = "self";
|
||||
roles.peer.tags.all = { };
|
||||
roles.peer.settings.folders = {
|
||||
documents = {
|
||||
path = "/var/lib/syncthing/documents";
|
||||
type = "sendreceive";
|
||||
};
|
||||
partly_shared = {
|
||||
devices = [
|
||||
"machine1"
|
||||
"machine4"
|
||||
];
|
||||
path = "~/music";
|
||||
type = "sendreceive";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
instances.small = {
|
||||
module.name = "syncthing-service";
|
||||
module.input = "self";
|
||||
roles.peer.machines = {
|
||||
machine3 = { };
|
||||
machine4 = { };
|
||||
};
|
||||
roles.peer.settings.folders = {
|
||||
pictures = {
|
||||
path = "~/pictures";
|
||||
type = "sendreceive";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ ... }:
|
||||
''
|
||||
start_all()
|
||||
|
||||
machine1.wait_for_unit("syncthing.service")
|
||||
machine2.wait_for_unit("syncthing.service")
|
||||
machine3.wait_for_unit("syncthing.service")
|
||||
machine4.wait_for_unit("syncthing.service")
|
||||
|
||||
machine1.wait_for_open_port(8384)
|
||||
machine2.wait_for_open_port(8384)
|
||||
machine3.wait_for_open_port(8384)
|
||||
machine4.wait_for_open_port(8384)
|
||||
|
||||
machine1.wait_for_open_port(22000)
|
||||
machine2.wait_for_open_port(22000)
|
||||
machine3.wait_for_open_port(22000)
|
||||
machine4.wait_for_open_port(22000)
|
||||
|
||||
# Check that the correct folders are synchronized
|
||||
# documents - all
|
||||
machine1.wait_for_file("/var/lib/syncthing/documents")
|
||||
machine2.wait_for_file("/var/lib/syncthing/documents")
|
||||
machine3.wait_for_file("/var/lib/syncthing/documents")
|
||||
machine4.wait_for_file("/var/lib/syncthing/documents")
|
||||
# music - machine 1 & 4
|
||||
machine1.wait_for_file("/var/lib/syncthing/music")
|
||||
machine4.wait_for_file("/var/lib/syncthing/music")
|
||||
# pictures - machine 3 & 4
|
||||
machine3.wait_for_file("/var/lib/syncthing/pictures")
|
||||
machine4.wait_for_file("/var/lib/syncthing/pictures")
|
||||
|
||||
machine1.succeed("echo document > /var/lib/syncthing/documents/document")
|
||||
machine1.succeed("echo music > /var/lib/syncthing/music/music")
|
||||
machine3.succeed("echo picture > /var/lib/syncthing/pictures/picture")
|
||||
|
||||
machine2.wait_for_file("/var/lib/syncthing/documents/document", 20)
|
||||
machine3.wait_for_file("/var/lib/syncthing/documents/document", 20)
|
||||
machine4.wait_for_file("/var/lib/syncthing/documents/document", 20)
|
||||
|
||||
machine4.wait_for_file("/var/lib/syncthing/music/music", 20)
|
||||
|
||||
machine4.wait_for_file("/var/lib/syncthing/pictures/picture", 20)
|
||||
'';
|
||||
}
|
||||
6
clanServices/syncthing/tests/vm/sops/machines/machine1/key.json
Executable file
6
clanServices/syncthing/tests/vm/sops/machines/machine1/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1numxr6m52fxrm9a7sdw4vdpkp463mm8qtuf5d0p0jde04wydfgtscwdx78",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
6
clanServices/syncthing/tests/vm/sops/machines/machine2/key.json
Executable file
6
clanServices/syncthing/tests/vm/sops/machines/machine2/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1aqng9vmlgth5aucu5ty2wa0kk9tvk7erj4s07hq03s6emu72fgxsqkrqql",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
6
clanServices/syncthing/tests/vm/sops/machines/machine3/key.json
Executable file
6
clanServices/syncthing/tests/vm/sops/machines/machine3/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1kul02mg50nxccsl38nvma0enrgx454wq0qdefllj4l0adqkllvls5wuhfr",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
6
clanServices/syncthing/tests/vm/sops/machines/machine4/key.json
Executable file
6
clanServices/syncthing/tests/vm/sops/machines/machine4/key.json
Executable file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1kqgx4elusxx4u8409gml5z6tvrsayqsphewsl93mtqn7pl2p5dwq9lujpj",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:XqOUvbQOkOUrxQAMrUSRGfOl2eYjGZGI5st1iZl9Re8ymQa0LtokGzknfSiSucwfP50YTo7NoQsNCRsq/nS0RWbby0TQceqSVSw=,iv:GT3UrKdanFy4aMRPTwuOIyvP7pT6MXGRIstgKZmvG/4=,tag:Bg3ZzCPpw3KHXwy2Hazqrg==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmWWJuTjBCaUhUeXhSRmZS\nVW9uQ09yOXVJVFFybzJsdmVmaEpOZVdEVVJNCmxxcUU3eG44cTlrY1lnVUFjdlZK\nOXNvR2pDMVJtZXFCbjJlTW1xVGoyTlUKLS0tIDlkMS9FNVJuZGZyODJzZzVRNzJQ\nNWpuaUxpMnRaUWt5bmw1TjBJV0dUOHcKmuHPHwzTUtkl7eZjZ4422C8cxGUnnzg1\nBPwAewDXEgq+clTXpU3UWjSvUNpqIkh9GDRE+qYfYWa7m36TrzACig==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:20:56Z",
|
||||
"mac": "ENC[AES256_GCM,data:INCJWY2hsAwF049zFpYUTccJvczfgrPQwu+YUt8s/vfdrAk+tmLqCVFh9idG97l/OIEVPLv1gFE3SlcC+kKCNQV4SAyZA62CTeeNdgSSqDDipjrFn4fr1L8ZenCn56/giW0GIB2bBCa5WUYaHtGDoG8f6HSqNIhjnY9/qmDLUWQ=,iv:Utda22592sXOEEKFRSgfP+yLgi6FQGeEFiT61ZiKcws=,tag:UWyEnSNt0ztxTh0FGRzTtA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:bDwReDwUe9t0lXtTRU5r6WY3OvV1qOvkkByG2PbjXtArF4w6y3X/i6SYCnoys3wMvAY32+uH13ETXDS7LR/6W4/+pCAGAb3ATqQ=,iv:84dC7UP6QkHVkLwSvmb/NjXCA9OJ+of49UfitU0Cuoo=,tag:oO15dzpH7BKjawPGI1+F0Q==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBVXpNTkhTU3A1OU5HZm9p\nb2t5YTIxZWU4Z3hZR0ljTUNRT2hvQXEwNjJRCjh4SXY1NWhPVFFTMWswaWo2cFJB\ndUx4V1RiRFQ1UkNQU24zaGlSV3ZYeE0KLS0tIGJ5ak9RS2JnNk0vU3BaMEZLTFI0\nZit5RDlZbWE1QzY0RHQzcU5pZy84TUkK3/HHFBGfA9EpU+WDrlM9w5rbmgOb7ZAi\nVQiO08PVGTOvsEDed5A2oIXImRopcuBATzKoi4DNXpYlpSI/scYuAA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:08Z",
|
||||
"mac": "ENC[AES256_GCM,data:qdNz02bNRHaWhTLxHqgvMRokpXZHvijyoP2c+KXMQxWP/7884SWQ4xoPSiUeLOPatLXpjXWQ/OBiDqQAzLkbC0/ujJH+BJQtV75xl6ytOjLbYw+hwatAR9C5GxiuFgKjLSsdaqzRlXJsULbf56edXeT+U/u5G8542WjF4AUat00=,iv:FYQRJQaRR0aPFyHyl0QCVPpjTXePXiZ2LjccPaybWh4=,tag:W/xkIgTUoHiTX9azluinAQ==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:2B9BeSPThkGX4sBAtm9c5Uc8vz5q+hSs8y0ibQ520s2EOYNWEEwP0e+9CBUmU1n0TKR4FP3HMHA0N7LKHBEXEb513I5lNHWgREc=,iv:qkmur96UjZ1BoWgvvihWk/7VRDg4snMeDA0s1MWdLb4=,tag:3djouRryDjcOFPv9Z8nLvg==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxd2wxQWQwSHl5R0ZlRkxX\nWXBuck04TStoTHRYMDJaQkNRcDQvZFV6U0U0ClROOVpiVXhCVTdzeXpVQlRTYWRP\naWE2R0VtaGwvUk9UcGRWbEZtQlh5ckkKLS0tIDNjdm40UCthbDBwSE9ZYXBwVC9h\nRENrMFV2a21QRjBBYWJ4bFZ3K2NxWDAKr9hgDOuG4lR6dChQCvw/VOQdRNF0Mj1k\nzQRfQaC8nfZGGOJtU09zv5+ZPYJdheRuz91M18qY5uA5qrch5Yrvww==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:21Z",
|
||||
"mac": "ENC[AES256_GCM,data:F6Ombz312QLl0BAkDbhN37KQ8gxMoukRqz8XkqANcdm8HyqfWL/V5Hxdt5Ve3HW4KzLO1lxb9M0/rQbtCj1hKMSnfM4XjP1R4ClLkU0knxeuLwbOtc4nN1qkaIs5fPcR0G9+gv+FnhZqDzosrhogDSWAayqiArwRWscMY4l716w=,iv:KYlcVQQGcWPIsL81VmTRmEBmC6PPT71i3ywcEbSfc5k=,tag:ogDnTv1KtXVtBDvhkjV1pw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:chE6ezkSGLHwvfqrUPiTLEoB63NhzBJgHQnGLe/0mwuR9iST3SpthUlKv7K8cezOvq7VVqJUoOGQSbqtuA04NUvwShVCBbqPjeA=,iv:50IV6ggQVnQpUpU+jfBB2z5/BZYs3lktGMfsWsCtJUs=,tag:BjtV31lmGOT5FposJA91WA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZSVAzVXcrMWdUQmNCNHo4\neWw5NnJjYkdHbkE1dmJPcDlNb1dpT3kxbFN3Cm1HWVFxblgrNkpaZmtOVHBpdEVF\nWXRMamdSUGxkSk5ic3p3cEZrNG5QZEkKLS0tIDdYOGZRclREL0o1bWZmVTZoMUlu\nQzJPZFlUdFE4NkE0di94QVRzRXgwb1EKkqzL7yOdALLd8I2GnKPG3d61XTbNMs4+\n52M+o+wA8h+mnImbyVOtU9D6AkxvbKywZLmNRukkPqnkLqu5IXoSMQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:DI61cHhur+0q+gGtXHA6uFyZyKY/hCTvo2MLhhWPCSUWzYJ+j37fHfH1KUZcol+5prhnUIRrC4AoVt+0MeoXrNj+3SyptTVo1MgqNBayQkBMxKYp++SbqlXnlkLD+XOohpw6+f67rGNecjNc/OwCcfXu7PZmFhAFkwC39hUm7l0=,iv:lyinQFzoXo37Zs1QtbM1Jla+KRSMSUcph7bIR6/X1nw=,tag:nFfq7KtaTwZkQ9joCUOE2w==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../users/admin
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"type": "age"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine1
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:H+qlkDBtl4xYPz7X56gamUTQfuDE5+m/BkDPK9963A9F,iv:paPRFOnxW8aSt/NYg4afp+M+8svt0j4G6mZaJms/c7o=,tag:9uxvemHUr6Jm0MzvSf9cxQ==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1numxr6m52fxrm9a7sdw4vdpkp463mm8qtuf5d0p0jde04wydfgtscwdx78",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBM1ZUaEwwOGVFSVF0dlRX\nQzdMRjJFZU5kSTNDTzVrZ1JzNHdROXpGNlI0CkZXQ0JkalloT2xNNEh5ZG5NT2Vn\nVlFKZTV6aTYwNmF2bVpHM01YdUtLeGMKLS0tIDRCVDZrNk5UdkVOODdnZFptbDlZ\nVnNNMGc4U0FvOHlsU2dwTE03TUtyL1kKoikATM2w95ca39e7K9mOezI9z4hSgUAA\nUiq4GH9Sn4jtuew2FXK2ZqedzgSQuSOob8iaYBUMGdWL7bfn2gZsHQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNMUQxZmxrZDR0OU0vd2tl\ndjlCNEFSeTVFRFRxa01xNXhRTDdFRWJtQkhFCitPV2hDNG9uWXE0a0gyK3NuSnc3\nblBKVjNkMzkyWHVKMzBKV1NzdFVsbXMKLS0tIDdkMTVrSk9pcjhlWkVISWNoQVU4\nTm1sQVlDbUVObFIrRkNObG5nMUdWVE0KWwIpPYL8HwcbUDUS6TG4DyJgAz+xz9lA\nLLRYARWF0zNJlOTHAIEksT14YSU3dplMxunwas2mLBjfYH0DIg2Org==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:20:56Z",
|
||||
"mac": "ENC[AES256_GCM,data:vN/zqTJO+szaDV1aJc/ZYAbe0ISc7dJvQg8prjVkYIZA+aAh5Hk2Kec6iz+gH23CqapYcsEYJ/qd24eRrSsxPSJuvYWJAgyrPasNXNuGNrsVnkvTyClTUGiBbhoac6yHu66BHVpH15BJUgeSmO7iVD7HD+KPy0p66agJwWQEcn0=,iv:GLRGWh131n5otLJjBGElmMGxGOL1bljGZ8zzTE/fLn4=,tag:a65wuRUzsBtO2rO+hJN0RA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine1
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:F5FWqPeOBJvuwzuomHd4IkWgGeBBmYmgSVeH/TSJKKG4BHAwgam8dT1oTWiKEd/SfLj6LY33N+s4fKqe3bdCCa5/CPC3P7ZP1RW0/fyAdftlXFpbmXjsFf1rsTcTPMPBxOdwbu6+w6ji16sEkKXsN/GQsk6bhT7gxEgy4ZXY1kiiE08TtIG8p6aIcES/sTXhNqvZvWkJeESh1pkVhfP0OSWp0x05RPvxg2FoU1IMcqmySZwL/Ltqp4I3pKNHmLzuTFfFmK10fC9OptBbPmPtp75k0pJcQ0xbzJAyMM1riGzhOOMO1Qj/DXcSPDVugrMYDZRpEqNGg+LPBuCF1l2e7UitPLY4/1g669lCAPR3stiSW3S2NZIsDsW/ubN2qYeMBSBM8NWpQPgcsrKELCgUExD3MFMO9G0Rl3sU1BsYw+jf+kZNZWDL/w9W6vYt73q3Nmu2DfPpGzwKLrnbBpqjgMJKev/9JG/gMejbMe5AK1leyr5TDi97ku3zTvQfur4YMWABiBswmIbgbjBZAJOIKzy10AJTFQbqNoGGaOz22i2RBJLVHU7CKoKyJL99Iun0MjSlIIv5hTZMXDnHPZxvLq4w0htA5U2VJNKg04YKaPpc5ceDLVt9kgEF6OKG7/5nwDoQ/cgjrwbzl85sWpWQgXxwpSDPN8A0rkyZkgxNch/tUASgUoOExJRd6ht0iJiFM3tW25z8NU1ZWijs287hCkXNac63ZnXCa/2gB2zj+iHHLzgGvsxfhFiM2smjC0CSNbteTYr3sgZszrJSDdyI3Aerx92UwN336Iu0pXAvAGh8SBLBdz1CcmAlldK08Z8tFxplqOaqCQ8AtuklfQQeh8/Lx0VW09HIGkjQp88xotW4bZ803rPb7pqJNTzQmSF/4JWtIJXEG8eRbyHXpTM9p9RYvlY7/yK75EH/vwq6eAu6Cm2AwUxLjqMv9uoAad0gij66DflQ93D3hOfJaF+yNLa+HTce/p0Ymqeo/YM3/UUQ/ZHVbPonULXkylYs8JFFVkf3DBJTGYB85UCZ/ObNyXFtk/GuzQ==,iv:zV2Wm02uMdGmdKR0qiYnTdTlyRec85wXwUJOE3mAeWo=,tag:8fw+x1O1/53ff0qOuoph/A==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1numxr6m52fxrm9a7sdw4vdpkp463mm8qtuf5d0p0jde04wydfgtscwdx78",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvbTg5alA5SzJoUTZRSFM0\ndk1XaTlkOXo3NVNxYUJCc1FWWG16Q3IwMERnCkU1S1hlT3ZxcTRhaWs4c1BaVkNi\ndEJ0dTNTV1MxN0QrV2dJUzUvRTlsV3MKLS0tIExSb20zTDhuVExUMG9JZGd2bG00\nL0NTanIwZE92N1IxNWR5NW5PTktpK0EKsBMTH1ln0H/dK2AgUBWiGtdcY2ujvu1H\nTR+X3MZfFHQdOIvW4dkhUJt7BnZ5cEOCUizE5oI1+DifjDqk1dd+VQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzNHJsaElGRWc3ZTQrQlBs\nb0NLMmJUNzhyOHFielFZa1VnRDcwQ2JaREU0CnhrR3hGVXF3SnpRZWE5dlc5VFVV\nT1A4SGNoT2s0MU1jdHJ5U0ZvK3l0emcKLS0tIDZULzBXZ294NXBSRVJVRHlwUkpr\nWi90bUpNQlAremhpaWZkVXpRcE5yL1UKp5KUA5g/trDODHcXI7SWjbWR/ozXpxRp\ngHbONszf4y4hjkHOCVvtQ2NmR/cF64nhCswZYlfssUb4aJBVrMlVEw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:20:56Z",
|
||||
"mac": "ENC[AES256_GCM,data:3l51gIfAbCnYvWjp6meJGsWQJPI7x4yswFe3WPLGtDJMPrRdOWW5UtU2Qb/rcoKGoSee5OJblOGvkloWTp++HebS2TxKEQ26qU9ycOq27vFt6mfmu4IVcmTqoVM3BPRh9md590lqdLyIsy/BULFBhe0jGBUQbXfDkfewronDOdU=,iv:JkVkjT9G5Z2+u/ZYDaoM8sRk5cBVWYR8fm48Gwfbr/Y=,tag:L9xZSAP3zQZfy6malNIFDw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
LJOGYGS-RQPWIHV-HD4B3GK-JZPVPK6-VI3IAY5-CWQWIXK-NJSQMFH-KXHOHA4
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine1
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:oYhwmtmsMpBKoUnC2sze5M7Qx3y9+ukpt7apmicJJOaDNliI8+NrrYntl56X70U4i+DN3Iz7qwPGGBxjveV0z7kIUEvlDiu3GgQmC/34omktyogb8dH/POoO+Z/E1CxKJEIn+rEU6Oqhg4aPKt/JbveW5wlevlQsMwW1oAtS701JJeY6KU6AfDerU0cS4K5+xw+dFg28pIvIIe02uDzTnFEXZYzvPk7UxfOE0IFxs6/zzPFoBbeFo2WIZZBMQYzP4AQMEiFxxaK7qthBwGuCEc7yMpW1uwVe9CZBfkVkg+wIX4DSm+0j8TiyikobsBxkvPUgqYHNyQaINJp82aGPOzFLsJ6ixf4RqgOzPikzdsyU8phi7waiDPDLf2rhZmnR,iv:9zhjW5wBlNtCxV1kBqzUlZaVRjAbr9LpxypJaIk4RPw=,tag:u1qxkOdHIqkelp/bchb4bg==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1numxr6m52fxrm9a7sdw4vdpkp463mm8qtuf5d0p0jde04wydfgtscwdx78",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFZGxnQXhHUWhONitsWnRk\nbSsvSHRYbHJLc1BjOGgyb3Fyem5VeXk0MENRCkpkdGVmb01rK2FCV25Oc1JGY3U4\nK0xIb0UvTCsrV3lDRkIwZmhscDN4TUkKLS0tIG1GOTFPZTlvcXVOMElHYWN5ZFBU\nbjB4Y3FmY2ZMMmZ6cUFlZ0NUL2c3Z2MK+Fn2tf9eX6Iy6GMsa5//fzKDAUUCFa4x\neeCStPrxTbmKWb4T2NxXrZYK73kvOAmEQenUasddZ24/SDhnnrlXbw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0N0V1Q1l1NS9JK1R1RG8z\nVE9rRnVqQW9mRGNvWHViZWlKS21qSkF4RENFCjl0OU1rMUFHUHVDR3NjSXc1Tnpr\ndE1HUGxQYXMzWW11Q2tlNUdjdXpqdk0KLS0tIGtDZGJpQ3FrbnFZVDJuYjN6bFFN\nZGZqUFNKMmpFSUd5QXFtSFVSWFlpelUKFzm5m/+ReOVDHpvgqAKs5Vwq4XVCPH0K\nzmxtSIAwey9lUxnWNkuKOUG07o89ACsVe6pVPLLqHpGLotvne68GRw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:20:56Z",
|
||||
"mac": "ENC[AES256_GCM,data:k92YniWJ8U75G0YJ8yt/Xgi5NtyeoJrSNCCMd+Znj9/JL8mWwevJ9aeR387TK7OjVyKp8TOXTSky3bOqZbr5BTHUQDRr0LlQDO6ozmN3CVOdKpK31hfIotTHNRCUcpq+CoEB4VyjeAAdnzCkvyhckZ/L/CtQpJkSGI02M+mHcwE=,iv:cG5Q75Y0zeGj6EezNY1rRD0ZLtzIxkcKKsSHV8Og1aM=,tag:dsSFoW/XzoeOHmH5sqJzzg==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine2
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:ZMxZz2bWSw4lKwXp+kdeblLJxpLxdWyZ9ZPBMkZeZGmf,iv:Dnpmvt8YomnwKeJ9ULWy8iAw9OspZ99glCYDtr2Ymkw=,tag:aBHwYdO8lZmtb8PbjCEeKA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1aqng9vmlgth5aucu5ty2wa0kk9tvk7erj4s07hq03s6emu72fgxsqkrqql",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHSWh4aDdUVnowNDBGRllw\nWitjU1pKQkhSNTdmMjZOUkJ6a1UwY2FkWEhzCjlIeGJvV2JpeTBXcVFCQzY2MXJC\nVlFsY3Uzd3R3cEJuVlZYYXd3dXkxSUEKLS0tIHpnT2Jjb1EzNUpXVlhseTBSdDd6\nYmorNlo3bXY4OGl3WXJQZ2dHbHlUVFUKdVPZd/2QtR/ELpf+vy5sXdGC0tS1N4uE\n5zsRpMBWQptheOUF0tNZAbw264gbYX/fece/myTJLISAPM9wZQd7ug==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5YzdiaDBxYURVcTVLQkF6\nMEVDd04wSXhYV1hKWXlhYVJVaUJtYUxmRGwwClU1SXNpUzJQNXVTTGQ2d2x0QmRV\ndWgyL2hMR2pTZWE5bGsvWmVYWFY5akkKLS0tICtLRHFuVjBlUldZQ2FCWTAza0Yy\nMWRSQTd4WWNjcGtYT1RqcS9ybWp1ZWMKidHF/OqLKVYWo02zHnSLm8BEN+qyJ2+i\nBqg62OqGLt8pbE/r1bxnxia4ZCgfc+xKtsWuARKq/BUBrKlMrBZGEA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:08Z",
|
||||
"mac": "ENC[AES256_GCM,data:JInxA9c2UQBDFzetaHU8KYNnp1JS3eBPyAMjsP6sNwOsRJBY8enckQANvhXF4Mo6eksx9RUhdcqd5zqO/uupnqV4AuARxwI8Sq3ak1r1fnH4uzOC/wV5hWIHdOfKrePuE1+ayo/mR0xqM3ipxuUfJ/cM0ktYrTvJypR1brHkJyg=,iv:Qs1M3UMUOUuPVG564xJAJAhRsj7GGrs+jqNbFjupGDg=,tag:WjTJT3N7FkVmdv2giNVECQ==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine2
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:AiUO8delOdyU9pl1q9DuSegdy/MMh8q30M1hXuinpKVC9Wdtw/NFUVfKSM6wX5d3hkBnZ+BmG75bMIkZXXJ6Cl3Q7l3M60HcBrDQaUYJ3tfuO5dJGSi9Ab5RtnWKmaA/5wtb71sM08PbmmkPXnVI2KgXwX23VnNPdlMXFRYdWuzbTD7U+S7BPKiINjMCyLlMlNfw7YidGufwCkYb6YbpJ+Z6EkfArkH6pGKlB38vaz0rSiVSBa4fxzD4rgx2lh98yJ8VC80O/PdpvYhWub1aHpK1j6gq6InptvLNIyUh0i/9YXHbjpXc/D7hhphF/tA0JUOFMyN0Ses8cvBPHLOMU3uM82aYk9lBfnn74vKsNk5IWeiPskzN6dwmHr6yJHQmDiFp9ll7Ujf9GU5DzJhQsVUFf1xb2wq05YMomEpDdDo0xz35L/w8hFQg5kKLDvzKCiDQuvYz4KdaDZqobZbJ+ICNx0dZHlBg6NGvYK3Q1TpNkR6qHDnOGYC3dbZzeskuLDnD6VScPrLvf0duOrNwE2QP5Eqtk/5r+yP4SJzNK9UMZhYcXIF2koel6dxhlEmJMBLryR/56+GFIWOHIebKUGJhtumj0cl21QAcZgeQ42oy6mza+K17u+SpRwb+Vl+d10zSauxV6jqHE/qec4ZxKyFJH1TDR4R3FcqY3Xb2323r0Lx3Z6h3QSuXaoSz3lZj1FnXNh5x4Xctb87cNd6u/woxL3X105OwMOxaI4SIt81WWSA7do9WFzfNjOUahyullq5RWNOapLPAmMjuSdJBeImFEbKPUst25vgkzs3x4PZkS02s6R+o3h3PoeTZPDIpNIv6Ye6sRy0txhF4nBmHKFNjoClxaDG6esdDlsyu+eyZKRy6/Hvfe/iimlyb+iQMLlmTLoES9B7J0F1JAwJhetdnfoa3pbgZsehb2b0BmqgHbytgA/nOlNWcbae+aX8HCj4Ju0RnYwObbjaVqHQoST/LjnnVJCo1y0UZ4CnOg7MNEKrtxRb3BV8s9Xp3BR3UPFN/gKWBGh/xyHoR3ZEJ0mxt3vjdymqMtBQ=,iv:4slGBU5rnFPaiFXr6Nzuk54ku76XL9+cFOF3IPWbZn4=,tag:+XOCNKwG2PqzY/PTzruouA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1aqng9vmlgth5aucu5ty2wa0kk9tvk7erj4s07hq03s6emu72fgxsqkrqql",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYbVZ0c2tqSEZJNkxTbGpC\nd1U3YVQwQnhmY21pY0R5SnJHL0lVZC81SFFzCjEyV2VwQng5ejc2L0F0ZUFrWDB6\nY1k0blJQbUhRQ0kweGM5Ty9uQXVGS0EKLS0tIG96S1NOQWlidXZORDlrYWZWcFUy\nb2RLa2pKNEhkSFlwdVFMRXlEOStkNVkKYahEQPOfncD2TGeT1JNPWCOG0ScUCJ0K\n2cYRCBxayd/ssfyEL3jV+TVSxFnKrAhtGJzP2xEAZFYJsHoLG+56sQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQcWdtYmpwYmJQV3BNL01x\nY0FURkczRjBKYWlzOFplcDZ4cktGMTNMN0VVCm5FQmorTzRMUzVSd2Y4VS94R050\nR0V5MXUvODh2Q0IxeU9RelJLTUZoWmsKLS0tIFFjT1pyS3NwWi9OY24xTytLN3NZ\nUDdqUEp4ek5tTGxvdFU4SGduR1VBeUkKX013r6b+KL1i3glEcpwgv/KzBEE9N9sj\nqgyZ3S5dhCY1LOquP/xnS7kqZzN+znv61F7577wV1pBy53KPOhl47Q==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:08Z",
|
||||
"mac": "ENC[AES256_GCM,data:Oiq0mU3K+zgaHhdlniCGgx22Sa1tEOmO+ddfDdD2IsMm1c9vAI4O+/5PsF/DE4h0WoORJsn88qfzPDva90YUJKQAJsflMHNk8jjExGhtVLVjxkpZk2EtpBzfg311riYzKl5vSJxLqp67Ks2dHyl8qHEH71dSgkXxVMIhPDUmJCg=,iv:B+Hi7qoRbBT53wH3cBSLoQocdXCcfKenkwnErTweMwY=,tag:88c4zp8a6WNfnJkRZvNFqw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
K2FAKIB-HUUM7M7-CUPDN5Y-NORZCJP-F43EFQY-UPV4AUO-GAI3VZW-PH5TRQX
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine2
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:9U+BF9rhGbNCokJoZEl347m1jJm9n+JOJFXSVrqYM+r2A7NPTDGBKIrnVpGdOM+8VmOIzAIROGb1h+wgQoHbr98YtVRx88D0n9bA7ZtSCNiACXzgTl/swaqYCPgJ5FtPvylNagK92SDwjpVSV7jfLA0kdE49HESg9dWIUlPS2CoORhW+FY4wynXdMEsgZmeRgtYFMubqshX/Ep07qnF9HjQmmDy5XKQ4uEm3HydlZOZvW6USwyRYIcqH8x5p1xzif5zYgUsu1iHAmbhtqzULJoe2rzuM3+7Av0LeGcQpXe3gkX/3+aA/SgGN5mJOtLalcwClow5HH0atlOopgy4523Z+jANvc4XwExpJbSYeJuqUTV20q1X16Kyph1O+XYOW,iv:dS7CcP6oPfdByqIW3sz4AUm2BfVInFmDDkxxbGhySmc=,tag:T5ZbP2hDaKNR/c2J/mlS6g==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1aqng9vmlgth5aucu5ty2wa0kk9tvk7erj4s07hq03s6emu72fgxsqkrqql",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcDZLcXpaODVma1RUNHE0\nMElMRkpRSWJwa2Q3dnFEQ0J2a1JpblZuWVFJCk1tUTV1U3VpYUJqajZKd3l3OHNE\nWDk4TzhQSlluOGdnNlcrUTBxaXJrRVEKLS0tIHdiOVhRTHI1emE5TnMxbEZJdm1W\nR2NxMVRhQ0xDT2o0cE1POXRQT2ZwMmMK+3x5+5pVrGPILDpCpqPKuVX5emmfDvFt\n2HjLIwy/2mWcsktXh2gk8KAD4WWL/JjatTuP+EP5zoOFcL5/U3S5Pg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArS3B3SjQ5ejRLU0xUT245\nWGNkaUErdTQ4ODNqSzY2aVFPeUlabHl2MkNnCnlzcUh0ZjlUcFBXbEJtZ2pDUmNr\nYWRvRG1EbkwrZVE3NlU2cWc4VSswQ3MKLS0tIGYvZFZWOXV3U2hMVU1XOHVCSXdp\nSm1vaC83c01TZms3Z0NqMHhqbzRNUGMKKiRAy58NZDz5rjNA3xYv8/dU4H5L0fo/\nQc3WsEXIIwnVHO0X6WN+qUpF8uTPAZW26wecspdsf/5NOJtM7o8+EA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:08Z",
|
||||
"mac": "ENC[AES256_GCM,data:QGID6lY6g1qvYXhtXrlNA3E+IjJZRKiB8+CDDLYGwx5vtX32M/WNVfzEs8bUdUDEJTUnfFI6tBHjFbKUVNFmbH2utZ3aiUGVJadVUzuWQf3HO/McyPwRMm47gt29U0iagUTSdVbzSROwdwCfXfZeyJUPXXNZgyA5LqL8puPS+jo=,iv:kZzMQQ+1shjScxxmMVSqK5ckXckgCtxbqa8uBZ9RTZI=,tag:do0RhBzfHmFZc8L90WXTqQ==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine3
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:1yqn7/zQsDblJjKK5nK9wLQjmvBTmsr4WAkWvjRJ2FLX,iv:F9+nsOQSgbyr/q498oI1wU/KiOsEvSUec/2VC9bzv8E=,tag:tklcKqwd9MmuJany0pg79g==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kul02mg50nxccsl38nvma0enrgx454wq0qdefllj4l0adqkllvls5wuhfr",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMa054akc5ekFzYVBYZk1Z\ndFoxRGxUcGZaNzF6RjlPRTRXZjh6b2xxTzBjCktuV2Q1WnhnM2NMdGRncFhXWHpG\nRHhlU01PVVNUMEF4K2x0NEhreHRKKzAKLS0tIG5YQ0YzaTlxNGtPTURNY3VtNGhO\ndUxPa1BwdXZ0SVJnZmNqYngwaW1CYjQKngYlXRD28yZ0j7WMmACTRCbi7aJ6xNlN\nor7I530+HUv1s067PRwMiBWbNesiAHHhhZjOsnSiyQ/IYr0R77huyg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBdnhOU2MwY2ZXM0p5Rlg2\nL2FmaDZYT3pIL3llenV1MVZDT0xiVklXWFdFCko2S1BxMFpPc2QxRjExVnhXTVNp\nZStpdis4RDlHcmNobmpNK1FtbFhaSTAKLS0tIHRJblljNTJxS0ZKdWY0dFF1cURP\nZGFDRGZ6ZnozU3NJT3EvSGNEaTlralEKywGQQwjS3bJ2yI8839ycWeknES+r4oMH\ncBG/zSbYTznyMQq2uEhAe74a8zf3pe48EnwPksiCcFLIG5IP1SE6Iw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:21Z",
|
||||
"mac": "ENC[AES256_GCM,data:06hwqU0gL2x8brDOB3y69igVBHwIkgUiuxYin6FPsjAQO84qsT8suQVGHmqPxHXrPM9fMvu9/Xqio0w/ar8NElQLmUlTkOads/pb/ykuC5mU/645q1spvV65i4C/spGrIA4XtSaeY9APuQGDr8wiQKdnvuAXSsuVSQvEhTpbBqs=,iv:UguEbHUFqZoV2g7DLeUpI8dd3tdoWhfIDLnj0h5i1Wg=,tag:CJ5/uOYKhleP2N1oTlvdxQ==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine3
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:Az/IU5qbln8kN2rkY9MK4dMtCgaIQDjXw3aqbFs/Kjh3wLy4jYM4XkvQBeZPqgmonbGtJ50eDLS14g6ABYCmpQtDPHUAVllMPlKJMiGL1/+YPXGfsQyPgOXmgWA6hFI24ZPEsQYCRwbXoDZLJMkTxUMlMQqZTWsXkICbrIYzyJUjkbK0uaSjqbu7vLt7HVgb6WBoFtdXdP3rD2BVo6LEZUTd4mQW9PB0FPms7WKsVnoWQpKmowentae/2cdx1KCvEmoJfvnAGB09woHDGJWzs3VSrlenPVOu1dN9NUaGI61WRLJT4/h47PBEt1Pw0ec5YK2h1Po0pZa8LaYNQ9ewx9cIXsvWpKgvi73zSe70Q4uWT7xHglZHhncR6wECXIpClpQW4Z0a7CgMujfMexK6Dh8fJZ72hA5ZN5RwoxuAlhN0HujFBMk77w5t1QrulyJs8+UQIqfNhrK0LGQKRn4AnW1R0Hc7bOzeE3C50oKmyU1kDyoDChce9A6fGXQ758kUxOYTZy0095LIEL5Q0OdCujdz30a1NiORuuuiXSCCIAfH1rUMeO49Q1T3QF8IRJScvD2Q81ggqeewjzt83d8KbCHWOtPiaAAWzNOHPS7N/x9efTqZmf8YxajnSeQDbqqyRayjFPs5AaTyk1pqFiIRKYH9cKJeEXr/CY6j+ykgDXDksAvg3fVXikdt4E5O/HaWxU55CJZmBfshdC8GWEetRtl6pfA5poTT4L79B6edF6TxtX4R9Js322UTTKxOTJ8fkx/0grXUa+KxUreb+jbN826AyL7kWr5Zt2clXbCFetjASoJn+tMQKeUmE8xe2V4qibTmPlkzuhUIVdVG7G8xg8w31lBSErF9/M4AaaWba93s6GCgqdJ5T7Duf9DnfaiQr011eCmKpfJxsUTGm1Gs55+YIDQZEraOgLr9BWjBGy1gCwQIhZtY/udKzFXvqB/KBr2/mzXNzPNmnrcElKkjxpcHF0RkJ5JGo630k2AOab3+KJzHoRFPI8/VzR2dh3rHQ7SjN2cdiJiGO18sD9Xc+9Me6jgVLrMf8NY=,iv:TIaxmvvQUzzM1hLdb2caQJbTTfgeea1LijeGa/5Jid8=,tag:e77+V1Ok2qNkI3MdUntqIw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kul02mg50nxccsl38nvma0enrgx454wq0qdefllj4l0adqkllvls5wuhfr",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwaGRjSEo3K2xKZE9HSDVX\nU3lRWWlnbm5QcEx6VG5nS1RyUmUxcnBPL0Y0ClE3VWZjclJybEJCZi9NdVJqeTBG\nM0hhV1FPdzVBT0NLRGZvdFdDMFNYN3MKLS0tIForRURHM1NrU2M3ZnhJb0FFWmwy\nUWo4QmJBS293aWtyRHQ3bTR5RWFpZWMKoGjITft9CoH3cVRXRGx02PTFFQMN8bPU\ngxNLcbWyLVSUvUZE1xYiVMh5aGMLlMoGwgwJBUAb/Cm744Yh6gz5Hg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5RkZxY0E1N0FOd2x4ZFlv\nZW5PbmIwZk9uS200N3N2SS9pU0ZzaU1ibUVVClRYRWlQbDRZNXVjZlp5cXpJRmh6\nUllUT3llVjRHSEpTNC94WjRFeTlIWUEKLS0tIEMvZ2M2VDByeGtpaUp6UFdtSWZC\naGFId2w1OXg4bnd0bFlCNUtSZDJlVTAKFLc3mW7y7M+HOO5TIEPPxznqXkWvUWEl\nVy8DA+hZAAsoRfGseFTD7ny9qJOLwnmpmW2m5dZTuPcRAFk/29DrGw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:21Z",
|
||||
"mac": "ENC[AES256_GCM,data:Y+XjgRgXHWrLZlmes1j2C7OLXxttm24aNTUUf7voSCUK/z3iIrqhsRs4OZfpLs1JyHNPYMhRu5kT1cjIQfZPwSNWPfw0QVvMGpgoNV8MGQW+G9mUxIvEy+F5yRGXiMd5bybyDgitADHG/6OxBd43IoEfkCpMs06yaEAievk+P9s=,iv:iZ9jP0cNmwHD/EViXzzmM8/6xIY+T2q+aMnQRH/JC1E=,tag:vT9/XUbuq7pltl5d1zLaew==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
UWOQP5L-DE3OCK6-BIQLRGV-JFZ7JTJ-VUYUFDN-F3PJNH4-VS2AJKR-PQJKQQD
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine3
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:pzKV11EWtow3USkwNwSO0qLAijfG8UbeVvDDkQJqd/S/lWF5gBTt+33uPTsBj2A0tyVglwHrGIuPl/WSxZqt4jIKogjeWrODzN6YCQC8I9/3JgCaCYKlDM2b4GTZxzC35YXD0CBmCcpGUgAvP3Awr5ga4SmAQLVHSR4MHODODRa2cj4TsReLLIf2JgFd1m4Jd6RS9YrWYBSTOp3btFzdJSISqKAHcIcEzMbb2uS8tBqmoWpG56snrzmchrBifB5jGe2RgN/HLN6+gp8p1MStEVWSBbmGszDtUH4k2hMKYpY+HEqHL2onbc+LIOo4ntpXagwD034Iw/mEfmGkgrf+AmnX8Gm6j/6CW8ILlypRYAvDucKvKRNOnWWrkR6XmiPq,iv:Zp/B5it4q49i3B1XT1F5II8Ajc9xjGpJ35SJQtLsr60=,tag:tqc3YsaNIPrvTR/6AVH8ww==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kul02mg50nxccsl38nvma0enrgx454wq0qdefllj4l0adqkllvls5wuhfr",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4SVJ4S1gvQjJYVTJjTVk4\nUkRrTXBhekhDUWlZQmtJWHJyQkk0Q1Nxcmh3CjdnazFxdmZGaEoxRCtkVnRwOGFW\nMnMzSHZOWUF3aWtmTGk3cTNlWjlpNkEKLS0tIE95VVFDQjRTRTdFNUNud2g5c21z\neHBHeHVMUTYvOHdFVW9kVWtSeFpodzQKXndWkTpSrJbzUpHStyWb4Q5p4p9ASjTc\nGQ4wcd3RLjsox/LfFw7NPXuRLQh7kRFgWxx3akjzeaVCbNOg7N2fKg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuQjBmeFAwQmNiVGhUTmtk\neDhaNTBaRmMyNHdaRVVJanorWE1Mc3kwVDFzCkxEMTFMRjdxYytLRk9HaWlaaTJv\nYTR5ZXRvMllYWHRDTlNXWFZlaXBwRmsKLS0tIDkxRVI3bXdKaXNMUGNWNTBLcVpw\nSXc4WHZDOUg0dFlDZnE3Mi9uMGFMcWMKsdxeoutdG3nwU24n2/qy9oTkhOueXKJ1\nbEuSY7OsQWp2tc6bVwx05G/R9bI9sYtA00FjJLLIR0Xk9M3ngor5/w==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:21Z",
|
||||
"mac": "ENC[AES256_GCM,data:GKWyUqU/FeOQ3um7h+82ZYh4aylxII+li8F55iRvFXRLK4A8J6z37AiDI7TOxqQ2XZIh6b8HZA0BpFN6HlkL+q4DpiuV8eTmke+hLq4H74M/m3Kgv4rQtrBupk2OUdXdsUu6ERJiIxW7XKvAhX6Hvm6GEDSSttbba5L5esi/xQg=,iv:qIy/MQXUpTzcDF6VvY7ERUDLs2XJUK/dNxTIFgfu9Wc=,tag:D2ocRXrxvEtTHX/t85EtpA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine4
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:5Bb2+eeTSoCvrJUFHeEL/ugPADUqkgxzBebyDHrg8v6G,iv:1TWwRiIm0u+zrydC+mlz51Dtz59FfX2fi6QjjO9+jdA=,tag:mkLCH2kbShRc6RdtAFaFtA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kqgx4elusxx4u8409gml5z6tvrsayqsphewsl93mtqn7pl2p5dwq9lujpj",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUam1sYWxDT3FQZy9ZNlln\nc0o4ZkdVVG1zNlRDbVpxdUJ6NFlUVzZsc1VnCjlobzF0dmtUZlp2U1IwdDdMMlVk\nVUt0ZFc3Ty9UQVNTb2dJc01ZbXFlUEEKLS0tIEVGcnNoSXd5b2NoS1hZNWEyMGdM\nZXgrNGhFMGp5LzlrdE4yOG15b0g3NVUKX46oVCCn8eQ60raWVtxRMVazQUJaPD5Q\n0e95w2IXfcN7YPn+CdIcWEoWJ5dbmGQ1DB9VBWrZQONdi1SHzyHx8g==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6QUZuWG1Ga1JpUUszVmFI\nL1BxTXk0M0hKLzdKVUQ5Tnp3Y2t1bnEreFRFClcxVmFvTWJQNVg4YkxXa0RmcHFk\nNW4yYTRRWFpJeFhTSVc4eEJzSFNFRFUKLS0tIGlhenZuRFcyamZNQmIya3BEWGhu\nckhsUE1QS2tFTUNUcFBqclN2Z3A1SGsK7e3Eoc7J0P5pyn4dqQAn67iBfcgokdNr\nSJqoyukHst9ZDJ04+n19mr02njxMz4MTt1Qw76q9s2sadU8iHsHnog==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:9pDg1EWTuPtIs+hfwtgQkX6HrIaI+IWyVY16SVq2bBrEd5uxkXQ2fL9P1tjIwo7iO8mZ6Ji3uTWZa/mqPXwJjdGOV5Jehg3y1S3Y0mPD4yN/Xy/eWrieIly3md+EVcDndFLyKkcnpd78VjJhjvdia2OC64MBqylGoZ1M7sWmj6U=,iv:VrUEQeFg+JeDuQznYgeNZiml8HTcfWv3gy4/mEMlXO8=,tag:bxALn8GfEJY7aQQP2FmSsw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine4
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:WK99B0F4/xudxleTboafseIybqtOaaF1j1Y/e0/DP2XB/Wsr6x+1S89ZaAsEh10221d+n+iz+X7eo2uPpEVqpP68qyLJDw3fGli3ilUaMZ+xxZMtboW4lGjg4q3UlGzD6XApFKCRRBbzqGHRbQAHD7RaBJOmz3TmXSsCXJDMyv9ac9ijf+8ko/mAi2yXEgZwOzaU9vU541Mqtux61GJ9RS+C+VJ2iKg+he+mMFMdbqRRAMB7QMqbeXoOKt5cF6mwUQJBlHqotF4/yy/gqCMlThAVSdLqc5McHTdv/oOQEfWH3UwZFxCP0T8mHxAkgI0ISlSzpESYf/GO1HYZLxggY546TWdJ4Mczq8j4Q118YPVi77++ZsqUuozVHN776pBdJHcLuo37hp/6MNqB2AOeCa8dwiF5bylHRiU+sfFXNNk/J3eO5NDwt3wujAiRsuLPdVQ0u3v8InUPIfbxaBr7FTksi1FLbm6oPBPBAJSIVl7L/MC8HBZZjywP1MCbO1kVpPbyP3C30PyKjvvhkWxHkv4YWbosuG0s7TxSxI2ngRjoPi7hbgmm4zpgiujfDthx93KEEqTxCf+/LBzxuKRuN+eP2y+5qAsRJL+opaETeyi3nap3hEKl1PTg9Vdt6PUW7sUDLWzhkNvAFqW4CbZ5HyKEsvZskdKP1anHXoLahlAzme9ag+aOxEgmlMN7Tdikmo7LxDrtU2GysH2miSj1ezQKkpq9kYkAogA9qBbr8N4o8jn8KZUDkqZXsD3W/WfF+xXVLWoKS5sbOmBvBu4QWGI2IDEBIwl/JRDwp12BKOraKNtG9v2oVm7rq/8Ulb52Z5MpBwHCwT2Yts7Y7RKLnZb+0Z4FWG4lqMz3gVGplZeXEjzbqj8J2YV2hd1Puk0B1OTwadQ2f6mFZRmxUmRLoaTMjGkkVKbaPb4g8G7zQEgJZj2y4FXZx/TqUm3ls04o5kgTuGML0wFrkzjtf3gQ0APwjqqax8/dqUXKWQOr3XeB5Be8OtDMfxws52M9pHjXXZxbPrWxxpqkcC/8fWoNNi0Umz3WKw==,iv:uyy4whMZL8gCqN5UESWxxK7o8THSAkOriyKqUzSvI6A=,tag:gXdVxcdc6nMmD10Y6QxEgw==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kqgx4elusxx4u8409gml5z6tvrsayqsphewsl93mtqn7pl2p5dwq9lujpj",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlakt2TzR5b0NHdjY5YW94\nM1NMcC96Q1RxWjM1aGFybHBFZ0NvOFdhdjB3CkR0dE45aFZ4ck40R0xsTjRXOGt2\ncWpSK0lqV1FITWQ2RnFwS2JDYmNrMHMKLS0tIEpJcm8xK3dOYWNKb0JRTHNncjdr\nUkVLV2tSMGhyamgzVi9IcE9yNDQ1NFEK5HbycP8g2i9tHVbAfqzeXh0Krqsus3xP\n+Ta6lmWW4vP0fvA9IgZcGJXY5gCpEaum6GkkvaB2zhxs7Uddk5bEsQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTTlJWaGQvekR4WUtEODkz\nYlFFMjhTVmZxdGNmU1lUbCtMNFdGeU50Um5RCnF2NTlsVm55VUNxc3k4dTc1c280\nYjVqV3dMZmxxR09VU0dSb0wzQ2NHczgKLS0tIGVFVjBPRFJTbWNjU3g4alJHeHM0\nQThYWlNQVHQ3K3lvS1Y1Si80RWlKSk0KJRpWoQj+VrMGfqlppNj9IHe/o5JzqQUW\nG+HiWay1SgQj14LM7G29fuYt39I9pdwuUMRYXJ4MwVq/wr0raHYoaw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:Rkek09R889QqIfKa11bF1olCmyj9l9xM5bgTBpcSqaJyFUPg4bA8I3PnnAMtIeEGtG8ioh51jHRpbjsp5BuZM+GhrhW0X5NWF3ZlWS+Gx4hyoJSZJGgNVFzvr5Wj3QkS+9mDLrxE/UfLJZU1/raleAV89r2BiXOHhJugNPCSmnw=,iv:NUQZG+DzYOVsVhJnDaqcfAIOmnfRg6GuBamp61e+CoA=,tag:voEZOktdGpScZQrJnwlKnQ==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
OORI2OC-364PI2W-LFOFX4X-7PB752I-QVLZWG3-EC4XJ4C-RPJQWTV-XYPRQA6
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/machines/machine4
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:e8f43yOO08ww3x9QY4D9K/51v2NdxzxnzKrPCkl0f/ElZv9iWdon2NyknjAA9sJEuy86eY8N0LkgqIodLoc3FoxmfADHU8BbhyFHjtoIpuTNV61eajW7vnVJA4zTtRxrEReNXe02muuHM/1sfXA+ocGKUK4r3pz6nSkKaE5VJFZl7e6gev1H2w6rSq3XMMp9V3AgRvyLtSQziKj3fRFFHhet+AopqU9uMb5t6zHa3Sj8sn8dw0HPrEQYelZyS0iAi0g7OHBMasHrDQYrtfd71SWoARuMN7JzSxE2/F+yeqGmwRdf1EMJf/1Xdrgta/f0Fzmt4+UzuciN6SsREGRBk+nGYQZrqsN+wk5bPet4P68+T2V1EFuCh4X8t25N36L0,iv:TmXHWEb3jgULdlIVOxmfUHL/ZrY4NneVkeJWJUOxzpg=,tag:lGor/T44A4yRCz+VZIVM6Q==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1kqgx4elusxx4u8409gml5z6tvrsayqsphewsl93mtqn7pl2p5dwq9lujpj",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsbEhCZ0JGYkVCTEwyeDF2\nYjU3cFNTb2pna2xjSzdHYlVrNDdDZGRCM0RvCmpJbHVsblBsbG1CQkh3NTVHWTEv\nR25HdFpkYktKNWZpZ2tvcEorTWRJZDgKLS0tIG9MQWpERTJnb2dDWlZ1NVgwMkdL\nMWYwamM3bC9pcHRXSk8yYnN4UmhhQnMKyJAUuClhEPLBoq8hDeopp0xkMbtekJrg\nfZXUAA6Wr/cwISLJHAFMa8DdBGBj9ICU6v/AMyo3Zfkv9gV/NTDmfw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvbU1DMFRoS1Fjc3FwWVJD\nUUdQQm5HelRYT3U1SXNJaFhkd3ZsYmFFQ3hnCnJaa2RpbWQ5WnZwWlJBZnZENnRG\nRENkUWorcFgzZ2hYMjI5Z2o0cHNWcTAKLS0tIGhMQVRmdjB0cWlJOTI2Y1N6dWRr\nSjVIVnpTV1VRS0NaZUwyeUt5ZTlVSEkKfyL4gE731vmdqDjWjPaZiSYkoZVkj4fj\nqKJMwG8VN58NnL06oExzqVGgFKR4CTas0auWQ76cs2gdv5n7Gm2NMw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-07-31T13:21:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:KVewXUwkR68EyGj7Ld8JfxWu10+rbLn74Pv8y0Vkh6ZFDmo76XtuamLrH74uNOtDGwGhBM53xzNVf7LHfiWmT3S1dLifrusV4BWXa7XX37ACxGEq2FYg3HuUKG6bYgykKfxc3vLv50NK8cZMSxsAyuP5z7efas6joKHuzA93ZSw=,iv:xnHbVGa67SjGMHiGsjvteOQRtA66kfsWY04ZfdV6Cw0=,tag:9AFqKEn6Hs/LweyQYVDJ0w==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
21
clanServices/syncthing/vars.nix
Normal file
21
clanServices/syncthing/vars.nix
Normal file
@@ -0,0 +1,21 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
clan.core.vars.generators.syncthing = {
|
||||
files.key = { };
|
||||
files.cert = { };
|
||||
files.api = { };
|
||||
files.id.secret = false;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.gnugrep
|
||||
pkgs.syncthing
|
||||
];
|
||||
script = ''
|
||||
syncthing generate --config "$out"
|
||||
mv "$out"/key.pem "$out"/key
|
||||
mv "$out"/cert.pem "$out"/cert
|
||||
cat "$out"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$out"/id
|
||||
cat "$out"/config.xml | grep -oP '<apikey>\K[^<]+' | uniq > "$out"/api
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -88,9 +88,17 @@
|
||||
files.user-password.deploy = false;
|
||||
|
||||
prompts.user-password = lib.mkIf settings.prompt {
|
||||
display = {
|
||||
group = settings.user;
|
||||
label = "password";
|
||||
required = false;
|
||||
helperText = ''
|
||||
Your password will be encrypted and stored securely using the secret store you've configured.
|
||||
'';
|
||||
};
|
||||
type = "hidden";
|
||||
persist = true;
|
||||
description = "You can autogenerate a password, if you leave this prompt blank.";
|
||||
description = "Leave empty to generate automatically";
|
||||
};
|
||||
|
||||
runtimeInputs = [
|
||||
|
||||
299
devFlake/flake-compat.nix
Normal file
299
devFlake/flake-compat.nix
Normal file
@@ -0,0 +1,299 @@
|
||||
# Compatibility function to allow flakes to be used by
|
||||
# non-flake-enabled Nix versions. Given a source tree containing a
|
||||
# 'flake.nix' and 'flake.lock' file, it fetches the flake inputs and
|
||||
# calls the flake's 'outputs' function. It then returns an attrset
|
||||
# containing 'defaultNix' (to be used in 'default.nix'), 'shellNix'
|
||||
# (to be used in 'shell.nix').
|
||||
|
||||
{
|
||||
src,
|
||||
system ? builtins.currentSystem or "unknown-system",
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
lockFilePath = src + "/flake.lock";
|
||||
|
||||
lockFile = builtins.fromJSON (builtins.readFile lockFilePath);
|
||||
|
||||
fetchTree =
|
||||
builtins.fetchTree or (
|
||||
info:
|
||||
if info.type == "github" then
|
||||
{
|
||||
outPath = fetchTarball (
|
||||
{
|
||||
url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}";
|
||||
}
|
||||
// (if info ? narHash then { sha256 = info.narHash; } else { })
|
||||
);
|
||||
rev = info.rev;
|
||||
shortRev = builtins.substring 0 7 info.rev;
|
||||
lastModified = info.lastModified;
|
||||
lastModifiedDate = formatSecondsSinceEpoch info.lastModified;
|
||||
narHash = info.narHash;
|
||||
}
|
||||
else if info.type == "git" then
|
||||
{
|
||||
outPath = builtins.fetchGit (
|
||||
{
|
||||
url = info.url;
|
||||
}
|
||||
// (if info ? rev then { inherit (info) rev; } else { })
|
||||
// (if info ? ref then { inherit (info) ref; } else { })
|
||||
// (if info ? submodules then { inherit (info) submodules; } else { })
|
||||
);
|
||||
lastModified = info.lastModified;
|
||||
lastModifiedDate = formatSecondsSinceEpoch info.lastModified;
|
||||
narHash = info.narHash;
|
||||
revCount = info.revCount or 0;
|
||||
}
|
||||
// (
|
||||
if info ? rev then
|
||||
{
|
||||
rev = info.rev;
|
||||
shortRev = builtins.substring 0 7 info.rev;
|
||||
}
|
||||
else
|
||||
{ }
|
||||
)
|
||||
else if info.type == "path" then
|
||||
{
|
||||
outPath = builtins.path {
|
||||
path = info.path;
|
||||
sha256 = info.narHash;
|
||||
};
|
||||
narHash = info.narHash;
|
||||
}
|
||||
else if info.type == "tarball" then
|
||||
{
|
||||
outPath = fetchTarball (
|
||||
{ inherit (info) url; } // (if info ? narHash then { sha256 = info.narHash; } else { })
|
||||
);
|
||||
}
|
||||
else if info.type == "gitlab" then
|
||||
{
|
||||
inherit (info) rev narHash lastModified;
|
||||
outPath = fetchTarball (
|
||||
{
|
||||
url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}";
|
||||
}
|
||||
// (if info ? narHash then { sha256 = info.narHash; } else { })
|
||||
);
|
||||
shortRev = builtins.substring 0 7 info.rev;
|
||||
}
|
||||
else if info.type == "sourcehut" then
|
||||
{
|
||||
inherit (info) rev narHash lastModified;
|
||||
outPath = fetchTarball (
|
||||
{
|
||||
url = "https://${info.host or "git.sr.ht"}/${info.owner}/${info.repo}/archive/${info.rev}.tar.gz";
|
||||
}
|
||||
// (if info ? narHash then { sha256 = info.narHash; } else { })
|
||||
);
|
||||
shortRev = builtins.substring 0 7 info.rev;
|
||||
}
|
||||
else
|
||||
# FIXME: add Mercurial, tarball inputs.
|
||||
throw "flake input has unsupported input type '${info.type}'"
|
||||
);
|
||||
|
||||
callFlake4 =
|
||||
flakeSrc: locks:
|
||||
let
|
||||
flake = import (flakeSrc + "/flake.nix");
|
||||
|
||||
inputs = builtins.mapAttrs (
|
||||
_n: v:
|
||||
if v.flake or true then
|
||||
callFlake4 (fetchTree (v.locked // v.info)) v.inputs
|
||||
else
|
||||
fetchTree (v.locked // v.info)
|
||||
) locks;
|
||||
|
||||
outputs = flakeSrc // (flake.outputs (inputs // { self = outputs; }));
|
||||
in
|
||||
assert flake.edition == 201909;
|
||||
outputs;
|
||||
|
||||
callLocklessFlake =
|
||||
flakeSrc:
|
||||
let
|
||||
flake = import (flakeSrc + "/flake.nix");
|
||||
outputs = flakeSrc // (flake.outputs ({ self = outputs; }));
|
||||
in
|
||||
outputs;
|
||||
|
||||
rootSrc =
|
||||
let
|
||||
# Try to clean the source tree by using fetchGit, if this source
|
||||
# tree is a valid git repository.
|
||||
tryFetchGit =
|
||||
src:
|
||||
if isGit && !isShallow then
|
||||
let
|
||||
res = builtins.fetchGit src;
|
||||
in
|
||||
if res.rev == "0000000000000000000000000000000000000000" then
|
||||
removeAttrs res [
|
||||
"rev"
|
||||
"shortRev"
|
||||
]
|
||||
else
|
||||
res
|
||||
else
|
||||
{
|
||||
outPath =
|
||||
# Massage `src` into a store path.
|
||||
if builtins.isPath src then
|
||||
if
|
||||
dirOf (toString src) == builtins.storeDir
|
||||
# `builtins.storePath` is not available in pure-eval mode.
|
||||
&& builtins ? currentSystem
|
||||
then
|
||||
# If it's already a store path, don't copy it again.
|
||||
builtins.storePath src
|
||||
else
|
||||
"${src}"
|
||||
else
|
||||
src;
|
||||
};
|
||||
# NB git worktrees have a file for .git, so we don't check the type of .git
|
||||
isGit = builtins.pathExists (src + "/.git");
|
||||
isShallow = builtins.pathExists (src + "/.git/shallow");
|
||||
|
||||
in
|
||||
{
|
||||
lastModified = 0;
|
||||
lastModifiedDate = formatSecondsSinceEpoch 0;
|
||||
}
|
||||
// (if src ? outPath then src else tryFetchGit src);
|
||||
|
||||
# Format number of seconds in the Unix epoch as %Y%m%d%H%M%S.
|
||||
formatSecondsSinceEpoch =
|
||||
t:
|
||||
let
|
||||
rem = x: y: x - x / y * y;
|
||||
days = t / 86400;
|
||||
secondsInDay = rem t 86400;
|
||||
hours = secondsInDay / 3600;
|
||||
minutes = (rem secondsInDay 3600) / 60;
|
||||
seconds = rem t 60;
|
||||
|
||||
# Courtesy of https://stackoverflow.com/a/32158604.
|
||||
z = days + 719468;
|
||||
era = (if z >= 0 then z else z - 146096) / 146097;
|
||||
doe = z - era * 146097;
|
||||
yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||
y = yoe + era * 400;
|
||||
doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||
mp = (5 * doy + 2) / 153;
|
||||
d = doy - (153 * mp + 2) / 5 + 1;
|
||||
m = mp + (if mp < 10 then 3 else -9);
|
||||
y' = y + (if m <= 2 then 1 else 0);
|
||||
|
||||
pad = s: if builtins.stringLength s < 2 then "0" + s else s;
|
||||
in
|
||||
"${toString y'}${pad (toString m)}${pad (toString d)}${pad (toString hours)}${pad (toString minutes)}${pad (toString seconds)}";
|
||||
|
||||
allNodes = builtins.mapAttrs (
|
||||
key: node:
|
||||
let
|
||||
sourceInfo =
|
||||
if key == lockFile.root then
|
||||
rootSrc
|
||||
else
|
||||
fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]);
|
||||
|
||||
subdir = if key == lockFile.root then "" else node.locked.dir or "";
|
||||
|
||||
outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);
|
||||
|
||||
flake = import (outPath + "/flake.nix");
|
||||
|
||||
inputs = builtins.mapAttrs (_inputName: inputSpec: allNodes.${resolveInput inputSpec}) (
|
||||
node.inputs or { }
|
||||
);
|
||||
|
||||
# Resolve a input spec into a node name. An input spec is
|
||||
# either a node name, or a 'follows' path from the root
|
||||
# node.
|
||||
resolveInput =
|
||||
inputSpec: if builtins.isList inputSpec then getInputByPath lockFile.root inputSpec else inputSpec;
|
||||
|
||||
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
|
||||
# root node, returning the final node.
|
||||
getInputByPath =
|
||||
nodeName: path:
|
||||
if path == [ ] then
|
||||
nodeName
|
||||
else
|
||||
getInputByPath
|
||||
# Since this could be a 'follows' input, call resolveInput.
|
||||
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
|
||||
(builtins.tail path);
|
||||
|
||||
outputs = flake.outputs (inputs // { self = result; });
|
||||
|
||||
result =
|
||||
outputs
|
||||
# We add the sourceInfo attribute for its metadata, as they are
|
||||
# relevant metadata for the flake. However, the outPath of the
|
||||
# sourceInfo does not necessarily match the outPath of the flake,
|
||||
# as the flake may be in a subdirectory of a source.
|
||||
# This is shadowed in the next //
|
||||
// sourceInfo
|
||||
// {
|
||||
# This shadows the sourceInfo.outPath
|
||||
inherit outPath;
|
||||
|
||||
inherit inputs;
|
||||
inherit outputs;
|
||||
inherit sourceInfo;
|
||||
_type = "flake";
|
||||
};
|
||||
|
||||
in
|
||||
if node.flake or true then
|
||||
assert builtins.isFunction flake.outputs;
|
||||
result
|
||||
else
|
||||
sourceInfo
|
||||
) lockFile.nodes;
|
||||
|
||||
result =
|
||||
if !(builtins.pathExists lockFilePath) then
|
||||
callLocklessFlake rootSrc
|
||||
else if lockFile.version == 4 then
|
||||
callFlake4 rootSrc (lockFile.inputs)
|
||||
else if lockFile.version >= 5 && lockFile.version <= 7 then
|
||||
allNodes.${lockFile.root}
|
||||
else
|
||||
throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}";
|
||||
|
||||
in
|
||||
rec {
|
||||
outputs = result;
|
||||
|
||||
defaultNix =
|
||||
builtins.removeAttrs result [ "__functor" ]
|
||||
// (
|
||||
if result ? defaultPackage.${system} then { default = result.defaultPackage.${system}; } else { }
|
||||
)
|
||||
// (
|
||||
if result ? packages.${system}.default then
|
||||
{ default = result.packages.${system}.default; }
|
||||
else
|
||||
{ }
|
||||
);
|
||||
|
||||
shellNix =
|
||||
defaultNix
|
||||
// (if result ? devShell.${system} then { default = result.devShell.${system}; } else { })
|
||||
// (
|
||||
if result ? devShells.${system}.default then
|
||||
{ default = result.devShells.${system}.default; }
|
||||
else
|
||||
{ }
|
||||
);
|
||||
}
|
||||
@@ -15,5 +15,5 @@
|
||||
|
||||
inputs.systems.url = "github:nix-systems/default";
|
||||
|
||||
outputs = _: { };
|
||||
outputs = inputs: inputs;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
sha256-LdjcFZLL8WNldUO2LbdqFlss/ERiGeXVqMee0IxV2z0=
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Used to update the private dev flake hash reference.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "Updating $PWD/private.narHash" >&2
|
||||
|
||||
nix --extra-experimental-features 'flakes nix-command' flake lock ./private
|
||||
nix --extra-experimental-features 'flakes nix-command' hash path ./private >./private.narHash
|
||||
|
||||
echo OK
|
||||
@@ -49,12 +49,16 @@ nav:
|
||||
- Guides:
|
||||
- Getting Started:
|
||||
- Creating Your First Clan: guides/getting-started/index.md
|
||||
- Create USB Installer: guides/getting-started/installer.md
|
||||
- Add Machines: guides/getting-started/add-machines.md
|
||||
- Add User: guides/getting-started/add-user.md
|
||||
- Add Services: guides/getting-started/add-services.md
|
||||
- Deploy Machine: guides/getting-started/deploy.md
|
||||
- Continuous Integration: guides/getting-started/check.md
|
||||
- Deploy to Physical Machine:
|
||||
- Create USB Installer: guides/getting-started/create-installer.md
|
||||
- Deploy Physical Machine: guides/getting-started/hardware-report-physical.md
|
||||
- Deploy to Virtual Machine: guides/getting-started/hardware-report-virtual.md
|
||||
- Configure Disk Config: guides/getting-started/choose-disk.md
|
||||
- Update Machine: guides/getting-started/update.md
|
||||
- Continuous Integration: guides/getting-started/flake-check.md
|
||||
- Using Services: guides/clanServices.md
|
||||
- Backup & Restore: guides/backups.md
|
||||
- Disk Encryption: guides/disk-encryption.md
|
||||
@@ -100,6 +104,7 @@ nav:
|
||||
- reference/clanServices/packages.md
|
||||
- reference/clanServices/sshd.md
|
||||
- reference/clanServices/state-version.md
|
||||
- reference/clanServices/syncthing.md
|
||||
- reference/clanServices/trusted-nix-caches.md
|
||||
- reference/clanServices/users.md
|
||||
- reference/clanServices/wifi.md
|
||||
|
||||
@@ -30,18 +30,17 @@ pkgs.stdenv.mkDerivation {
|
||||
];
|
||||
};
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
pkgs.python3
|
||||
uml-c4
|
||||
]
|
||||
++ (with pkgs.python3Packages; [
|
||||
mkdocs
|
||||
mkdocs-material
|
||||
mkdocs-macros
|
||||
mkdocs-redoc-tag
|
||||
mkdocs-redirects
|
||||
]);
|
||||
nativeBuildInputs = [
|
||||
pkgs.python3
|
||||
uml-c4
|
||||
]
|
||||
++ (with pkgs.python3Packages; [
|
||||
mkdocs
|
||||
mkdocs-material
|
||||
mkdocs-macros
|
||||
mkdocs-redoc-tag
|
||||
mkdocs-redirects
|
||||
]);
|
||||
configurePhase = ''
|
||||
pushd docs
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
self,
|
||||
config,
|
||||
inputs,
|
||||
privateInputs ? { },
|
||||
privateInputs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
@@ -156,7 +154,8 @@
|
||||
type = types.submoduleWith {
|
||||
modules = [
|
||||
{ noInstanceOptions = true; }
|
||||
] ++ mapAttrsToList fakeInstanceOptions serviceModules;
|
||||
]
|
||||
++ mapAttrsToList fakeInstanceOptions serviceModules;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -169,22 +168,19 @@
|
||||
# modules = docModules;
|
||||
# };
|
||||
|
||||
packages = lib.optionalAttrs ((privateInputs ? nuschtos) || (inputs ? nuschtos)) {
|
||||
docs-options =
|
||||
(privateInputs.nuschtos or inputs.nuschtos)
|
||||
.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch
|
||||
packages = {
|
||||
docs-options = privateInputs.nuschtos.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch {
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [
|
||||
{
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [
|
||||
{
|
||||
name = "Clan";
|
||||
modules = docModules;
|
||||
urlPrefix = "https://git.clan.lol/clan/clan-core/src/branch/main/";
|
||||
}
|
||||
];
|
||||
};
|
||||
name = "Clan";
|
||||
modules = docModules;
|
||||
urlPrefix = "https://git.clan.lol/clan/clan-core/src/branch/main/";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ If you used `clan-core` as an input attribute for your flake:
|
||||
|
||||
```nix
|
||||
# ↓ module.input = "clan-core"
|
||||
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core"
|
||||
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||
```
|
||||
|
||||
## Simplified Example
|
||||
|
||||
@@ -24,6 +24,10 @@ pkgs.mkShell {
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging nixos-anywhere
|
||||
|
||||
If you encounter a bug in a complex shell script such as `nixos-anywhere`, start by replacing the `nixos-anywhere` command with a local checkout of the project, look in the [contribution](./CONTRIBUTING.md) section for an example.
|
||||
|
||||
## The Debug Flag
|
||||
|
||||
You can enhance your debugging process with the `--debug` flag in the `clan` command. When you add this flag to any command, it displays all subprocess commands initiated by `clan` in a readable format, along with the source code position that triggered them. This feature makes it easier to understand and trace what's happening under the hood.
|
||||
|
||||
@@ -17,7 +17,7 @@ inputs = {
|
||||
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||
|
||||
clan-core = {
|
||||
url = "git+https://git.clan.lol/clan/clan-core";
|
||||
url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||
inputs.nixpkgs.follows = "nixpkgs"; # Don't do this if your machines are on nixpkgs stable.
|
||||
# New
|
||||
inputs.flake-parts.follows = "flake-parts";
|
||||
|
||||
@@ -49,7 +49,7 @@ The example shows how to add a user called `jon`:
|
||||
2. Add this user to `all` machines
|
||||
3. Define the `name` of the user to be `jon`
|
||||
|
||||
The `users` service creates a `/home/jon` directory, allows `jon` to sign in and will take care of the users password as part of [deployment](./deploy.md).
|
||||
The `users` service creates a `/home/jon` directory, allows `jon` to sign in and will take care of the user's password.
|
||||
|
||||
For more information see [clanService/users](../../reference/clanServices/users.md)
|
||||
|
||||
|
||||
76
docs/site/guides/getting-started/choose-disk.md
Normal file
76
docs/site/guides/getting-started/choose-disk.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Configure Disk Config
|
||||
|
||||
By default clan uses [disko](https://github.com/nix-community/disko) which allows for declarative disk partitioning.
|
||||
|
||||
To see what disk templates are available run:
|
||||
```{.shellSession hl_lines="10" .no-copy}
|
||||
$ clan templates list
|
||||
|
||||
Available 'clan' template
|
||||
├── <builtin>
|
||||
│ ├── default: Initialize a new clan flake
|
||||
│ ├── flake-parts: Flake-parts
|
||||
│ └── minimal: for clans managed via (G)UI
|
||||
Available 'disko' templates
|
||||
├── <builtin>
|
||||
│ └── single-disk: A simple ext4 disk with a single partition
|
||||
Available 'machine' templates
|
||||
├── <builtin>
|
||||
│ ├── demo-template: Demo machine for the CLAN project
|
||||
│ ├── flash-installer: Initialize a new flash-installer machine
|
||||
│ ├── new-machine: Initialize a new machine
|
||||
│ └── test-morph-template: Morph a machine
|
||||
```
|
||||
|
||||
|
||||
For this guide we will select the `single-disk` template, that uses `A simple ext4 disk with a single partition`.
|
||||
|
||||
!!! tip
|
||||
For advanced partitioning, see [Disko templates](https://github.com/nix-community/disko-templates) or [Disko examples](https://github.com/nix-community/disko/tree/master/example).
|
||||
You can also [contribute a disk template to clan core](https://docs.clan.lol/guides/disko-templates/community/)
|
||||
|
||||
|
||||
To setup a disk schema for a machine run
|
||||
|
||||
```bash
|
||||
clan templates apply disk single-disk jon --set mainDisk ""
|
||||
```
|
||||
|
||||
Which should fail and give the valid options for the specific hardware:
|
||||
|
||||
```shellSession
|
||||
Invalid value for placeholder mainDisk - Valid options:
|
||||
/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368
|
||||
```
|
||||
|
||||
Re-run the command with the correct disk:
|
||||
|
||||
```bash
|
||||
clan templates apply disk single-disk jon --set mainDisk "/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368"
|
||||
```
|
||||
|
||||
Should now be successful
|
||||
|
||||
```shellSession
|
||||
Applied disk template 'single-disk' to machine 'jon'
|
||||
```
|
||||
|
||||
A disko.nix file should be created in `machines/jon`
|
||||
You can have a look and customize it if needed.
|
||||
|
||||
!!! Danger
|
||||
Don't change the `disko.nix` after the machine is installed for the first time, unless you really know what you are doing.
|
||||
Changing disko configuration requires wiping and reinstalling the machine.
|
||||
|
||||
## Deploy the machine
|
||||
|
||||
**Finally deployment time!**
|
||||
|
||||
This command is destructive and will format your disk and install NixOS on it! It is equivalent to appending `--phases kexec,disko,install,reboot`.
|
||||
|
||||
|
||||
```bash
|
||||
clan machines install [MACHINE] --target-host root@<IP>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# USB Installer Image for Physical Machines (optional)
|
||||
# USB Installer Image for Physical Machines
|
||||
|
||||
To install Clan on physical machines, you need to use our custom installer image. This is necessary for proper installation and operation.
|
||||
|
||||
!!! note "Using a Cloud VM?"
|
||||
If you're using a cloud provider's virtual machine (VM), you can skip this section and go directly to the [Add Machines](add-machines.md) step. In this scenario, we automatically use [nixos-anywhere](https://github.com/nix-community/nixos-anywhere) to replace the kernel during runtime.
|
||||
!!! note "Deploying to a Virtual Machine?"
|
||||
If you're deploying to a virtual machine (VM), you can skip this section and go directly to the [Deploy Virtual Machine](./hardware-report-virtual.md) step. In this scenario, we automatically use [nixos-anywhere](https://github.com/nix-community/nixos-anywhere) to replace the kernel during runtime.
|
||||
|
||||
??? info "Why nixos-anywhere Doesn't Work on Physical Hardware?"
|
||||
nixos-anywhere relies on [kexec](https://wiki.archlinux.org/title/Kexec) to replace the running kernel with our custom one. This method often has compatibility issues with real hardware, especially systems with dedicated graphics cards like laptops and servers, leading to crashes and black screens.
|
||||
@@ -55,7 +55,7 @@ sudo umount /dev/sdb1
|
||||
- Set your preferred language and keymap
|
||||
|
||||
```bash
|
||||
clan flash write --flake git+https://git.clan.lol/clan/clan-core \
|
||||
clan flash write --flake https://git.clan.lol/clan/clan-core/archive/main.tar.gz \
|
||||
--ssh-pubkey $HOME/.ssh/id_ed25519.pub \
|
||||
--keymap us \
|
||||
--language en_US.UTF-8 \
|
||||
@@ -1,275 +0,0 @@
|
||||
# Deploy a machine
|
||||
|
||||
Now that you have created a machines, added some services and setup secrets. This guide will walk through how to deploy it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
!!! important "General Requirements"
|
||||
- [x] RAM > 2GB
|
||||
- [x] **Two Computers**: You need one computer that you're getting ready (we'll call this the Target Computer) and another one to set it up from (we'll call this the Setup Computer). Make sure both can talk to each other over the network using SSH.
|
||||
- [x] **Machine configuration**: See our basic [adding and configuring machine guide](./add-machines.md)
|
||||
|
||||
## Physical Hardware
|
||||
|
||||
!!! note "skip this if using a cloud VM"
|
||||
|
||||
Steps:
|
||||
|
||||
- Create a NixOS installer image and transfer it to a bootable USB drive as described in the [installer](./installer.md).
|
||||
- Boot the target machine and connect it to a network that makes it reachable from your setup computer.
|
||||
- Note down a reachable ip address (*ipv4*, *ipv6* or *tor*)
|
||||
|
||||
---
|
||||
|
||||
The installer will generate a password and local addresses on boot, then run ssh with these preconfigured.
|
||||
The installer shows it's deployment relevant information in two formats, a text form, as well as a QR code.
|
||||
|
||||
Sample boot screen shows:
|
||||
|
||||
- Root password
|
||||
- IP address
|
||||
- Optional Tor and mDNS details
|
||||
|
||||
```{ .bash .annotate .no-copy .nohighlight}
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ┌───────────────────────────┐ │
|
||||
│ │███████████████████████████│ # This is the QR Code (1) │
|
||||
│ │██ ▄▄▄▄▄ █▀▄█▀█▀▄█ ▄▄▄▄▄ ██│ │
|
||||
│ │██ █ █ █▀▄▄▄█ ▀█ █ █ ██│ │
|
||||
│ │██ █▄▄▄█ █▀▄ ▀▄▄▄█ █▄▄▄█ ██│ │
|
||||
│ │██▄▄▄▄▄▄▄█▄▀ ▀▄▀▄█▄▄▄▄▄▄▄██│ │
|
||||
│ │███▀▀▀ █▄▄█ ▀▄ ▄▀▄█ ███│ │
|
||||
│ │██▄██▄▄█▄▄▀▀██▄▀ ▄▄▄ ▄▀█▀██│ │
|
||||
│ │██ ▄▄▄▄▄ █▄▄▄▄ █ █▄█ █▀ ███│ │
|
||||
│ │██ █ █ █ █ █ ▄▄▄ ▄▀▀ ██│ │
|
||||
│ │██ █▄▄▄█ █ ▄ ▄ ▄ ▀█ ▄███│ │
|
||||
│ │██▄▄▄▄▄▄▄█▄▄▄▄▄▄█▄▄▄▄▄█▄███│ │
|
||||
│ │███████████████████████████│ │
|
||||
│ └───────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │Root password: cheesy-capital-unwell # password (2) │ │
|
||||
│ │Local network addresses: │ │
|
||||
│ │enp1s0 UP 192.168.178.169/24 metric 1024 fe80::21e:6ff:fe45:3c92/64 │ │
|
||||
│ │enp2s0 DOWN │ │
|
||||
│ │wlan0 DOWN # connect to wlan (3) │ │
|
||||
│ │Onion address: 6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion │ │
|
||||
│ │Multicast DNS: nixos-installer.local │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ Press 'Ctrl-C' for console access │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
1. This is not an actual QR code, because it is displayed rather poorly on text sites.
|
||||
This would be the actual content of this specific QR code prettified:
|
||||
```json
|
||||
{
|
||||
"pass": "cheesy-capital-unwell",
|
||||
"tor": "6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion",
|
||||
"addrs": [
|
||||
"2001:9e8:347:ca00:21e:6ff:fe45:3c92"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To generate the actual QR code, that would be displayed use:
|
||||
```shellSession
|
||||
echo '{"pass":"cheesy-capital-unwell","tor":"6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion","addrs":["2001:9e8:347:ca00:21e:6ff:fe45:3c92"]}' | nix run nixpkgs#qrencode -- -s 2 -m 2 -t utf8
|
||||
```
|
||||
2. The root password for the installer medium.
|
||||
This password is autogenerated and meant to be easily typeable.
|
||||
3. See [how to connect to wlan](./installer.md#optional-connect-to-wifi-manually).
|
||||
|
||||
!!! tip
|
||||
Use [KDE Connect](https://apps.kde.org/de/kdeconnect/) for easyily sharing QR codes from phone to desktop
|
||||
|
||||
## Cloud VMs
|
||||
|
||||
!!! note "skip this if using a physical machine"
|
||||
|
||||
Clan supports any cloud machine if it is reachable via SSH and supports `kexec`.
|
||||
|
||||
Steps:
|
||||
|
||||
- Go to the configuration panel and note down how to connect to the machine via ssh.
|
||||
|
||||
!!! tip "NixOS can cause strange issues when booting in certain cloud environments."
|
||||
If on Linode: Make sure that the system uses "Direct Disk boot kernel" (found in the configuration panel)
|
||||
|
||||
## Setting `targetHost`
|
||||
|
||||
In your nix files set the targetHost (reachable ip) that you retrieved in the previous step.
|
||||
|
||||
```{.nix title="clan.nix" hl_lines="9"}
|
||||
{
|
||||
# Ensure this is unique among all clans you want to use.
|
||||
meta.name = "my-clan";
|
||||
|
||||
inventory.machines = {
|
||||
# Define machines here.
|
||||
# The machine name will be used as the hostname.
|
||||
jon = {
|
||||
deploy.targetHost = "root@192.168.192.4"; # (1)
|
||||
};
|
||||
};
|
||||
# ...
|
||||
# elided
|
||||
}
|
||||
```
|
||||
|
||||
1. Use the ip address of your targetMachine that you want to deploy. If using the [flash-installer](./installer.md) it should display its local ip-address when booted.
|
||||
|
||||
!!! warning
|
||||
The use of `root@` in the target address implies SSH access as the `root` user.
|
||||
Ensure that the root login is secured and only used when necessary.
|
||||
|
||||
See also [how to set TargetHost](../target-host.md) for other methods.
|
||||
|
||||
## Retrieve the hardware report
|
||||
|
||||
By default clan uses [nixos-facter](https://github.com/nix-community/nixos-facter) which captures detailed information about the machine or virtual environment.
|
||||
|
||||
To generate the hardware-report (`facter.json`) run:
|
||||
|
||||
```bash
|
||||
clan machines update-hardware-config <machineName>
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```shell-session
|
||||
$ clan machines update-hardware-config jon
|
||||
[jon] $ nixos-facter
|
||||
Successfully generated: ./machines/jon/facter.json
|
||||
```
|
||||
|
||||
See [update-hardware-config cli reference](../../reference/cli/machines.md#machines-update-hardware-config) for further configuration possibilities if needed.
|
||||
|
||||
## Configure your disk schema
|
||||
|
||||
By default clan uses [disko](https://github.com/nix-community/disko) which allows for declarative disk partitioning.
|
||||
|
||||
To setup a disk schema for a machine run
|
||||
|
||||
```bash
|
||||
clan templates apply disk single-disk jon --set mainDisk ""
|
||||
```
|
||||
|
||||
Which should fail and give the valid options for the specific hardware:
|
||||
|
||||
```shellSession
|
||||
Invalid value for placeholder mainDisk - Valid options:
|
||||
/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368
|
||||
```
|
||||
|
||||
Re-run the command with the correct disk:
|
||||
|
||||
```bash
|
||||
clan templates apply disk single-disk jon --set mainDisk "/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368"
|
||||
```
|
||||
|
||||
Should now be successful
|
||||
|
||||
```shellSession
|
||||
Applied disk template 'single-disk' to machine 'jon'
|
||||
```
|
||||
|
||||
A disko.nix file should be created in `machines/jon`
|
||||
You can have a look and customize it if needed.
|
||||
|
||||
!!! tip
|
||||
For advanced partitioning, see [Disko templates](https://github.com/nix-community/disko-templates) or [Disko examples](https://github.com/nix-community/disko/tree/master/example).
|
||||
|
||||
!!! Danger
|
||||
Don't change the `disko.nix` after the machine is installed for the first time.
|
||||
|
||||
Changing disko configuration requires wiping and reinstalling the machine.
|
||||
|
||||
Unless you really know what you are doing.
|
||||
|
||||
## Deploy the machine
|
||||
|
||||
**Finally deployment time!** Use one of the following commands to build and deploy the image via SSH onto your machine.
|
||||
|
||||
### Deployment Commands
|
||||
|
||||
#### Using password auth
|
||||
|
||||
```bash
|
||||
clan machines install [MACHINE] --target-host <IP>
|
||||
```
|
||||
|
||||
#### Using QR JSON
|
||||
|
||||
```bash
|
||||
clan machines install [MACHINE] --json "[JSON]"
|
||||
```
|
||||
|
||||
#### Using QR image file
|
||||
|
||||
```bash
|
||||
clan machines install [MACHINE] --png [PATH]
|
||||
```
|
||||
|
||||
#### Option B: Cloud VM
|
||||
|
||||
```bash
|
||||
clan machines install [MACHINE] --target-host <IP>
|
||||
```
|
||||
|
||||
!!! success
|
||||
Your machine is all set up. 🎉 🚀
|
||||
|
||||
## Post-Deployment: Updating Machines
|
||||
|
||||
### Updating
|
||||
|
||||
Update a single machine:
|
||||
|
||||
```bash
|
||||
clan machines update jon
|
||||
```
|
||||
|
||||
Update all machines:
|
||||
|
||||
```bash
|
||||
clan machines update
|
||||
```
|
||||
|
||||
### Build Host Configuration
|
||||
|
||||
If a machine is too resource-limited, use another host.
|
||||
|
||||
If the machine does not have enough resources to run the NixOS evaluation or build itself,
|
||||
it is also possible to specify a build host.
|
||||
|
||||
During an update, the CLI will SSH into the build host and run `nixos-rebuild` from there.
|
||||
|
||||
```{.nix hl_lines="5" .no-copy}
|
||||
clan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.core.networking.buildHost = "root@<host_or_ip>";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Excluding from Automatic Updates
|
||||
|
||||
To exclude machines from being updated when running `clan machines update` without any machines specified,
|
||||
one can set the `clan.deployment.requireExplicitUpdate` option to true:
|
||||
|
||||
```{.nix hl_lines="5" .no-copy}
|
||||
clan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.deployment.requireExplicitUpdate = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
This is useful for machines that are not always online or are not part of the regular update cycle.
|
||||
114
docs/site/guides/getting-started/hardware-report-physical.md
Normal file
114
docs/site/guides/getting-started/hardware-report-physical.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Installing a Physical Machine
|
||||
|
||||
Now that you have created a machine, added some services, and set up secrets, this guide will walk you through how to deploy it.
|
||||
|
||||
|
||||
### Step 0. Prerequisites
|
||||
- [x] RAM > 2GB
|
||||
- [x] **Two Computers**: You need one computer that you're getting ready (we'll call this the Target Computer) and another one to set it up from (we'll call this the Setup Computer). Make sure both can talk to each other over the network using SSH.
|
||||
- [x] **Machine configuration**: See our basic [adding and configuring machine guide](./add-machines.md)
|
||||
- [x] **Initialized secrets**: See [secrets](../secrets.md) for how to initialize your secrets.
|
||||
- [x] **USB Flash Drive**: See [Clan Installer](./create-installer.md)
|
||||
|
||||
|
||||
### Image Installer
|
||||
This method makes use of the [image installers](./create-installer.md).
|
||||
|
||||
The installer will randomly generate a password and local addresses on boot, then run a SSH server with these preconfigured.
|
||||
The installer shows its deployment relevant information in two formats, a text form, as well as a QR code.
|
||||
|
||||
|
||||
This is an example of the booted installer.
|
||||
|
||||
```{ .bash .annotate .no-copy .nohighlight}
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ┌───────────────────────────┐ │
|
||||
│ │███████████████████████████│ # This is the QR Code (1) │
|
||||
│ │██ ▄▄▄▄▄ █▀▄█▀█▀▄█ ▄▄▄▄▄ ██│ │
|
||||
│ │██ █ █ █▀▄▄▄█ ▀█ █ █ ██│ │
|
||||
│ │██ █▄▄▄█ █▀▄ ▀▄▄▄█ █▄▄▄█ ██│ │
|
||||
│ │██▄▄▄▄▄▄▄█▄▀ ▀▄▀▄█▄▄▄▄▄▄▄██│ │
|
||||
│ │███▀▀▀ █▄▄█ ▀▄ ▄▀▄█ ███│ │
|
||||
│ │██▄██▄▄█▄▄▀▀██▄▀ ▄▄▄ ▄▀█▀██│ │
|
||||
│ │██ ▄▄▄▄▄ █▄▄▄▄ █ █▄█ █▀ ███│ │
|
||||
│ │██ █ █ █ █ █ ▄▄▄ ▄▀▀ ██│ │
|
||||
│ │██ █▄▄▄█ █ ▄ ▄ ▄ ▀█ ▄███│ │
|
||||
│ │██▄▄▄▄▄▄▄█▄▄▄▄▄▄█▄▄▄▄▄█▄███│ │
|
||||
│ │███████████████████████████│ │
|
||||
│ └───────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │Root password: cheesy-capital-unwell # password (2) │ │
|
||||
│ │Local network addresses: │ │
|
||||
│ │enp1s0 UP 192.168.178.169/24 metric 1024 fe80::21e:6ff:fe45:3c92/64 │ │
|
||||
│ │enp2s0 DOWN │ │
|
||||
│ │wlan0 DOWN # connect to wlan (3) │ │
|
||||
│ │Onion address: 6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion │ │
|
||||
│ │Multicast DNS: nixos-installer.local │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ Press 'Ctrl-C' for console access │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
1. This is not an actual QR code, because it is displayed rather poorly on text sites.
|
||||
This would be the actual content of this specific QR code prettified:
|
||||
```json
|
||||
{
|
||||
"pass": "cheesy-capital-unwell",
|
||||
"tor": "6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion",
|
||||
"addrs": [
|
||||
"2001:9e8:347:ca00:21e:6ff:fe45:3c92"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To generate the actual QR code, that would be displayed use:
|
||||
```shellSession
|
||||
echo '{"pass":"cheesy-capital-unwell","tor":"6evxy5yhzytwpnhc2vpscrbti3iktxdhpnf6yim6bbs25p4v6beemzyd.onion","addrs":["2001:9e8:347:ca00:21e:6ff:fe45:3c92"]}' | nix run nixpkgs#qrencode -- -s 2 -m 2 -t utf8
|
||||
```
|
||||
2. The root password for the installer medium.
|
||||
This password is autogenerated and meant to be easily typeable.
|
||||
3. See how to connect the installer medium to wlan [here](./create-installer.md).
|
||||
|
||||
!!!tip
|
||||
For easy sharing of deployment information via QR code, we highly recommend using [KDE Connect](https://apps.kde.org/de/kdeconnect/).
|
||||
|
||||
There are two ways to deploy your machine:
|
||||
|
||||
|
||||
=== "Password"
|
||||
### Generating a Hardware Report
|
||||
|
||||
The following command will generate a hardware report with [nixos-facter](https://github.com/nix-community/nixos-facter) and writes it back into your machine folder. The `--phases kexec` flag makes sure we are not yet formatting anything, instead if the target system is not a NixOS machine it will use [kexec](https://wiki.archlinux.org/title/Kexec) to switch to a NixOS kernel.
|
||||
|
||||
|
||||
```terminal
|
||||
clan machines install [MACHINE] \
|
||||
--update-hardware-config nixos-facter \
|
||||
--phases kexec \
|
||||
--target-host root@192.168.178.169
|
||||
```
|
||||
|
||||
=== "QR Code"
|
||||
### Generating a Hardware Report
|
||||
|
||||
The following command will generate a hardware report with [nixos-facter](https://github.com/nix-community/nixos-facter) and writes it back into your machine folder. The `--phases kexec` flag makes sure we are not yet formatting anything, instead if the target system is not a NixOS machine it will use [kexec](https://wiki.archlinux.org/title/Kexec) to switch to a NixOS kernel.
|
||||
|
||||
#### Using a JSON String or File Path
|
||||
Copy the JSON string contained in the QR Code and provide its path or paste it directly:
|
||||
```terminal
|
||||
clan machines install [MACHINE] --json [JSON] \
|
||||
--update-hardware-config nixos-facter \
|
||||
--phases kexec
|
||||
```
|
||||
|
||||
#### Using an Image Containing the QR Code
|
||||
Provide the path to an image file containing the QR code displayed by the installer:
|
||||
```terminal
|
||||
clan machines install [MACHINE] --png [PATH] \
|
||||
--update-hardware-config nixos-facter \
|
||||
--phases kexec
|
||||
```
|
||||
|
||||
|
||||
If you are using our template `[MACHINE]` would be `jon`
|
||||
30
docs/site/guides/getting-started/hardware-report-virtual.md
Normal file
30
docs/site/guides/getting-started/hardware-report-virtual.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Generate a VM Hardware Report
|
||||
|
||||
Now that you have created a machine, added some services, and set up secrets, this guide will walk you through how to deploy it.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
- [x] RAM > 2GB
|
||||
- [x] **Two Computers**: You need one computer that you're getting ready (we'll call this the Target Computer) and another one to set it up from (we'll call this the Setup Computer). Make sure both can talk to each other over the network using SSH.
|
||||
- [x] **Machine configuration**: See our basic [adding and configuring machine guide](./add-machines.md)
|
||||
|
||||
|
||||
Clan supports any cloud machine if it is reachable via SSH and supports `kexec`.
|
||||
|
||||
|
||||
??? tip "NixOS can cause strange issues when booting in certain cloud environments."
|
||||
If on Linode: Make sure that the system uses "Direct Disk boot kernel" (found in the configuration panel)
|
||||
|
||||
|
||||
The following command will generate a hardware report with [nixos-facter](https://github.com/nix-community/nixos-facter) and writes it back into your machine folder. The `--phases kexec` flag makes sure we are not yet formatting anything, instead if the target system is not a NixOS machine it will use [kexec](https://wiki.archlinux.org/title/Kexec) to switch to a NixOS kernel.
|
||||
|
||||
|
||||
```terminal
|
||||
clan machines install [MACHINE] \
|
||||
--update-hardware-config nixos-facter \
|
||||
--phases kexec \
|
||||
--target-host myuser@<IP>
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
After running the above command, be aware that the SSH login user changes from `myuser` to `root`. For subsequent SSH connections to the target machine, use `root` as the login user. This change occurs because the system switches to the NixOS kernel using `kexec`.
|
||||
@@ -41,7 +41,7 @@ By the end of this guide, you'll have a fresh NixOS configuration ready to push
|
||||
Create a new clan
|
||||
|
||||
```bash
|
||||
nix run git+https://git.clan.lol/clan/clan-core#clan-cli --refresh -- flakes create
|
||||
nix run https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli --refresh -- flakes create
|
||||
```
|
||||
|
||||
This should prompt for a *name*:
|
||||
@@ -118,17 +118,3 @@ To change the name of your clan edit `meta.name` in the `clan.nix` or `flake.nix
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
You can continue with **any** of the following steps at your own pace:
|
||||
|
||||
- [x] [Install Nix & Clan CLI](./index.md)
|
||||
- [x] [Initialize Clan](./index.md#add-clan-cli-to-your-shell)
|
||||
- [ ] [Create USB Installer (optional)](./installer.md)
|
||||
- [ ] [Add Machines](./add-machines.md)
|
||||
- [ ] [Add a User](./add-user.md)
|
||||
- [ ] [Add Services](./add-services.md)
|
||||
- [ ] [Deploy](./deploy.md) - Requires configured secrets
|
||||
- [ ] [Setup CI (optional)](./check.md)
|
||||
|
||||
109
docs/site/guides/getting-started/update.md
Normal file
109
docs/site/guides/getting-started/update.md
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
# Update Your Machines
|
||||
|
||||
Clan CLI enables you to remotely update your machines over SSH. This requires setting up a target address for each target machine.
|
||||
|
||||
### Setting `targetHost`
|
||||
|
||||
In your Nix files, set the `targetHost` to the reachable IP address of your new machine. This eliminates the need to specify `--target-host` with every command.
|
||||
|
||||
|
||||
```{.nix title="clan.nix" hl_lines="9"}
|
||||
{
|
||||
# Ensure this is unique among all clans you want to use.
|
||||
meta.name = "my-clan";
|
||||
|
||||
inventory.machines = {
|
||||
# Define machines here.
|
||||
# The machine name will be used as the hostname.
|
||||
jon = {
|
||||
deploy.targetHost = "root@192.168.192.4"; # (1)
|
||||
};
|
||||
};
|
||||
# [...]
|
||||
}
|
||||
```
|
||||
The use of `root@` in the target address implies SSH access as the `root` user.
|
||||
Ensure that the root login is secured and only used when necessary.
|
||||
|
||||
|
||||
### Setting a Build Host
|
||||
|
||||
If the machine does not have enough resources to run the NixOS evaluation or build itself,
|
||||
it is also possible to specify a build host instead.
|
||||
During an update, the cli will ssh into the build host and run `nixos-rebuild` from there.
|
||||
|
||||
|
||||
```{.nix hl_lines="5" .no-copy}
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.core.networking.buildHost = "root@<host_or_ip>";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
You can also override the build host via the command line:
|
||||
|
||||
```bash
|
||||
# Build on a remote host
|
||||
clan machines update jon --build-host root@192.168.1.10
|
||||
|
||||
# Build locally (useful for testing or when the target has limited resources)
|
||||
clan machines update jon --build-host local
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Make sure that the CPU architecture is the same for the buildHost as for the targetHost.
|
||||
Example:
|
||||
If you want to deploy to a macOS machine, your architecture is an ARM64-Darwin, that means you need a second macOS machine to build it.
|
||||
|
||||
### Updating Machine Configurations
|
||||
|
||||
Execute the following command to update the specified machine:
|
||||
|
||||
```bash
|
||||
clan machines update jon
|
||||
```
|
||||
|
||||
You can also update all configured machines simultaneously by omitting the machine name:
|
||||
|
||||
```bash
|
||||
clan machines update
|
||||
```
|
||||
|
||||
|
||||
### Excluding a machine from `clan machine update`
|
||||
|
||||
To exclude machines from being updated when running `clan machines update` without any machines specified,
|
||||
one can set the `clan.deployment.requireExplicitUpdate` option to true:
|
||||
|
||||
```{.nix hl_lines="5" .no-copy}
|
||||
buildClan {
|
||||
# ...
|
||||
machines = {
|
||||
"jon" = {
|
||||
clan.deployment.requireExplicitUpdate = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
This is useful for machines that are not always online or are not part of the regular update cycle.
|
||||
|
||||
### Uploading Flake Inputs
|
||||
|
||||
When updating remote machines, flake inputs are usually fetched by the build host.
|
||||
However, if your flake inputs require authentication (e.g., private repositories),
|
||||
you can use the `--upload-inputs` flag to upload all inputs from your local machine:
|
||||
|
||||
```bash
|
||||
clan machines update jon --upload-inputs
|
||||
```
|
||||
|
||||
This is particularly useful when:
|
||||
- Your flake references private Git repositories
|
||||
- Authentication credentials are only available on your local machine
|
||||
- The build host doesn't have access to certain network resources
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user