Compare commits

...

127 Commits

Author SHA1 Message Date
Jörg Thalheim
d74b9c06e1 mv out of place test_host_interface to localhost_test 2025-08-08 15:11:49 +02:00
Jörg Thalheim
f248cc91ad switch to flake-compat for private flake 2025-08-08 15:06:57 +02:00
lassulus
e2cb75784c Merge pull request 'Add default bootstrapNodes for data-mesher service' (#4555) from fix-4424 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4555
Reviewed-by: lassulus <clanlol@lassul.us>
2025-08-08 12:24:51 +00:00
pinpox
a8d48b22f8 Add default bootstrapnodes for data-mesher service 2025-08-08 11:18:08 +02:00
hsjobeki
c0f2bcf751 Merge pull request 'API/Machine: refactor api returns readonly' (#4627) from readonly into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4627
2025-08-08 08:54:49 +00:00
Johannes Kirschbauer
20c23fa64b API/Machine: refactor api returns readonly 2025-08-08 10:44:32 +02:00
clan-bot
23573e16c4 Merge pull request 'Update flake-parts' (#4620) from update-flake-parts into main 2025-08-08 07:18:42 +00:00
gitea-actions[bot]
eaee4e8cad Update flake-parts 2025-08-08 17:08:03 +10:00
clan-bot
10e43a8884 Merge pull request 'Update nixpkgs' (#4443) from update-nixpkgs into main 2025-08-08 05:57:37 +00:00
Michael Hoang
dc1cd03717 Merge pull request 'cli: fix missing newline in error message' (#4634) from push-lnmsprtyuntw into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4634
2025-08-08 05:30:05 +00:00
Michael Hoang
a71a5880c1 treewide: reformat 2025-08-08 15:28:37 +10:00
gitea-actions[bot]
6b137f21de Update nixpkgs 2025-08-08 15:28:37 +10:00
Michael Hoang
fbc14bf20f Merge pull request 'docs: fix command in Secrets guide' (#4635) from push-mxkpvktxwypw into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4635
2025-08-08 05:25:38 +00:00
Michael Hoang
2f2f3b6898 cli: fix missing newline in error message 2025-08-08 15:19:19 +10:00
Michael Hoang
3ae0f37bcb docs: fix command in Secrets guide 2025-08-08 15:16:58 +10:00
clan-bot
e49d432542 Merge pull request 'Update Clan Core for Checks' (#4633) from update-clan-core-for-checks into main 2025-08-08 03:00:34 +00:00
clan-bot
76955533cf Update pinned clan-core for checks 2025-08-08 02:51:46 +00:00
hsjobeki
d0ebc75135 Merge pull request 'ui/install: hook up stepper store and api' (#4626) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4626
2025-08-07 14:21:21 +00:00
Johannes Kirschbauer
40503306d1 cli/flash: fixup types 2025-08-07 16:09:36 +02:00
hsjobeki
da99407e74 Merge pull request 'Vars: rename classmethod to get_machine_generators' (#4629) from vars-fixing into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4629
2025-08-07 13:50:38 +00:00
Johannes Kirschbauer
915178765b Vars: rename classmethod to get_machine_generators 2025-08-07 15:31:17 +02:00
Johannes Kirschbauer
518de45d41 ui/install: hook up stepper store and api 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
7d23189c1c ui/intall: extend stories to mock router and api 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
eec55f73a2 ui/stepper: add stepper store to hook 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
484d274c3c ui/queries: add required flash data queries 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
a4b20f9167 UI/queries: migrate existing queries to useApiClient 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
dc7291c62b UI/api: add api client provider
This allows to switch out the used api backend for testing purposes.
Or for different plattforms
2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
a814a44bc6 UI/Select: add async option loading 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
86a6177126 UI/useClan: add error debugging 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
4536a5b4f5 clan/flash: provide defaults for verbose flash options 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
a9cfda9acb dirs: add local path to clan_core flake in dirs 2025-08-07 13:46:07 +02:00
Johannes Kirschbauer
b9f60218d7 UI/install: create installer improve wording 2025-08-07 13:46:07 +02:00
clan-bot
f69e28a133 Merge pull request 'Update Clan Core for Checks' (#4625) from update-clan-core-for-checks into main 2025-08-07 03:00:25 +00:00
clan-bot
1968230c28 Update pinned clan-core for checks 2025-08-07 02:51:46 +00:00
clan-bot
9cad074732 Merge pull request 'Update treefmt-nix' (#4621) from update-treefmt-nix into main 2025-08-06 15:20:13 +00:00
gitea-actions[bot]
4859a9ab7c Update treefmt-nix 2025-08-06 15:01:29 +00:00
hsjobeki
b53ecdc89d Merge pull request 'UI/install: add machine progress, minor stepper fixes' (#4619) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4619
2025-08-06 14:49:30 +00:00
Johannes Kirschbauer
19603e1a1c UI/install: add machine progress 2025-08-06 16:44:30 +02:00
Johannes Kirschbauer
7d20f3a33b UI/install: create installer improve wording 2025-08-06 16:43:48 +02:00
Johannes Kirschbauer
fa03c190f8 UI/install: split initial choice 2025-08-06 16:43:24 +02:00
Johannes Kirschbauer
65101ad55a UI/steps: make step footer next text customizable 2025-08-06 16:42:45 +02:00
Johannes Kirschbauer
e5db3e269b UI/stepper: hooks add helper to more typesafe define steps 2025-08-06 16:42:03 +02:00
Johannes Kirschbauer
073750e4c5 clanServices: update description of generators that can be left empty 2025-08-06 16:41:11 +02:00
DavHau
8bafbcb295 machines update: use 'localhost' for local build 2025-08-06 19:06:20 +07:00
hsjobeki
dbef6ced77 Merge pull request 'cubes and lighting: refinements on coloring of cubes and lighting to fit with design' (#4617) from ui/3d-cubes-refinement into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4617
2025-08-06 10:47:00 +00:00
Timo
65e7f9e6ca cubes and lighting: refinements on coloring of cubes and lighting to fit with design 2025-08-06 12:33:53 +02:00
Mic92
e1062ed97c Merge pull request 'docs/update: mention build-host local and uploading flake inputs' (#4614) from local-build into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4614
2025-08-06 10:18:28 +00:00
Jörg Thalheim
2eb1a56d8f update.md: mention build-host local and uploading flake inputs 2025-08-06 12:14:35 +02:00
clan-bot
0f499fc651 Merge pull request 'Update Clan Core for Checks' (#4608) from update-clan-core-for-checks into main 2025-08-06 03:00:32 +00:00
clan-bot
bcb7a1aa60 Update pinned clan-core for checks 2025-08-06 02:51:47 +00:00
Mic92
273c83ec27 Merge pull request 'update/flake-upload: use ssh-ng:// for nix copy' (#4597) from local-build into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4597
2025-08-05 20:47:23 +00:00
clan-bot
c74d7857da Merge pull request 'Update flake-parts' (#4607) from update-flake-parts into main 2025-08-05 20:12:05 +00:00
gitea-actions[bot]
11405966c6 Update flake-parts 2025-08-05 20:00:50 +00:00
hsjobeki
220839598d Merge pull request 'UI/install: bootstrap visuals for {createImage, Installer}' (#4605) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4605
2025-08-05 17:34:14 +00:00
Jörg Thalheim
44dcfa7844 rename --fetch-local to --upload-inputs 2025-08-05 19:31:29 +02:00
Jörg Thalheim
98f685f3ca update/flake-upload: set correct remote-program for macOS targets 2025-08-05 19:31:29 +02:00
Johannes Kirschbauer
9e43285ba8 UI/install: bootstrap steps for {DiskSchema, Vars, Summary} 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
c0bc0417a6 UI/install: fix metaHeader reactive 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
c90b69d499 UI/install: clean up create steps 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
0240acdf3e UI/modal: move common styling into meta header 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
92726ecebc UI/install: installer steps bootstrap visuals {TargetHost,hw_report} 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
b8e9546762 UI/install: bootstrap visuals for createInstaller 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
2039f034b1 UI/steps: minor layout fixes 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
0a329f43a8 UI/Modal: add 'disablePadding' 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
bde0a2845c UI/LoadingBar: allow injecting props 2025-08-05 19:29:06 +02:00
Johannes Kirschbauer
af3c6282c9 UI/Alert: make description optional 2025-08-05 19:29:06 +02:00
hsjobeki
73ab4d2a6e Merge pull request 'ui/install: add disk selection step to image create' (#4598) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4598
2025-08-05 16:16:54 +00:00
Johannes Kirschbauer
cc269c4f58 ui/install: add disk selection step to image create 2025-08-05 16:22:44 +02:00
Johannes Kirschbauer
20021a92ea ui/next-button: fix interface should extend button 2025-08-05 16:22:02 +02:00
Johannes Kirschbauer
7b54e9b033 ui/loading-bar: move into component 2025-08-05 16:20:56 +02:00
hsjobeki
7971eceb74 Merge pull request 'UI: extend components to prepare install workflows' (#4576) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4576
2025-08-05 13:36:31 +00:00
Johannes Kirschbauer
49a5763f69 Install: split steps into files 2025-08-05 15:32:43 +02:00
Johannes Kirschbauer
10694e58c8 install: use typed stepper 2025-08-05 15:10:38 +02:00
Johannes Kirschbauer
0d919c4fce hooks/stepper: add generic stepper hook 2025-08-05 15:09:29 +02:00
Johannes Kirschbauer
8cccf757a8 Fix: modal header slot was renamed to metaHeader 2025-08-05 13:52:03 +02:00
Johannes Kirschbauer
80c8cc8628 HostFileInput: allow overriding placeholder 2025-08-05 13:48:49 +02:00
Johannes Kirschbauer
ab63f0d7a4 divider: add extra class prop 2025-08-05 13:48:49 +02:00
Johannes Kirschbauer
06e0461ec9 Modal: add metaHeader slot, fix border styling 2025-08-05 13:48:49 +02:00
Johannes Kirschbauer
60ba00dd8f Select: add simple select dropdown for single select 2025-08-05 13:48:49 +02:00
Johannes Kirschbauer
90ef55f040 Label: add support for kobalte select 2025-08-05 13:48:49 +02:00
Johannes Kirschbauer
de81a5d810 Modal: prepare for install flow 2025-08-05 13:48:49 +02:00
Mic92
3fe65f1f12 Merge pull request 'machines update: support local build' (#4515) from local-build into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4515
2025-08-05 11:28:50 +00:00
Jörg Thalheim
6bb998f9dd update/flake-upload: use ssh-ng:// for nix copy
I had concurrency issues with `nix copy` and the ssh:// protocol when
using a machine both as the build host/target host (for different
machines), where it make the result path partially available to a
different update command thread.
2025-08-05 13:26:34 +02:00
DavHau
af7ce9b8ed machines update: support local build
Now the user can pass `--build-host local`, to select the local machine as a build host, in which case no ssh is used.

This means the admin machine does not necessarily have ssh set up to itself, which was confusing for many users.

Also this makes it easier to re-use a well configured nix remote build setup which is only available on the local machine. Eg if `--build-host local` nix' defaults for remote builds on that machine will be utilized.
2025-08-05 13:16:59 +02:00
DavHau
b74193514d ssh: refactor callers to use new Host interface 2025-08-05 13:16:59 +02:00
DavHau
c33fd4e504 ssh: Introduce LocalHost vs. Remote via Host interface
Motivation: local builds and deployments without ssh

Add a new interface `Host` which is implemented bei either `Remote` or `Localhost`

This simplifies all interactions with hosts. THe caller does ot need to know if the Host is remote or local in mot cases anymore
2025-08-05 13:16:59 +02:00
pinpox
65f3cb562a Merge pull request 'Reduce targetHost warning level' (#4594) from fix-target-warning into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4594
2025-08-05 09:55:30 +00:00
Mic92
355ff648d7 Merge pull request 'consistently use tarball urls in documentation' (#4589) from no-git-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4589
Reviewed-by: pinpox <clan@pablo.tools>
2025-08-05 09:55:15 +00:00
pinpox
f314eb04d6 Reduce targetHost warning level
The documentation currently lists setting targetHost in the NixOS
configuration as a slower, but valid option. Especially for dynamic
values, this is the recommended way but it results in a lot of annyoing
warnings.

This lowers the warning level, so it will only get printed on --debug
2025-08-05 11:52:06 +02:00
clan-bot
ebe206cdc0 Merge pull request 'Update Clan Core for Checks' (#4593) from update-clan-core-for-checks into main 2025-08-05 09:02:50 +00:00
clan-bot
2a138d3248 Update pinned clan-core for checks 2025-08-05 08:53:28 +00:00
Kenji Berthold
77810b1d4f Merge pull request 'clanServices: migrate syncthing module to clanServices' (#4558) from ke-migrate-clan-module-syncthing into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4558
2025-08-05 08:25:07 +00:00
a-kenji
77c840c9ba services/syncthing: Add basic usage documentation 2025-08-05 10:00:30 +02:00
a-kenji
9df7e6df1e services/syncthing: Add eval-test 2025-08-05 10:00:30 +02:00
a-kenji
a5e51f658d clanServices: migrate syncthing module to clanServices
Migrate the syncthing module from `clanModules` to `clanServices`.
2025-08-05 10:00:01 +02:00
clan-bot
98d5b3651b Merge pull request 'Update sops-nix' (#4591) from update-sops-nix into main 2025-08-04 20:12:26 +00:00
gitea-actions[bot]
713a1a550e Update sops-nix 2025-08-04 20:01:21 +00:00
Jörg Thalheim
d51d656391 consistently use tarball urls in documentation
otherwise users not using our templates will find themselves missing
git.
2025-08-04 11:20:14 +02:00
lassulus
0f79af697e Merge pull request 'simplify select debug output logic, add better error messages' (#4582) from select-debug into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4582
2025-08-02 20:55:31 +00:00
lassulus
0119fc06ca clan-cli select: show if select is cached or not 2025-08-02 21:26:39 +02:00
lassulus
5361261bd5 clan select: better error 2025-08-02 17:59:12 +02:00
lassulus
86e7bcc389 clan select: simplify select logging 2025-08-02 17:19:35 +02:00
clan-bot
79281aba90 Merge pull request 'Update flake-parts' (#4581) from update-flake-parts into main 2025-08-02 00:13:08 +00:00
gitea-actions[bot]
dade91c292 Update flake-parts 2025-08-02 00:00:52 +00:00
clan-bot
d285a0e716 Merge pull request 'Update treefmt-nix' (#4579) from update-treefmt-nix into main 2025-08-01 20:13:21 +00:00
gitea-actions[bot]
a97128db17 Update treefmt-nix 2025-08-01 20:01:25 +00:00
brianmcgee
ff7b49be5f Merge pull request 'feat: ui/auto-resizing-textarea' (#4562) from ui/auto-resizing-textarea into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4562
2025-08-01 09:40:33 +00:00
Luis Hebendanz
0b816a2672 Merge pull request 'Fix getting-started guide' (#4545) from Qubasa/clan-core:migrate-dyndns into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4545
2025-08-01 08:30:09 +00:00
hsjobeki
e6ec331da0 Merge pull request 'vars: add display attribute submodule for customisable ux' (#4559) from vars-display into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4559
Reviewed-by: DavHau <d.hauer.it@gmail.com>
2025-08-01 07:55:07 +00:00
Qubasa
0b05b0b1ec docs: review fixups
docs: review fixups

docs: review fixups

docs: fixup links in cli

docs: fixup links in cli
2025-08-01 14:53:31 +07:00
Michael Hoang
efd9beba15 Merge pull request 'docs: macOS' (#4563) from push-xptxwrqwvymq into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4563
2025-08-01 05:49:35 +00:00
Michael Hoang
dc03a9183f docs: macOS 2025-08-01 15:45:41 +10:00
Johannes Kirschbauer
ab3158ca07 vars/decrypt_dependencies: simplify 2025-08-01 04:01:43 +00:00
Brian McGee
75a1f7b67f feat(ui): auto-resizing textarea 2025-07-31 18:50:39 +01:00
brianmcgee
d453720a57 Merge pull request 'feat(ui): add tooltips for general section in machine detail pane' (#4561) from feat/machine-detail-tooltips into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4561
2025-07-31 17:48:27 +00:00
Brian McGee
a4331cc109 feat(ui): add tooltips for general section in machine detail pane 2025-07-31 18:38:56 +01:00
hsjobeki
434ce1af49 Merge pull request 'vars/list: doogfood get_machines into cli' (#4549) from vars-dog into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4549
2025-07-31 17:19:42 +00:00
Johannes Kirschbauer
488ee1ae63 users/display: add display properties 2025-07-31 16:45:20 +02:00
Johannes Kirschbauer
fc2e619046 vars: add display attribute submodule for customizable ux 2025-07-31 16:35:15 +02:00
Johannes Kirschbauer
cf6c3604ca generators_from_flake: vars always bind to store 2025-07-31 16:16:36 +02:00
hsjobeki
a3ea62caba Merge pull request 'docs: add vars/gaph doc-strings' (#4554) from vars-docs-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4554
2025-07-31 13:37:29 +00:00
Johannes Kirschbauer
e2e4837b29 docs: add vars/gaph doc-strings 2025-07-31 15:26:22 +02:00
Johannes Kirschbauer
96fc3d409a vars/list: untangle generators_from_flake and get_generators 2025-07-31 15:17:57 +02:00
Johannes Kirschbauer
392f244361 vars/list: doogfood get_machines into cli
This is important otherwise cli diverges from api
2025-07-31 14:02:50 +02:00
Qubasa
d2529704d5 docs: Split up getting-started guide in a Physical and Virtual installation, and properly document how to install on non-NixOS machines
docs: git add docs
2025-07-31 17:06:44 +07:00
Qubasa
62a3503987 clan-lib: Always set a static private key for nixos-anywhere, to make --phases work properly 2025-07-31 17:06:00 +07:00
Qubasa
c39aa89e29 docs: Add a nixos-anywhere debugging hint 2025-07-31 17:06:00 +07:00
215 changed files with 5517 additions and 2104 deletions

View File

@@ -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 = ./.;

View File

@@ -1,6 +1,6 @@
{ fetchgit }:
fetchgit {
url = "https://git.clan.lol/clan/clan-core.git";
rev = "ba8a80eccf091fc7f99aef3895e31617d3813d20";
sha256 = "189srg4mc5y3prapm8day0x0wpibbqc72hrnl61agsmiq7cfmbkd";
rev = "d0ebc75135b125fd509558c7680fa2459af91195";
sha256 = "1k9wpy661dhwas7z05jkn8157pgmr408dn67zd9px9iphmrf7bry";
}

View File

@@ -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
{

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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}",

View File

@@ -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

View File

@@ -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 ];

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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.

View File

@@ -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

View File

@@ -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)"

View File

@@ -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;
};

View File

@@ -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);
};
}

View File

@@ -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);
};
};
};
}

View File

@@ -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 =

View File

@@ -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 = {

View File

@@ -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;
};
};

View 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.

View 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;
};
};
}

View 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;
};
};
}

View 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";
};
};
}

View 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)
'';
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -0,0 +1 @@
LJOGYGS-RQPWIHV-HD4B3GK-JZPVPK6-VI3IAY5-CWQWIXK-NJSQMFH-KXHOHA4

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -0,0 +1 @@
K2FAKIB-HUUM7M7-CUPDN5Y-NORZCJP-F43EFQY-UPV4AUO-GAI3VZW-PH5TRQX

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -0,0 +1 @@
UWOQP5L-DE3OCK6-BIQLRGV-JFZ7JTJ-VUYUFDN-F3PJNH4-VS2AJKR-PQJKQQD

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

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

View File

@@ -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"
}
}

View File

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

View File

@@ -0,0 +1 @@
OORI2OC-364PI2W-LFOFX4X-7PB752I-QVLZWG3-EC4XJ4C-RPJQWTV-XYPRQA6

View File

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

View File

@@ -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"
}
}

View File

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

View 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
'';
};
}

View File

@@ -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
View 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
{ }
);
}

View File

@@ -15,5 +15,5 @@
inputs.systems.url = "github:nix-systems/default";
outputs = _: { };
outputs = inputs: inputs;
}

View File

@@ -1 +0,0 @@
sha256-LdjcFZLL8WNldUO2LbdqFlss/ERiGeXVqMee0IxV2z0=

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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/";
}
];
};
};
};
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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";

View File

@@ -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)

View 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>
```

View File

@@ -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 \

View File

@@ -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.

View 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`

View 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`.

View File

@@ -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)

View 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