Compare commits

..

560 Commits

Author SHA1 Message Date
Johannes Kirschbauer
fea4c2dc48 vm_manager: make timesink happy 2025-08-13 20:17:37 +02:00
Johannes Kirschbauer
80bbc6d7a3 cli/clan-inspect: remove unsused command 2025-08-13 19:47:38 +02:00
clan-bot
765bdb262a Merge pull request 'Update clan-core-for-checks in devFlake' (#4731) from update-devFlake-clan-core-for-checks into main 2025-08-13 15:22:38 +00:00
gitea-actions[bot]
05c00fbe82 Update clan-core-for-checks in devFlake 2025-08-13 15:01:35 +00:00
clan-bot
7e97734797 Merge pull request 'Update clan-core-for-checks in devFlake' (#4727) from update-devFlake-clan-core-for-checks into main 2025-08-13 13:57:32 +00:00
gitea-actions[bot]
6384c4654e Update clan-core-for-checks in devFlake 2025-08-13 13:54:09 +00:00
DavHau
72d3ad09a4 vars: refactor - pass Machine objects to run_generators 2025-08-13 12:45:47 +00:00
DavHau
a535450ec0 vars: refactor - unify get_generators and _get_closure 2025-08-13 12:45:47 +00:00
Mic92
aaeb616f82 Merge pull request 'Drop update-private-flake-inputs ci action' (#4730) from init-wireguard-service into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4730
2025-08-13 12:42:59 +00:00
Jörg Thalheim
434edeaae1 drop update-private-flake-inputs 2025-08-13 14:35:43 +02:00
Mic92
a4efd3cb16 Merge pull request 'update-sops-nix2' (#4719) from update-sops-nix2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4719
2025-08-13 12:34:37 +00:00
Jörg Thalheim
13131ccd6e docs/wireguard: put requirements at the top 2025-08-13 14:34:15 +02:00
hsjobeki
3a8309b01f Merge pull request 'UI/install: add loading animation' (#4723) from install-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4723
2025-08-13 12:19:23 +00:00
Johannes Kirschbauer
10065a7c8f UI/install: add loading to button 2025-08-13 14:15:52 +02:00
Johannes Kirschbauer
176b54e29d UI/Button: move state out of the button 2025-08-13 14:15:29 +02:00
Jörg Thalheim
be048d8307 morph/flash: use patched clan-core-for-checks
the other one doesn't override flake.lock
2025-08-13 11:41:09 +00:00
gitea-actions[bot]
52fcab30e7 Update sops-nix 2025-08-13 11:41:09 +00:00
Mic92
d3b423328f Merge pull request 'Add wireguard service module' (#3354) from init-wireguard-service into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3354
2025-08-13 10:55:48 +00:00
Jörg Thalheim
1177e84dcc vars/generate: print the files that were found when files are missing
this helps fixing typos in the generator scripts
2025-08-13 12:29:52 +02:00
pinpox
414952dfa3 Add wireguard service module 2025-08-13 12:29:52 +02:00
DavHau
24194011ac vars: refactor - remove unnecessary return values
The boolean return value signaling if anything was ran or not isn't that useful. We are not doing anything with it.
2025-08-13 12:54:05 +07:00
DavHau
4f78a8ff94 Merge pull request 'networking_3' (#4507) from networking_3 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4507
2025-08-13 05:20:03 +00:00
DavHau
068b5d4c1e install: fix error message when target host not specified 2025-08-13 12:04:14 +07:00
DavHau
adccef4757 install: fix torify package not available 2025-08-13 12:04:14 +07:00
Qubasa
980d94d47d clan_cli: Improve cli message if no networks present 2025-08-13 12:04:14 +07:00
lassulus
a50b25eea2 clan-cli network: refactor, use new networking in ssh and install commands 2025-08-13 12:04:14 +07:00
lassulus
017989841d refactor: remove DeployInfo class and use Network/Remote directly
- Remove DeployInfo class entirely, replacing with direct Remote usage
- Update parse_qr_json_to_networks to return dict with network and remote
- Refactor all code to work with Remote lists instead of DeployInfo
- Add get_remote_for_machine context manager for network connections
- Update tests to use new Network/Remote structure
2025-08-13 12:04:14 +07:00
lassulus
c14a5fcc69 refactor: move ssh/upload.py from cli to lib
Move the upload module to clan_lib to better organize SSH-related
utilities. Updated all imports across the codebase.
2025-08-13 12:04:14 +07:00
clan-bot
4f60345ba7 Merge pull request 'Update clan-core-for-checks in devFlake' (#4726) from update-devFlake-clan-core-for-checks into main 2025-08-13 00:21:42 +00:00
gitea-actions[bot]
ece48d3b5f Update clan-core-for-checks in devFlake 2025-08-13 00:01:32 +00:00
clan-bot
4eea8d24f0 Merge pull request 'Update clan-core-for-checks in devFlake' (#4725) from update-devFlake-clan-core-for-checks into main 2025-08-12 20:26:23 +00:00
gitea-actions[bot]
49099df3fb Update clan-core-for-checks in devFlake 2025-08-12 20:01:32 +00:00
Johannes Kirschbauer
62ccba9fb5 ui/install: test connection 2025-08-12 21:04:18 +02:00
Johannes Kirschbauer
0b44770f1f UI/install: add loading animation 2025-08-12 20:45:55 +02:00
hsjobeki
61c3d7284a Merge pull request 'pkgs/clan/lib(install): implement separate nixos-anywhere install phases' (#4710) from ke-install-phases into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4710
2025-08-12 15:34:15 +00:00
clan-bot
44b1be5ed4 Merge pull request 'Update clan-core-for-checks in devFlake' (#4717) from update-devFlake-clan-core-for-checks into main 2025-08-12 15:30:32 +00:00
Jörg Thalheim
88871bea69 clan_lib/flash: remove trailing whitespace 2025-08-12 17:14:52 +02:00
Johannes Kirschbauer
5141ea047c install: init secrets 2025-08-12 17:11:58 +02:00
gitea-actions[bot]
ff6a03a646 Update clan-core-for-checks in devFlake 2025-08-12 15:01:31 +00:00
Johannes Kirschbauer
bc379c985d ui/install: update storybook mock data 2025-08-12 16:35:34 +02:00
Johannes Kirschbauer
69d8b029d6 ui/install: fix alignment of some steps 2025-08-12 16:35:34 +02:00
Johannes Kirschbauer
f3617b0407 ui/vars: sanitize generator and prompt field names 2025-08-12 16:35:34 +02:00
Johannes Kirschbauer
a5205681cc ui/select: fix z-index of trigger 2025-08-12 16:35:34 +02:00
Johannes Kirschbauer
9880847d43 install: add progress to ui 2025-08-12 16:35:34 +02:00
a-kenji
8aa88b22ab pkgs/clan/lib(install): implement separate nixos-anywhere install phases
Split the `nixos-anywhere` phases into it's components,
so we provide the user with better feedback.

Closes: #4682
2025-08-12 16:35:34 +02:00
brianmcgee
ff979eba61 Merge pull request 'ui/integrate-clan-tags-machine-detail' (#4716) from ui/integrate-clan-tags-machine-detail into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4716
2025-08-12 14:20:27 +00:00
Brian McGee
5d1abbd303 feat(ui): integrate tags info from field schema into tags section 2025-08-12 15:16:59 +01:00
Brian McGee
92e9bb2ed8 feat(ui): integrate list_tags api call into machine detail 2025-08-12 14:46:43 +01:00
brianmcgee
ea75c9bfa9 Merge pull request 'feat(ui): add small and transparent variants for Alert component' (#4713) from feat/small-variant-for-alert into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4713
Reviewed-by: hsjobeki <hsjobeki@gmail.com>
2025-08-12 12:04:31 +00:00
hsjobeki
2adf65482d Merge pull request 'feat(api): add list_inventory_tags' (#4692) from feat/machine-tags-writeability into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4692
2025-08-12 11:33:49 +00:00
DavHau
5684ddf104 vars: health check also for API not just cli 2025-08-12 11:28:02 +00:00
Johannes Kirschbauer
f74e444120 api/tags: add docs 2025-08-12 13:19:11 +02:00
Johannes Kirschbauer
0ef57bfc8e api/tags: add init.py for pytest 2025-08-12 13:07:36 +02:00
Brian McGee
8f43af3c48 feat(ui): add transparent option for Alert component 2025-08-12 11:52:38 +01:00
Brian McGee
eeaec583cb feat(ui): add small variant for Alert component 2025-08-12 11:52:37 +01:00
Johannes Kirschbauer
a9d1ff83f2 api/tags: split list into options and non-configurable tags 2025-08-12 12:41:15 +02:00
DavHau
89cb22147c Revert "machines update: support --target-host localhost"
This reverts commit a2818d4946cc66a08b9dd7a1ab95dc48ea708fe3.

Setting `--target-host localhost` breaks with:
sudo: no askpass program specified, try setting SUDO_ASKPASS
2025-08-12 17:39:40 +07:00
Jörg Thalheim
1006fc755e clanTest/vars-executor: add debugging to finalScript 2025-08-12 12:38:47 +02:00
clan-bot
f100177df3 Merge pull request 'Update clan-core-for-checks in devFlake' (#4709) from update-devFlake-clan-core-for-checks into main 2025-08-12 10:26:57 +00:00
Johannes Kirschbauer
cbd3b08296 api/tags: add from all possible sources 2025-08-12 11:05:10 +01:00
Brian McGee
2608bee30a feat(api): add list_inventory_tags 2025-08-12 11:05:10 +01:00
gitea-actions[bot]
a29459a384 Update clan-core-for-checks in devFlake 2025-08-12 10:01:30 +00:00
DavHau
1abdd45821 vars: add doc comments for fix() and health_check() 2025-08-12 09:13:54 +00:00
brianmcgee
b058fcc8eb Merge pull request 'fix(ui): swap colors for inverted/non-inverted in Divider component' (#4696) from fix/invert-default-color-scheme-divider into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4696
2025-08-12 09:09:20 +00:00
Brian McGee
24ae95a007 fix(ui): swap colors for inverted/non-inverted in Divider component
Fixes #4602
2025-08-12 10:00:40 +01:00
brianmcgee
39510b613f Merge pull request 'fix color=inherit in typography component' (#4693) from fix/typography-color-inherit into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4693
2025-08-12 09:00:20 +00:00
Brian McGee
dcdab61d13 feat(ui): improve color=inherit example in typography story 2025-08-12 09:56:26 +01:00
hsjobeki
f86fe07b63 Merge pull request 'fix/commit-mono-woff2' (#4695) from fix/commit-mono-woff2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4695
2025-08-12 07:51:57 +00:00
Brian McGee
84bf9fe3cf fix(ui): load CommitMono via woff2 instead of OTF
Fixes #4611
2025-08-12 07:48:21 +00:00
Luis Hebendanz
9737ce51b1 Merge pull request 'Fix clan select hiding error messages' (#4704) from Qubasa/clan-core:clan_select_error_msg into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4704
2025-08-12 07:46:35 +00:00
Qubasa
3c1c6c1942 flake.py: Add a custom error message for missing clan export
test_clan_create_api: Fix check for SelectCmdError
2025-08-12 14:41:26 +07:00
Qubasa
0904c9da60 flake.py: Don't hide error messages 2025-08-12 14:18:19 +07:00
Luis Hebendanz
73e03c21f7 Merge pull request 'templates: Fix missing export of clan = clan.config;' (#4703) from Qubasa/clan-core:fix_templates2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4703
2025-08-12 07:05:07 +00:00
Qubasa
743f05a1b5 templates: Fix missing export of clan = clan.config; 2025-08-12 13:50:09 +07:00
DavHau
e28b8dc944 vars: refactor - health checks on multiple generators 2025-08-12 13:12:49 +07:00
clan-bot
8d871aafe8 Merge pull request 'Update clan-core-for-checks in devFlake' (#4701) from update-devFlake-clan-core-for-checks into main 2025-08-12 05:25:52 +00:00
DavHau
26559ff88b Merge pull request 'machines update: support --target-host localhost' (#4623) from jfly/clan-core:localhost-as-target-host-without-ssh into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4623
2025-08-12 05:10:53 +00:00
gitea-actions[bot]
b39aead1db Update clan-core-for-checks in devFlake 2025-08-12 05:01:29 +00:00
DavHau
caae6c7a55 docs: improve wording on getting-started
I found the header text to be confusing at places:
- `at least one machine to push to`: Pushing what? Where? DO I ned a git server? Do you mean installing?

-> I re-phrased the text

Also removed the tree command, as this is not available on most systems by default
2025-08-12 11:33:28 +07:00
DavHau
df90fb20a0 clan-lib/hosts: reintroduce test for localhost 2025-08-12 10:18:55 +07:00
clan-bot
d6577ec05d Merge pull request 'Update clan-core-for-checks in devFlake' (#4698) from update-devFlake-clan-core-for-checks into main 2025-08-12 00:17:53 +00:00
gitea-actions[bot]
9a66170aa1 Update clan-core-for-checks in devFlake 2025-08-12 00:01:27 +00:00
clan-bot
3effd8fd9a Merge pull request 'Update clan-core-for-checks in devFlake' (#4697) from update-devFlake-clan-core-for-checks into main 2025-08-11 20:18:06 +00:00
hsjobeki
86d6b42f4c Merge pull request 'api/machines: add tests for tags readOnly' (#4694) from readonly-tags into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4694
2025-08-11 20:09:25 +00:00
gitea-actions[bot]
5e2ffa9491 Update clan-core-for-checks in devFlake 2025-08-11 20:01:27 +00:00
Mic92
ba58d6d91a Merge pull request 'devFlake: switch back to main' (#4691) from update-nixpkgs-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4691
2025-08-11 16:44:31 +00:00
Jörg Thalheim
38e2d00bbc flash test: enable useNixStoreImage
we had some weird corruption, this hopefully helps. Unclear if it's
slower..
2025-08-11 18:23:28 +02:00
Johannes Kirschbauer
4f29f2e2ca api/machines: add tests for tags readOnly 2025-08-11 18:07:02 +02:00
Jörg Thalheim
5c23e24315 devFlake: switch back to main 2025-08-11 18:02:56 +02:00
Johannes Kirschbauer
1009c61c9f api/machines: use get_value_by_path utility for nested dict access 2025-08-11 17:49:25 +02:00
hsjobeki
0817f83b0b Merge pull request 'api/machines: expose readonly tags' (#4690) from readonly-tags into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4690
2025-08-11 15:40:42 +00:00
Mic92
4f191f3ebe Merge pull request 'Bump nixpkgs + ci fixes' (#4688) from update-nixpkgs-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4688
2025-08-11 15:35:55 +00:00
Johannes Kirschbauer
9f48b7a2fa inventory: rename 'unmerge-lists' to 'list_difference' 2025-08-11 17:35:31 +02:00
Johannes Kirschbauer
b17466c84b api/machines: expose readonly tags 2025-08-11 17:30:35 +02:00
Jörg Thalheim
d2814efcde update: sort precached vars 2025-08-11 17:18:01 +02:00
Jörg Thalheim
6a5a83f57a devFlake: switch to clan-core-for-checks to this branch 2025-08-11 17:17:27 +02:00
Jörg Thalheim
9e9ab22c37 skip private inputs again 2025-08-11 17:16:34 +02:00
gitea-actions[bot]
2840d3a5fc Update nixpkgs 2025-08-11 17:16:34 +02:00
clan-bot
a305f98586 Merge pull request 'Update clan-core-for-checks in devFlake' (#4689) from update-devFlake-clan-core-for-checks into main 2025-08-11 15:13:45 +00:00
hsjobeki
96fe387399 Merge pull request 'api: init notification queue' (#4678) from ui-notify into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4678
2025-08-11 15:13:42 +00:00
gitea-actions[bot]
d022f26c2c Update clan-core-for-checks in devFlake 2025-08-11 15:01:28 +00:00
Johannes Kirschbauer
d1f5a8e263 ui/install: set fixed dimensions for modal 2025-08-11 17:00:20 +02:00
Mic92
a6a5c1e21d Merge pull request 'zt-tcp-relay: 2023-07-11 -> 2025-07-03' (#4687) from zt-tcp-relay into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4687
2025-08-11 14:27:35 +00:00
Jörg Thalheim
f1867bdd7a zt-tcp-relay: 2023-07-11 -> 2025-07-03 2025-08-11 16:23:10 +02:00
DavHau
ee8e44d255 vars/generators: refactor - identify generators by name + machine
This brings us one step closer towards re-generating over multiple machines reliably
2025-08-11 21:02:55 +07:00
Mic92
f730f4fa06 Merge pull request 'make clan-core-for-checks a dev flake input' (#4247) from private-flake-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4247
2025-08-11 13:45:22 +00:00
Jörg Thalheim
567570e89c make clan-core-for-checks a dev flake input 2025-08-11 15:36:40 +02:00
Johannes Kirschbauer
54797dd5f5 ui/install: hook up notification bus 2025-08-11 15:30:55 +02:00
Johannes Kirschbauer
c70c588c1c ui/api: init message bus subscriber hooks 2025-08-11 15:24:41 +02:00
Johannes Kirschbauer
bb6fab1168 api: init notification queue 2025-08-11 15:24:41 +02:00
Johannes Kirschbauer
0859a86ce0 ui/api: simplify types in api 2025-08-11 15:24:41 +02:00
hsjobeki
1524dc963e Merge pull request 'ui/install: hook up {cancel, close} method' (#4685) from install-cancel into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4685
2025-08-11 13:24:26 +00:00
Johannes Kirschbauer
eebd3fa4ec ui/install: hook up {cancel, close} method 2025-08-11 15:15:00 +02:00
brianmcgee
a1ff794d57 Merge pull request 'feat: ui/machine-detail-writeability' (#4577) from ui/machine-detail-writeability into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4577
2025-08-11 11:40:30 +00:00
pinpox
6693cda465 Merge pull request 'Refactor admin module' (#4681) from fix-admin-service-split into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4681
2025-08-11 11:36:24 +00:00
Brian McGee
bf0691587d feat(ui): integrate machine writeability 2025-08-11 12:34:00 +01:00
brianmcgee
deecb966ce Merge pull request 'wip(ui): MachineTags component and tags section in machine detail pane' (#4560) from ui/machine-tags into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4560
2025-08-11 11:31:11 +00:00
Brian McGee
2d2d9c9dca chore(ui): remove some console.log statements 2025-08-11 12:21:36 +01:00
Brian McGee
e0e16de144 feat(ui): MachineTags component and tags section in machine detail pane 2025-08-11 12:21:33 +01:00
pinpox
75c60a6103 Refactor admin module 2025-08-11 13:13:24 +02:00
pinpox
1373670dfc Merge pull request 'Migrate dyndns to clanServices' (#4390) from migrate-dyndns into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4390
2025-08-11 10:23:02 +00:00
pinpox
03b13e9ed4 Migrate dyndns to clanServices 2025-08-11 12:19:21 +02:00
pinpox
a79027c312 Merge pull request 'Improve hello-world example service' (#4679) from better-hello into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4679
2025-08-11 10:16:18 +00:00
pinpox
bdcdf4e788 Merge branch 'main' into better-hello 2025-08-11 10:11:42 +00:00
pinpox
e3ed9d7b4b Improve hello-world example service 2025-08-11 12:03:04 +02:00
Mic92
ddf2b57b3f Merge pull request 'install: precache vars' (#4680) from install-vars into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4680
2025-08-11 10:02:33 +00:00
Jörg Thalheim
5ab3a164c8 install: precache vars 2025-08-11 11:51:06 +02:00
DavHau
073027f7c6 vars/generate: refactor - mark private as private 2025-08-11 11:46:25 +07:00
DavHau
d0374c0d7c vars/keygen: add --no-interactive flags
This is useful to force on-interactive mode with automatic key selection. In unit tests this is necessary. When debugging a test on a real terminal interactive mode is not desired
2025-08-11 11:29:25 +07:00
clan-bot
6137701532 Merge pull request 'Update Clan Core for Checks' (#4675) from update-clan-core-for-checks into main 2025-08-11 03:03:57 +00:00
clan-bot
1560c5f8cf Update pinned clan-core for checks 2025-08-11 02:51:46 +00:00
Kenji Berthold
5d884cecc2 Merge pull request 'formatter: init sizelint' (#4673) from kenji/init-sizelint into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4673
2025-08-10 21:57:10 +00:00
a-kenji
8a3cade082 formatter: init sizelint 2025-08-10 23:50:34 +02:00
Kenji Berthold
10b4389309 Merge pull request 'chore(inputs): bump treefmt-nix' (#4672) from kenji/ke-bump-treefmt into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4672
2025-08-10 21:49:12 +00:00
a-kenji
2879c72a89 chore(inputs): bump treefmt-nix
Flake lock file updates:

• Updated input 'treefmt-nix':
    'github:numtide/treefmt-nix/1298185c05a56bff66383a20be0b41a307f52228?narHash=sha256-B%2B3g9%2B76KlGe34Yk9za8AF3RL%2BlnbHXkLiVHLjYVOAc%3D' (2025-08-06)
  → 'github:numtide/treefmt-nix/7d81f6fb2e19bf84f1c65135d1060d829fae2408?narHash=sha256-2vX8QjO5lRsDbNYvN9hVHXLU6oMl%2BV/PsmIiJREG4rE%3D' (2025-08-10)
2025-08-10 23:41:48 +02:00
clan-bot
547e912c4e Merge pull request 'Update treefmt-nix in devFlake' (#4671) from update-devFlake-treefmt-nix into main 2025-08-10 20:28:18 +00:00
Kenji Berthold
87125f1ff7 Merge branch 'main' into update-devFlake-treefmt-nix 2025-08-10 20:25:17 +00:00
pinpox
804f606384 Merge pull request 'Migrate localbackup to clanServices' (#4656) from migrate-localbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4656
2025-08-10 20:14:27 +00:00
gitea-actions[bot]
997c7de942 Update treefmt-nix in devFlake 2025-08-10 20:06:20 +00:00
hsjobeki
e7323999f2 Merge pull request 'Sops: generate key should always 'generate' a key pair when beeing called' (#4664) from sops-keys into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4664
2025-08-10 12:09:47 +00:00
Johannes Kirschbauer
31d3997358 Secrets: fix tests 2025-08-10 13:06:42 +02:00
Johannes Kirschbauer
62b748624d Secrets: allow to generate additional keys with --new 2025-08-10 13:04:34 +02:00
Johannes Kirschbauer
29f440a482 Sops: generate key should always 'generate' a key pair when beeing called
Check if you want to generate a new key as a caller
2025-08-10 13:04:34 +02:00
clan-bot
f15fd1be52 Merge pull request 'Update Clan Core for Checks' (#4662) from update-clan-core-for-checks into main 2025-08-10 02:59:55 +00:00
clan-bot
beaacf81c6 Update pinned clan-core for checks 2025-08-10 02:51:46 +00:00
pinpox
1ae023f4bf Remove old backup test 2025-08-10 00:56:36 +02:00
hsjobeki
9becd4e0c4 Merge pull request 'Vars: remove spurious 'fake_prompt' with mocked method' (#4659) from cleaner into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4659
2025-08-09 22:53:18 +00:00
Johannes Kirschbauer
454b09a67e Vars: remove unused return 2025-08-10 00:19:08 +02:00
Johannes Kirschbauer
787781c2ad Vars: remove spurious 'fake_prompt' with mocked method 2025-08-10 00:18:01 +02:00
hsjobeki
469c6ba42d Merge pull request 'UI: Resolve some more install blockers' (#4657) from feat-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4657
2025-08-09 18:12:35 +00:00
hsjobeki
21f335fa15 Merge pull request 'fix: remove spurious test file' (#4658) from cleaner into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4658
2025-08-09 18:08:30 +00:00
Johannes Kirschbauer
d98b76e734 fix: remove spurious test file 2025-08-09 20:04:11 +02:00
Johannes Kirschbauer
e2cb1fd83f UI/install: run generators 2025-08-09 20:02:06 +02:00
Johannes Kirschbauer
91646b323a API/generators: adjust filtering to match cli logic 2025-08-09 19:58:23 +02:00
Johannes Kirschbauer
121548ffb7 UI/onboarding: init admin instance 2025-08-09 19:56:23 +02:00
Johannes Kirschbauer
966a3ee919 UI/queries: init generators query 2025-08-09 19:56:23 +02:00
Johannes Kirschbauer
d007b0f1b3 API/generators: expose display attribute of prompts 2025-08-09 19:56:23 +02:00
Johannes Kirschbauer
034982bff2 API/modules: init create_service_instance endpoint 2025-08-09 19:56:04 +02:00
pinpox
7c37bddeea Add localbackup clan service 2025-08-09 19:52:41 +02:00
Kenji Berthold
c1a87e5c6a Merge pull request 'pkgs/clan/lib(directory): Add API function to query the configured directory' (#4655) from kenji/ke-add-clan-dir-api into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4655
2025-08-09 15:29:55 +00:00
a-kenji
e5bea3d49a pkgs/clan/lib(directory): Add API function to query the configured directory
Similar to the implementation in #4526
Co-authored-by: Mayeu <m@mayeu.me>
2025-08-09 15:39:43 +02:00
hsjobeki
40682972ef Merge pull request 'UI/install: add configure disk' (#4653) from feat-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4653
2025-08-09 11:55:56 +00:00
Johannes Kirschbauer
6035455cba Root-password: add display attributes 2025-08-09 13:48:32 +02:00
Johannes Kirschbauer
9be53a9a63 UI/install: add configure disk 2025-08-09 13:48:06 +02:00
Kenji Berthold
29ec9dbe26 Merge pull request 'pkgs/clan/lib: Handle basecase of directory functionality' (#4654) from kenji/ke-add-directory into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4654
2025-08-09 11:12:23 +00:00
a-kenji
a1874c940e pkgs/cli/lib: Fix api test 2025-08-09 12:48:13 +02:00
a-kenji
d115705cb4 pkgs/clan/lib/directory: Improve error message formatting 2025-08-09 12:32:18 +02:00
a-kenji
eceb6eb999 pkgs/clan/lib: Handle basecase of directory functionality 2025-08-09 12:30:28 +02:00
hsjobeki
d25cace522 Merge pull request 'Host: convert to proctocol to avoid forced inheritance' (#4647) from host-protocol into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4647
2025-08-09 09:29:09 +00:00
hsjobeki
3c6567e67d Merge pull request 'Zerotier: make networkId nullable' (#4652) from install-fixing into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4652
2025-08-09 09:22:23 +00:00
Johannes Kirschbauer
628e45293e Update: avoid mutating and shadowing arguments 2025-08-09 11:18:50 +02:00
Johannes Kirschbauer
954c14513a Zerotier: make networkId nullable
This prevents vars from evaluating if the var doesn't exist
Bootstrapping deadlock
2025-08-09 11:17:16 +02:00
clan-bot
cb8a01d448 Merge pull request 'Update Clan Core for Checks' (#4651) from update-clan-core-for-checks into main 2025-08-09 03:00:19 +00:00
clan-bot
8e53e42b74 Update pinned clan-core for checks 2025-08-09 02:51:46 +00:00
Kenji Berthold
ed596a57f0 Merge pull request 'templates(flake-parts-minimal): Fix erroneous removal' (#4648) from kenji/ke-fix-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4648
2025-08-08 23:07:00 +00:00
a-kenji
b6bccd218a templates(flake-parts-minimal): Fix erroneous removal 2025-08-09 00:56:21 +02:00
Johannes Kirschbauer
1df9b6e97d Remote: set default for commance_prefix 2025-08-08 22:48:19 +02:00
Johannes Kirschbauer
58fa7ac32b Host: convert to proctocol to avoid forced inheritance 2025-08-08 22:33:05 +02:00
hsjobeki
110d1d4921 Merge pull request 'UI/Install workflow: integrate api until hardware report' (#4646) from ui-more into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4646
2025-08-08 19:23:00 +00:00
hsjobeki
46aee098c4 Merge pull request 'API: add sane defaults to some endpoints' (#4645) from readonly into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4645
2025-08-08 19:08:02 +00:00
Johannes Kirschbauer
9d6735e8c4 Ui/install: machine targetHost & hw_report steps 2025-08-08 21:04:39 +02:00
Johannes Kirschbauer
47c94c51b6 UI: finish up create installer subflow 2025-08-08 21:03:53 +02:00
Johannes Kirschbauer
1eb567682c API/ts: fix empty object is not nullish 2025-08-08 21:03:03 +02:00
Johannes Kirschbauer
fcd83e7a60 Machine: add install button 2025-08-08 21:02:30 +02:00
Johannes Kirschbauer
af4b00408a UI/queries: add machine hw query 2025-08-08 21:01:24 +02:00
Johannes Kirschbauer
aaff3b9b38 UI/queries: remove unused argument 2025-08-08 21:01:03 +02:00
Johannes Kirschbauer
c13741602c UI/select: make z-index adjustable 2025-08-08 20:59:55 +02:00
Johannes Kirschbauer
6cbe221f44 UI/modal: fix styling bugs 2025-08-08 20:58:53 +02:00
Johannes Kirschbauer
3cf8f605d5 flash: default keymap and lang 2025-08-08 20:56:45 +02:00
Johannes Kirschbauer
cc07e0ea44 update hw config: add nixos-facter as default 2025-08-08 20:56:15 +02:00
Johannes Kirschbauer
ccb9340478 Host: add None as default to command_prefix 2025-08-08 20:55:54 +02:00
Mic92
df096fe53b Merge pull request 'switch to flake-compat for private flake' (#4639) from flake-compat into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4639
2025-08-08 13:14:07 +00:00
Jörg Thalheim
f248cc91ad switch to flake-compat for private flake 2025-08-08 15:06:57 +02:00
Jeremy Fleischman
1129862293 machines update: support --target-host localhost
This is basically
<af7ce9b8ed>,
but with support for deploying locally.

This failed to install secrets to `/var/lib/sops-nix`. That's because
our `LocalHost` didn't have support for sudo yet. I fixed that.
2025-08-08 12:42:26 +00: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
Mic92
d19ac1b9f5 Merge pull request 'machines update: refactor - simplify' (#4506) from simplify-update into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4506
2025-07-30 18:42:02 +00:00
Jörg Thalheim
57eec8edb4 bump clan-core-for-checks 2025-07-30 20:15:44 +02:00
Jörg Thalheim
e99981cfaf flake: fix privateInputs loading in nix store contexts
When clan-core is fetched via fetchgit (e.g. in tests), the devFlake/private
directory exists but cannot be loaded as a flake. This causes errors when
building test machines.

Fix by:
1. Adding a .skip-private-inputs marker file in clan-core-for-checks to
   explicitly disable private inputs in test contexts
2. Checking for this marker file before attempting to load private inputs
3. Keeping the original tryEval approach as a fallback for compatibility

This ensures tests can run without errors while preserving the ability to
load private inputs in development environments.
2025-07-30 20:15:44 +02:00
Jörg Thalheim
ae0ea37437 add update test 2025-07-30 19:13:17 +02:00
Jörg Thalheim
15557cb532 test/installation: drop out-dated comment 2025-07-30 17:58:12 +02:00
Jörg Thalheim
8f3a0b59f3 update-hardware-configuration: remove unused CLAN_FLAKE 2025-07-30 17:49:28 +02:00
Jörg Thalheim
10f731c974 container-test-driver: fix rebuild and make container-test-driver importable 2025-07-30 17:49:28 +02:00
hsjobeki
0e5c8d1a33 Merge pull request 'ui/hostfile: convert to use css modules' (#4540) from ui-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4540
2025-07-30 14:34:52 +00:00
Johannes Kirschbauer
e5f8c515cd ui/hostfile: convert to use css modules 2025-07-30 16:30:46 +02:00
hsjobeki
e856d4018a Merge pull request 'ui/button: hide loader when not loading' (#4539) from ui-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4539
2025-07-30 13:50:35 +00:00
Johannes Kirschbauer
17b75500fb ui/button: hide loader when not loading 2025-07-30 15:46:43 +02:00
Kenji Berthold
cf8b7f63fc Merge pull request 'pkgs/clan/lib: Move get_clan_directories to dirs' (#4538) from kenji/ke-directory-move into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4538
2025-07-30 13:17:37 +00:00
a-kenji
62c4f735ed pkgs/clan/lib: Move get_clan_directories to dirs 2025-07-30 15:07:05 +02:00
brianmcgee
cba951b2c5 Merge pull request 'feat: ui/machine-detail-pane' (#4531) from ui/machine-detail-pane into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4531
2025-07-30 12:59:27 +00:00
clan-bot
ef6f652b92 Merge pull request 'Update treefmt-nix' (#4492) from update-treefmt-nix into main 2025-07-30 12:39:59 +00:00
Johannes Kirschbauer
3d51cee4bb ui/modal: autofocus first input 2025-07-30 12:31:10 +00:00
Kenji Berthold
1791743444 Merge pull request 'pkgs/clan/lib: Add clan api to get the relative clan directory' (#4534) from kenji/ke-add-computed-directory-function into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4534
Reviewed-by: hsjobeki <hsjobeki@gmail.com>
2025-07-30 12:30:30 +00:00
gitea-actions[bot]
6208a6e857 Update treefmt-nix 2025-07-30 12:28:55 +00:00
Johannes Kirschbauer
4759cce8a4 ui/modal: autofocus first input 2025-07-30 14:27:10 +02:00
a-kenji
c7ad875e7e pkgs/clan/lib: Add clan api to get the relative clan directory
This is a Continuation of: #4519
2025-07-30 14:22:23 +02:00
hsjobeki
2ef292942f Merge pull request 'ui/button: fix loader needs explizit styling, not stylable via css leakage anymore' (#4536) from ui-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4536
2025-07-30 12:20:45 +00:00
Johannes Kirschbauer
b83f5d2ffc ui/button: fix loader needs explizit styling, not stylable via css leakage anymore 2025-07-30 14:16:23 +02:00
hsjobeki
567e8b57cd Merge pull request 'ui/modal: use css modules' (#4535) from ui-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4535
2025-07-30 11:55:35 +00:00
Johannes Kirschbauer
7f1a7da5c7 ui/modal: use css modules 2025-07-30 13:51:47 +02:00
hsjobeki
bb92ffb898 Merge pull request 'ui/toolbarButton: fix selected state' (#4533) from ui-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4533
2025-07-30 11:45:19 +00:00
Johannes Kirschbauer
7ed62c427c ui/toolbarButton: fix selected state 2025-07-30 13:40:08 +02:00
Johannes Kirschbauer
596458d809 fix: set fixed height for SidebarSectionForm controls 2025-07-30 12:32:59 +01:00
Brian McGee
f677d96acf feat(ui): add sidebar pane for machine detail 2025-07-30 12:32:58 +01:00
Brian McGee
2c3b0f3771 feat(ui): use keyed show to re-render Machine route when route changes 2025-07-30 12:32:57 +01:00
Brian McGee
ae20230a57 feat(ui): change machineID to machineName
And no longer base64 encode it in url params or cache keys.

The term used in the API is name, so this is aligning with that.
2025-07-30 12:32:57 +01:00
Kenji Berthold
549ba9bdc2 Merge pull request 'pkgs/cli/lib: Remove obsolete clan creation function' (#4532) from kenji/ke-add-computed-directory into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4532
2025-07-30 10:52:16 +00:00
a-kenji
e167137672 pkgs/cli/lib: Remove obsolete clan creation function 2025-07-30 12:41:42 +02:00
hsjobeki
e36735119c Merge pull request 'ui/loader: transform to use css modules' (#4530) from ui-css into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4530
2025-07-30 09:07:59 +00:00
Johannes Kirschbauer
f8cdac2a63 ui/debug: remove floating debug buttons 2025-07-30 11:04:22 +02:00
Johannes Kirschbauer
ea63b4411e ui/splashscreen: transform to use css modules 2025-07-30 11:03:56 +02:00
Johannes Kirschbauer
a070fc74c1 ui/loader: transform to use css modules 2025-07-30 11:03:56 +02:00
DavHau
b30686269b machines update: fix lacks a signature by a trusted key
Despite using `root` as the ssh user, `ssh-ng` still fails with:

`error: cannot add path '/nix/store/...' because it lacks a signature by a trusted key
`

This does not happen with `ssh` instead of `ssh-ng`
2025-07-30 15:15:57 +07:00
Jörg Thalheim
1626d179a0 run_machine_update: document missing flag 2025-07-30 09:54:50 +02:00
Jörg Thalheim
6ec38c33d7 container-test-driver: fixup /etc/passwd for unprivileged user
By default /etc/passwd in container build sandboxes have two users
(root,nixbld) mapped to root. This confuses nix especially it behaves
different if it runs as root. setuid/setgid() is not enough because ssh
will break if the current uid does not exist in /etc/passwd.
Along with this we now also only run the setup for setting up the
network bridge and cgroup filesystems once and not per container.
2025-07-30 09:54:50 +02:00
Jörg Thalheim
fdfbed1a3f nixos_test_lib/setup_ssh_connection: no forward in container tests 2025-07-30 09:54:50 +02:00
Jörg Thalheim
f44b8c63c2 nixos_test_lib/prepare_test_flake: return a Path instead of str 2025-07-30 09:54:50 +02:00
Jörg Thalheim
092ac21dcd git_clan_flake_toplevel: gracefully handle permission errors
since this code is used as a default in the cli parser, we should not
crash on OSError because we wouldn't be abe to perform basic tasks such
as --help.
2025-07-30 09:54:50 +02:00
Jörg Thalheim
bd6f7b03af inline find_git_repo_root 2025-07-30 09:54:50 +02:00
Jörg Thalheim
0908a2efb8 don't resolve absolute paths for flake uri
pathlib.Path("git+file:///foo").resolve() might resolve to urls like
PosixPath('/home/joerg/work/clan/clan-core/git+file:/foo'). If those
then actually exist, this can have weird behavior. We should in general
avoid changing directories for everything except for subprocess.run.
2025-07-30 09:54:50 +02:00
Jörg Thalheim
6c84b2e100 container-test-driver: also setup network if we start a single container 2025-07-30 09:54:50 +02:00
Jörg Thalheim
de65619442 update: explain why we use sudo rather than --use-remote-sudo 2025-07-30 09:54:50 +02:00
DavHau
85dda9e125 machines update: refactor - simplify
This is an attempt to reduce cognitive load when trying to understand the host related logic in run_machine_update.

The change should not affect behavior.

Done:
- make it very clear early on, that build_host == target_host if build_host is not set.
- rename some variables to make relations more clear
- remove `deploy_host` variable. unnecessary complexity
- remove `become_root` variable. After simplifying via boolean algebra, this boils down to `build_host == target_host`.
2025-07-30 09:54:50 +02:00
hsjobeki
7961a92d32 Merge pull request 'ui/toolbar: fix overly specifify css selectors' (#4525) from toolbar into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4525
2025-07-30 06:48:52 +00:00
hsjobeki
50ba21316e Merge pull request 'ui/sidebar: fix close animation' (#4524) from sidebar-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4524
2025-07-29 17:14:33 +00:00
Johannes Kirschbauer
08342578f1 ui/toolbar: fix story types 2025-07-29 19:12:27 +02:00
Johannes Kirschbauer
9954653657 ui/toolbar: use css modules 2025-07-29 19:10:42 +02:00
Johannes Kirschbauer
6e71b541aa ui/toolbar: fix overly specifify css selectors 2025-07-29 18:50:34 +02:00
Johannes Kirschbauer
0f72f12461 ui/sidebar: fix close animation
Animation needs static layout as a starting point
2025-07-29 18:03:00 +02:00
Kenji Berthold
db579e169c Merge pull request 'pkgs/clan/lib: Fix directory functionality' (#4519) from kenji/ke-fix-directory-usage into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4519
2025-07-29 16:01:35 +00:00
a-kenji
31438d6781 pkgs/clan/lib: Fix directory functionality
Fix the directory functionality of clan (clan.directory).
The python API interface was not able to distinguish if the directory
was set to anything other than `self.src`.
Breaking every command that relied on the clan directory, for example:
- `clan machines update`
- `clan machines update-hardware-config`
See more in #2906

This is the first step in fixing all those commands.
Individual command support and implementation will be implemented in
follow ups.
2025-07-29 17:51:12 +02:00
Kenji Berthold
eac21c5176 Merge pull request 'pkgs/clan/lib: Fix documentation of from_ssh_uri in the Remote class' (#4523) from kenji/ke-fix-remote-documentation into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4523
2025-07-29 15:47:47 +00:00
Kenji Berthold
2bd432bdb7 Merge pull request 'pkgs/clan/cli: Fix typo in machines update' (#4522) from kenji/ke-machines-update-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4522
2025-07-29 15:46:18 +00:00
a-kenji
7ef09343ed pkgs/clan/lib: Fix documentation of from_ssh_uri in the Remote class 2025-07-29 17:37:50 +02:00
a-kenji
8c2cee0e44 pkgs/clan/cli: Fix typo in machines update 2025-07-29 17:32:45 +02:00
hsjobeki
b421698f70 Merge pull request 'templates: fix urls for relative file paths' (#4520) from fix-template-urls into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4520
2025-07-29 15:19:20 +00:00
hsjobeki
857b9d0260 Merge pull request 'docs/templates: add more docs for template urls' (#4521) from docs-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4521
2025-07-29 15:11:15 +00:00
Johannes Kirschbauer
2776294de0 templates: url add support for home and abspath 2025-07-29 17:04:15 +02:00
Johannes Kirschbauer
c90b8d7401 templates/cli: add more help 2025-07-29 16:58:19 +02:00
Johannes Kirschbauer
5c746311c7 templates: init docs 2025-07-29 16:50:48 +02:00
Johannes Kirschbauer
7784df8180 templates: fix urls for relative file paths 2025-07-29 15:01:29 +02:00
Kenji Berthold
5d0ca5aff8 Merge pull request 'pkgs/clan/lib: Fix clan template creation when already in a flake' (#4501) from kenji/ke-clan-flakes-create-existing-flake-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4501
2025-07-29 11:19:04 +00:00
a-kenji
3ef6b2f715 pkgs/clan/cli: Add test for builtin flakeref 2025-07-29 13:07:48 +02:00
Kenji Berthold
58053748b9 Merge pull request 'pkgs/clan/cli: Add clan flake validation to clan vars check' (#4513) from kenji/ke-vars-check-validation into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4513
2025-07-29 11:03:45 +00:00
DavHau
19a8101e98 Merge pull request 'pkgs/cli/create: Show less output by default' (#4499) from kenji/ke-create-show-less-output into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4499
2025-07-29 10:45:16 +00:00
pinpox
e5cb5afb4b Merge pull request 'Migrate postgresql to clan.core' (#4466) from postgres-core into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4466
2025-07-29 09:50:08 +00:00
pinpox
b75cf516f6 Merge branch 'main' into postgres-core 2025-07-29 09:41:50 +00:00
pinpox
3c58e2f04e Migrate postgresql to clan.core 2025-07-29 10:33:14 +02:00
Kenji Berthold
d814e98e94 Merge pull request 'pkgs/cli: Validate clan flake for clan machines list' (#4512) from kenji/ke-fix-list into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4512
2025-07-29 08:29:36 +00:00
a-kenji
35315d9596 pkgs/clan/cli: Add clan flake validation to clan vars check
This now gives a clearer error than:
```
Traceback (most recent call last):
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/bin/.clan-wrapped", line 9, in <module>
    sys.exit(main())
             ~~~~^^
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/lib/python3.13/site-packages/clan_cli/cli.py", line 516, in main
    args.func(args)
    ~~~~~~~~~^^^^^^
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/lib/python3.13/site-packages/clan_cli/vars/check.py", line 113, in check_command
    ok = check_vars(args.machine, args.flake, generator_name=args.generator)
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/lib/python3.13/site-packages/clan_cli/vars/check.py", line 103, in check_vars
    status = vars_status(machine_name, flake, generator_name=generator_name)
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/lib/python3.13/site-packages/clan_cli/vars/check.py", line 41, in vars_status
    generators = Generator.generators_from_flake(machine.name, machine.flake)
  File "/nix/store/mznnb8il3njp6jxn5i57d0myjdh6cs0i-clan-cli/lib/python3.13/site-packages/clan_cli/vars/generate.py", line 67, in generators_from_flake
    generators_data = flake.select_machine(
                      ^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'select_machine'
```

When not being in a flake.
2025-07-29 10:25:28 +02:00
a-kenji
86ac1c4405 pkgs/cli: Validate clan flake for clan machines list 2025-07-29 10:14:34 +02:00
hsjobeki
a06ba7f0f9 Merge pull request 'ui/refactor: move machine specifics from scene into MachineManager' (#4511) from ui-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4511
2025-07-29 08:08:43 +00:00
Johannes Kirschbauer
323de27651 ui: fixup types 2025-07-29 10:03:42 +02:00
Johannes Kirschbauer
782e8b330d UI: move machine specifics into MachineManager 2025-07-29 10:01:48 +02:00
Johannes Kirschbauer
682d8c786c ui: add MachineManager
Handles maping solidjs signals to updating and maintaining a map of MachineRepr
2025-07-29 10:01:18 +02:00
Johannes Kirschbauer
9e32be4e48 ui: add machineRepr to handle machine visual representation 2025-07-29 10:00:36 +02:00
Johannes Kirschbauer
686976a143 ui: add objectRegistry for memory management 2025-07-29 10:00:12 +02:00
hsjobeki
a2404f5fbb Merge pull request 'ui: disable scene rotation' (#4510) from ui-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4510
2025-07-28 18:42:14 +00:00
Johannes Kirschbauer
a6a25075f7 ui: disable scene rotation 2025-07-28 20:38:17 +02:00
hsjobeki
ec71badc3c Merge pull request 'ui: fix memory management in renderLoop' (#4509) from ui-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4509
2025-07-28 18:34:02 +00:00
Johannes Kirschbauer
1c4469e20c ui: fix memory management in renderLoop 2025-07-28 20:30:28 +02:00
hsjobeki
6fa4348aa6 Merge pull request 'ui: move rendering logic into renderLoop singleton' (#4508) from ui-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4508
2025-07-28 18:25:29 +00:00
Johannes Kirschbauer
dac06531d4 ui: move rendering logic into renderLoop singleton 2025-07-28 20:20:42 +02:00
DavHau
cb89fb97f1 clan machines update: add --fetch-local feature
Motivation: updating a machine fails, if it depends on a private github repo, as the remote will likely not be authenticated.

This adds a new flag `--fetch-local` to `clan machines update` which fetches all flake inputs prior to building, then uploads them to the build-host.

This also adds a new error message, when flake inputs could not fetched, to hint the user to use `--fetch-local`
2025-07-28 17:01:42 +07:00
hsjobeki
6a8d7aa5fd Merge pull request 'api: init get_machine_writeability' (#4504) from cli-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4504
2025-07-28 08:47:43 +00:00
Luis Hebendanz
63bcfc4809 Merge pull request 'pkgs/cli: Remove uncommented logic from creation test' (#4497) from kenji/ke-remove-uncommented into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4497
2025-07-28 03:48:57 +00:00
Johannes Kirschbauer
e73350f6af test: fix add modules 2025-07-27 12:48:04 +02:00
Johannes Kirschbauer
98a0b9600b api/writability: add docstring 2025-07-27 12:47:37 +02:00
Johannes Kirschbauer
abeb517a22 api/writability: add unit test 2025-07-27 00:03:05 +02:00
Johannes Kirschbauer
fbdbcfa6d5 InventoryStore: factor write into _write for actual disk interaction 2025-07-27 00:02:47 +02:00
Johannes Kirschbauer
303af9af6b api: init get_machine_writeability 2025-07-27 00:01:51 +02:00
Johannes Kirschbauer
414e412e7e persist/writeability: expose is writeable key helper 2025-07-27 00:01:36 +02:00
Johannes Kirschbauer
c2e84f11af persist/util: add field helper 2025-07-27 00:01:07 +02:00
Johannes Kirschbauer
bf2eb000d5 api/set_machine: add unit tests 2025-07-26 23:59:51 +02:00
hsjobeki
b01029ccd4 Merge pull request 'pyproject: remove global SLF001 ignore' (#4503) from cli-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4503
2025-07-26 18:28:06 +00:00
Johannes Kirschbauer
798c1a9277 pyproject: remove global SLF001 ignore
ignoring SLF001 (private member access) globally is not ideal, as it disables a valuable check throughout the entire codebase
disable SLF001 only for test files instead
2025-07-26 20:24:20 +02:00
hsjobeki
d6327e0bc9 Merge pull request 'adr-01: add clarifying sentence' (#4502) from adr-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4502
2025-07-26 16:01:46 +00:00
Johannes Kirschbauer
f5b2be63d5 adr-01: add clarifying sentence 2025-07-26 17:58:02 +02:00
Kenji Berthold
6e904de655 Merge pull request 'pkgs/cli: machines install handle invalid character' (#4488) from kenji/ke-clan-machines-install-prompt into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4488
2025-07-26 13:29:10 +00:00
a-kenji
0a43721a45 pkgs/clan/lib: Fix clan template creation when already in a flake
Fix clan template creation when already in a flake.
Currently we already fail with very clear and descriptive error when
trying to evaluate the template of the flake we are in:
```
Failed to select template 'flake-parts' from flake '/tmp/superclan' (via attribute path: /tmp/superclan#clanInternals.templates.clan."flake-parts")
```

This is undesired behavior.
When we are trying to create a clan with `clan flakes create`.
We can't rely on the fact that the flake we are currently in exports flake templates.

Now we *try* to evaluate the flake we are in upon creation.
If there are no clan templates available, we now will fall back to
builtin templates.

Closes: #4472
2025-07-26 15:21:44 +02:00
a-kenji
51eb7bd0b5 pkgs/cli/create: Show less output by default
This masks the output of `nix flake update`, since it is quite verbose
and takes attention away to usually more interesting and pressing
information.

Example:
```
unpacking 'https://git.clan.lol/clan/clan-core/archive/main.tar.gz' into the Git cache...
warning: creating lock file "/tmp/hoowowo/clan/flake.lock":
• Added input 'clan-core':
    '1d8ac7b1b5.tar.gz?narHash=sha256-eBxi0ZMwaALfMsP70N0FRMlOSq0qePv%2BjebVBHXlOqk%3D' (2025-07-25)
• Added input 'clan-core/data-mesher':
    '18dfd42bdb.tar.gz?narHash=sha256-jyoEbaXa8/MwVQ%2BPajUdT63y3gYhgD9o7snO/SLaikw%3D' (2025-07-21)
• Added input 'clan-core/data-mesher/flake-parts':
    follows 'clan-core/flake-parts'
• Added input 'clan-core/data-mesher/nixpkgs':
    follows 'clan-core/nixpkgs'
• Added input 'clan-core/data-mesher/treefmt-nix':
    follows 'clan-core/treefmt-nix'
• Added input 'clan-core/disko':
    'github:nix-community/disko/545aba02960caa78a31bd9a8709a0ad4b6320a5c?narHash=sha256-7lrVrE0jSvZHrxEzvnfHFE/Wkk9DDqb%2BmYCodI5uuB8%3D' (2025-07-21)
• Added input 'clan-core/disko/nixpkgs':
    follows 'clan-core/nixpkgs'
• Added input 'clan-core/flake-parts':
    'github:hercules-ci/flake-parts/644e0fc48951a860279da645ba77fe4a6e814c5e?narHash=sha256-TVcTNvOeWWk1DXljFxVRp%2BE0tzG1LhrVjOGGoMHuXio%3D' (2025-07-21)
• Added input 'clan-core/flake-parts/nixpkgs-lib':
    follows 'clan-core/nixpkgs'
• Added input 'clan-core/nix-darwin':
    'github:nix-darwin/nix-darwin/e04a388232d9a6ba56967ce5b53a8a6f713cdfcf?narHash=sha256-HsJM3XLa43WpG%2B665aGEh8iS8AfEwOIQWk3Mke3e7nk%3D' (2025-06-30)
• Added input 'clan-core/nix-darwin/nixpkgs':
    follows 'clan-core/nixpkgs'
• Added input 'clan-core/nix-select':
    '69d8bf5961.tar.gz?narHash=sha256-IVaoOGDIvAa/8I0sdiiZuKptDldrkDWUNf/%2BezIRhyc%3D' (2025-04-18)
• Added input 'clan-core/nixos-facter-modules':
    'github:nix-community/nixos-facter-modules/14df13c84552a7d1f33c1cd18336128fbc43f920?narHash=sha256-uP9Xxw5XcFwjX9lNoYRpybOnIIe1BHfZu5vJnnPg3Jc%3D' (2025-06-20)
• Added input 'clan-core/nixpkgs':
    'https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre827262.be9e214982e2/nixexprs.tar.xz?narHash=sha256-lUi%2BsPH7Kuh9uP3PyfgbENcJGReUM8Ffk9GxGBFbSN8%3D' (1980-01-01)
• Added input 'clan-core/sops-nix':
    'github:Mic92/sops-nix/2c8def626f54708a9c38a5861866660395bb3461?narHash=sha256-GllP7cmQu7zLZTs9z0J2gIL42IZHa9CBEXwBY9szT0U%3D' (2025-07-15)
• Added input 'clan-core/sops-nix/nixpkgs':
    follows 'clan-core/nixpkgs'
• Added input 'clan-core/systems':
    'github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e?narHash=sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768%3D' (2023-04-09)
• Added input 'clan-core/treefmt-nix':
    'github:numtide/treefmt-nix/421b56313c65a0815a52b424777f55acf0b56ddf?narHash=sha256-tzbhc4XttkyEhswByk5R38l%2BztN9UDbnj0cTcP6Hp9A%3D' (2025-07-20)
• Added input 'clan-core/treefmt-nix/nixpkgs':
    follows 'clan-core/nixpkgs'
• Added input 'flake-parts':
    'github:hercules-ci/flake-parts/644e0fc48951a860279da645ba77fe4a6e814c5e?narHash=sha256-TVcTNvOeWWk1DXljFxVRp%2BE0tzG1LhrVjOGGoMHuXio%3D' (2025-07-21)
• Added input 'flake-parts/nixpkgs-lib':
    follows 'clan-core/nixpkgs'
• Added input 'nixpkgs':
    follows 'clan-core/nixpkgs'
```

Those are 48 lines that seldom carry actual useful information.
This can be shown on running `clan flakes create` with the `--debug`
flag.

Closes: #4496
2025-07-26 14:11:56 +02:00
Kenji Berthold
1d8ac7b1b5 Merge pull request 'pkgs/cli/lib: Allow clan templates list to function outside a clan' (#4490) from kenji/ke-templates-list-without-clan into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4490
Reviewed-by: hsjobeki <hsjobeki@gmail.com>
2025-07-25 14:32:15 +00:00
a-kenji
5b5f1975c5 pkgs/cli/lib: Allow clan templates list to function outside a clan
Allow `clan templates list` to function outside a clan.
Currently when bootstrapping a clan and trying to list the templates
it fails as follows:

```
Traceback (most recent call last):
  File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/bin/.clan-wrapped", line 9, in <module>
    sys.exit(main())
             ~~~~^^
  File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_cli/cli.py", line 516, in main
    args.func(args)
    ~~~~~~~~~^^^^^^
  File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_cli/templates/list.py", line 11, in list_command
    templates = list_templates(args.flake)
  File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_lib/templates/__init__.py", line 20, in list_templates
    custom_templates = flake.select("clanInternals.inventoryClass.templatesPerSource")
                       ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'select'
```

With the change we get the following output:
```
Available 'clan' templates
├── <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>
│   ├── flash-installer: Initialize a new flash-installer machine
│   └── new-machine: Initialize a new machine
```

Allowing to check for available templates without needing to have a
clan, which improves the bootstrapping experience.
2025-07-25 16:14:43 +02:00
Kenji Berthold
bac2f15668 Merge pull request 'docs: Fix typos in hero section of the documentation index' (#4495) from kenji/ke-docs-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4495
2025-07-25 12:45:19 +00:00
Kenji Berthold
3804c62c7d Merge pull request 'docs: Fix grammar of getting started card' (#4494) from kenji/ke-fix-grammar into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4494
2025-07-25 12:45:13 +00:00
a-kenji
326f418c88 pkgs/cli: Remove uncommented logic from creation test 2025-07-25 14:44:20 +02:00
a-kenji
9ebba12e5b docs: Fix typos in hero section of the documentation index 2025-07-25 14:30:32 +02:00
a-kenji
1924d222e1 docs: Fix grammar of getting started card 2025-07-25 14:29:19 +02:00
Luis Hebendanz
15d88ba595 Merge pull request 'docs: Replace backup guide with the new one from Bruno Adele' (#4493) from Qubasa/clan-core:improv_docs3 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4493
2025-07-25 10:34:01 +00:00
Qubasa
986e74663a docs: Replace backup guide with the new one from Bruno Adele
docs: fix build errors
2025-07-25 17:30:05 +07:00
hsjobeki
2d85230097 Merge pull request 'templates(default): Enable modern GNOME options by default' (#4489) from kenji/ke-templates-enable-gnome into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4489
2025-07-25 09:57:05 +00:00
Luis Hebendanz
0e1fe60d8a Merge pull request 'Improve landing page for docs, re-enable footer navigation' (#4491) from Qubasa/clan-core:improv_docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4491
2025-07-25 09:32:44 +00:00
Qubasa
cad7d2d95f docs: reword concepts description
docs: fix build errors
2025-07-25 16:29:22 +07:00
Qubasa
e1f57cd618 docs: Improve the documentation index page 2025-07-25 16:07:18 +07:00
Qubasa
51b4b0b647 docs: FIx old nix symlinks not being cleaned up 2025-07-25 15:20:26 +07:00
Qubasa
abc78bac57 docs: Add a navigation footer button 2025-07-25 15:20:03 +07:00
Luis Hebendanz
510ab2811a Merge pull request 'docs(borgbackup): add detailed usage and management guide for borgbackup clanServices' (#4484) from badele/fork-clan-core:docs/clanservices-borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4484
2025-07-25 07:43:44 +00:00
Bruno Adelé
5e81b26b87 fix(readme): correct relative link to state documentation 2025-07-25 09:08:08 +02:00
Bruno Adelé
2618d0d68f Merge branch 'main' into docs/clanservices-borgbackup 2025-07-24 22:43:19 +00:00
lassulus
55d944ff55 Merge pull request 'networking module part 2' (#4471) from networking_2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4471
2025-07-24 20:34:27 +00:00
lassulus
1a5b77d47a refactor: generalize Tor support to SOCKS5 proxy in network module
- Replace Tor-specific implementation with generic SOCKS5 proxy support
- Change `tor_socks` boolean to `socks_port` and `socks_wrapper` parameters
- Move Tor functionality to clan_lib.network.tor submodule
- Add connection context managers to NetworkTechnologyBase
- Improve network abstraction with proper remote() and connection() methods
- Update all callers to use new SOCKS5 proxy interface
- Fix network ping command to properly handle connection contexts

This allows for more flexible proxy configurations beyond just Tor,
while maintaining backward compatibility for Tor usage.
2025-07-24 22:26:44 +02:00
lassulus
9e85c64139 clan-cli flake: show cache file location 2025-07-24 22:24:34 +02:00
lassulus
7dd9e6b97c clan-cli vars: show which var we are getting in debug log 2025-07-24 22:24:15 +02:00
a-kenji
6cd75f5abd templates(default): Enable modern GNOME options by default
Closes: #4474
2025-07-24 22:07:46 +02:00
a-kenji
6cea3e6c60 pkgs/cli: machines install handle invalid character
Re-request prompt, if invalid character is specified.
None is still treated as no as per CLI hint [y/N].
We now also accept Y/N.

Closes: #4475
2025-07-24 22:00:31 +02:00
hsjobeki
f5b4e44aed Merge pull request 'docs: unify documentation' (#4485) from migration-docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4485
2025-07-24 14:59:03 +00:00
Johannes Kirschbauer
b6a04e4f12 docs: restore index page 2025-07-24 16:54:49 +02:00
Johannes Kirschbauer
caaf9dc4f3 docs: unify documentation
Strictly enforce diataxis
Use resource driven approach
Can extend later to add 'developer' link index page
2025-07-24 16:51:57 +02:00
Luis Hebendanz
9668c318dc Merge pull request 'fix flake select logging' (#4483) from Qubasa/clan-core:fix_flake_select_logging into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4483
2025-07-24 11:44:21 +00:00
Bruno Adelé
e5befb9226 docs(borgbackup): add detailed usage and management guide 2025-07-24 13:36:04 +02:00
Qubasa
033f34c0b8 ruff: Ignore accessed internal variable error as it is needed in tests quite often
remove incorrect doc change
2025-07-24 18:30:19 +07:00
Qubasa
7146c97362 clan_lib: Fix flake.select logging, now we log the first time select queries a path for the first time, it doesn't matter if it is cached or not. 2025-07-24 18:27:40 +07:00
brianmcgee
428451dca6 Merge pull request 'feat(ui): animate sidebar pane entry/exit' (#4482) from ui/sidebar-pane-animation into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4482
2025-07-24 10:44:49 +00:00
Brian McGee
d3d1489829 feat(ui): animate sidebar pane entry/exit 2025-07-24 11:40:54 +01:00
Qubasa
b74aa31b87 clan-lib: Fix missing logging for flake.select execution 2025-07-24 17:29:09 +07:00
brianmcgee
20550baa38 Merge pull request 'fix(ui): increase z index for sidebar dropdown' (#4481) from fix/sidebar-dropdown-z-index into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4481
2025-07-24 09:27:16 +00:00
Brian McGee
f18e70dda6 fix(ui): increase z index for sidebar dropdown 2025-07-24 10:23:43 +01:00
hsjobeki
5ddeb41a5d Merge pull request 'ui/cubes: add labels' (#4469) from scene-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4469
2025-07-24 08:56:41 +00:00
brianmcgee
5d431094bb Merge pull request 'feat(ui): waiting for necessary queries before dropping clan loader' (#4479) from ui/refine-initial-loading into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4479
2025-07-24 08:52:54 +00:00
Johannes Kirschbauer
fb5229a5f3 ui/cubes: adjust label style 2025-07-24 10:52:21 +02:00
Brian McGee
694059d3ce feat(ui): waiting for necessary queries before dropping clan loader 2025-07-24 09:48:57 +01:00
hsjobeki
2299feb809 Merge pull request 'docs/options: expose all clan options in NüschtOS search' (#4478) from migration-docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4478
2025-07-24 07:50:51 +00:00
Johannes Kirschbauer
59105bd1da docs/options: expose all clan options in NüschtOS search 2025-07-24 09:42:21 +02:00
Luis Hebendanz
9018ffce7a Merge pull request 'clan-lib: Remove injected "op_key" argument from all functions and do it over the threadcontext instead. Remove double threading in http server' (#4477) from Qubasa/clan-core:get_rid_of_opkey into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4477
2025-07-24 07:38:58 +00:00
Qubasa
94662b722d clan-lib: Remove injected "op_key" argument from all functions and do it over the threadcontext instead. Remove double threading in http server 2025-07-24 14:25:20 +07:00
pinpox
0ffad32657 Merge pull request 'Add general intro doc text' (#4470) from add-banner-doctext into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4470
2025-07-23 16:36:03 +00:00
pinpox
50803c2e25 Add general intro doc text 2025-07-23 18:32:28 +02:00
Johannes Kirschbauer
334fe45adc ui/cubes: add labels 2025-07-23 16:41:24 +02:00
hsjobeki
ebdd3e8413 Merge pull request 'ui/cubes: reactive wiring, use orthographic camera' (#4468) from scene-progress into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4468
2025-07-23 14:09:42 +00:00
hsjobeki
ffe58fc189 Merge pull request 'feat(ui): move toolbar lower down' (#4467) from ui/refine-toolbar-position into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4467
2025-07-23 14:07:02 +00:00
Johannes Kirschbauer
7065464227 ui/cubes: reactive updates, use orthographic 2025-07-23 16:05:51 +02:00
Johannes Kirschbauer
5f567e2473 hooks: add maybeMachine hook 2025-07-23 16:04:55 +02:00
Johannes Kirschbauer
46ffcdf182 ui/css: format extra css 2025-07-23 16:04:41 +02:00
Johannes Kirschbauer
9afeec5683 ui: remove left over process-compose-2d.yml 2025-07-23 16:04:16 +02:00
Luis Hebendanz
329047e865 Merge pull request 'Move developer guides to the "Developer" section' (#4462) from Qubasa/clan-core:dev_docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4462
2025-07-23 12:10:45 +00:00
Qubasa
5c7e6b3830 docs: Move developer guides into the Developer section
nix fmt

address davhau review
2025-07-23 18:31:19 +07:00
hsjobeki
1e51439414 Merge pull request 'pytest: add simple clan_flake function' (#4453) from api-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4453
2025-07-23 10:27:05 +00:00
Brian McGee
a472f7f696 feat(ui): move toolbar lower down 2025-07-23 11:23:31 +01:00
Johannes Kirschbauer
29c764773f pytest: clan_flake allow usage of plain dicts 2025-07-23 12:15:54 +02:00
brianmcgee
af056f2355 Merge pull request 'feat(ui): set a fixed width for welcome screen' (#4464) from ui/max-width-onboarding into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4464
2025-07-23 09:32:12 +00:00
Brian McGee
6803f3c6f5 feat(ui): set a fixed width for welcome screen 2025-07-23 10:27:33 +01:00
brianmcgee
6b9ce0da66 Merge pull request 'feat(ui): add sidebar and flesh out app routes' (#4463) from ui/add-sidebar-nav into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4463
2025-07-23 09:26:43 +00:00
Brian McGee
38d62af1ba feat(ui): add sidebar and flesh out app routes 2025-07-23 10:16:00 +01:00
Luis Hebendanz
c880ab7cc1 Merge pull request 'feat(docs): enhance styling for typeset' (#4461) from badele/fork-clan-core:docs/update-style into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4461
2025-07-23 08:50:44 +00:00
Bruno Adelé
613a1fb553 feat(docs): enhance styling for typeset 2025-07-23 10:08:52 +02:00
Kenji Berthold
14f255c2d5 Merge pull request 'pkgs/cli: Fix fstring interplolation' (#4459) from kenji/ke-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4459
2025-07-23 07:55:04 +00:00
hsjobeki
eaa5a9a204 Merge pull request 'ui/scene: add timeout for splashscreen' (#4460) from ui-scene-2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4460
2025-07-23 07:55:01 +00:00
Johannes Kirschbauer
34ccbcc13d ui/scene: add timeout for splashscreen 2025-07-23 09:50:59 +02:00
a-kenji
f58a120db1 pkgs/cli: Fix fstring interplolation
Closes: #4458
2025-07-23 09:43:51 +02:00
Qubasa
5b59cfbc34 docs: Remove emojies from getting started 2025-07-23 13:57:51 +07:00
DavHau
cc69892e3b create clan: better info about existing sop keys
When creating a new clan, the key selection now looks like this:
```
Found existing admin keys on this machine:
1: type: AGE
   pubkey: age1xyz...
   source: /home/grmpf/.config/sops/age/keys.txt
2: type: PGP
   pubkey: abc...
   source: SOPS_PGP_FP
Select keys to use (comma-separated list of numbers, or leave empty to select all):
```

This is achieved by adding a `source` attribute to `SopsKey`.
2025-07-23 13:22:19 +07:00
DavHau
c94330ee9c clan create: fix failure when path was single word
This should better be fixed with types. It should be possible to initialize a flake from a Path, making it very clear that a path `foo` is meant and not a remote flake called `foo`
2025-07-23 12:33:57 +07:00
Jörg Thalheim
377056e80c clan flakes create: initialize keys automatically (#4435)
fixes https://git.clan.lol/clan/clan-core/issues/2665
fixes https://git.clan.lol/clan/clan-core/issues/4407

Co-authored-by: DavHau <d.hauer.it@gmail.com>
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4435
Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Co-committed-by: Jörg Thalheim <joerg@thalheim.io>
2025-07-23 04:44:55 +00:00
Johannes Kirschbauer
1dbaff7b61 pytest: add simple clan_flake function
Takes a clan nix expression as a string
Is empty by default and does nothing
Expensive lockfile patching is done once per session
2025-07-22 23:24:40 +02:00
clan-bot
bf416f1b5f Merge pull request 'Update disko' (#4452) from update-disko into main 2025-07-22 20:12:35 +00:00
gitea-actions[bot]
d83bcf638f Update disko 2025-07-22 20:00:49 +00:00
Kenji Berthold
acfe3b0a04 Merge pull request 'pkgs/clan: Fix common command flags registering' (#4451) from kenji/ke-completions-add-flake into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4451
2025-07-22 18:10:15 +00:00
a-kenji
04f36a4cb1 pkgs/clan: Fix common command flags registering
Fix common command flags registering.
Register the common command flags before triggering autocomplete,
that way we can use the flags in the autocompletions themselves.
2025-07-22 19:56:07 +02:00
hsjobeki
41a0138c16 Merge pull request 'clan/create: api fixes and unit tests' (#4449) from api-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4449
2025-07-22 17:28:37 +00:00
Johannes Kirschbauer
f1be729206 create/clan: unit tests init 2025-07-22 19:17:41 +02:00
a-kenji
cacd853374 pkgs/cli: Support the flake argument for clan shell completions 2025-07-22 19:06:42 +02:00
brianmcgee
07caa6890f Merge pull request 'chore(ui): finish simplifying clan query params' (#4450) from ui/simplify-clan-params into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4450
2025-07-22 16:49:29 +00:00
Brian McGee
9706285474 chore(ui): finish simplifying clan query params 2025-07-22 17:45:23 +01:00
Kenji Berthold
1510b4014b Merge pull request 'pkgs/cli: Autocomplete various vars subcommands' (#4447) from kenji/ke-complete-vars into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4447
2025-07-22 16:27:32 +00:00
lassulus
d5e0f7e505 Merge pull request 'fix: handle arbitrary store paths references in flake cache' (#4441) from fix-flake-caching into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4441
2025-07-22 16:18:15 +00:00
Johannes Kirschbauer
b9e5cf1220 clan/create: use post_processing hook 2025-07-22 18:14:56 +02:00
Johannes Kirschbauer
f4eb59c373 create/clan: add validation to create arguments 2025-07-22 18:13:56 +02:00
brianmcgee
09b92084c8 Merge pull request 'ui/simplify-clan-params' (#4448) from ui/simplify-clan-params into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4448
2025-07-22 16:13:37 +00:00
Johannes Kirschbauer
06257d044a test_create: fix duplicated variables 2025-07-22 18:12:51 +02:00
Johannes Kirschbauer
34ca7a4a7b create/clan: add abstraction for initial meta
Directly passing through persisted data is bad
2025-07-22 18:12:17 +02:00
brianmcgee
ce70be5ca3 Merge pull request 'Add tanstack devtools to UI' (#4446) from feat/tanstack-devstools into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4446
2025-07-22 16:11:27 +00:00
Brian McGee
dd3051d62b chore(ui): simplify clan uri params
Now that everything lives under `/clans/:clanURI` we don't need to handle the case where the param might be null.
2025-07-22 17:08:12 +01:00
Brian McGee
5f290fed7f chore(ui): remove solid-devtools
It's a chrome extension which is pointless inside of webview which is webkit.
2025-07-22 17:07:44 +01:00
Johannes Kirschbauer
a34ec8ed22 templates: add post_process hook for tests or other extensions 2025-07-22 18:07:26 +02:00
Johannes Kirschbauer
4597b207e7 pytest: fixtures offline_flake_hook init 2025-07-22 18:06:47 +02:00
Johannes Kirschbauer
9257cb02ee validator: hostname init 2025-07-22 18:06:27 +02:00
a-kenji
cd8a1d9a32 pkgs/cli: Autocomplete various vars subcommands
Add autocomplete for `vars` for the following subcommands:

```
clan vars get [machine] [var_id]
clan vars set [machine] [var_id]
```
2025-07-22 18:03:55 +02:00
Brian McGee
ee9ae21bd2 feat(ui): add tanstack devtools for debugging queries 2025-07-22 16:33:53 +01:00
Jörg Thalheim
bd1451ce18 fix: handle arbitrary store paths references in flake cache
Previously, paths like /nix/store/hash-file.nix:123 were incorrectly
treated as pure store paths and wrapped in {"outPath": ...}, breaking
the cache. This fix:

- Adds helper functions to properly detect and handle store references
- Distinguishes between pure store paths and paths with metadata (line numbers)
- Supports multiple store references in a single string
- Handles custom NIX_STORE_DIR correctly
- Ensures existence checks work for all store references

Also fixes test_cache_gc to delete NIX_REMOTE for proper local store testing.
2025-07-22 17:13:04 +02:00
pinpox
a94cc4b7f7 Merge pull request 'Add wait_for_file testing helper' (#4442) from add-wait-for-file into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4442
2025-07-22 14:38:07 +00:00
pinpox
cf2ccd7e14 Add wait_for_file testing helper 2025-07-22 16:27:20 +02:00
hsjobeki
69ab00b34b Merge pull request 'store: move merge_objects into persistence helpers' (#4440) from api-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4440
2025-07-22 13:12:04 +00:00
Johannes Kirschbauer
0043870882 merge_objects: add unit tests 2025-07-22 15:01:00 +02:00
Johannes Kirschbauer
0ea42ae541 store: move merge_objects into persistence helpers 2025-07-22 15:01:00 +02:00
Kenji Berthold
ad50cfbcbb Merge pull request 'docs: Fix typo' (#4439) from kenji/ke-typo-getting-started into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4439
2025-07-22 11:30:58 +00:00
Kenji Berthold
cf65ae81cf Merge pull request 'pkgs/cli: Add disko template completion to clan templates apply disk' (#4438) from kenji/ke-complete-disko into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4438
2025-07-22 11:26:58 +00:00
a-kenji
19ca7d9a77 docs: Fix typo 2025-07-22 13:26:06 +02:00
Kenji Berthold
0b2ee45526 Merge pull request 'pkgs/cli: Add completions to clan flakes create --template [TEMPLATE]' (#4437) from kenji/ke-complete-template into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4437
2025-07-22 11:25:04 +00:00
a-kenji
28e39ada84 pkgs/cli: Add disko template completion to clan templates apply disk 2025-07-22 13:04:45 +02:00
a-kenji
fb52b955cc pkgs/cli: Add completions to clan flakes create --template [TEMPLATE]
Add completions to `clan flakes create --template [TEMPLATE]`
2025-07-22 13:01:45 +02:00
pinpox
77f75b916d Merge pull request 'Fix store symlinks in container test' (#4436) from fix-container-symlinks into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4436
2025-07-22 10:45:02 +00:00
pinpox
97022ba873 Fix store symlinks in container test 2025-07-22 12:28:11 +02:00
Luis Hebendanz
aee71b3fd6 Merge pull request 'pkgs/cli: Validate flake for network subcommands' (#4433) from kenji/ke-networks-validate-flake into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4433
2025-07-22 05:27:45 +00:00
Luis Hebendanz
76535852e4 Merge pull request 'pkgs/cli: Add machine to output while uploading sources' (#4429) from kenji/ke-update-add-machine into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4429
2025-07-22 05:27:06 +00:00
Kenji Berthold
a694e8d122 Merge pull request 'pkgs/cli: Fix typo in networking help' (#4431) from kenji/ke-network-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4431
2025-07-21 20:38:54 +00:00
Kenji Berthold
93fee8263f Merge pull request 'pkgs/cli: Fix typo in networking list help' (#4432) from kenji/ke-networks-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4432
2025-07-21 20:23:55 +00:00
clan-bot
28859641eb Merge pull request 'Update flake-parts' (#4430) from update-flake-parts into main 2025-07-21 20:18:37 +00:00
a-kenji
3a2be243c0 pkgs/cli: Add machine to output while uploading sources
Add the `machine` to it's output while uploading sources.
2025-07-21 22:15:43 +02:00
a-kenji
9fdf41813a pkgs/cli: Validate flake for network subcommands
When running for example `clan networks list` we now get a reasonable
error message, instead of:
```
Traceback (most recent call last):
  File "/nix/store/8ygq8bfxqydk2917mmg32wy9wb0qzzzd-clan-cli/bin/.clan-wrapped", line 9, in <module>
    sys.exit(main())
             ~~~~^^
  File "/nix/store/8ygq8bfxqydk2917mmg32wy9wb0qzzzd-clan-cli/lib/python3.13/site-packages/clan_cli/cli.py", line 516, in main
    args.func(args)
    ~~~~~~~~~^^^^^^
  File "/nix/store/8ygq8bfxqydk2917mmg32wy9wb0qzzzd-clan-cli/lib/python3.13/site-packages/clan_cli/network/list.py", line 12, in list_command
    networks = networks_from_flake(flake)
  File "/nix/store/8ygq8bfxqydk2917mmg32wy9wb0qzzzd-clan-cli/lib/python3.13/site-packages/clan_lib/network/network.py", line 106, in networks_from_flake
    networks_ = flake.select("clan.exports.instances.*.networking")
                ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'select'
```
2025-07-21 22:13:48 +02:00
a-kenji
04f3a9480f pkgs/cli: Fix typo in networking list help 2025-07-21 22:07:29 +02:00
a-kenji
f7762b3119 pkgs/cli: Fix typo in networking help 2025-07-21 22:06:03 +02:00
gitea-actions[bot]
634e4116cf Update flake-parts 2025-07-21 20:00:52 +00:00
Kenji Berthold
015c09b0e5 Merge pull request 'docs: Fix typos in getting-started guide' (#4428) from kenji/ke-getting-started-fix-typo into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4428
2025-07-21 18:54:37 +00:00
Kenji Berthold
6e0a43c777 Merge pull request 'clanServices/zerotier: Make moon configuration optional' (#4427) from kenji/ke-zerotier-make-moon-optional into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4427
2025-07-21 18:54:30 +00:00
a-kenji
7fc527b649 docs: Fix typos in getting-started guide 2025-07-21 20:47:41 +02:00
a-kenji
2f0ba0782a clanServices/zerotier: Make moon configuration optional
Make moon configuration optional. Before the `attrNames` evaluated the
attributes eagerly, which in practice meant that you had to set a moon,
if there was a controller configured, which is not on purpose.
2025-07-21 20:43:25 +02:00
hsjobeki
bc3b6c792f Merge pull request 'services: fix extraModules as path' (#4422) from fix-extra-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4422
2025-07-21 17:56:58 +00:00
Johannes Kirschbauer
b5a3d617fd services: fix extraModules as path 2025-07-21 19:51:16 +02:00
Mic92
579492f071 Merge pull request 'migration guide: fix moon example' (#4423) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4423
2025-07-21 17:11:14 +00:00
Jörg Thalheim
0ed02da28f migration guide: fix moon example 2025-07-21 19:07:47 +02:00
Mic92
4abfbb05a2 Merge pull request 'extend migration guide' (#4421) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4421
2025-07-21 16:23:58 +00:00
Jörg Thalheim
6126cccbcc extend migration guide 2025-07-21 18:10:58 +02:00
brianmcgee
9e77d16e6d Merge pull request 'fix(ui): alignment issues with forms' (#4418) from ui/minor-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4418
2025-07-21 12:13:36 +00:00
Brian McGee
53752d4a69 fix(ui): alignment issues with forms 2025-07-21 13:09:53 +01:00
DavHau
38955f763f clan default template: add inputs to specialArgs 2025-07-21 18:39:51 +07:00
brianmcgee
bd97896899 Merge pull request 'fix(ui): remove extra margin in modal title' (#4415) from ui/minor-fixes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4415
2025-07-21 10:22:15 +00:00
Brian McGee
d6efeb3295 fix(ui): remove extra margin in modal title 2025-07-21 11:18:22 +01:00
523 changed files with 16803 additions and 7209 deletions

View File

@@ -1,28 +0,0 @@
name: "Update pinned clan-core for checks"
on:
repository_dispatch:
workflow_dispatch:
schedule:
- cron: "51 2 * * *"
jobs:
update-pinned-clan-core:
runs-on: nix
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Update clan-core for checks
run: nix run .#update-clan-core-for-checks
- name: Create pull request
env:
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
run: |
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
git commit -am "Update pinned clan-core for checks"
# Use shared PR creation script
export PR_BRANCH="update-clan-core-for-checks"
export PR_TITLE="Update Clan Core for Checks"
export PR_BODY="This PR updates the pinned clan-core flake input that is used for checks."
./.gitea/workflows/create-pr.sh

View File

@@ -19,8 +19,7 @@ jobs:
uses: Mic92/update-flake-inputs-gitea@main
with:
# Exclude private flakes and update-clan-core checks flake
exclude-patterns: "devFlake/private/flake.nix,checks/impure/flake.nix"
exclude-patterns: "checks/impure/flake.nix"
auto-merge: true
gitea-token: ${{ secrets.CI_BOT_TOKEN }}
github-token: ${{ secrets.CI_BOT_GITHUB_TOKEN }}

View File

@@ -1,40 +0,0 @@
name: "Update private flake inputs"
on:
repository_dispatch:
workflow_dispatch:
schedule:
- cron: "0 3 * * *" # Run daily at 3 AM
jobs:
update-private-flake:
runs-on: nix
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Update private flake inputs
run: |
# Update the private flake lock file
cd devFlake/private
nix flake update
cd ../..
# Update the narHash
bash ./devFlake/update-private-narhash
- name: Create pull request
env:
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
run: |
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
# Check if there are any changes
if ! git diff --quiet; then
git add devFlake/private/flake.lock devFlake/private.narHash
git commit -m "Update dev flake"
# Use shared PR creation script
export PR_BRANCH="update-dev-flake"
export PR_TITLE="Update dev flake"
export PR_BODY="This PR updates the dev flake inputs and corresponding narHash."
else
echo "No changes detected in dev flake inputs"
fi

View File

@@ -24,7 +24,7 @@ If you're new to Clan and eager to dive in, start with our quickstart guide and
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
- **Secrets Management**: Securely manage secrets by consulting [secrets](https://docs.clan.lol/guides/getting-started/secrets/)<!-- [secrets.md](docs/site/guides/getting-started/secrets.md) -->.
- **Secrets Management**: Securely manage secrets by consulting [Vars](https://docs.clan.lol/concepts/generators/)<!-- [secrets.md](docs/site/concepts/generators.md) -->.
### Contributing to Clan

View File

@@ -1,210 +0,0 @@
{ self, ... }:
{
clan.machines.test-backup = {
imports = [ self.nixosModules.test-backup ];
fileSystems."/".device = "/dev/null";
boot.loader.grub.device = "/dev/null";
};
clan.inventory.services = {
borgbackup.test-backup = {
roles.client.machines = [ "test-backup" ];
roles.server.machines = [ "test-backup" ];
};
};
flake.nixosModules = {
test-backup =
{
pkgs,
lib,
...
}:
let
dependencies =
[
pkgs.stdenv.drvPath
]
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{
imports = [
# Do not import inventory modules. They should be configured via 'clan.inventory'
#
# TODO: Configure localbackup via inventory
self.clanModules.localbackup
];
# Borgbackup overrides
services.borgbackup.repos.test-backups = {
path = "/var/lib/borgbackup/test-backups";
authorizedKeys = [ (builtins.readFile ../assets/ssh/pubkey) ];
};
clan.borgbackup.destinations.test-backup.repo = lib.mkForce "borg@machine:.";
clan.core.networking.targetHost = "machine";
networking.hostName = "machine";
programs.ssh.knownHosts = {
machine.hostNames = [ "machine" ];
machine.publicKey = builtins.readFile ../assets/ssh/pubkey;
};
services.openssh = {
enable = true;
settings.UsePAM = false;
settings.UseDns = false;
hostKeys = [
{
path = "/root/.ssh/id_ed25519";
type = "ed25519";
}
];
};
users.users.root.openssh.authorizedKeys.keyFiles = [ ../assets/ssh/pubkey ];
# This is needed to unlock the user for sshd
# Because we use sshd without setuid binaries
users.users.borg.initialPassword = "hello";
systemd.tmpfiles.settings."vmsecrets" = {
"/root/.ssh/id_ed25519" = {
C.argument = "${../assets/ssh/privkey}";
z = {
mode = "0400";
user = "root";
};
};
"/etc/secrets/ssh.id_ed25519" = {
C.argument = "${../assets/ssh/privkey}";
z = {
mode = "0400";
user = "root";
};
};
"/etc/secrets/borgbackup/borgbackup.ssh" = {
C.argument = "${../assets/ssh/privkey}";
z = {
mode = "0400";
user = "root";
};
};
"/etc/secrets/borgbackup/borgbackup.repokey" = {
C.argument = builtins.toString (pkgs.writeText "repokey" "repokey12345");
z = {
mode = "0400";
user = "root";
};
};
};
clan.core.facts.secretStore = "vm";
clan.core.vars.settings.secretStore = "vm";
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
environment.etc.install-closure.source = "${closureInfo}/store-paths";
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = lib.mkForce 3;
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
};
system.extraDependencies = dependencies;
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
clan.core.state.test-service = {
preBackupScript = ''
touch /var/test-service/pre-backup-command
'';
preRestoreScript = ''
touch /var/test-service/pre-restore-command
'';
postRestoreScript = ''
touch /var/test-service/post-restore-command
'';
folders = [ "/var/test-service" ];
};
fileSystems."/mnt/external-disk" = {
device = "/dev/vdb"; # created in tests with virtualisation.emptyDisks
autoFormat = true;
fsType = "ext4";
options = [
"defaults"
"noauto"
];
};
clan.localbackup.targets.hdd = {
directory = "/mnt/external-disk";
preMountHook = ''
touch /run/mount-external-disk
'';
postUnmountHook = ''
touch /run/unmount-external-disk
'';
};
};
};
perSystem =
{ pkgs, ... }:
let
clanCore = self.checks.x86_64-linux.clan-core-for-checks;
in
{
checks = pkgs.lib.mkIf pkgs.stdenv.isLinux {
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
]
++
# import the inventory generated nixosModules
self.clan.clanInternals.inventoryClass.machines.test-backup.machineImports;
clan.core.settings.directory = ./.;
};
testScript = ''
import json
start_all()
# dummy data
machine.succeed("mkdir -p /var/test-backups /var/test-service")
machine.succeed("echo testing > /var/test-backups/somefile")
# create
machine.succeed("clan backups create --debug --flake ${clanCore} test-backup")
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
machine.succeed("test -f /run/mount-external-disk")
machine.succeed("test -f /run/unmount-external-disk")
# list
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
out = machine.succeed("clan backups list --debug --flake ${clanCore} test-backup").strip()
print(out)
assert backup_id in out, f"backup {backup_id} not found in {out}"
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
assert localbackup_id in out, "localbackup not found in {out}"
## borgbackup restore
machine.succeed("rm -f /var/test-backups/somefile")
machine.succeed(f"clan backups restore --debug --flake ${clanCore} test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
machine.succeed("test -f /var/test-service/pre-restore-command")
machine.succeed("test -f /var/test-service/post-restore-command")
machine.succeed("test -f /var/test-service/pre-backup-command")
## localbackup restore
machine.succeed("rm -rf /var/test-backups/somefile /var/test-service/ && mkdir -p /var/test-service")
machine.succeed(f"clan backups restore --debug --flake ${clanCore} test-backup localbackup '{localbackup_id}' >&2")
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
machine.succeed("test -f /var/test-service/pre-restore-command")
machine.succeed("test -f /var/test-service/post-restore-command")
machine.succeed("test -f /var/test-service/pre-backup-command")
'';
} { inherit pkgs self; };
};
};
}

View File

@@ -1,6 +1,6 @@
{ fetchgit }:
fetchgit {
url = "https://git.clan.lol/clan/clan-core.git";
rev = "eea93ea22c9818da67e148ba586277bab9e73cea";
sha256 = "sha256-PV0Z+97QuxQbkYSVuNIJwUNXMbHZG/vhsA9M4cDTCOE=";
rev = "5d884cecc2585a29b6a3596681839d081b4de192";
sha256 = "09is1afmncamavb2q88qac37vmsijxzsy1iz1vr6gsyjq2rixaxc";
}

View File

@@ -2,6 +2,7 @@
self,
lib,
inputs,
privateInputs ? { },
...
}:
let
@@ -19,18 +20,29 @@ let
nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { };
in
{
imports = filter pathExists [
./backups/flake-module.nix
../nixosModules/clanCore/machine-id/tests/flake-module.nix
../nixosModules/clanCore/state-version/tests/flake-module.nix
./devshell/flake-module.nix
./flash/flake-module.nix
./impure/flake-module.nix
./installation/flake-module.nix
./morph/flake-module.nix
./nixos-documentation/flake-module.nix
./dont-depend-on-repo-root.nix
];
imports =
let
clanCoreModulesDir = ../nixosModules/clanCore;
getClanCoreTestModules =
let
moduleNames = attrNames (builtins.readDir clanCoreModulesDir);
testPaths = map (
moduleName: clanCoreModulesDir + "/${moduleName}/tests/flake-module.nix"
) moduleNames;
in
filter pathExists testPaths;
in
getClanCoreTestModules
++ filter pathExists [
./devshell/flake-module.nix
./flash/flake-module.nix
./impure/flake-module.nix
./installation/flake-module.nix
./update/flake-module.nix
./morph/flake-module.nix
./nixos-documentation/flake-module.nix
./dont-depend-on-repo-root.nix
];
flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] (
system:
let
@@ -88,11 +100,11 @@ in
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
nixos-test-zt-tcp-relay = self.clanLib.test.containerTest ./zt-tcp-relay nixosTestArgs;
nixos-test-matrix-synapse = self.clanLib.test.containerTest ./matrix-synapse nixosTestArgs;
nixos-test-postgresql = self.clanLib.test.containerTest ./postgresql nixosTestArgs;
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
service-dummy-test = import ./service-dummy-test nixosTestArgs;
wireguard = import ./wireguard nixosTestArgs;
service-dummy-test-from-flake = import ./service-dummy-test-from-flake nixosTestArgs;
};
@@ -146,9 +158,12 @@ in
'';
clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } ''
cp -r ${pkgs.callPackage ./clan-core-for-checks.nix { }} $out
chmod +w $out/flake.lock
cp -r ${privateInputs.clan-core-for-checks} $out
chmod -R +w $out
cp ${../flake.lock} $out/flake.lock
# Create marker file to disable private flake loading in tests
touch $out/.skip-private-inputs
'';
};
packages = lib.optionalAttrs (pkgs.stdenv.isLinux) {

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
{
@@ -60,6 +61,10 @@
nodes.target = {
virtualisation.emptyDiskImages = [ 4096 ];
virtualisation.memorySize = 4096;
virtualisation.useNixStoreImage = true;
virtualisation.writableStore = true;
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
environment.etc."install-closure".source = "${closureInfo}/store-paths";
@@ -78,8 +83,8 @@
start_all()
# Some distros like to automount disks with spaces
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdb && mount /dev/vdb "/mnt/with spaces"')
machine.succeed("clan flash write --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdb test-flash-machine-${pkgs.hostPlatform.system}")
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
machine.succeed("clan flash write --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
'';
} { inherit pkgs self; };
};

View File

@@ -1,6 +1,7 @@
{
self,
lib,
privateInputs,
...
}:
@@ -149,17 +150,17 @@
# vm-test-run-test-installation-> target: To debug, enter the VM and run 'systemctl status backdoor.service'.
checks =
let
# Custom Python package for port management utilities
closureInfo = pkgs.closureInfo {
rootPaths = [
self.checks.x86_64-linux.clan-core-for-checks
privateInputs.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
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) {
@@ -225,7 +226,7 @@
"install",
"--phases", "disko,install",
"--debug",
"--flake", flake_dir,
"--flake", str(flake_dir),
"--yes", "test-install-machine-without-system",
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
"-i", ssh_conn.ssh_key,
@@ -289,9 +290,6 @@
assert not os.path.exists(hw_config_file), "hardware-configuration.nix should not exist initially"
assert not os.path.exists(facter_file), "facter.json should not exist initially"
# Set CLAN_FLAKE for the commands
os.environ["CLAN_FLAKE"] = flake_dir
# Test facter backend
clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",

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

@@ -1,73 +0,0 @@
({
name = "postgresql";
nodes.machine =
{ self, config, ... }:
{
imports = [
self.nixosModules.clanCore
self.clanModules.postgresql
self.clanModules.localbackup
];
clan.postgresql.users.test = { };
clan.postgresql.databases.test.create.options.OWNER = "test";
clan.postgresql.databases.test.restore.stopOnRestore = [ "sample-service" ];
clan.localbackup.targets.hdd.directory = "/mnt/external-disk";
clan.core.settings.directory = ./.;
systemd.services.sample-service = {
wantedBy = [ "multi-user.target" ];
script = ''
while true; do
echo "Hello, world!"
sleep 5
done
'';
};
environment.systemPackages = [ config.services.postgresql.package ];
};
testScript =
{ nodes, ... }:
''
start_all()
machine.wait_for_unit("postgresql")
machine.wait_for_unit("sample-service")
# Create a test table
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -c 'CREATE TABLE test (id serial PRIMARY KEY);' test")
machine.succeed("/run/current-system/sw/bin/localbackup-create >&2")
timestamp_before = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
machine.succeed("test -e /mnt/external-disk/snapshot.0/machine/var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'INSERT INTO test DEFAULT VALUES;'")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'DROP TABLE test;'")
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("rm -rf /var/backup/postgres")
machine.succeed("NAME=/mnt/external-disk/snapshot.0 FOLDERS=/var/backup/postgres/test /run/current-system/sw/bin/localbackup-restore >&2")
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
machine.succeed("""
set -x
${nodes.machine.clan.core.state.test.postRestoreCommand}
""")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
timestamp_after = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
assert timestamp_before < timestamp_after, f"{timestamp_before} >= {timestamp_after}: expected sample-service to be restarted after restore"
# Check that the table is still there
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'SELECT * FROM test;'")
output = machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql --csv -c \"SELECT datdba::regrole FROM pg_database WHERE datname = 'test'\"")
owner = output.split("\n")[1]
assert owner == "test", f"Expected database owner to be 'test', got '{owner}'"
# check if restore works if the database does not exist
machine.succeed("runuser -u postgres -- dropdb test")
machine.succeed("${nodes.machine.clan.core.state.test.postRestoreCommand}")
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
'';
})

View File

@@ -29,18 +29,10 @@ nixosLib.runTest (
testScript =
{ nodes, ... }:
''
import subprocess
from nixos_test_lib.nix_setup import setup_nix_in_nix # type: ignore[import-untyped]
setup_nix_in_nix(None) # No closure info for this test
def run_clan(cmd: list[str], **kwargs) -> str:
import subprocess
clan = "${clan-core.packages.${hostPkgs.system}.clan-cli}/bin/clan"
clan_args = ["--flake", "${config.clan.test.flakeForSandbox}"]
return subprocess.run(
["${hostPkgs.util-linux}/bin/unshare", "--user", "--map-user", "1000", "--map-group", "1000", clan, *cmd, *clan_args],
**kwargs,
check=True,
).stdout
setup_nix_in_nix(None) # No closure info for this test
start_all()
admin1.wait_for_unit("multi-user.target")
@@ -60,7 +52,13 @@ nixosLib.runTest (
# Check that the file is in the '0644' mode
assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}"
run_clan(["machines", "list"])
# Run clan command
result = subprocess.run(
["${
clan-core.packages.${hostPkgs.system}.clan-cli
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
check=True
)
'';
}
)

View File

@@ -0,0 +1,307 @@
{ self, ... }:
{
# Machine for update test
clan.machines.test-update-machine = {
imports = [
self.nixosModules.test-update-machine
# Import the configuration file that will be created/updated during the test
./test-update-machine/configuration.nix
];
};
flake.nixosModules.test-update-machine =
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
self.clanLib.test.minifyModule
../../lib/test/container-test-driver/nixos-module.nix
];
# Apply patch to fix x-initrd.mount filesystem handling in switch-to-configuration-ng
nixpkgs.overlays = [
(_final: prev: {
switch-to-configuration-ng = prev.switch-to-configuration-ng.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [ ./switch-to-configuration-initrd-mount-fix.patch ];
});
})
];
networking.hostName = "update-machine";
environment.etc."install-successful".text = "ok";
# Enable SSH and add authorized key for testing
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;
boot.kernelParams = [ "boot.shell_on_fail" ];
boot.isContainer = true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
# Preserve the IP addresses assigned by the test framework
# (based on virtualisation.vlans = [1] and node number 1)
networking.interfaces.eth1 = {
useDHCP = false;
ipv4.addresses = [
{
address = "192.168.1.1";
prefixLength = 24;
}
];
ipv6.addresses = [
{
address = "2001:db8:1::1";
prefixLength = 64;
}
];
};
# Define the mounts that exist in the container to prevent them from being stopped
fileSystems = {
"/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
options = [ "x-initrd.mount" ];
};
"/nix/.rw-store" = {
device = "tmpfs";
fsType = "tmpfs";
options = [
"mode=0755"
];
};
"/nix/store" = {
device = "overlay";
fsType = "overlay";
options = [
"lowerdir=/nix/.ro-store"
"upperdir=/nix/.rw-store/upper"
"workdir=/nix/.rw-store/work"
];
};
};
};
perSystem =
{
pkgs,
...
}:
{
checks =
pkgs.lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.stdenv.hostPlatform.system == "x86_64-linux")
{
nixos-test-update =
let
closureInfo = pkgs.closureInfo {
rootPaths = [
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);
};
in
self.clanLib.test.containerTest {
name = "update";
nodes.machine = {
imports = [ self.nixosModules.test-update-machine ];
};
extraPythonPackages = _p: [
self.legacyPackages.${pkgs.system}.nixosTestLib
];
testScript = ''
import tempfile
import os
import subprocess
from nixos_test_lib.ssh import setup_ssh_connection # type: ignore[import-untyped]
from nixos_test_lib.nix_setup import prepare_test_flake # type: ignore[import-untyped]
start_all()
machine.wait_for_unit("multi-user.target")
# Verify initial state
machine.succeed("test -f /etc/install-successful")
machine.fail("test -f /etc/update-successful")
# Set up test environment
with tempfile.TemporaryDirectory() as temp_dir:
# Prepare test flake and Nix store
flake_dir = prepare_test_flake(
temp_dir,
"${self.checks.x86_64-linux.clan-core-for-checks}",
"${closureInfo}"
)
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
# Set up SSH connection
ssh_conn = setup_ssh_connection(
machine,
temp_dir,
"${../assets/ssh/privkey}"
)
# 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)
# 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)
for line in agent_output.splitlines():
if line.startswith("SSH_AUTH_SOCK="):
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",
"machines",
"update",
"--debug",
"--flake", flake_dir,
"--host-key-check", "none",
"--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/target-host-update-successful")
##############
print("TEST: update with --build-host")
# Update configuration again
with open(machine_config_path, "w") as f:
f.write("""
{
environment.etc."build-host-update-successful".text = "ok";
}
""")
# 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",
"--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}",
], check=True)
# Verify the second update was successful
machine.succeed("test -f /etc/build-host-update-successful")
'';
} { inherit pkgs self; };
};
};
}

View File

@@ -0,0 +1,17 @@
diff --git a/src/main.rs b/src/main.rs
index 8baf5924a7db..1234567890ab 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1295,6 +1295,12 @@ won't take effect until you reboot the system.
for (mountpoint, current_filesystem) in current_filesystems {
// Use current version of systemctl binary before daemon is reexeced.
+
+ // Skip filesystem comparison if x-initrd.mount is present in options
+ if current_filesystem.options.contains("x-initrd.mount") {
+ continue;
+ }
+
let unit = path_to_unit_name(&current_system_bin, &mountpoint);
if let Some(new_filesystem) = new_filesystems.get(&mountpoint) {
if current_filesystem.fs_type != new_filesystem.fs_type

View File

@@ -0,0 +1,3 @@
{
# Initial empty configuration
}

View File

@@ -0,0 +1,115 @@
{
pkgs,
nixosLib,
clan-core,
lib,
...
}:
nixosLib.runTest (
{ ... }:
let
machines = [
"controller1"
"controller2"
"peer1"
"peer2"
"peer3"
];
in
{
imports = [
clan-core.modules.nixosTest.clanTest
];
hostPkgs = pkgs;
name = "wireguard";
clan = {
directory = ./.;
modules."@clan/wireguard" = import ../../clanServices/wireguard/default.nix;
inventory = {
machines = lib.genAttrs machines (_: { });
instances = {
/*
wg-test-one
controller2 controller1
peer2 peer1 peer3
*/
wg-test-one = {
module.name = "@clan/wireguard";
module.input = "self";
roles.controller.machines."controller1".settings = {
endpoint = "192.168.1.1";
};
roles.controller.machines."controller2".settings = {
endpoint = "192.168.1.2";
};
roles.peer.machines = {
peer1.settings.controller = "controller1";
peer2.settings.controller = "controller2";
peer3.settings.controller = "controller1";
};
};
# TODO: Will this actually work with conflicting ports? Can we re-use interfaces?
#wg-test-two = {
# module.name = "@clan/wireguard";
# roles.controller.machines."controller1".settings = {
# endpoint = "192.168.1.1";
# port = 51922;
# };
# roles.peer.machines = {
# peer1 = { };
# };
#};
};
};
};
testScript = ''
start_all()
# Show all addresses
machines = [peer1, peer2, peer3, controller1, controller2]
for m in machines:
m.systemctl("start network-online.target")
for m in machines:
m.wait_for_unit("network-online.target")
m.wait_for_unit("systemd-networkd.service")
print("\n\n" + "="*60)
print("STARTING PING TESTS")
print("="*60)
for m1 in machines:
for m2 in machines:
if m1 != m2:
print(f"\n--- Pinging from {m1.name} to {m2.name}.wg-test-one ---")
m1.wait_until_succeeds(f"ping -c1 {m2.name}.wg-test-one >&2")
'';
}
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
{
"data": "ENC[AES256_GCM,data:zDF0RiBqaawpg+GaFkuLPomJ01Xu+lgY5JfUzaIk2j03XkCzIf8EMrmn6pRtBP3iUjPBm+gQSTQk6GHTONrixA5hRNyETV+UgQw=,iv:zUUCAGZ0cz4Tc2t/HOjVYNsdnrAOtid/Ns5ak7rnyCk=,tag:z43WtNSue4Ddf7AVu21IKA==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlY1NEdjAzQm5RMFZWY3BJ\nclp6c01FdlZFK3dOSDB4cHc1NTdwMXErMFJFCnIrRVFNZEFYOG1rVUhFd2xsbTJ2\nVkJHNmdOWXlOcHJoQ0QzM1VyZmxmcGcKLS0tIFk1cEx4dFdvNGRwK1FWdDZsb1lR\nV2d1RFZtNzZqVFdtQ1FzNStEcEgyUUkKx8tkxqJz/Ko3xgvhvd6IYiV/lRGmrY13\nUZpYWR9tsQwZAR9dLjCyVU3JRuXeGB1unXC1CO0Ff3R0A/PuuRHh+g==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:37Z",
"mac": "ENC[AES256_GCM,data:8RGOUhZ2LGmC9ugULwHDgdMrtdo9vzBm3BJmL4XTuNJKm0NlKfgNLi1E4n9DMQ+kD4hKvcwbiUcwSGE8jZD6sm7Sh3bJi/HZCoiWm/O/OIzstli2NNDBGvQBgyWZA5H+kDjZ6aEi6icNWIlm5gsty7KduABnf5B3p0Bn5Uf5Bio=,iv:sGZp0XF+mgocVzAfHF8ATdlSE/5zyz5WUSRMJqNeDQs=,tag:ymYVBRwF5BOSAu5ONU2qKw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"data": "ENC[AES256_GCM,data:dHM7zWzqnC1QLRKYpbI2t63kOFnSaQy6ur9zlkLQf17Q03CNrqUsZtdEbwMnLR3llu7eVMhtvVRkXjEkvn3leb9HsNFmtk/DP70=,iv:roEZsBFqRypM106O5sehTzo7SySOJUJgAR738rTtOo8=,tag:VDd9/6uU0SAM7pWRLIUhUQ==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKTEVYUmVGbUtOcHZ4cnc3\nKzNETnlxaVRKYTI3eWVHdEoyc3l2SnhsZ1J3CnB2RnZrOXM5Uml6TThDUlZjY25J\nbkJ6eUZ2ckN1NWpNUU9IaE93UDJQdlEKLS0tIC95ZDhkU0R1VHhCdldxdW4zSmps\nN3NqL1cvd05hRTRPdDA3R2pzNUFFajgKS+DJH14fH9AvEAa3PoUC1jEqKAzTmExN\nl32FeHTHbGMo1PKeaFm+Eg0WSpAmFE7beBunc5B73SW30ok6x4FcQw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:47Z",
"mac": "ENC[AES256_GCM,data:77EnuBQyguvkCtobUg8/6zoLHjmeGDrSBZuIXOZBMxdbJjzhRg++qxQjuu6t0FoWATtz7u4Y3/jzUMGffr/N5HegqSq0D2bhv7AqJwBiVaOwd80fRTtM+YiP/zXsCk52Pj/Gadapg208bDPQ1BBDOyz/DrqZ7w//j+ARJjAnugI=,iv:IuTDmJKZEuHXJXjxrBw0gP2t6vpxAYEqbtpnVbavVCY=,tag:4EnpX6rOamtg1O+AaEQahQ==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"data": "ENC[AES256_GCM,data:wcSsqxTKiMAnzPwxs5DNjcSdLyjVQ9UOrZxfSbOkVfniwx6F7xz6dLNhaDq7MHQ0vRWpg28yNs7NHrp52bYFnb/+eZsis46WiCw=,iv:B4t1lvS2gC601MtsmZfEiEulLWvSGei3/LSajwFS9Vs=,tag:hnRXlZyYEFfLJUrw1SqbSQ==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAybUgya2VEdzMvRG1hdkpu\nM2pGNmcyVmcvYVZ1ZjJlY3A1bXFUUUtkMTI0CmJoRFZmejZjN2UxUXNuc1k5WnE2\nNmxIcnpNQ1lJZ3ZKSmhtSlVURXJTSUUKLS0tIGU4Wi9yZ3VYekJkVW9pNWFHblFk\na0gzbTVKUWdSam1sVjRUaUlTdVd5YWMKntRc9yb9VPOTMibp8QM5m57DilP01N/X\nPTQaw8oI40znnHdctTZz7S+W/3Te6sRnkOhFyalWmsKY0CWg/FELlA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:58Z",
"mac": "ENC[AES256_GCM,data:8nq+ugkUJxE24lUIySySs/cAF8vnfqr936L/5F0O1QFwNrbpPmKRXkuwa6u0V+187L2952Id20Fym4ke59f3fJJsF840NCKDwDDZhBZ20q9GfOqIKImEom/Nzw6D0WXQLUT3w8EMyJ/F+UaJxnBNPR6f6+Kx4YgStYzCcA6Ahzg=,iv:VBPktEz7qwWBBnXE+xOP/EUVy7/AmNCHPoK56Yt/ZNc=,tag:qXONwOLFAlopymBEf5p4Sw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"data": "ENC[AES256_GCM,data:4d3ri0EsDmWRtA8vzvpPRLMsSp4MIMKwvtn0n0pRY05uBPXs3KcjnweMPIeTE1nIhqnMR2o2MfLah5TCPpaFax9+wxIt74uacbg=,iv:0LBAldTC/hN4QLCxgXTl6d9UB8WmUTnj4sD2zHQuG2w=,tag:zr/RhG/AU4g9xj9l2BprKw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvV0JnZDhlU1piU1g2cng0\ncytKOEZ6WlZlNGRGUjV3MmVMd2Nzc0ZwelgwCjBGdThCUGlXbVFYdnNoZWpJZ3Vm\nc2xkRXhxS09vdzltSVoxLzhFSVduak0KLS0tIE5DRjJ6cGxiVlB1eElHWXhxN1pJ\nYWtIMDMvb0Z6akJjUzlqeEFsNHkxL2cKpghv/QegnXimeqd9OPFouGM//jYvoVmw\n2d4mLT2JSMkEhpfGcqb6vswhdJfCiKuqr2B4bqwAnPMaykhsm8DFRQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:20:08Z",
"mac": "ENC[AES256_GCM,data:BzlQVAJ7HzcxNPKB3JhabqRX/uU0EElj172YecjmOflHnzz/s9xgfdAfJK/c53hXlX4LtGPnubH7a8jOolRq98zmZeBYE27+WLs2aN7Ufld6mYk90/i7u4CqR+Fh2Kfht04SlUJCjnS5A9bTPwU9XGRHJ0BiOhzTuSMUJTRaPRM=,iv:L50K5zc1o99Ix9nP0pb9PRH+VIN2yvq7JqKeVHxVXmc=,tag:XFLkSCsdbTPxbasDYYxcFQ==,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:qfLm6+g1vYnESCik9uyBeKsY6Ju2Gq3arnn2I8HHNO67Ri5BWbOQTvtz7WT8/q94RwVjv8SGeJ/fsJSpwLSrJSbqTZCPAnYwzzQ=,iv:PnA9Ao8RRELNhNQYbaorstc0KaIXRU7h3+lgDCXZFHk=,tag:VeLgYQYwqthYihIoQTwYiA==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNWVVQaDJFd0N3WHptRC9Z\nZTgxTWh5bnU1SkpqRWRXZnhPaFhpSVJmVEhrCjFvdHFYenNWaFNrdXlha09iS2xj\nOTZDcUNkcHkvTDUwNjM4Z3gxUkxreUEKLS0tIE5oY3Q2bWhsb2FSQTVGTWVSclJw\nWllrelRwT3duYjJJbTV0d3FwU1VuNlkK2eN3fHFX/sVUWom8TeZC9fddqnSCsC1+\nJRCZsG46uHDxqLcKIfdFWh++2t16XupQYk3kn+NUR/aMc3fR32Uwjw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:20:18Z",
"mac": "ENC[AES256_GCM,data:nUwsPcP1bsDjAHFjQ1NlVkTwyZY4B+BpzNkMx9gl0rE14j425HVLtlhlLndhRp+XMpnDldQppLAAtSdzMsrw8r5efNgTRl7cu4Fy/b9cHt84k7m0aou5lrGus9SV1bM7/fzC9Xm7CSXBcRzyDGVsKC6UBl1rx+ybh7HyAN05XSo=,iv:It57H+zUUNPkoN1D8sYwyZx5zIFIga7mydhGUHYBCGE=,tag:mBQdYqUpjPknbYa13qESyw==,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/controller1

View File

@@ -0,0 +1,19 @@
{
"data": "ENC[AES256_GCM,data:noe913+28JWkoDkGGMu++cc1+j5NPDoyIhWixdsowoiVO3cTWGkZ88SUGO5D,iv:ynYMljwqMcBdk8RpVcw/2Jflg2RCF28r4fKUgIAF8B4=,tag:+TsXDJgfUhKgg4iQVXKKlQ==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhYVRReTZBQ05GYmVBVjhS\nNXM5aFlhVzZRaVl6UHl6S3JnMC9Sb1dwZ1ZjCmVuS2dEVExYZWROVklUZWFCSnM2\nZnlxbVNseTM2c0Q0TjhsT3NzYmtqREUKLS0tIHBRTFpvVGt6d1cxZ2lFclRsUVhZ\nZDlWaG9PcXVrNUZKaEgxWndjUDVpYjgKt0eOhAgcYdkg9JSEakx4FjChLTn3pis+\njOkuGd4JfXMKcwC7vJV5ygQBxzVJSBw+RucP7sYCBPK0m8Voj94ntw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1rnkc2vmrupy9234clyu7fpur5kephuqs3v7qauaw5zeg00jqjdasefn3cc",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6MFJqNHNraG9DSnJZMFdz\ndU8zVXNTamxROFd1dWtuK2RiekhPdHhleVhFCi8zNWJDNXJMRUlDdjc4Q0UycTIz\nSGFGSmdnNU0wZWlDaTEwTzBqWjh6SFkKLS0tIEJOdjhOMDY2TUFLb3RPczNvMERx\nYkpSeW5VOXZvMlEvdm53MDE3aUFTNjgKyelSTjrTIR9I3rJd3krvzpsrKF1uGs4J\n4MtmQj0/3G+zPYZVBx7b3HF6B3f1Z7LYh05+z7nCnN/duXyPnDjNcg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:37Z",
"mac": "ENC[AES256_GCM,data:+DmIkPG/H6tCtf8CvB98E1QFXv08QfTcCB3CRsi+XWnIRBkryRd/Au9JahViHMdK7MED8WNf84NWTjY2yH4y824/DjI8XXNMF1iVMo0CqY42xbVHtUuhXrYeT+c8CyEw+M6zfy1jC0+Bm3WQWgagz1G6A9SZk3D2ycu0N08+axA=,iv:kwBjTYebIy5i2hagAajSwwuKnSkrM9GyrnbeQXB2e/w=,tag:EgKJ5gVGYj1NGFUduxLGfg==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1 @@
lQfR7GhivN87XoXruTGOPjVPhNu1Brt//wyc3pdwE20=

View File

@@ -0,0 +1 @@
7470bb5c79df224a9b7f5a2259acd2e46db763c27e24cb3416c8b591cb328077

View File

@@ -0,0 +1 @@
fd51:19c1:3b:f700

View File

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

View File

@@ -0,0 +1,19 @@
{
"data": "ENC[AES256_GCM,data:2kehACgvNgoYGPwnW7p86BR0yUu689Chth6qZf9zoJtuTY9ATS68dxDyBc5S,iv:qb2iDUtExegTeN3jt6SA8RnU61W5GDDhn56QXiQT4gw=,tag:pSGPICX5p6qlZ1WMVoIEYQ==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSTTR5TDY4RE9VYmlCK1dL\nWkVRcVZqVDlsbmQvUlJmdzF2b1Z1S0k3NngwCkFWNzRVaERtSmFsd0o2aFJOb0ZX\nSU9yUnVaNi9IUjJWeGRFcEpDUXo5WkEKLS0tIEczNkxiYnJsTWRoLzFhQVF1M21n\nWnZEdGV1N2N5d1FZQkJUQ1IrdGFLblkKPTpha2bxS8CCAMXWTDKX/WOcdvggaP3Y\nqewyahDNzb4ggP+LNKp55BtwFjdvoPoq4BpYOOgMRbQMMk+H1o9WFw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1t2hhg99d4p2yymuhngcy5ccutp8mvu7qwvg5cdhck303h9e7ha9qnlt635",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYcEZ6Tzk3M0pkV0tOdTBj\nenF2a0tHNnhBa0NrazMwV1VBbXBZR3pzSHpvCnBZOEU0VlFHS1FHcVpTTDdPczVV\nV0RFSlZ0VmIzWGoydEdKVXlIUE9OOEkKLS0tIFZ0cWVBR1loeVlWa2c4U3oweXE2\ncm1ja0JCS3U5Nk41dlAzV2NabDc2bDQKdgCDNnpRZlFPnEGlX6fo0SQX4yOB+E6r\ntnSwofR3xxZvkyme/6JJU5qBZXyCXEAhKMRkFyvJANXzMJAUo/Osow==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:48Z",
"mac": "ENC[AES256_GCM,data:e3EkL8vwRhLsec83Zi9DE3PKT+4RwgiffpN4QHcJKTgmDW6hzizWc5kAxbNWGJ9Qqe6sso2KY7tc+hg1lHEsmzjCbg153p8h+7lVI2XT6adi/CS8WZ2VpeL+0X9zDQCjqHmrESZAYFBdkLqO4jucdf0Pc3CKKD+N3BDDTwSUvHM=,iv:xvR7dJL8sdYen00ovrYT8PNxhB9XxSWDSRz1IK23I/o=,tag:OyhAvllBgfAp3eGeNpR/Nw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1 @@
5Z7gbLFbXpEFfomW2pKyZBpZN5xvUtiqrIL0GVfNtQ8=

View File

@@ -0,0 +1 @@
c3672fdb9fb31ddaf6572fc813cf7a8fe50488ef4e9d534c62d4f29da60a1a99

View File

@@ -0,0 +1 @@
fd51:19c1:c1:aa00

View File

@@ -0,0 +1,19 @@
{
"data": "ENC[AES256_GCM,data:b+akw85T3D9xc75CPLHucR//k7inpxKDvgpR8tCNKwNDRVjVHjcABhfZNLXW,iv:g11fZE8UI0MVh9GKdjR6leBlxa4wN7ZubozXG/VlBbw=,tag:0YkzWCW3zJ3Mt3br/jmTYw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1jts52rzlqcwjc36jkp56a7fmjn3czr7kl9ta2spkfzhvfama33sqacrzzd",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWkJUR0pIa2xOSEw2dThm\nYlNuOHZCVW93Wkc5LzE4YmpUTHRkZlk3ckc4CnN4M3ZRMWNFVitCT3FyWkxaR0di\nb0NmSXFhRHJmTWg0d05OcWx1LytscEEKLS0tIEtleTFqU3JrRjVsdHpJeTNuVUhF\nWEtnOVlXVXRFamFSak5ia2F2b0JiTzAKlhOBZvZ4AN+QqAYQXvd6YNmgVS4gtkWT\nbV3bLNTgwtrDtet9NDHM8vdF+cn5RZxwFfgmTbDEow6Zm8EXfpxj/g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6YVYyQkZqMTJYQTlyRG5Y\nbnJ2UkE1TS9FZkpSa2tQbk1hQjViMi9OcGk0CjFaZUdjU3JtNzh0bDFXdTdUVW4x\nanFqZHZjZjdzKzA2MC8vTWh3Uy82UGcKLS0tIDhyOFl3UGs3czdoMlpza3UvMlB1\nSE90MnpGc05sSCtmVWg0UVNVdmRvN2MKHlCr4U+7bsoYb+2fgT4mEseZCEjxrtLu\n55sR/4YH0vqMnIBnLTSA0e+WMrs3tQfseeJM5jY/ZNnpec1LbxkGTg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:19:58Z",
"mac": "ENC[AES256_GCM,data:gEoEC9D2Z7k5F8egaY1qPXT5/96FFVsyofSBivQ28Ir/9xHX2j40PAQrYRJUWsk/GAUMOyi52Wm7kPuacw+bBcdtQ0+MCDEmjkEnh1V83eZ/baey7iMmg05uO92MYY5o4e7ZkwzXoAeMCMcfO0GqjNvsYJHF1pSNa+UNDj+eflw=,iv:dnIYpvhAdvUDe9md53ll42krb0sxcHy/toqGc7JFxNA=,tag:0WkZU7GeKMD1DQTYaI+1dg==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1 @@
juK7P/92N2t2t680aLIRobHc3ts49CsZBvfZOyIKpUc=

View File

@@ -0,0 +1 @@
b36142569a74a0de0f9b229f2a040ae33a22d53bef5e62aa6939912d0cda05ba

View File

@@ -0,0 +1 @@
6987:50a0:9b93:4337

View File

@@ -0,0 +1,19 @@
{
"data": "ENC[AES256_GCM,data:apX2sLwtq6iQgLJslFwiRMNBUe0XLzLQbhKfmb2pKiJG7jGNHUgHJz3Ls4Ca,iv:HTDatm3iD5wACTkkd3LdRNvJfnfg75RMtn9G6Q7Fqd4=,tag:Mfehlljnes5CFD1NJdk27A==,type:str]",
"sops": {
"age": [
{
"recipient": "age12nqnp0zd435ckp5p0v2fv4p2x4cvur2mnxe8use2sx3fgy883vaq4ae75e",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVZzFyMUZsd2V2VWxOUmhP\nZE8yZTc4Q0RkZisxR25NemR1TzVDWmJZVjBVClA1MWhsU0xzSG16aUx3cWFWKzlG\nSkxrT09OTkVqLzlWejVESE1QWHVJaFkKLS0tIGxlaGVuWU43RXErNTB3c3FaUnM3\nT0N5M253anZkbnFkZWw2VHA0eWhxQW8Kd1PMtEX1h0Hd3fDLMi++gKJkzPi9FXUm\n+uYhx+pb+pJM+iLkPwP/q6AWC7T0T4bHfekkdzxrbsKMi73x/GrOiw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqVzRIMWdlNjVwTURyMFkv\nSUhiajZkZVNuWklRYit6cno4UzNDa2szOFN3CkQ2TWhHb25pbmR1MlBsRXNLL2lx\ncVZ3c3BsWXN2aS9UUVYvN3I4S0xUSmMKLS0tIE5FV0U5aXVUZk9XL0U0Z2ZSNGd5\nbU9zY3IvMlpSNVFLYkRNQUpUYVZOWFUK7j4Otzb8CJTcT7aAj9/irxHEDXh1HkTg\nzz7Ho8/ZncNtaCVHlHxjTgVW9d5aIx8fSsV9LRCFwHMtNzvwj1Nshg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:20:08Z",
"mac": "ENC[AES256_GCM,data:e7WNVEz78noHBiz6S3A6qNfop+yBXB3rYN0k4GvaQKz3b99naEHuqIF8Smzzt4XrbbiPKu2iLa5ddLBlqqsi32UQUB8JS9TY7hvW8ol+jpn0VxusGCXW9ThdDEsM/hXiPyr331C73zTvbOYI1hmcGMlJL9cunVRO9rkMtEqhEfo=,iv:6zt7wjIs1y5xDHNK+yLOwoOuUpY7/dOGJGT6UWAFeOg=,tag:gzFTgoxhoLzUV0lvzOhhfg==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1 @@
XI9uSaQRDBCb82cMnGzGJcbqRfDG/IXZobyeL+kV03k=

View File

@@ -0,0 +1 @@
360f9fce4a984eb87ce2a673eb5341ecb89c0f62126548d45ef25ff5243dd646

View File

@@ -0,0 +1 @@
3b21:3ced:003e:89b3

View File

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

View File

@@ -0,0 +1,19 @@
{
"data": "ENC[AES256_GCM,data:Gluvjes/3oH5YsDq00JDJyJgoEFcj56smioMArPSt309MDGExYX2QsCzeO1q,iv:oBBJRDdTj/1dWEvzhdFKQ2WfeCKyavKMLmnMbqnU5PM=,tag:2WNFxKz2dWyVcybpm5N4iw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtQWpjRmhZTFdPa2VSZkFN\nbUczMlY5bDBmMTdoMy8xcWxMaXpWVitMZGdjCnRWb2Y3eGpHU1hmNHRJVFBqbU5w\nVEZGdUIrQXk0U0dUUEZ6bE5EMFpTRHMKLS0tIGpYSmZmQThJUTlvTHpjc05ZVlM4\nQWhTOWxnUHZnYlJ3czE3ZUJ0L3ozWTQK3a7N0Zpzo4sUezYveqvKR49RUdJL23eD\n+cK5lk2xbtj+YHkeG+dg7UlHfDaicj0wnFH1KLuWmNd1ONa6eQp3BQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1sglr4zp34drjfydzeweq43fz3uwpul3hkh53lsfa9drhuzwmkqyqn5jegp",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3a2FOWlVsSkdnendrYmUz\ndEpuL1hZSWNFTUtDYm14S3V1aW9KS3hsazJRCkp2SkFFbi9hbGJpNks1MlNTL0s5\nTk5pcUMxaEJobkcvWmRGeU9jMkdNdzAKLS0tIDR6M0Y5eE1ETHJJejAzVW1EYy9v\nZCtPWHJPUkhuWnRzSGhMUUtTa280UmMKXvtnxyop7PmRvTOFkV80LziDjhGh93Pf\nYwhD/ByD/vMmr21Fd6PVHOX70FFT30BdnMc1/wt7c/0iAw4w4GoQsA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-08-13T09:20:18Z",
"mac": "ENC[AES256_GCM,data:3nXMTma0UYXCco+EM8UW45cth7DVMboFBKyesL86GmaG6OlTkA2/25AeDrtSVO13a5c2jC6yNFK5dE6pSe5R9f0BoDF7d41mgc85zyn+LGECNWKC6hy6gADNSDD6RRuV1S3FisFQl1F1LD8LiSWmg/XNMZzChNlHYsCS8M+I84g=,iv:pu5VVXAVPmVoXy0BJ+hq5Ar8R0pZttKSYa4YS+dhDNc=,tag:xp1S/4qExnxMTGwhfLJrkA==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
}
}

View File

@@ -0,0 +1 @@
t6qN4VGLR+VMhrBDNKQEXZVyRsEXs1/nGFRs5DI82F8=

View File

@@ -0,0 +1 @@
e3facc99b73fe029d4c295f71829a83f421f38d82361cf412326398175da162a

View File

@@ -0,0 +1 @@
e42b:bf85:33f4:f0b1

View File

@@ -4,7 +4,7 @@ description = "Statically configure borgbackup with sane defaults."
!!! Danger "Deprecated"
Use [borgbackup](borgbackup.md) instead.
Don't use borgbackup-static through [inventory](../../guides/inventory.md).
Don't use borgbackup-static through [inventory](../../concepts/inventory.md).
This module implements the `borgbackup` backend and implements sane defaults
for backup management through `borgbackup` for members of the clan.

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

@@ -61,7 +61,6 @@ in
};
};
imports = [
../postgresql
(lib.mkRemovedOptionModule [
"clan"
"matrix-synapse"
@@ -106,57 +105,56 @@ in
};
};
clan.postgresql.users.matrix-synapse = { };
clan.postgresql.databases.matrix-synapse.create.options = {
clan.core.postgresql.enable = true;
clan.core.postgresql.users.matrix-synapse = { };
clan.core.postgresql.databases.matrix-synapse.create.options = {
TEMPLATE = "template0";
LC_COLLATE = "C";
LC_CTYPE = "C";
ENCODING = "UTF8";
OWNER = "matrix-synapse";
};
clan.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
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

@@ -1,224 +1,9 @@
{ lib, ... }:
{
pkgs,
lib,
config,
...
}:
let
createDatabaseState =
db:
let
folder = "/var/backup/postgres/${db.name}";
current = "${folder}/pg-dump";
compression = lib.optionalString (lib.versionAtLeast config.services.postgresql.package.version "16") "--compress=zstd";
in
{
folders = [ folder ];
preBackupScript = ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
mkdir -p "${folder}"
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
mv "${current}.tmp" ${current}
'';
postRestoreScript = ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
pkgs.gnugrep
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
echo "Waiting for postgres to be ready..."
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
if ! systemctl is-active postgresql; then exit 1; fi
sleep 0.1
done
if [[ -e "${current}" ]]; then
(
systemctl stop ${lib.concatStringsSep " " db.restore.stopOnRestore}
trap "systemctl start ${lib.concatStringsSep " " db.restore.stopOnRestore}" EXIT
mkdir -p "${folder}"
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
runuser -u postgres -- dropdb "${db.name}"
fi
runuser -u postgres -- pg_restore -C -d postgres "${current}"
)
else
echo No database backup found, skipping restore
fi
'';
};
createDatabase = db: ''
CREATE DATABASE "${db.name}" ${
lib.concatStringsSep " " (
lib.mapAttrsToList (name: value: "${name} = '${value}'") db.create.options
)
}
'';
cfg = config.clan.postgresql;
userClauses = lib.mapAttrsToList (
_: user:
''$PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' ''
) cfg.users;
databaseClauses = lib.mapAttrsToList (
name: db:
lib.optionalString db.create.enable ''$PSQL -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${name}'" | grep -q 1 || $PSQL -d postgres -c ${lib.escapeShellArg (createDatabase db)} ''
) cfg.databases;
in
{
options.clan.postgresql = {
# we are reimplemeting ensureDatabase and ensureUser options here to allow to create databases with options
databases = lib.mkOption {
description = "Databases to create";
default = { };
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
description = "Database name.";
};
service = lib.mkOption {
type = lib.types.str;
default = name;
description = "Service name that we associate with the database.";
};
# set to false, in case the upstream module uses ensureDatabase option
create.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Create the database if it does not exist.";
};
create.options = lib.mkOption {
description = "Options to pass to the CREATE DATABASE command.";
type = lib.types.lazyAttrsOf lib.types.str;
default = { };
example = {
TEMPLATE = "template0";
LC_COLLATE = "C";
LC_CTYPE = "C";
ENCODING = "UTF8";
OWNER = "foo";
};
};
restore.stopOnRestore = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "List of systemd services to stop before restoring the database.";
};
};
}
)
);
};
users = lib.mkOption {
description = "Users to create";
default = { };
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options.name = lib.mkOption {
description = "User name";
type = lib.types.str;
default = name;
};
}
)
);
};
};
config = {
services.postgresql.settings = {
wal_level = "replica";
max_wal_senders = 3;
};
services.postgresql.enable = true;
# We are duplicating a bit the upstream module but allow to create databases with options
systemd.services.postgresql.postStart = ''
PSQL="psql --port=${builtins.toString config.services.postgresql.settings.port}"
while ! $PSQL -d postgres -c "" 2> /dev/null; do
if ! kill -0 "$MAINPID"; then exit 1; fi
sleep 0.1
done
${lib.concatStringsSep "\n" userClauses}
${lib.concatStringsSep "\n" databaseClauses}
'';
clan.core.state = lib.mapAttrs' (
_: db: lib.nameValuePair db.service (createDatabaseState db)
) config.clan.postgresql.databases;
environment.systemPackages = builtins.map (
db:
let
folder = "/var/backup/postgres/${db.name}";
current = "${folder}/pg-dump";
in
pkgs.writeShellScriptBin "postgres-db-restore-command-${db.name}" ''
export PATH=${
lib.makeBinPath [
config.services.postgresql.package
config.systemd.package
pkgs.coreutils
pkgs.util-linux
pkgs.zstd
pkgs.gnugrep
]
}
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
sleep 1
done
echo "Waiting for postgres to be ready..."
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
if ! systemctl is-active postgresql; then exit 1; fi
sleep 0.1
done
if [[ -e "${current}" ]]; then
(
${lib.optionalString (db.restore.stopOnRestore != [ ]) ''
systemctl stop ${builtins.toString db.restore.stopOnRestore}
trap "systemctl start ${builtins.toString db.restore.stopOnRestore}" EXIT
''}
mkdir -p "${folder}"
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
runuser -u postgres -- dropdb "${db.name}"
fi
runuser -u postgres -- pg_restore -C -d postgres "${current}"
)
else
echo No database backup found, skipping restore
fi
''
) (builtins.attrValues config.clan.postgresql.databases);
};
imports = [
(lib.mkRemovedOptionModule [
"clan"
"postgresql"
] "The postgresql module has been migrated to a clan core option. Use clan.core.postgresql instead")
];
}

View File

@@ -12,7 +12,7 @@ After the system was installed/deployed the following command can be used to dis
clan vars get [machine_name] root-password/root-password
```
See also: [Vars](../../guides/vars-backend.md)
See also: [Vars](../../concepts/generators.md)
To regenerate the password run:
```

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

@@ -0,0 +1,40 @@
---
description = "A secure, file synchronization app for devices over networks, offering a private alternative to cloud services."
features = [ "inventory" ]
[constraints]
roles.introducer.min = 1
roles.introducer.max = 1
---
**Warning**: This module was written with our VM integration in mind likely won't work outside of this context. They will be generalized in future.
## Usage
We recommend configuring this module as an sync-service through the provided options. Although it provides a Web GUI through which more usage scenarios are supported.
## Features
- **Private and Secure**: Syncthing uses TLS encryption to secure data transfer between devices, ensuring that only the intended devices can read your data.
- **Decentralized**: No central server is involved in the data transfer. Each device communicates directly with others.
- **Open Source**: The source code is openly available for audit and contribution, fostering trust and continuous improvement.
- **Cross-Platform**: Syncthing supports multiple platforms including Windows, macOS, Linux, BSD, and Android.
- **Real-time Synchronization**: Changes made to files are synchronized in real-time across all connected devices.
- **Web GUI**: It includes a user-friendly web interface for managing devices and configurations. (`127.0.0.1:8384`)
## Configuration
- **Share Folders**: Select folders to share with connected devices and configure permissions and synchronization parameters.
!!! info
Clan automatically discovers other devices. Automatic discovery requires one machine to be an [introducer](#clan.syncthing.introducer)
If that is not the case you can add the other device by its Device ID manually.
You can find and share Device IDs under the "Add Device" button in the Web GUI. (`127.0.0.1:8384`)
## Troubleshooting
- **Sync Conflicts**: Resolve synchronization conflicts manually by reviewing file versions and modification times in the Web GUI (`127.0.0.1:8384`).
## Support
- **Documentation**: Extensive documentation is available on the [Syncthing website](https://docs.syncthing.net/).

View File

@@ -0,0 +1,6 @@
# Dont import this file
# It is only here for backwards compatibility.
# Dont author new modules with this file.
{
imports = [ ./roles/peer.nix ];
}

View File

@@ -0,0 +1,6 @@
{ ... }:
{
imports = [
../shared.nix
];
}

View File

@@ -0,0 +1,21 @@
{ config, lib, ... }:
let
instanceNames = builtins.attrNames config.clan.inventory.services.syncthing;
instanceName = builtins.head instanceNames;
instance = config.clan.inventory.services.syncthing.${instanceName};
introducer = builtins.head instance.roles.introducer.machines;
introducerId = "${config.clan.core.settings.directory}/vars/per-machine/${introducer}/syncthing/id/value";
in
{
imports = [
../shared.nix
];
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
if builtins.pathExists introducerId then
builtins.readFile introducerId
else
throw "${introducerId} does not exists. Please run `clan vars generate ${introducer}` to generate the introducer device id"
);
}

View File

@@ -2,11 +2,49 @@
config,
pkgs,
lib,
settings,
introducerID,
...
}:
{
options.clan.syncthing = {
id = lib.mkOption {
description = ''
The ID of the machine.
It is generated automatically by default.
'';
type = lib.types.nullOr lib.types.str;
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
default = config.clan.core.vars.generators.syncthing.files."id".value;
defaultText = "config.clan.core.vars.generators.syncthing.files.\"id\".value";
};
introducer = lib.mkOption {
description = ''
The introducer for the machine.
'';
type = lib.types.nullOr lib.types.str;
default = null;
};
autoAcceptDevices = lib.mkOption {
description = ''
Auto accept incoming device requests.
Should only be used on the introducer.
'';
type = lib.types.bool;
default = false;
};
autoShares = lib.mkOption {
description = ''
Auto share the following Folders by their ID's with introduced devices.
Should only be used on the introducer.
'';
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"folder1"
"folder2"
];
};
};
imports = [
{
# Syncthing ports: 8384 for remote access to GUI
@@ -27,7 +65,7 @@
{
assertion = lib.all (
attr: builtins.hasAttr attr config.services.syncthing.settings.folders
) settings.autoShares;
) config.clan.syncthing.autoShares;
message = ''
Syncthing: If you want to AutoShare a folder, you need to have it configured on the sharing device.
'';
@@ -42,8 +80,12 @@
services.syncthing = {
enable = true;
overrideFolders = lib.mkDefault (if (introducerID == null) then true else false);
overrideDevices = lib.mkDefault (if (introducerID == null) then true else false);
overrideFolders = lib.mkDefault (
if (config.clan.syncthing.introducer == null) then true else false
);
overrideDevices = lib.mkDefault (
if (config.clan.syncthing.introducer == null) then true else false
);
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;
@@ -56,13 +98,13 @@
devices =
{ }
// (
if (introducerID == null) then
if (config.clan.syncthing.introducer == null) then
{ }
else
{
"${introducerID}" = {
"${config.clan.syncthing.introducer}" = {
name = "introducer";
id = introducerID;
id = config.clan.syncthing.introducer;
introducer = true;
autoAcceptFolders = true;
};
@@ -70,7 +112,6 @@
);
};
};
systemd.services.syncthing-auto-accept =
let
baseAddress = "127.0.0.1:8384";
@@ -79,7 +120,7 @@
SharedFolderById = "/rest/config/folders/";
apiKey = config.clan.core.vars.generators.syncthing.files."apikey".path;
in
lib.mkIf settings.autoAcceptDevices {
lib.mkIf config.clan.syncthing.autoAcceptDevices {
description = "Syncthing auto accept devices";
requisite = [ "syncthing.service" ];
after = [ "syncthing.service" ];
@@ -97,7 +138,7 @@
${lib.getExe pkgs.curl} -X POST -d "{\"deviceId\": $ID}" -H "Content-Type: application/json" -H "X-API-Key: $APIKEY" ${baseAddress}${postNewDevice}
# get all shared folders by their ID
for folder in ${builtins.toString settings.autoShares}; do
for folder in ${builtins.toString config.clan.syncthing.autoShares}; do
SHARED_IDS=$(${lib.getExe pkgs.curl} -X GET -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder" | ${lib.getExe pkgs.jq} ."devices")
PATCHED_IDS=$(echo $SHARED_IDS | ${lib.getExe pkgs.jq} ".+= [{\"deviceID\": $ID, \"introducedBy\": \"\", \"encryptionPassword\": \"\"}]")
${lib.getExe pkgs.curl} -X PATCH -d "{\"devices\": $PATCHED_IDS}" -H "X-API-Key: $APIKEY" ${baseAddress}${SharedFolderById}"$folder"
@@ -106,7 +147,7 @@
'';
};
systemd.timers.syncthing-auto-accept = lib.mkIf settings.autoAcceptDevices {
systemd.timers.syncthing-auto-accept = lib.mkIf config.clan.syncthing.autoAcceptDevices {
description = "Syncthing Auto Accept";
wantedBy = [ "syncthing-auto-accept.service" ];
@@ -121,7 +162,7 @@
let
apiKey = config.clan.core.vars.generators.syncthing.files."apikey".path;
in
lib.mkIf settings.autoAcceptDevices {
lib.mkIf config.clan.syncthing.autoAcceptDevices {
description = "Set the api key";
after = [ "syncthing-init.service" ];
wantedBy = [ "multi-user.target" ];
@@ -141,6 +182,7 @@
};
clan.core.vars.generators.syncthing = {
migrateFact = "syncthing";
files."key".group = config.services.syncthing.group;
files."key".owner = config.services.syncthing.user;

View File

@@ -16,7 +16,7 @@ After the system was installed/deployed the following command can be used to dis
clan vars get [machine_name] root-password/root-password
```
See also: [Vars](../../guides/vars-backend.md)
See also: [Vars](../../concepts/generators.md)
To regenerate the password run:
```

View File

@@ -10,7 +10,6 @@ in
{
imports = [
../postgresql
(lib.mkRemovedOptionModule [
"clan"
"vaultwarden"
@@ -57,15 +56,17 @@ in
config = {
clan.postgresql.users.vaultwarden = { };
clan.postgresql.databases.vaultwarden.create.options = {
clan.core.postgresql.enable = true;
clan.core.postgresql.users.vaultwarden = { };
clan.core.postgresql.databases.vaultwarden.create.options = {
TEMPLATE = "template0";
LC_COLLATE = "C";
LC_CTYPE = "C";
ENCODING = "UTF8";
OWNER = "vaultwarden";
};
clan.postgresql.databases.vaultwarden.restore.stopOnRestore = [ "vaultwarden" ];
clan.core.postgresql.databases.vaultwarden.restore.stopOnRestore = [ "vaultwarden" ];
services.nginx = {
enable = true;

View File

@@ -41,25 +41,13 @@
};
};
};
perInstance =
{ settings, ... }:
{
nixosModule =
{ ... }:
{
imports = [
# We don't have a good way to specify dependencies between
# clanServices for now. When it get's implemtende, we should just
# use the ssh and users modules here.
./ssh.nix
./root-password.nix
];
_module.args = { inherit settings; };
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys;
};
};
};
# We don't have a good way to specify dependencies between
# clanServices for now. When it get's implemtende, we should just
# use the ssh and users modules here.
imports = [
./ssh.nix
./root-password.nix
];
}

View File

@@ -1,39 +1,55 @@
# We don't have a way of specifying dependencies between clanServices for now.
# When it get's added this file should be removed and the users module used instead.
{
config,
pkgs,
...
}:
{
roles.default.perInstance =
{ ... }:
{
nixosModule =
{
config,
pkgs,
...
}:
{
users.mutableUsers = false;
users.users.root.hashedPasswordFile =
config.clan.core.vars.generators.root-password.files.password-hash.path;
users.mutableUsers = false;
users.users.root.hashedPasswordFile =
config.clan.core.vars.generators.root-password.files.password-hash.path;
clan.core.vars.generators.root-password = {
files.password-hash.neededFor = "users";
clan.core.vars.generators.root-password = {
files.password-hash.neededFor = "users";
files.password.deploy = false;
files.password.deploy = false;
runtimeInputs = [
pkgs.coreutils
pkgs.mkpasswd
pkgs.xkcdpass
];
runtimeInputs = [
pkgs.coreutils
pkgs.mkpasswd
pkgs.xkcdpass
];
prompts.password.type = "hidden";
prompts.password.persist = true;
prompts.password.description = "You can autogenerate a password, if you leave this prompt blank.";
prompts.password.display = {
group = "Root User";
label = "Password";
required = false;
helperText = ''
Your password will be encrypted and stored securely using the secret store you've configured.
'';
};
script = ''
prompt_value="$(cat "$prompts"/password)"
if [[ -n "''${prompt_value-}" ]]; then
echo "$prompt_value" | tr -d "\n" > "$out"/password
else
xkcdpass --numwords 5 --delimiter - --count 1 | tr -d "\n" > "$out"/password
fi
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
'';
};
prompts.password.type = "hidden";
prompts.password.persist = true;
prompts.password.description = "Leave empty to generate automatically";
script = ''
prompt_value="$(cat "$prompts"/password)"
if [[ -n "''${prompt_value-}" ]]; then
echo "$prompt_value" | tr -d "\n" > "$out"/password
else
xkcdpass --numwords 5 --delimiter - --count 1 | tr -d "\n" > "$out"/password
fi
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
'';
};
};
};
}

View File

@@ -1,115 +1,124 @@
{
config,
pkgs,
lib,
settings,
...
}:
let
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
roles.default.perInstance =
{ settings, ... }:
{
nixosModule =
domains = stringSet settings.certificateSearchDomains;
in
{
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.HostCertificate = lib.mkIf (
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";
};
};
config,
pkgs,
lib,
...
}:
let
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
clan.core.vars.generators.openssh = {
files."ssh.id_ed25519" = { };
files."ssh.id_ed25519.pub".secret = false;
migrateFact = "openssh";
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
'';
};
domains = stringSet settings.certificateSearchDomains;
programs.ssh.knownHosts.clan-sshd-self-ed25519 = {
hostNames = [
"localhost"
config.networking.hostName
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
};
in
{
clan.core.vars.generators.openssh-rsa = lib.mkIf settings.rsaHostKey.enable {
files."ssh.id_rsa" = { };
files."ssh.id_rsa.pub".secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
'';
};
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys;
clan.core.vars.generators.openssh-cert = lib.mkIf (settings.certificateSearchDomains != [ ]) {
files."ssh.id_ed25519-cert.pub".secret = false;
dependencies = [
"openssh"
"openssh-ca"
];
validation = {
name = config.clan.core.settings.machine.name;
domains = lib.genAttrs settings.certificateSearchDomains lib.id;
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.HostCertificate = lib.mkIf (
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";
};
};
clan.core.vars.generators.openssh = {
files."ssh.id_ed25519" = { };
files."ssh.id_ed25519.pub".secret = false;
migrateFact = "openssh";
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
'';
};
programs.ssh.knownHosts.clan-sshd-self-ed25519 = {
hostNames = [
"localhost"
config.networking.hostName
]
++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
};
clan.core.vars.generators.openssh-rsa = lib.mkIf settings.rsaHostKey.enable {
files."ssh.id_rsa" = { };
files."ssh.id_rsa.pub".secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
'';
};
clan.core.vars.generators.openssh-cert = lib.mkIf (settings.certificateSearchDomains != [ ]) {
files."ssh.id_ed25519-cert.pub".secret = false;
dependencies = [
"openssh"
"openssh-ca"
];
validation = {
name = config.clan.core.settings.machine.name;
domains = lib.genAttrs settings.certificateSearchDomains lib.id;
};
runtimeInputs = [
pkgs.openssh
pkgs.jq
];
script = ''
ssh-keygen \
-s $in/openssh-ca/id_ed25519 \
-I ${config.clan.core.settings.machine.name} \
-h \
-n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \
$in/openssh/ssh.id_ed25519.pub
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
'';
};
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
share = true;
files.id_ed25519.deploy = false;
files."id_ed25519.pub" = {
deploy = false;
secret = false;
};
runtimeInputs = [
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
'';
};
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
certAuthority = true;
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificateSearchDomains;
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
};
};
};
runtimeInputs = [
pkgs.openssh
pkgs.jq
];
script = ''
ssh-keygen \
-s $in/openssh-ca/id_ed25519 \
-I ${config.clan.core.settings.machine.name} \
-h \
-n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \
$in/openssh/ssh.id_ed25519.pub
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
'';
};
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
share = true;
files.id_ed25519.deploy = false;
files."id_ed25519.pub" = {
deploy = false;
secret = false;
};
runtimeInputs = [
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
'';
};
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) {
certAuthority = true;
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificateSearchDomains;
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
};
}

View File

@@ -1,9 +1,59 @@
BorgBackup (short: Borg) gives you:
## Usage
- Space efficient storage of backups.
- Secure, authenticated encryption.
- Compression: lz4, zstd, zlib, lzma or none.
- Mountable backups with FUSE.
```nix
inventory.instances = {
borgbackup = {
module = {
name = "borgbackup";
input = "clan";
};
roles.client.machines."jon".settings = {
destinations."storagebox" = {
repo = "username@$hostname:/./borgbackup";
rsh = ''ssh -oPort=23 -i /run/secrets/vars/borgbackup/borgbackup.ssh'';
};
};
roles.server.machines = { };
};
};
```
The input should be named according to your flake input. Jon is configured as a
client machine with a destination pointing to a Hetzner Storage Box.
## Overview
This guide explains how to set up and manage
[BorgBackup](https://borgbackup.readthedocs.io/) for secure, efficient backups
in a clan network. BorgBackup provides:
- Space efficient storage of backups with deduplication
- Secure, authenticated encryption
- Compression: lz4, zstd, zlib, lzma or none
- Mountable backups with FUSE
- Easy installation on multiple platforms: Linux, macOS, BSD, …
- Free software (BSD license).
- Backed by a large and active open-source community.
## Roles
### 1. Client
Clients are machines that create and send backups to various destinations. Each
client can have multiple backup destinations configured.
### 2. Server
Servers act as backup repositories, receiving and storing backups from client
machines. They can be dedicated backup servers within your clan network.
## Backup destinations
This service allows you to perform backups to multiple `destinations`.
Destinations can be:
- **Local**: Local disk storage
- **Server**: Your own borgbackup server (using the `server` role)
- **Third-party services**: Such as Hetzner's Storage Box
For a more comprehensive guide on backups look into the guide section.

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

@@ -0,0 +1,86 @@
A Dynamic-DNS (DDNS) service continuously keeps one or more DNS records in sync with the current public IP address of your machine.
In *clan* this service is backed by [qdm12/ddns-updater](https://github.com/qdm12/ddns-updater).
> Info
> ddns-updater itself is **heavily opinionated and version-specific**. Whenever you need the exhaustive list of flags or
> provider-specific fields refer to its *versioned* documentation **not** the GitHub README
---
# 1. Configuration model
Internally ddns-updater consumes a single file named `config.json`.
A minimal configuration for the registrar *Namecheap* looks like:
```json
{
"settings": [
{
"provider": "namecheap",
"domain": "sub.example.com",
"password": "e5322165c1d74692bfa6d807100c0310"
}
]
}
```
Another example for *Porkbun*:
```json
{
"settings": [
{
"provider": "porkbun",
"domain": "domain.com",
"api_key": "sk1_…",
"secret_api_key": "pk1_…",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
When you write a `clan.nix` the **common** fields (`provider`, `domain`, `period`, …) are already exposed as typed
*Nix options*.
Registrar-specific or very new keys can be passed through an open attribute set called **extraSettings**.
---
# 2. Full Porkbun example
Manage three records `@`, `home` and `test` of the domain
`jon.blog` and refresh them every 15 minutes:
```nix title="clan.nix" hl_lines="10-11"
inventory.instances = {
dyndns = {
roles.default.machines."jon" = { };
roles.default.settings = {
period = 15; # minutes
settings = {
"all-jon-blog" = {
provider = "porkbun";
domain = "jon.blog";
# (1) tell the secret-manager which key we are going to store
secret_field_name = "secret_api_key";
# everything below is copied verbatim into config.json
extraSettings = {
host = "@,home,test"; # (2) comma-separated list of sub-domains
ip_version = "ipv4";
ipv6_suffix = "";
api_key = "pk1_4bb2b231275a02fdc23b7e6f3552s01S213S"; # (3) public safe to commit
};
};
};
};
};
};
```
1. `secret_field_name` tells the *vars-generator* to store the entered secret under the specified JSON field name in the configuration.
2. ddns-updater allows multiple hosts by separating them with a comma.
3. The `api_key` above is *public*; the corresponding **private key** is retrieved through `secret_field_name`.

View File

@@ -0,0 +1,277 @@
{ ... }:
{
_class = "clan.service";
manifest.name = "clan-core/dyndns";
manifest.description = "A dynamic DNS service to update domain IPs";
manifest.categories = [ "Network" ];
manifest.readme = builtins.readFile ./README.md;
roles.default = {
interface =
{ lib, ... }:
{
options = {
server = {
enable = lib.mkEnableOption "dyndns webserver";
domain = lib.mkOption {
type = lib.types.str;
description = "Domain to serve the webservice on";
};
port = lib.mkOption {
type = lib.types.int;
default = 54805;
description = "Port to listen on";
};
acmeEmail = lib.mkOption {
type = lib.types.str;
description = ''
Email address for account creation and correspondence from the CA.
It is recommended to use the same email for all certs to avoid account
creation limits.
'';
};
};
period = lib.mkOption {
type = lib.types.int;
default = 5;
description = "Domain update period in minutes";
};
settings = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ ... }:
{
options = {
provider = lib.mkOption {
example = "namecheap";
type = lib.types.str;
description = "The dyndns provider to use";
};
domain = lib.mkOption {
type = lib.types.str;
example = "example.com";
description = "The top level domain to update.";
};
secret_field_name = lib.mkOption {
example = "api_key";
type = lib.types.enum [
"password"
"token"
"api_key"
"secret_api_key"
];
default = "password";
description = "The field name for the secret";
};
extraSettings = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = ''
Extra settings for the provider.
Provider specific settings: https://github.com/qdm12/ddns-updater#configuration
'';
};
};
}
)
);
default = { };
description = "Configuration for which domains to update";
};
};
};
perInstance =
{ settings, ... }:
{
nixosModule =
{
config,
lib,
pkgs,
...
}:
let
name = "dyndns";
cfg = settings;
# We dedup secrets if they have the same provider + base domain
secret_id = opt: "${name}-${opt.provider}-${opt.domain}";
secret_path =
opt: config.clan.core.vars.generators."${secret_id opt}".files."${secret_id opt}".path;
# We check that a secret has not been set in extraSettings.
extraSettingsSafe =
opt:
if (builtins.hasAttr opt.secret_field_name opt.extraSettings) then
throw "Please do not set ${opt.secret_field_name} in extraSettings, it is automatically set by the dyndns module."
else
opt.extraSettings;
service_config = {
settings = builtins.catAttrs "value" (
builtins.attrValues (
lib.mapAttrs (_: opt: {
value =
(extraSettingsSafe opt)
// {
domain = opt.domain;
provider = opt.provider;
}
// {
"${opt.secret_field_name}" = secret_id opt;
};
}) cfg.settings
)
);
};
secret_generator = _: opt: {
name = secret_id opt;
value = {
share = true;
migrateFact = "${secret_id opt}";
prompts.${secret_id opt} = {
type = "hidden";
persist = true;
};
};
};
in
{
imports = lib.optional cfg.server.enable (
lib.modules.importApply ./nginx.nix {
inherit config;
inherit settings;
inherit lib;
}
);
clan.core.vars.generators = lib.mkIf (cfg.settings != { }) (
lib.mapAttrs' secret_generator cfg.settings
);
users.groups.${name} = lib.mkIf (cfg.settings != { }) { };
users.users.${name} = lib.mkIf (cfg.settings != { }) {
group = name;
isSystemUser = true;
description = "User for ${name} service";
home = "/var/lib/${name}";
createHome = true;
};
services.nginx = lib.mkIf cfg.server.enable {
virtualHosts = {
"${cfg.server.domain}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:${toString cfg.server.port}";
};
};
};
};
systemd.services.${name} = lib.mkIf (cfg.settings != { }) {
path = [ ];
description = "Dynamic DNS updater";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
MYCONFIG = "${builtins.toJSON service_config}";
SERVER_ENABLED = if cfg.server.enable then "yes" else "no";
PERIOD = "${toString cfg.period}m";
LISTENING_ADDRESS = ":${toString cfg.server.port}";
GODEBUG = "netdns=go"; # We need to set this untill this has been merged. https://github.com/NixOS/nixpkgs/pull/432758
};
serviceConfig =
let
pyscript =
pkgs.writers.writePython3Bin "generate_secret_config.py"
{
libraries = [ ];
doCheck = false;
}
''
import json
from pathlib import Path
import os
cred_dir = Path(os.getenv("CREDENTIALS_DIRECTORY"))
config_str = os.getenv("MYCONFIG")
def get_credential(name):
secret_p = cred_dir / name
with open(secret_p, 'r') as f:
return f.read().strip()
config = json.loads(config_str)
print(f"Config: {config}")
for attrset in config["settings"]:
if "password" in attrset:
attrset['password'] = get_credential(attrset['password'])
elif "token" in attrset:
attrset['token'] = get_credential(attrset['token'])
elif "secret_api_key" in attrset:
attrset['secret_api_key'] = get_credential(attrset['secret_api_key'])
elif "api_key" in attrset:
attrset['api_key'] = get_credential(attrset['api_key'])
else:
raise ValueError(f"Missing secret field in {attrset}")
# create directory data if it does not exist
data_dir = Path('data')
data_dir.mkdir(mode=0o770, exist_ok=True)
# Create a temporary config file
# with appropriate permissions
tmp_config_path = data_dir / '.config.json'
tmp_config_path.touch(mode=0o660, exist_ok=False)
# Write the config with secrets back
with open(tmp_config_path, 'w') as f:
f.write(json.dumps(config, indent=4))
# Move config into place
config_path = data_dir / 'config.json'
tmp_config_path.rename(config_path)
# Set file permissions to read
# and write only by the user and group
for file in data_dir.iterdir():
file.chmod(0o660)
'';
in
{
ExecStartPre = lib.getExe pyscript;
ExecStart = lib.getExe pkgs.ddns-updater;
LoadCredential = lib.mapAttrsToList (_: opt: "${secret_id opt}:${secret_path opt}") cfg.settings;
User = name;
Group = name;
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ReadOnlyPaths = "/";
PrivateDevices = "yes";
ProtectKernelModules = "yes";
ProtectKernelTunables = "yes";
WorkingDirectory = "/var/lib/${name}";
ReadWritePaths = [
"/proc/self"
"/var/lib/${name}"
];
Restart = "always";
RestartSec = 60;
};
};
};
};
};
}

View File

@@ -0,0 +1,19 @@
{ lib, ... }:
let
module = lib.modules.importApply ./default.nix { };
in
{
clan.modules = {
dyndns = module;
};
perSystem =
{ ... }:
{
clan.nixosTests.dyndns = {
imports = [ ./tests/vm/default.nix ];
clan.modules."@clan/dyndns" = module;
};
};
}

View File

@@ -0,0 +1,50 @@
{
config,
lib,
settings,
...
}:
{
security.acme.acceptTerms = true;
security.acme.defaults.email = settings.server.acmeEmail;
networking.firewall.allowedTCPPorts = [
443
80
];
services.nginx = {
enable = true;
statusPage = lib.mkDefault true;
recommendedBrotliSettings = lib.mkDefault true;
recommendedGzipSettings = lib.mkDefault true;
recommendedOptimisation = lib.mkDefault true;
recommendedProxySettings = lib.mkDefault true;
recommendedTlsSettings = lib.mkDefault true;
# Nginx sends all the access logs to /var/log/nginx/access.log by default.
# instead of going to the journal!
commonHttpConfig = "access_log syslog:server=unix:/dev/log;";
resolver.addresses =
let
isIPv6 = addr: builtins.match ".*:.*:.*" addr != null;
escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr;
cloudflare = [
"1.1.1.1"
"2606:4700:4700::1111"
];
resolvers =
if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
in
map escapeIPv6 resolvers;
sslDhparam = config.security.dhparams.params.nginx.path;
};
security.dhparams = {
enable = true;
params.nginx = { };
};
}

View File

@@ -0,0 +1,77 @@
{
pkgs,
...
}:
{
name = "service-dyndns";
clan = {
directory = ./.;
inventory = {
machines.server = { };
instances = {
dyndns-test = {
module.name = "@clan/dyndns";
module.input = "self";
roles.default.machines."server".settings = {
server = {
enable = true;
domain = "test.example.com";
port = 54805;
acmeEmail = "test@example.com";
};
period = 1;
settings = {
"test.example.com" = {
provider = "namecheap";
domain = "example.com";
secret_field_name = "password";
extraSettings = {
host = "test";
server = "dynamicdns.park-your-domain.com";
};
};
};
};
};
};
};
};
nodes = {
server = {
# Disable firewall for testing
networking.firewall.enable = false;
# Mock ACME for testing (avoid real certificate requests)
security.acme.defaults.server = "https://localhost:14000/dir";
};
};
testScript = ''
start_all()
# Test that dyndns service starts (will fail without secrets, but that's expected)
server.wait_for_unit("multi-user.target")
# Test that nginx service is running
server.wait_for_unit("nginx.service")
# Test that nginx is listening on expected ports
server.wait_for_open_port(80)
server.wait_for_open_port(443)
# Test that the dyndns user was created
# server.succeed("getent passwd dyndns")
# server.succeed("getent group dyndns")
#
# Test that the home directory was created
server.succeed("test -d /var/lib/dyndns")
# Test that nginx configuration includes our domain
server.succeed("${pkgs.nginx}/bin/nginx -t")
print("All tests passed!")
'';
}

View File

@@ -1,3 +1,9 @@
# Example clan service. See https://docs.clan.lol/guides/services/community/
# for more details
# The test for this module in ./tests/vm/default.nix shows an example of how
# the service is used.
{ packages }:
{ ... }:
{
@@ -5,30 +11,94 @@
manifest.name = "clan-core/hello-word";
manifest.description = "This is a test";
roles.peer = {
# This service provides two roles: "morning" and "evening". Roles can be
# defined in this file directly (e.g. the "morning" role) or split up into a
# separate file (e.g. the "evening" role)
roles.morning = {
interface =
{ lib, ... }:
{
options.foo = lib.mkOption {
# Here we define the settings for this role. They will be accessible
# via `roles.morning.settings` in the role
options.greeting = lib.mkOption {
type = lib.types.str;
# default = "";
description = "Some option";
default = "Good morning";
description = "The greeting to use";
};
};
# Maps over all instances and produces one result per instance.
perInstance =
{
# Role settings for this machine/instance
settings,
# The name of this instance of the service
instanceName,
# The current machine
machine,
# All roles of this service, with their assigned machines
roles,
...
}:
{
# Analog to 'perSystem' of flake-parts.
# For every instance of this service we will add a nixosModule to a morning-machine
nixosModule =
{ config, ... }:
{
# Interaction examples what you could do here:
# - Get some settings of this machine
# settings.ipRanges
#
# - Get all evening names:
# allEveningNames = lib.attrNames roles.evening.machines
#
# - Get all roles of the machine:
# machine.roles
#
# - Get the settings that where applied to a specific evening machine:
# roles.evening.machines.peer1.settings
imports = [ ];
environment.etc.hello.text = "${settings.greeting} World!";
};
};
};
# The impnlementation of the evening role is in a separate file. We have kept
# the interface here, so we can see all settings of the service in one place,
# but you can also move it to the respective file
roles.evening = {
interface =
{ lib, ... }:
{
options.greeting = lib.mkOption {
type = lib.types.str;
default = "Good evening";
description = "The greeting to use";
};
};
};
imports = [ ./evening.nix ];
# This part gets applied to all machines, regardless of their role.
perMachine =
{ machine, ... }:
{
nixosModule = {
clan.core.vars.generators.hello = {
files.hello = {
secret = false;
};
script = ''
echo "Hello world from ${machine.name}" > $out/hello
'';
nixosModule =
{ pkgs, ... }:
{
environment.systemPackages = [
(pkgs.writeShellScriptBin "greet-world" ''
#!${pkgs.bash}/bin/bash
set -euo pipefail
cat /etc/hello
echo " I'm ${machine.name}"
'')
];
};
};
};
}

View File

@@ -0,0 +1,12 @@
{
roles.evening.perInstance =
{ settings, ... }:
{
nixosModule =
{ ... }:
{
imports = [ ];
environment.etc.hello.text = "${settings.greeting} World!";
};
};
}

View File

@@ -27,20 +27,10 @@ let
module.name = "hello-world";
module.input = "self";
roles.peer.machines.jon = { };
roles.evening.machines.jon = { };
};
};
};
# NOTE:
# If you wonder why 'self-zerotier-redux':
# A local module has prefix 'self', otherwise it is the name of the 'input'
# The rest is the name of the service as in the instance 'module.name';
#
# -> ${module.input}-${module.name}
# In this case it is 'self-zerotier-redux'
# This is usually only used internally, but we can use it to test the evaluation of service module in isolation
# evaluatedService =
# testFlake.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-zerotier-redux.config;
in
{
test_simple = {

View File

@@ -5,22 +5,35 @@
directory = ./.;
inventory = {
machines.peer1 = { };
machines.peer2 = { };
instances."test" = {
module.name = "hello-service";
module.input = "self";
roles.peer.machines.peer1 = { };
# Assign the roles to the two machines
roles.morning.machines.peer1 = { };
roles.evening.machines.peer2 = {
# Set roles settings for the peers, where we want to differ from
# the role defaults
settings = {
greeting = "Good night";
};
};
};
};
};
testScript =
{ nodes, ... }:
{ ... }:
''
start_all()
# peer1 should have the 'hello' file
value = peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.hello.files.hello.path}")
assert value.strip() == "Hello world from peer1", value
value = peer1.succeed("greet-world")
assert value.strip() == "Good morning World! I'm peer1", value
value = peer2.succeed("greet-world")
assert value.strip() == "Good night World! I'm peer2", value
'';
}

View File

@@ -0,0 +1,35 @@
## Features
- Creates incremental snapshots using rsnapshot
- Supports multiple backup targets
- Mount/unmount hooks for external storage
- Pre/post backup hooks for custom scripts
- Configurable snapshot retention
- Automatic state folder detection
## Usage
Enable the localbackup service and configure backup targets:
```nix
instances = {
localbackup = {
module.name = "@clan/localbackup";
module.input = "self";
roles.default.machines."machine".settings = {
targets.external= {
directory = "/mnt/backup";
mountpoint = "/mnt/backup";
};
};
};
};
```
## Commands
The service provides these commands:
- `localbackup-create`: Create a new backup
- `localbackup-list`: List available backups
- `localbackup-restore`: Restore from backup (requires NAME and FOLDERS environment variables)

View File

@@ -0,0 +1,267 @@
{ ... }:
{
_class = "clan.service";
manifest.name = "localbackup";
manifest.description = "Automatically backups current machine to local directory.";
manifest.categories = [ "System" ];
manifest.readme = builtins.readFile ./README.md;
roles.default = {
interface =
{ lib, ... }:
{
options = {
targets = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
name = lib.mkOption {
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
default = name;
description = "the name of the backup job";
};
directory = lib.mkOption {
type = lib.types.str;
description = "the directory to backup";
};
mountpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "mountpoint of the directory to backup. If set, the directory will be mounted before the backup and unmounted afterwards";
};
preMountHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run before the directory is mounted";
};
postMountHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run after the directory is mounted";
};
preUnmountHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run before the directory is unmounted";
};
postUnmountHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run after the directory is unmounted";
};
preBackupHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run before the backup";
};
postBackupHook = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = null;
description = "Shell commands to run after the backup";
};
};
}
)
);
# default = { };
description = "List of directories where backups are stored";
};
snapshots = lib.mkOption {
type = lib.types.int;
default = 20;
description = "Number of snapshots to keep";
};
};
};
perInstance =
{
settings,
...
}:
{
nixosModule =
{
config,
lib,
pkgs,
...
}:
let
mountHook = target: ''
if [[ -x /run/current-system/sw/bin/localbackup-mount-${target.name} ]]; then
/run/current-system/sw/bin/localbackup-mount-${target.name}
fi
if [[ -x /run/current-system/sw/bin/localbackup-unmount-${target.name} ]]; then
trap "/run/current-system/sw/bin/localbackup-unmount-${target.name}" EXIT
fi
'';
uniqueFolders = lib.unique (
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
);
rsnapshotConfig = target: ''
config_version 1.2
snapshot_root ${target.directory}
sync_first 1
cmd_cp ${pkgs.coreutils}/bin/cp
cmd_rm ${pkgs.coreutils}/bin/rm
cmd_rsync ${pkgs.rsync}/bin/rsync
cmd_ssh ${pkgs.openssh}/bin/ssh
cmd_logger ${pkgs.inetutils}/bin/logger
cmd_du ${pkgs.coreutils}/bin/du
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
${lib.optionalString (target.postBackupHook != null) ''
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
set -efu -o pipefail
${target.postBackupHook}
''}
''}
retain snapshot ${builtins.toString settings.snapshots}
${lib.concatMapStringsSep "\n" (folder: ''
backup ${folder} ${config.networking.hostName}/
'') uniqueFolders}
'';
in
{
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 settings.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 settings.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}
''
) settings.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}
''
) settings.targets;
clan.core.backups.providers.localbackup = {
# TODO list needs to run locally or on the remote machine
list = "localbackup-list";
create = "localbackup-create";
restore = "localbackup-restore";
};
};
};
};
}

View File

@@ -0,0 +1,16 @@
{ lib, ... }:
let
module = lib.modules.importApply ./default.nix { };
in
{
clan.modules.localbackup = module;
perSystem =
{ ... }:
{
clan.nixosTests.localbackup = {
imports = [ ./tests/vm/default.nix ];
clan.modules."@clan/localbackup" = module;
};
};
}

View File

@@ -0,0 +1,62 @@
{ ... }:
{
name = "service-localbackup";
clan = {
directory = ./.;
test.useContainers = true;
inventory = {
machines.machine = { };
instances = {
localbackup = {
module.name = "@clan/localbackup";
module.input = "self";
roles.default.machines."machine".settings = {
targets.hdd = {
directory = "/mnt/external-disk";
preMountHook = ''
touch /run/mount-external-disk
'';
postUnmountHook = ''
touch /run/unmount-external-disk
'';
};
};
};
};
};
};
nodes.machine = {
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
};
testScript = ''
import json
start_all()
machine.systemctl("start network-online.target")
machine.wait_for_unit("network-online.target")
# dummy data
machine.succeed("mkdir -p /var/test-backups")
machine.succeed("echo testing > /var/test-backups/somefile")
# create
machine.succeed("localbackup-create >&2")
machine.wait_until_succeeds("! systemctl is-active localbackup-job-serverone >&2")
# list
snapshot_list = machine.succeed("localbackup-list").strip()
assert json.loads(snapshot_list)[0]["name"].strip() == "hdd::/mnt/external-disk/snapshot.0"
# borgbackup restore
machine.succeed("rm -f /var/test-backups/somefile")
machine.succeed("NAME=/mnt/external-disk/snapshot.0 FOLDERS=/var/test-backups /run/current-system/sw/bin/localbackup-restore >&2")
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
'';
}

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