Compare commits

..

305 Commits

Author SHA1 Message Date
DavHau
84a21d1bab generate_test_vars: fix it 2025-07-09 16:20:37 +07:00
kenji
28d5294292 Merge pull request 'pkgs/clan: Add test for clan flash list' (#4281) from kenji/ke-test-flash into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4281
2025-07-09 09:08:34 +00:00
a-kenji
3a52189ed6 pkgs/clan: Add test for clan flash list
Add a test for `clan flash list`.
This tests the basic functionality, as well as that listing actually
works.
2025-07-09 11:02:24 +02:00
kenji
5c33e02e24 Merge pull request 'pkgs/clan: Add test for clan templates list' (#4282) from kenji/ke-test-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4282
2025-07-09 08:37:26 +00:00
a-kenji
2aa2145876 pkgs/clan: Add test for clan templates list
Add a test for clan templates list.
This tests the basic functionality, as well as that listing actually
works.
2025-07-09 10:29:29 +02:00
hsjobeki
fa517e1149 Merge pull request 'openapi: improve spec compat with swagger.io' (#4279) from openapi into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4279
2025-07-09 07:52:49 +00:00
Johannes Kirschbauer
3828a0cf49 openapi: improve spec compat with swagger.io 2025-07-09 09:40:06 +02:00
hsjobeki
75501a914b Merge pull request 'API Improvements' (#4276) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4276
2025-07-08 21:17:12 +00:00
Johannes Kirschbauer
c9b8bdd6de api/docs: sort resources into tree order 2025-07-08 23:06:20 +02:00
hsjobeki
5a4a7e9158 Merge pull request 'UI: init cubes scene' (#4277) from ui-scene into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4277
2025-07-08 20:40:42 +00:00
Johannes Kirschbauer
83ad0ae836 UI: fix lint 2025-07-08 22:36:14 +02:00
Johannes Kirschbauer
57163cf135 UI: Cubes improve memory usage 2025-07-08 22:33:52 +02:00
Johannes Kirschbauer
13185d005d UI: dispay selected cube base 2025-07-08 22:14:44 +02:00
Johannes Kirschbauer
18a6b57673 UI: Init CubesScene
UI: init cube base scene
2025-07-08 22:14:42 +02:00
kenji
31f2c5106d Merge pull request 'refactor: decouple vars stores from machine instances' (#4269) from davhau/vars-new into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4269
2025-07-08 18:11:03 +00:00
Johannes Kirschbauer
4dfd151cd2 api: rename 'run_machine_deploy' into 'run_machine_update' 2025-07-08 17:21:18 +02:00
Johannes Kirschbauer
8ddd2b607e api/disk_schema: make getter consistent 2025-07-08 17:20:59 +02:00
hsjobeki
b4544b824a Merge pull request 'api/disk_schema: rename getter consistent' (#4274) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4274
2025-07-08 14:43:29 +00:00
hsjobeki
50122c2215 Merge pull request 'docs: disable footer navigation' (#4275) from docs-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4275
2025-07-08 14:42:36 +00:00
Johannes Kirschbauer
c65b35edc1 docs: disable footer navigation
This gives the content more space
Especially for plugins (options, developer tabs)
2025-07-08 16:38:33 +02:00
Johannes Kirschbauer
2eb6ee2264 api/disk_schema: make getter consistent 2025-07-08 16:31:51 +02:00
hsjobeki
ec1363aedf Merge pull request 'UI: remove 2d-ui, its broken now since we deleted the symlinked files in #4266' (#4273) from ui-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4273
2025-07-08 14:28:56 +00:00
Johannes Kirschbauer
90495d4157 UI: remove 2d-ui, its broken now since we deleted the symlinked files in #4266 2025-07-08 16:23:30 +02:00
kenji
b8fa4b4677 Merge pull request 'pkgs/cli: Add regression test for clan show' (#4272) from kenji/ke-test-add-show into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4272
2025-07-08 14:01:02 +00:00
a-kenji
ec8effcd46 pkgs/cli: Add regression test for clan show 2025-07-08 15:55:01 +02:00
hsjobeki
09b8e2f49c Merge pull request 'Templates/default: move clan configuration into its own file' (#4262) from templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4262
2025-07-08 13:46:19 +00:00
Johannes Kirschbauer
7fefc8e3b2 Tests: dont expect a hardware-configuration.nix file 2025-07-08 15:34:16 +02:00
Johannes Kirschbauer
62cadb8fbe Docs: mention clan.nix file, which is present in the default template now 2025-07-08 14:58:24 +02:00
Johannes Kirschbauer
f8748d021b tests: dont expect .clan-flake file, it is not strictly required 2025-07-08 14:58:24 +02:00
kenji
b84df095a2 Merge pull request 'templates(minimal): Add .envrc' (#4268) from kenji/ke-template into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4268
2025-07-08 11:58:36 +00:00
DavHau
dbd48a54a3 refactor: decouple vars stores from machine instances
Stores now get machine context from generator objects instead of storing
it internally. This enables future machine-independent generators and
reduces coupling.

- StoreBase.__init__ only takes flake parameter
- Store methods receive machine as explicit parameter
- Fixed all callers to pass machine context
2025-07-08 18:30:16 +07:00
Luis Hebendanz
3b2f0e2029 Merge pull request 'Add middleware interface to clan-app' (#4265) from Qubasa/clan-core:generalize_webview into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4265
2025-07-08 11:16:36 +00:00
Qubasa
639d227055 clan-app: Fix delete tasks
clan-app: Fix delete tasks
2025-07-08 18:11:59 +07:00
a-kenji
7ac4d257d9 templates(minimal): Add .envrc
Add .envrc also to the minimal template to homogenize our templates.
2025-07-08 13:09:04 +02:00
brianmcgee
e45e809553 Merge pull request 'prep-ui-version2' (#4266) from prep-ui-version2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4266
2025-07-08 10:57:54 +00:00
Qubasa
48c7613556 clan-cli: Add verb 'cancel' to openapi 2025-07-08 17:43:14 +07:00
Brian McGee
fe89d954da fix(ui): display required asterisk in label 2025-07-08 11:41:34 +01:00
Brian McGee
b8604d334b feat(ui): prep V2
Preparation for rebuilding the UI with V2 components.
2025-07-08 11:41:33 +01:00
Qubasa
50cbe3c825 clan-app: Move json.loads to try catch 2025-07-08 17:38:24 +07:00
Qubasa
acab3b8905 clan-app: Ignore ruff errors 2025-07-08 17:32:59 +07:00
Qubasa
eb6166796c clan-app: Generalize architecture for API requests 2025-07-08 17:32:59 +07:00
Qubasa
6d8fd42faa clan-app: Add plug and play middleware interface 2025-07-08 17:32:59 +07:00
Qubasa
494830326d clan-app: Add plug and play middleware interface 2025-07-08 17:32:59 +07:00
Luis Hebendanz
edfad04305 Merge pull request 'fix_ruff_regression' (#4264) from Qubasa/clan-core:fix_ruff_regression into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4264
2025-07-08 10:30:45 +00:00
Qubasa
81d3437ff7 clan-cli: Fix Ruff linting errors
clan-cli: Ruff fixes

ignore noqa lint

fix more ruff issues
2025-07-08 17:25:02 +07:00
Qubasa
d656167cf2 ruff: Ignore TRY301 'raise-within-try' as there are legitimate reasons to do this 2025-07-08 17:23:32 +07:00
Qubasa
49e83954c5 clan-cli: Fix regression in ruff linter, where linter rules got overriden by local pyproject.toml
clan-app: Fix ruff regression where linter rules got overriden by local pyproject.toml
2025-07-08 17:23:32 +07:00
Johannes Kirschbauer
8d1e0353f8 test: don't expect a machines folder 2025-07-08 11:53:20 +02:00
Johannes Kirschbauer
05658589a0 Docs: execute 'clan show' as first step 2025-07-08 10:24:01 +02:00
Johannes Kirschbauer
809a115e58 clan/show: fix cli command 2025-07-08 10:15:35 +02:00
Johannes Kirschbauer
79d8d0707b Templates/default/modules/gnome: Add doc-comment - what the module does, how to use it 2025-07-08 10:06:22 +02:00
Johannes Kirschbauer
b2179c9293 Templates/default: remove predefined machines 2025-07-08 10:05:35 +02:00
Johannes Kirschbauer
e33af96705 Templates/default: move clan configuration into its own file
Doing this with the idea in mind, that flake-parts / default should define the same clan in clan.nix
We can add a CI check to ensure both are the same files
They got desynced in the past and describe completely different clans now
2025-07-08 10:04:17 +02:00
Johannes Kirschbauer
14a221d1d1 Docs: remove 'replaces' sentence; it is not describing any purpose 2025-07-08 10:01:51 +02:00
brianmcgee
9f9ab3de19 Merge pull request 'feat(ui): SidebarPane component' (#4248) from ui/sidebar-pane into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4248
2025-07-08 07:37:47 +00:00
hsjobeki
9739a5ae2b Merge pull request 'templates: rename 'new_clan' to default' (#4244) from templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4244
2025-07-08 07:31:22 +00:00
Mic92
54446d751f Merge pull request 'checks/backup: no longer depend on self' (#4258) from self into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4258
2025-07-07 19:57:30 +00:00
Jörg Thalheim
7bc8e091a5 checks/backup: no longer depend on self 2025-07-07 21:51:51 +02:00
Mic92
3462d458ac Merge pull request 'override-inputs: filter out self' (#4257) from improve-perf into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4257
2025-07-07 19:32:51 +00:00
Jörg Thalheim
bd42d67b0c override-inputs: filter out self 2025-07-07 21:25:33 +02:00
Mic92
d99ca36f9f Merge pull request 'checks/eval-module-clan-vars: optimize to use filtered source' (#4255) from borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4255
2025-07-07 19:02:25 +00:00
Jörg Thalheim
57f9cd9eee checks/eval-module-clan-vars: optimize to use filtered source
- Replace self.filter with lib.fileset for more precise filtering
- Remove unnecessary clan-core dependency from the test
- Test only needs lib and pkgs, not the full flake context
- Prevents unnecessary rebuilds when unrelated files change
2025-07-07 20:55:04 +02:00
Mic92
a9ec94b0df Merge pull request 'checks/inventory: optimize eval tests to use filtered sources' (#4254) from borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4254
2025-07-07 18:48:58 +00:00
Jörg Thalheim
c64dbceceb checks/inventory: optimize eval tests to use filtered sources
Replace full flake source (self) with minimal filtered filesets to prevent
unnecessary rebuilds when unrelated files change. All three inventory eval
tests now use the same unified fileset containing only necessary files.

This follows the same optimization pattern applied to other eval tests,
significantly reducing rebuild frequency during development.
2025-07-07 20:41:20 +02:00
Mic92
5d924e0c98 Merge pull request 'docs: no longer depend on self' (#4253) from borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4253
2025-07-07 18:31:35 +00:00
Jörg Thalheim
6a6688019b docs: no longer depend on self 2025-07-07 20:24:11 +02:00
Mic92
f33172fa73 Merge pull request 'don't rebuild eval tests on each ci run' (#4252) from borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4252
2025-07-07 18:13:57 +00:00
Jörg Thalheim
00914311a4 don't rebuild eval tests on each ci run 2025-07-07 20:05:45 +02:00
Mic92
ceeb40d9ac Merge pull request 'checks/borgbackup: don't rebuild on every pull request' (#4251) from borgbackup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4251
2025-07-07 17:44:16 +00:00
Jörg Thalheim
afab33056e checks/borgbackup: don't rebuild on every pull request 2025-07-07 19:35:48 +02:00
Mic92
a5183f4b4c Merge pull request 'avoid shebang in update-private-flake-inputs' (#4250) from fix-devflake-tryeval into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4250
2025-07-07 16:56:21 +00:00
Jörg Thalheim
a686d7523b avoid shebang in update-private-flake-inputs 2025-07-07 18:48:11 +02:00
Mic92
56b784992d Merge pull request 'devFlake: don't load if sources have been filtered out' (#4249) from fix-devflake-tryeval into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4249
2025-07-07 16:47:27 +00:00
Jörg Thalheim
5f723dc376 devFlake: don't load if sources have been filtered out 2025-07-07 18:38:01 +02:00
Brian McGee
1609989734 feat(ui): SidebarPane component
* implement Divider component using Kobalte's Separator
* refine read only state of form components to match the Sidebar Pane design
* introduce a SidebarPane component with sections that can toggle between editing and view states.
2025-07-07 17:31:58 +01:00
Mic92
0c07d5cfe0 Merge pull request 'add dev flake pattern' (#4245) from private-flake into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4245
2025-07-07 16:02:29 +00:00
Jörg Thalheim
9c37ef4cbe add dev flake pattern
This allows us to have dev dependencies which are not propagated to the user.
2025-07-07 15:59:09 +00:00
Jörg Thalheim
783b6a8b06 add gitea action to update private flake inputs 2025-07-07 15:59:09 +00:00
Jörg Thalheim
4f13049ee2 put flake input overrides into a helper function 2025-07-07 15:59:09 +00:00
Johannes Kirschbauer
2f4f303048 create/clan: do initial commit 2025-07-07 15:50:00 +00:00
Johannes Kirschbauer
d02868b950 templates: add .gitignore files to all templates 2025-07-07 15:50:00 +00:00
Johannes Kirschbauer
4f7d82671f Templates: remove 'minimal-flake-parts' 2025-07-07 15:50:00 +00:00
Johannes Kirschbauer
0dce3fc7ec templates: rename 'new_clan' to default 2025-07-07 15:50:00 +00:00
brianmcgee
a635f9c6fe Merge pull request 'ui: Modal component' (#4241) from feat/modal into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4241
2025-07-07 15:16:50 +00:00
Mic92
a8ed1c30e4 Merge pull request 'make treefmt work with git-worktrees' (#4246) from pytest into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4246
2025-07-07 15:07:53 +00:00
Jörg Thalheim
c0c41d52bd make treefmt work with git-worktrees 2025-07-07 16:55:36 +02:00
hsjobeki
bb236bb543 Merge pull request 'Docs: add missing documentation to api functions' (#4243) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4243
2025-07-07 14:02:08 +00:00
Johannes Kirschbauer
d7cf79faa7 openapi: error on missing api function docstring 2025-07-07 15:48:36 +02:00
Johannes Kirschbauer
dab11cb020 docs/api: add docstrings to {list_mdns_services, set_clan_details} 2025-07-07 15:47:14 +02:00
Johannes Kirschbauer
f2cb6fef41 api: remove unused get_directory 2025-07-07 15:45:51 +02:00
Johannes Kirschbauer
655b87ad04 docs/api: add docstrings to {run_machine_install,run_machine_deploy} 2025-07-07 15:41:02 +02:00
Johannes Kirschbauer
d462ae501e docs/api: add docstrings to {check_machine_ssh_login} 2025-07-07 15:38:09 +02:00
Johannes Kirschbauer
59a8c402ba docs/api: add docstrings to {delete_machine} 2025-07-07 15:36:16 +02:00
Johannes Kirschbauer
3b309ea74b docs/api: add docstrings to {get_flash_options, run_machine_flash} 2025-07-07 15:34:49 +02:00
Johannes Kirschbauer
508cd3c784 docs/api: add docstrings to {get_clan_details} 2025-07-07 15:31:06 +02:00
Johannes Kirschbauer
2bff7403df docs/api: add docstrings to {create_clan} 2025-07-07 15:29:19 +02:00
Johannes Kirschbauer
b5a6e809d0 docs/api: add docstrings to {get_generators, run_generators} 2025-07-07 15:22:44 +02:00
Johannes Kirschbauer
ec28c5c307 api/machines: document {get_machine,get_machine_details} 2025-07-07 15:13:23 +02:00
hsjobeki
10f9e5d11b Merge pull request 'api/generators: remove term 'vars' interact purely with 'generators'' (#4242) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4242
2025-07-07 13:04:00 +00:00
Johannes Kirschbauer
b8ba8b79ca api/check_machine_ssh_reachable: add function docs 2025-07-07 15:02:35 +02:00
Johannes Kirschbauer
fd07d02d2d openapi: warn on missing description 2025-07-07 14:52:49 +02:00
Johannes Kirschbauer
2a3d1efc6f api: expose docstring as function description 2025-07-07 14:51:15 +02:00
Johannes Kirschbauer
947e0a5488 openapi: add strict verb checking 2025-07-07 14:35:56 +02:00
Mic92
57b5520143 Merge pull request 'Add missing f to f-string' (#4234) from jfly/clan-core:oops-f-string into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4234
2025-07-07 12:30:20 +00:00
Mic92
9fd1031f4d Merge pull request 'Fix bug? member_id -> member_ip' (#4235) from jfly/clan-core:possible-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4235
2025-07-07 12:30:08 +00:00
Johannes Kirschbauer
c382e8f1f3 api/tasks: rename 'cancel_task' into 'delete_task' 2025-07-07 14:07:53 +02:00
Johannes Kirschbauer
cf92303f31 api/hw: rename 'describe_machine_hardware' into 'get_machine_hardware_summary' 2025-07-07 14:05:57 +02:00
Johannes Kirschbauer
80d0dc9805 api/hw: rename generate_machine_hardware_info into 'run' 2025-07-07 14:04:39 +02:00
Johannes Kirschbauer
4e2cbb188c api/generators: remove term 'vars' interact purely with 'generators' 2025-07-07 13:59:12 +02:00
Brian McGee
eb6460fb40 feat(ui): update playwright to match version in nixpkgs 2025-07-07 12:51:22 +01:00
hsjobeki
155bd36d2b Merge pull request 'api/tasks: prefix impure actions with run' (#4239) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4239
2025-07-07 11:28:07 +00:00
Johannes Kirschbauer
40ea5bf591 api/machine checks: rename, add checkResult 2025-07-07 13:13:00 +02:00
hsjobeki
0cd9c84de0 Merge pull request 'machine/host: degrade into info and add docs' (#4238) from host-info into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4238
Reviewed-by: Luis Hebendanz <consulting@qube.email>
2025-07-07 11:10:05 +00:00
Johannes Kirschbauer
e1ea44a2cc api/clan: rename 'update_clan_meta' -> 'set_clan_details' 2025-07-07 12:51:32 +02:00
Johannes Kirschbauer
7c4865e8b0 api/keygen: add todo comment 2025-07-07 12:49:37 +02:00
Johannes Kirschbauer
b032cd4a29 api/admin: remove maybe_get_admin_public_keys 2025-07-07 12:43:11 +02:00
DavHau
61edc1e06f Refactor StoreBase to take machine name string instead of Machine object
- Updated StoreBase.__init__ to accept machine: str and flake: Flake
- Modified all StoreBase subclasses (in_repo, vm, fs, sops, password_store) to match new signature
- Added select_machine method to Flake class for machine-specific attribute selection
- Updated Machine.select to use the new Flake.select_machine method
- Fixed all test cases to pass machine name and flake to store constructors
- Maintained backward compatibility by keeping the same external API

This reduces coupling between the store system and the Machine class,
making the architecture more modular and flexible.
2025-07-07 10:24:11 +00:00
Johannes Kirschbauer
c369f3b5d1 api/tasks: prefix impure actions with run 2025-07-07 12:09:43 +02:00
hsjobeki
0cc1f072f7 Merge pull request 'api/clan: rename 'show_clan_meta' -> 'get_clan_details'' (#4236) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4236
2025-07-07 10:00:10 +00:00
Johannes Kirschbauer
a2a011a47f machine/host: degrade into info and add docs 2025-07-07 11:52:46 +02:00
Brian McGee
e1796e19e4 feat(ui): refine Fieldset API 2025-07-07 10:51:43 +01:00
Johannes Kirschbauer
972adc7a7c api: chore rename outdated reference 2025-07-07 10:53:32 +02:00
Johannes Kirschbauer
e1b4f296e3 api: rename 'show_mdns' -> 'list_mdns_services' 2025-07-07 10:49:46 +02:00
Johannes Kirschbauer
1cb2156d87 api: rename to get_flash_options 2025-07-07 10:48:14 +02:00
Johannes Kirschbauer
84703fa293 docs: improve docstring for 'list_block_devices' 2025-07-07 10:46:26 +02:00
Johannes Kirschbauer
0e10122d54 api/clan: rename 'show_clan_meta' -> 'get_clan_details' 2025-07-07 10:41:00 +02:00
brianmcgee
ecd731024c Merge pull request 'feat(ui): alert component' (#4199) from ui/alerts into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4199
2025-07-07 08:11:13 +00:00
Jeremy Fleischman
e0da575201 Fix bug? member_id -> member_ip
(I stumbled across this while reading code, I haven't tested this at
all.)
2025-07-07 00:49:45 -07:00
Jeremy Fleischman
3577c689bd Add missing f to f-string 2025-07-07 00:48:32 -07:00
renovate[bot]
885103bfa4 chore(deps): lock file maintenance 2025-07-07 05:40:16 +00:00
Michael Hoang
afc1ca37bd Merge pull request 'cli: don't log every public key we find' (#4233) from push-lynrrnswopmw into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4233
2025-07-07 05:38:03 +00:00
Michael Hoang
4aa536a1bf cli: don't log every public key we find 2025-07-07 15:23:46 +10:00
Michael Hoang
c61dfbf8dd Merge pull request 'treewide: don't generate SSH keys with builder hostname' (#4232) from push-suwrloyoqvlq into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4232
2025-07-07 04:51:21 +00:00
Michael Hoang
e6785fa1d0 treewide: don't generate SSH keys with builder hostname 2025-07-07 14:39:57 +10:00
Michael Hoang
89ea01fd04 Merge pull request 'docs: misc improvements' (#4231) from push-xlwnnlrownnv into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4231
2025-07-07 04:03:33 +00:00
Michael Hoang
a8a08e21e4 clanServices/sshd: add README 2025-07-07 13:54:26 +10:00
Michael Hoang
700f571598 docs: fix highlighting in code block 2025-07-07 13:54:26 +10:00
Michael Hoang
08c15b3d9b docs: remove colon from headings 2025-07-07 13:54:26 +10:00
lassulus
2848b6d5d6 Merge pull request 'vars password-store: fix secret mangling due to string encoding' (#4227) from pass-fix-bytes into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4227
2025-07-07 00:50:58 +00:00
lassulus
ddc1059799 vars password-store: fix secret mangling due to string encoding 2025-07-07 02:35:17 +02:00
renovate[bot]
b690515dd7 Update data-mesher digest to a2166c1 2025-07-07 00:10:13 +00:00
lassulus
e9cef9c7c1 Merge pull request 'rename lingering clan.vars -> clan.core.vars' (#4224) from rip_clan_vars into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4224
2025-07-06 23:33:31 +00:00
lassulus
ca69864a20 rename lingering clan.vars -> clan.core.vars 2025-07-07 00:59:52 +02:00
hsjobeki
5436f284fb Merge pull request 'API: refactor into resource oriented names' (#4223) from api-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4223
2025-07-06 19:11:31 +00:00
Johannes Kirschbauer
00df032635 vars/api: rename 'get_generators_closure' into 'get_machine_generators' 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
a2c016718a api/hardware: consolidate into 'describe_machine_hardware' 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
d1abebf068 api/inventory: remove 'inventory' from api entirely 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
9635fb03b7 api/flash: refactor into 'list_flash_options' 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
f48c596617 vars/api: rename, unregister some unused vars functions 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
0589c71601 Vars: rename public functions into 'create_machine_vars' 2025-07-06 20:57:42 +02:00
Johannes Kirschbauer
a2c2d73e49 Vars: rename 'keygen' to 'create_secrets_user' 2025-07-06 20:57:42 +02:00
hsjobeki
99b22dfcbf Merge pull request 'Templates/cli: move display command into it own category' (#4222) from clan-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4222
2025-07-06 18:26:45 +00:00
Johannes Kirschbauer
cd04686663 Docs: update index 2025-07-06 20:06:17 +02:00
Johannes Kirschbauer
2b3e847c28 machine: rename standalone 'get_host' to 'get_machine_host' 2025-07-06 19:47:58 +02:00
Johannes Kirschbauer
d0ec4fd8e6 Templates/cli: move display command into it own category 2025-07-06 19:36:57 +02:00
hsjobeki
bb5c523ac8 Merge pull request 'Templates: remove InputPrio and related classes' (#4221) from clan-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4221
2025-07-06 17:19:31 +00:00
Johannes Kirschbauer
4df4f5220b Templates: remove InputPrio and related classes 2025-07-06 19:08:45 +02:00
renovate[bot]
a082fd2ed9 Lock file maintenance 2025-07-06 15:00:31 +00:00
hsjobeki
3161c10aa8 Merge pull request 'templates_url: add clan template url test' (#4216) from clan-templates into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4216
2025-07-06 14:54:42 +00:00
Johannes Kirschbauer
7ad8ed1af0 Templates: fix invalid mock flake 2025-07-06 16:43:38 +02:00
Johannes Kirschbauer
94919dc9b8 Fix/ui: update create argument 2025-07-06 15:48:35 +02:00
Johannes Kirschbauer
1502cfa4a7 Templates: migrate clan templates to flake identifiers 2025-07-06 15:37:10 +02:00
Johannes Kirschbauer
cce0207225 Templates: remove outdated check for 'configuration.nix' in machine templates 2025-07-06 15:37:10 +02:00
Johannes Kirschbauer
38f98645ac Templates: replace leftover MachineID, by Machine 2025-07-06 15:37:10 +02:00
Johannes Kirschbauer
74d2ae0619 templates_url: add clan template url test 2025-07-06 15:37:10 +02:00
lassulus
c122201ff2 Merge pull request 'Revert "make host key check an enum instead of an literal type"' (#4220) from revert_host_key_check into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4220
2025-07-06 13:19:00 +00:00
lassulus
e72795904d Revert "make host key check an enum instead of an literal type"
This reverts commit 543c518ed0.
2025-07-06 14:51:19 +02:00
hsjobeki
32ddb4ffa7 Merge pull request 'Templates/list: display templates via exposed nix value' (#4219) from templates-list into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4219
2025-07-06 12:49:58 +00:00
Johannes Kirschbauer
db6220b57b Templates/list: display templates via exposed nix value 2025-07-06 14:37:03 +02:00
lassulus
e929f36f80 Merge pull request 'vars/password-store: replace passBackend option with passPackage' (#4134) from lassulus/passage_compat into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4134
2025-07-06 11:44:27 +00:00
hsjobeki
f71460c4f9 Merge pull request 'clan-cli: fix incorrect field name in deploy warning messages. The warning for missing buildHost/targetHost always showed targetHost in the path, even when buildHost was the missing field.' (#4217) from pr-4215 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4217
2025-07-06 10:54:55 +00:00
lassulus
8302f3ffde vars/password-store: replace passBackend option with passPackage
The `clan.core.vars.settings.passBackend` option has been replaced with
`clan.vars.password-store.passPackage` to provide better type safety and
clearer configuration.

Changes:
- Remove problematic mkRemovedOptionModule that caused circular dependency
- Add proper option definition with assertion-based migration
- Users setting the old option get clear migration instructions
- Normal evaluation continues to work for users not using the old option

Migration: Replace `clan.core.vars.settings.passBackend = "passage"`
with `clan.vars.password-store.passPackage = pkgs.passage`
2025-07-06 12:46:39 +02:00
lassulus
bd82de6001 fix(flake): handle file paths with line numbers in cache existence check
The is_cached method now correctly handles store paths that have line
numbers appended (e.g., /nix/store/file.nix:123:456). Previously, these
paths would fail the existence check because the exact path with line
numbers doesn't exist as a file.

The fix adds a helper method that:
- First checks if the exact path exists
- If not, and the path contains colons, validates that the suffix
  consists only of numbers (line:column format)
- If valid, strips the line numbers and checks the base file path

This ensures that cached references to specific file locations are
properly validated while avoiding false positives with files that
have colons in their names.
2025-07-06 12:44:15 +02:00
adeci
06613de825 clan-cli: fix incorrect field name in deploy warning messages. The warning for missing buildHost/targetHost always showed targetHost in the path, even when buildHost was the missing field. 2025-07-06 12:44:02 +02:00
hsjobeki
76af63ee1c Merge pull request 'lib/get_host: improve abstraction, turn missconfiguration into a warning' (#4201) from cli-fixup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4201
2025-07-06 10:38:03 +00:00
Johannes Kirschbauer
3baa43fd87 cli/update: refactor machine selection logic into 'get_machines_for_update' 2025-07-06 12:27:28 +02:00
Johannes Kirschbauer
a6b8ca06ab machines/list: rename helper to instantiate_inventory_to_machines 2025-07-06 12:24:16 +02:00
Johannes Kirschbauer
f7faf2cd63 machines/list: remove duplicate query_machines_by_tags 2025-07-06 12:23:47 +02:00
Johannes Kirschbauer
bff3908bb1 CLI: update requireExplicitUpdate in help 2025-07-06 12:22:25 +02:00
Johannes Kirschbauer
d0613b4030 cli: return validated list from validate_machine_names 2025-07-06 12:22:00 +02:00
Johannes Kirschbauer
52b711667e lib/get_host: improve abstraction, turn missconfiguration into a warning
Motivation: A warning should encourage consistent usage of inventory.machines setting targetHost inside the machine should be considered a custom override

Changing the warning strings to avoid the term 'nix'/'json' both inventory and nixos machines are nix features
2025-07-06 12:08:00 +02:00
lassulus
13d6db98d1 Merge pull request 'better_select_output' (#4213) from better_select_output into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4213
2025-07-06 00:24:06 +00:00
lassulus
195134dd5e clan_cli: better select debug output 2025-07-06 01:17:55 +02:00
lassulus
0670f0ad32 clan_cli flake: remove apply from select, as it will break stuff in horrible ways
Since apply changes the structure of the retuned value, the cache will
be confused about the structure and in subsequent request will use this
wrong structure.

For example: we would use builtins.attrNames on inputs, the flake will
forever think that inputs is a list of strings and will report errors
whenever we try to fetch subkeys from it
2025-07-06 01:17:55 +02:00
lassulus
daf843eeab clan_cli run: add trace runOption to disable verbose traces in debug mode 2025-07-05 19:48:50 +02:00
lassulus
291b742fd7 Merge pull request 'clan_cli machines update: remove caching of sometimes missing pass config' (#4212) from fix_update into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4212
2025-07-05 17:42:45 +00:00
lassulus
f7d6c23aaa clan_cli machines update: remove caching of sometimes missing pass config
This config value is not set if people don't use pass, it's also at the wrong location
We could cache it with a maybe, but we plan to move it anyway
2025-07-05 18:39:53 +02:00
DavHau
d143359a2d refactor: reduce coupling to Machine class in vars module
- Change Generator class to store machine name as string instead of Machine reference
- Update Generator.generators_from_flake() to only require machine name and flake
- Refactor check_vars() to accept machine name and flake instead of Machine object
- Create Machine instances only when needed for specific operations

This continues the effort to reduce dependencies on the Machine class,
making the codebase more modular and easier to refactor.
2025-07-05 16:30:05 +07:00
DavHau
448e60f866 refactor: remove Machine.vars_generators() method
Replace all calls to machine.vars_generators() with direct calls to
Generator.generators_from_flake() to make the dependency more explicit
and remove unnecessary indirection.

This reduces coupling to the Machine class, making the codebase more
modular and easier to refactor in the future.
2025-07-05 15:26:31 +07:00
renovate[bot]
324e934204 chore(deps): update disko digest to da6109c 2025-07-04 16:50:11 +00:00
Mic92
3f6e5968b5 Merge pull request 'Qubasa-hsjobeki/bump-nixpkgs' (#4205) from Qubasa-hsjobeki/bump-nixpkgs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4205
2025-07-04 16:47:23 +00:00
Jörg Thalheim
e4c8aba5bc zerotierone: disable tests on macos 2025-07-04 18:39:01 +02:00
Jörg Thalheim
76503b2a92 terminate_process_group: also properly yield iterator when we return early 2025-07-04 18:39:01 +02:00
Jörg Thalheim
d585052007 migrate all projects to python 3.13 linting 2025-07-04 18:39:01 +02:00
Jörg Thalheim
65904d8d8e clan-cli: handle None in union types to prevent TypeError
Add comprehensive test coverage for union types with None to prevent
regression of the issubclass() TypeError that was occurring when
checking if None is in a union type.
2025-07-04 18:39:01 +02:00
Jörg Thalheim
d5aa917ee7 migrate all projects to python 3.13 linting 2025-07-04 18:39:01 +02:00
Mic92
cb9284360f Merge pull request 'change install test to run clan outside of the VM' (#3906) from vm-test into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3906
2025-07-04 15:17:14 +00:00
Qubasa
3f1fdc0aae treefmt/ruff: Set python lint version to 3.13. Fix all new lints coming up. 2025-07-04 17:11:31 +02:00
Johannes Kirschbauer
b35ca4f1a8 Chore: bump nixpkgs 2025-07-04 17:11:31 +02:00
Jörg Thalheim
76e653f37f nixoTestLib: split setting up function for port-forwarding and setuping up flake 2025-07-04 16:56:42 +02:00
Michael Hoang
10737f7d94 Merge pull request 'clanServices/wifi: fix autoConnect setting not doing anything' (#4204) from push-msokoouryrwq into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4204
2025-07-04 14:33:49 +00:00
Michael Hoang
eb54fdc741 clanServices/wifi: fix autoConnect setting not doing anything 2025-07-05 00:05:48 +10:00
pinpox
4aa90f009f Merge pull request 'Re-introduce state-version option' (#4179) from state-version-option-again into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4179
2025-07-04 14:02:05 +00:00
Jörg Thalheim
247151e93f only override identify/host_key_check in a single place 2025-07-04 14:36:05 +02:00
Jörg Thalheim
543c518ed0 make host key check an enum instead of an literal type
this is more typesafe at runtime.
2025-07-04 14:36:05 +02:00
Jörg Thalheim
7f4f11751e nixosTestLib: use xargs for copying store inputs 2025-07-04 14:36:05 +02:00
Jörg Thalheim
a53efb9386 nixosTestLib: substitute dependencies on tools in 2025-07-04 14:36:05 +02:00
Jörg Thalheim
c509f333e4 nixosTestLib: fix various linting issues 2025-07-04 14:36:05 +02:00
Jörg Thalheim
ea93d8fec7 inline create_test_machine again 2025-07-04 14:36:05 +02:00
Jörg Thalheim
68b2aaea89 setup_nix_in_nix: use cp intead of shutil
it's faster and handles symlinks
2025-07-04 14:36:05 +02:00
Jörg Thalheim
1e7453ab04 move nixosTestLib to pkgs/testing 2025-07-04 14:36:05 +02:00
Jörg Thalheim
c148ece02e move setup_nix_in_nix into nixos_test_lib 2025-07-04 14:36:02 +02:00
Jörg Thalheim
b526242744 share more code between installation and update test 2025-07-04 14:14:28 +02:00
Jörg Thalheim
76b0a9bf13 add -i option to update-hardware-config 2025-07-04 14:14:28 +02:00
Jörg Thalheim
541732462b add port_utils module for installation testions 2025-07-04 14:14:28 +02:00
Jörg Thalheim
1558a366de bump clan-core-for-checks 2025-07-04 14:14:28 +02:00
Jörg Thalheim
6aab8ffd0c change install test to run clan outside of the VM 2025-07-04 14:14:28 +02:00
pinpox
ae9d219dea Merge branch 'main' into state-version-option-again 2025-07-04 11:56:15 +00:00
hsjobeki
899051a570 Merge pull request 'clan_lib/openapi: add openapi rendering' (#4200) from lib-openapi into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4200
2025-07-04 11:54:13 +00:00
hsjobeki
a44740d902 Merge pull request 'Clan_lib: add filtering by tag to list API' (#4197) from cli-fixup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4197
2025-07-04 11:53:43 +00:00
Johannes Kirschbauer
ba0397242f api: rename script to openapi.py 2025-07-04 13:40:59 +02:00
Luis Hebendanz
79560ac202 Merge pull request 'clan-app: Implement dynamic groups and array based filtering of logs and groups' (#4190) from Qubasa/clan-core:add_clan_group into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4190
2025-07-04 11:06:50 +00:00
Qubasa
52aaad272f clan-app: Implement dynamic log groups into javascript callApi
nix fmt
2025-07-04 17:49:00 +07:00
Johannes Kirschbauer
62c1db9769 Docs: init redoc internal rest inspired docs 2025-07-04 12:45:39 +02:00
Johannes Kirschbauer
b41029ea48 clan_lib/openapi: add openapi rendering 2025-07-04 12:09:16 +02:00
pinpox
a0a9cef2a6 Add state-version nixos option
This reverts commit 695574988e.
2025-07-04 11:54:12 +02:00
Brian McGee
1f26135381 feat(ui): alert component 2025-07-04 10:51:18 +01:00
brianmcgee
14b428216d Merge pull request 'ui/form-components' (#4118) from ui/form-components into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4118
2025-07-04 08:30:22 +00:00
Johannes Kirschbauer
91df5c258e lib/convert_inventory_to_machines: add classmethod for common conversion of Machine and InventoryMachine 2025-07-04 10:18:14 +02:00
Johannes Kirschbauer
fcb38820ec lib/get_host: improve abstraction, turn missconfiguration into a warning
Motivation: A warning should encourage consistent usage of inventory.machines setting targetHost inside the machine should be considered a custom override

Changing the warning strings to avoid the term 'nix'/'json' both inventory and nixos machines are nix features
2025-07-04 10:02:06 +02:00
Johannes Kirschbauer
6d85cc0ff2 Clan_lib: add filtering by tag to list API 2025-07-04 09:48:21 +02:00
Johannes Kirschbauer
10fbae0c15 cli: move some references to duplicate list machines 2025-07-04 09:32:43 +02:00
Qubasa
aef1edf8e3 clan_lib: Move load_in_all_api_functions to clan_lib 2025-07-04 14:22:40 +07:00
Qubasa
18735a150f clan_lib: Heavily simplified log_manager and test suite to concentrate on actually used features 2025-07-04 14:05:31 +07:00
Qubasa
c354a87765 clan_lib: Add better docstrings to log_manager 2025-07-04 13:35:34 +07:00
Qubasa
70d57cb267 clan_lib: expose log_manager with API.register properly 2025-07-04 13:19:09 +07:00
Qubasa
24b8cb799a clan-cli: Print function name to raise for unsupported types by API.register 2025-07-04 13:17:01 +07:00
renovate[bot]
68e61d66d7 chore(deps): update sops-nix digest to 3633fc4 2025-07-04 05:30:12 +00:00
DavHau
2e191d7db8 Merge pull request 'reverting 4c2bb0791d47c91130541333b296b2e8a39c4d08' (#4194) from davhau/dave into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4194
2025-07-04 03:51:22 +00:00
DavHau
969b7606a6 reverting 4c2bb0791d 2025-07-04 10:35:30 +07:00
Mic92
631d17b6e9 Merge pull request 'actually import state-version' (#4193) from fix-state-version into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4193
2025-07-03 18:32:15 +00:00
Jörg Thalheim
ba5b81abf0 actually import state-version 2025-07-03 20:19:19 +02:00
lassulus
1bcd2be478 Merge pull request 'refactor: remove _serialized field and implement efficient vars selection' (#4187) from remove_serialized into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4187
2025-07-03 17:18:27 +00:00
lassulus
a6409f921b refactor: remove _serialized field and implement efficient vars selection
- Remove _serialized field from vars interface to prevent serialization
  errors with throwing passBackend field
- Implement direct selection of generator fields using multi-select syntax
- Refactor vars_generators() to use new Generator.from_flake() method that
  selects only safe fields (avoiding non-serializable values)
- Remove unused legacy methods: Generator.from_json(), Var.from_json(),
  Prompt.from_json()
- Update precaching to match new selection approach

This fixes the serialization errors that were preventing vars from working
with the new password-store implementation by avoiding the problematic
_serialized field entirely.
2025-07-03 18:53:36 +02:00
lassulus
8f9d88a104 flake: prevent outPath in multiselect to avoid serialization issues
When using multiselect with outPath like {outPath,?meta}, nix evaluation
collapses the attrset to just the outPath string, breaking further selection.
Add validation during selector parsing to catch this and provide a clear error.
2025-07-03 18:53:36 +02:00
Brian McGee
9003204b54 fix(ui): disabled storybook derivation
Something about passing orientation in any of the Form stories is causing the browser to crash

`npm run test-storybook-static` works fine in the devshell.

Disabling for now as it's slowing down development.
2025-07-03 17:50:42 +01:00
Brian McGee
7939cfc9a9 feat(ui): flatten the Field pattern and introduce Orienter component 2025-07-03 17:50:41 +01:00
Brian McGee
7232892feb feat(ui): simplify timeout speedup for button stories 2025-07-03 17:50:40 +01:00
Brian McGee
c3ba72e82c feat(ui): Combobox component and style tooltip for label 2025-07-03 17:50:40 +01:00
Brian McGee
17b4f95055 feat(ui): simplify form components
Better pass through to the underlying Kobalte API without re-defining types.
2025-07-03 17:50:39 +01:00
Brian McGee
3c72ad1c92 fix(ui): hover and inverted colors for checkbox 2025-07-03 17:50:39 +01:00
Brian McGee
5b46136ca8 feat(ui): add fieldset component 2025-07-03 17:50:38 +01:00
Brian McGee
04c59c76ee feat(ui): add form field with text, textarea and checkbox support 2025-07-03 17:50:38 +01:00
hsjobeki
fbb93c8412 Merge pull request 'docs: update vpn setup instructions' (#4192) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4192
2025-07-03 14:32:13 +00:00
Johannes Kirschbauer
e0993559db docs: add zerotier debugging commands 2025-07-03 16:25:10 +02:00
Qubasa
76bba13a7f clan-app: Implement dynamic groups and array based filtering of logs and groups 2025-07-03 17:49:34 +07:00
hsjobeki
12c2c4ee89 Merge pull request 'inventory: fix missing default for instances.*.module' (#4189) from fix-inventory into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4189
2025-07-03 10:13:57 +00:00
Johannes Kirschbauer
f8d36634ee inventory: fix missing default for instances.*.module 2025-07-03 12:06:29 +02:00
Johannes Kirschbauer
b27ed51284 docs: update vpn setup instructions 2025-07-03 12:04:56 +02:00
Johannes Kirschbauer
a81701b59a docs: fix wrong roles assignment syntax 2025-07-03 11:42:26 +02:00
lassulus
609db2f00c Merge pull request 'refactor: remove deployment.json and use direct selectors' (#4142) from lassulus/remove-deployment-json into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4142
2025-07-02 19:13:39 +00:00
lassulus
40065c7a00 refactor: remove deployment.json and use direct selectors
- Remove deployment.json file generation from outputs.nix
- Add throw for deprecated deployment.file usage with upgrade instructions
- Remove vars data from deployment.data
- Update Machine class to use direct select() calls instead of deployment property
- Update all deployment property accesses to use direct selectors
- Add precaching for frequently accessed values in update.py:
  - Module paths for facts and vars
  - Deployment settings (requireExplicitUpdate, nixosMobileWorkaround)
  - Services and generators data
  - Secret upload locations
- This removes unnecessary JSON serialization and makes the code more composable
2025-07-02 20:56:23 +02:00
hsjobeki
2e4cbdc7c8 Merge pull request 'machines/update: queue update only for machines_to_update' (#4186) from cli-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4186
2025-07-02 17:16:12 +00:00
Johannes Kirschbauer
9aa7be3aba machines/update: queue update only for machines_to_update 2025-07-02 19:05:37 +02:00
hsjobeki
b2e8b8bf59 Merge pull request 'clan.deployment: unify deprecated options' (#4184) from cli-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4184
2025-07-02 16:48:19 +00:00
Johannes Kirschbauer
4c2bb0791d clan.deployment: unify deprecated option 2025-07-02 18:37:29 +02:00
hsjobeki
5cc8f3b2b3 Merge pull request 'docs/secrets: setup move plugins section to the bottom. Normal users don't have that, smart people read from bottom up' (#4185) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4185
2025-07-02 16:32:31 +00:00
Johannes Kirschbauer
fb5dca567e docs/secrets: setup move plugins section to the bottom. Normal users don't have that, smart people read from bottom up 2025-07-02 18:23:48 +02:00
Johannes Kirschbauer
97bdf49814 cli: fix listing maschines by their name 2025-07-02 18:18:48 +02:00
hsjobeki
b8feb652f6 Merge pull request 'docs/index: encourage non-linear flow' (#4183) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4183
2025-07-02 15:13:50 +00:00
Johannes Kirschbauer
58c9c929ba docs/index: add progress tracker, encourage non-linear flow 2025-07-02 17:02:16 +02:00
Johannes Kirschbauer
58862215ab docs: rephrase add machines for planned dynamic templates 2025-07-02 17:01:40 +02:00
Johannes Kirschbauer
667bbffb3f docs: add emojis to getting-started 2025-07-02 17:00:51 +02:00
hsjobeki
31b1725f6f Merge pull request 'docs: clarify project structure exploration in Getting Started' (#4182) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4182
2025-07-02 14:39:42 +00:00
Johannes Kirschbauer
0bd4074927 docs: clarify project structure exploration in Getting Started 2025-07-02 16:32:30 +02:00
hsjobeki
749a847d83 Merge pull request 'docs: remove step numbers' (#4181) from docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4181
2025-07-02 14:01:43 +00:00
Johannes Kirschbauer
faf6ac82eb docs: remove api reference build command 2025-07-02 15:53:48 +02:00
Johannes Kirschbauer
6c7beb7aaa docs: remove step numbers
Numerating steps is not informative and considered bad style
People tend to follow documentation non-linearly
leaving of at one point, starting over from the middle
Documentation should be written in a way that allow for that
2025-07-02 15:53:24 +02:00
Mic92
d7dcb55001 Merge pull request 'vars: make debug logging less verbose' (#4171) from merge-when-green-joerg into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4171
2025-07-02 11:48:59 +00:00
Luis Hebendanz
d3d337a51e Merge pull request 'clan-app: Init machine based API logging' (#4133) from Qubasa/clan-core:improve_ui_logs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4133
2025-07-02 11:30:22 +00:00
Qubasa
2fd6426f28 clan-app: whitelist necessary any usage in api./index.tsx 2025-07-02 18:11:28 +07:00
Jörg Thalheim
f70f6d6d80 clan_lib/test_create: fix test when running outside of the sandbox... 2025-07-02 13:04:46 +02:00
Qubasa
b9a386c881 clan-cli: api.py add python header 2025-07-02 17:59:24 +07:00
Qubasa
db3e8b9984 clan-app: Add logging middleware 2025-07-02 17:59:05 +07:00
hsjobeki
117224e6a4 Merge pull request 'pytest: improve logging a bit' (#4180) from pytest-logs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4180
2025-07-02 10:47:14 +00:00
Johannes Kirschbauer
ea77b48d83 pytest: improve logging a bit 2025-07-02 12:35:02 +02:00
Qubasa
5d99d0e1e7 clan-app: simplified task function, moved them to a separate file 2025-07-02 16:18:37 +07:00
Qubasa
1ec67ecfaf webview-lib: Moved repo to gitea, updated revision. Removed set_icon 2025-07-02 16:16:37 +07:00
Qubasa
d5064ce465 clan-app: Add pygdb.sh for debugging crashes in webview-lib 2025-07-02 16:00:55 +07:00
Qubasa
9080e7c7f6 clan-app: Fix .local.env not being sourced 2025-07-02 16:00:34 +07:00
Qubasa
8e00363584 ui-2d: Fix build errors 2025-07-02 15:59:50 +07:00
hsjobeki
672db4a33f Merge pull request 'clanServices: add flake level exports' (#4172) from flake-exports into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4172
2025-07-02 08:42:07 +00:00
Johannes Kirschbauer
82c80a9a53 clan/exportsModule: add missing default 2025-07-02 10:32:56 +02:00
Johannes Kirschbauer
16116505ab flake-exports: add options documentation 2025-07-02 10:22:09 +02:00
hsjobeki
80713f93af Merge pull request 'Revert "Merge pull request 'Make state-version generation an clan option' (#4056) from state-version-option into main"' (#4176) from revert-state-version into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4176
2025-07-02 07:37:37 +00:00
Johannes Kirschbauer
b6f00ed1f6 test: enable unsafe teardown of tempDir
aarch64 libc or kernel might be stricter in keeping files locked while they're open.
This causes failed teardown of the tmpdir
2025-07-02 09:27:05 +02:00
renovate[bot]
d6646ecc62 chore(deps): update data-mesher digest to 246f0d6 2025-07-02 00:20:19 +00:00
renovate[bot]
fc1c64985f chore(deps): update flake-parts digest to 7782624 2025-07-02 00:00:16 +00:00
Johannes Kirschbauer
695574988e Revert "Merge pull request 'Make state-version generation an clan option' (#4056) from state-version-option into main"
This reverts commit a101ff1f11, reversing
changes made to f12b62d8b4.
2025-07-01 18:15:11 +02:00
kenji
dc6648520f Merge pull request 'docs(clan): Improve state-version documentation' (#4174) from kenji/ke-state-version into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4174
2025-07-01 15:40:33 +00:00
Johannes Kirschbauer
454936336f clanServices: add test to ensure nixosModule is imported 2025-07-01 17:35:04 +02:00
a-kenji
0093836272 docs(clan): Improve state-version documentation 2025-07-01 17:32:20 +02:00
hsjobeki
e026ada443 Merge pull request 'clanServices: remove recursive services' (#4173) from revert-recursive-services into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4173
2025-07-01 15:11:57 +00:00
Johannes Kirschbauer
38bb2dfb56 clanServices: remove recursive services
Initially added in #3972 we've decided to remove them, because they are to complex to use correctly
2025-07-01 17:03:58 +02:00
Johannes Kirschbauer
d10fe7a8ee clanServices: add flake level exports 2025-07-01 16:54:19 +02:00
506 changed files with 10361 additions and 18491 deletions

75
.gitea/workflows/create-pr.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Shared script for creating pull requests in Gitea workflows
set -euo pipefail
# Required environment variables:
# - CI_BOT_TOKEN: Gitea bot token for authentication
# - PR_BRANCH: Branch name for the pull request
# - PR_TITLE: Title of the pull request
# - PR_BODY: Body/description of the pull request
if [[ -z "${CI_BOT_TOKEN:-}" ]]; then
echo "Error: CI_BOT_TOKEN is not set" >&2
exit 1
fi
if [[ -z "${PR_BRANCH:-}" ]]; then
echo "Error: PR_BRANCH is not set" >&2
exit 1
fi
if [[ -z "${PR_TITLE:-}" ]]; then
echo "Error: PR_TITLE is not set" >&2
exit 1
fi
if [[ -z "${PR_BODY:-}" ]]; then
echo "Error: PR_BODY is not set" >&2
exit 1
fi
# Push the branch
git push origin "+HEAD:${PR_BRANCH}"
# Create pull request
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
-H "Authorization: token $CI_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"head\": \"${PR_BRANCH}\",
\"base\": \"main\",
\"title\": \"${PR_TITLE}\",
\"body\": \"${PR_BODY}\"
}" \
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
pr_number=$(echo "$resp" | jq -r '.number')
if [[ "$pr_number" == "null" ]]; then
echo "Error creating pull request:" >&2
echo "$resp" | jq . >&2
exit 1
fi
echo "Created pull request #$pr_number"
# Merge when checks succeed
while true; do
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
-H "Authorization: token $CI_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Do": "merge",
"merge_when_checks_succeed": true,
"delete_branch_after_merge": true
}' \
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
msg=$(echo "$resp" | jq -r '.message')
if [[ "$msg" != "Please try again later" ]]; then
break
fi
echo "Retrying in 2 seconds..."
sleep 2
done
echo "Pull request #$pr_number merge initiated"

View File

@@ -19,35 +19,10 @@ jobs:
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"
git push origin +HEAD:update-clan-core-for-checks
set -x
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
-H "Authorization: token $CI_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"head": "update-clan-core-for-checks",
"base": "main",
"title": "Update Clan Core for Checks",
"body": "This PR updates the pinned clan-core flake input that is used for checks."
}' \
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
pr_number=$(echo "$resp" | jq -r '.number')
# Merge when succeed
while true; do
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
-H "Authorization: token $CI_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Do": "merge",
"merge_when_checks_succeed": true,
"delete_branch_after_merge": true
}' \
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
msg=$(echo $resp | jq -r '.message')
if [[ "$msg" != "Please try again later" ]]; then
break
fi
echo "Retrying in 2 seconds..."
sleep 2
done
# 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

@@ -0,0 +1,40 @@
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

@@ -19,11 +19,11 @@
...
}:
let
dependencies = [
self
pkgs.stdenv.drvPath
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-backup.config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
dependencies =
[
pkgs.stdenv.drvPath
]
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{

View File

@@ -47,14 +47,6 @@ nixosLib.runTest (
clientone =
{ config, pkgs, ... }:
let
dependencies = [
clan-core
pkgs.stdenv.drvPath
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{
services.openssh.enable = true;
@@ -65,15 +57,6 @@ nixosLib.runTest (
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
environment.etc.install-closure.source = "${closureInfo}/store-paths";
nix.settings = {
substituters = pkgs.lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = pkgs.lib.mkForce 3;
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
};
system.extraDependencies = dependencies;
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
};

View File

@@ -1,6 +1,6 @@
{ fetchgit }:
fetchgit {
url = "https://git.clan.lol/clan/clan-core.git";
rev = "28131afbbcd379a8ff04c79c66c670ef655ed889";
sha256 = "1294cwjlnc341fl6zbggn4rgq8z33gqkcyggjfvk9cf7zdgygrf6";
rev = "eea93ea22c9818da67e148ba586277bab9e73cea";
sha256 = "sha256-PV0Z+97QuxQbkYSVuNIJwUNXMbHZG/vhsA9M4cDTCOE=";
}

View File

@@ -50,8 +50,6 @@
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
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in

View File

@@ -1,63 +1,9 @@
{
self,
lib,
...
}:
let
installer =
{ modulesPath, pkgs, ... }:
let
dependencies = [
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
self.clan.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.clan.deployment.file
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.nixos-anywhere
pkgs.bubblewrap
pkgs.buildPackages.xorg.lndir
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{
imports = [
(modulesPath + "/../tests/common/auto-format-root-device.nix")
];
networking.useNetworkd = true;
services.openssh.enable = true;
services.openssh.settings.UseDns = false;
services.openssh.settings.PasswordAuthentication = false;
system.nixos.variant_id = "installer";
environment.systemPackages = [
self.packages.${pkgs.system}.clan-cli-full
pkgs.nixos-facter
];
environment.etc."install-closure".source = "${closureInfo}/store-paths";
virtualisation.emptyDiskImages = [ 512 ];
virtualisation.diskSize = 8 * 1024;
virtualisation.rootDevice = "/dev/vdb";
# both installer and target need to use the same diskImage
virtualisation.diskImage = "./target.qcow2";
virtualisation.memorySize = 3048;
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = lib.mkForce 3;
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
experimental-features = [
"nix-command"
"flakes"
];
};
users.users.nonrootuser = {
isNormalUser = true;
openssh.authorizedKeys.keyFiles = [ ../assets/ssh/pubkey ];
extraGroups = [ "wheel" ];
};
security.sudo.wheelNeedsPassword = false;
system.extraDependencies = dependencies;
};
in
{
# The purpose of this test is to ensure `clan machines install` works
@@ -106,6 +52,25 @@ in
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.nonrootuser = {
isNormalUser = true;
openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
extraGroups = [ "wheel" ];
home = "/home/nonrootuser";
createHome = true;
};
users.users.root.openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
# Allow users to manage their own SSH keys
services.openssh.authorizedKeysFiles = [
"/root/.ssh/authorized_keys"
"/home/%u/.ssh/authorized_keys"
"/etc/ssh/authorized_keys.d/%u"
];
security.sudo.wheelNeedsPassword = false;
boot.consoleLogLevel = lib.mkForce 100;
boot.kernelParams = [ "boot.shell_on_fail" ];
@@ -182,55 +147,199 @@ in
# vm-test-run-test-installation-> target: waiting for the VM to finish booting
# vm-test-run-test-installation-> target: Guest root shell did not produce any data yet...
# vm-test-run-test-installation-> target: To debug, enter the VM and run 'systemctl status backdoor.service'.
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux && !pkgs.stdenv.isAarch64) {
nixos-test-installation = self.clanLib.test.baseTest {
name = "installation";
nodes.target = {
services.openssh.enable = true;
virtualisation.diskImage = "./target.qcow2";
virtualisation.useBootLoader = true;
checks =
let
# Custom Python package for port management utilities
closureInfo = pkgs.closureInfo {
rootPaths = [
self.checks.x86_64-linux.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
};
nodes.installer = installer;
in
pkgs.lib.mkIf (pkgs.stdenv.isLinux && !pkgs.stdenv.isAarch64) {
nixos-test-installation = self.clanLib.test.baseTest {
name = "installation";
nodes.target = (import ./test-helpers.nix { inherit lib pkgs self; }).target;
extraPythonPackages = _p: [
self.legacyPackages.${pkgs.system}.nixosTestLib
];
testScript = ''
installer.start()
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]
installer.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519")
def create_test_machine(oldmachine, qemu_test_bin: str, **kwargs):
"""Create a new test machine from an installed disk image"""
start_command = [
f"{qemu_test_bin}/bin/qemu-kvm",
"-cpu",
"max",
"-m",
"3048",
"-virtfs",
"local,path=/nix/store,security_model=none,mount_tag=nix-store",
"-drive",
f"file={oldmachine.state_dir}/target.qcow2,id=drive1,if=none,index=1,werror=report",
"-device",
"virtio-blk-pci,drive=drive1",
"-netdev",
"user,id=net0",
"-device",
"virtio-net-pci,netdev=net0",
]
machine = create_machine(start_command=" ".join(start_command), **kwargs)
driver.machines.append(machine)
return machine
installer.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v nonrootuser@localhost hostname")
installer.succeed("cp -r ${self.checks.x86_64-linux.clan-core-for-checks} test-flake && chmod -R +w test-flake")
target.start()
installer.succeed("clan machines install --no-reboot --debug --flake test-flake --yes test-install-machine-without-system --target-host nonrootuser@localhost --update-hardware-config nixos-facter >&2")
installer.shutdown()
# 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}"
)
# We are missing the test instrumentation somehow. Test this later.
target.state_dir = installer.state_dir
target.start()
target.wait_for_unit("multi-user.target")
'';
} { inherit pkgs self; };
# Set up SSH connection
ssh_conn = setup_ssh_connection(
target,
temp_dir,
"${../assets/ssh/privkey}"
)
nixos-test-update-hardware-configuration = self.clanLib.test.baseTest {
name = "update-hardware-configuration";
nodes.installer = installer;
# Run clan install from host using port forwarding
clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines",
"install",
"--phases", "disko,install",
"--debug",
"--flake", flake_dir,
"--yes", "test-install-machine-without-system",
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE'],
"--update-hardware-config", "nixos-facter",
]
testScript = ''
installer.start()
installer.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../assets/ssh/privkey} /root/.ssh/id_ed25519")
installer.wait_until_succeeds("timeout 2 ssh -o StrictHostKeyChecking=accept-new -v nonrootuser@localhost hostname")
installer.succeed("cp -r ${self.checks.x86_64-linux.clan-core-for-checks} test-flake && chmod -R +w test-flake")
installer.fail("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
installer.fail("test -f test-flake/machines/test-install-machine/facter.json")
subprocess.run(clan_cmd, check=True)
installer.succeed("clan machines update-hardware-config --debug --flake test-flake test-install-machine-without-system nonrootuser@localhost >&2")
installer.succeed("test -f test-flake/machines/test-install-machine-without-system/facter.json")
installer.succeed("rm test-flake/machines/test-install-machine-without-system/facter.json")
# Shutdown the installer machine gracefully
try:
target.shutdown()
except BrokenPipeError:
# qemu has already exited
pass
installer.succeed("clan machines update-hardware-config --debug --backend nixos-generate-config --flake test-flake test-install-machine-without-system nonrootuser@localhost >&2")
installer.succeed("test -f test-flake/machines/test-install-machine-without-system/hardware-configuration.nix")
installer.succeed("rm test-flake/machines/test-install-machine-without-system/hardware-configuration.nix")
'';
} { inherit pkgs self; };
};
# Create a new machine instance that boots from the installed system
installed_machine = create_test_machine(target, "${pkgs.qemu_test}", name="after_install")
installed_machine.start()
installed_machine.wait_for_unit("multi-user.target")
installed_machine.succeed("test -f /etc/install-successful")
'';
} { inherit pkgs self; };
nixos-test-update-hardware-configuration = self.clanLib.test.baseTest {
name = "update-hardware-configuration";
nodes.target = (import ./test-helpers.nix { inherit lib pkgs self; }).target;
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]
target.start()
# 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}"
)
# Set up SSH connection
ssh_conn = setup_ssh_connection(
target,
temp_dir,
"${../assets/ssh/privkey}"
)
# Verify files don't exist initially
hw_config_file = os.path.join(flake_dir, "machines/test-install-machine/hardware-configuration.nix")
facter_file = os.path.join(flake_dir, "machines/test-install-machine/facter.json")
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",
"machines",
"update-hardware-config",
"--debug",
"--flake", ".",
"--host-key-check", "none",
"test-install-machine-without-system",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE'],
f"nonrootuser@localhost:{ssh_conn.host_port}"
]
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
if result.returncode != 0:
print(f"Clan update-hardware-config failed: {result.stderr.decode()}")
raise Exception(f"Clan update-hardware-config failed with return code {result.returncode}")
facter_without_system_file = os.path.join(flake_dir, "machines/test-install-machine-without-system/facter.json")
assert os.path.exists(facter_without_system_file), "facter.json should exist after update"
os.remove(facter_without_system_file)
# Test nixos-generate-config backend
clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines",
"update-hardware-config",
"--debug",
"--backend", "nixos-generate-config",
"--host-key-check", "none",
"--flake", ".",
"test-install-machine-without-system",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE'],
f"nonrootuser@localhost:{ssh_conn.host_port}"
]
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
if result.returncode != 0:
print(f"Clan update-hardware-config (nixos-generate-config) failed: {result.stderr.decode()}")
raise Exception(f"Clan update-hardware-config failed with return code {result.returncode}")
hw_config_without_system_file = os.path.join(flake_dir, "machines/test-install-machine-without-system/hardware-configuration.nix")
assert os.path.exists(hw_config_without_system_file), "hardware-configuration.nix should exist after update"
'';
} { inherit pkgs self; };
};
};
}

View File

@@ -0,0 +1,44 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "nixos-test-lib"
version = "1.0.0"
description = "NixOS test utilities for clan VM testing"
authors = [
{name = "Clan Core Team"}
]
dependencies = []
[project.optional-dependencies]
dev = [
"mypy",
"ruff"
]
[tool.setuptools.packages.find]
where = ["."]
include = ["nixos_test_lib*"]
[tool.setuptools.package-data]
"nixos_test_lib" = ["py.typed"]
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
warn_unused_configs = true
[tool.ruff]
target-version = "py312"
line-length = 88
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D", # docstrings
"ANN", # type annotations
"COM812", # trailing comma
"ISC001", # string concatenation
]

View File

@@ -0,0 +1,173 @@
{
lib,
pkgs,
self,
...
}:
let
# Common target VM configuration used by both installation and update tests
target =
{ modulesPath, pkgs, ... }:
{
imports = [
(modulesPath + "/../tests/common/auto-format-root-device.nix")
];
networking.useNetworkd = true;
services.openssh.enable = true;
services.openssh.settings.UseDns = false;
services.openssh.settings.PasswordAuthentication = false;
system.nixos.variant_id = "installer";
environment.systemPackages = [
pkgs.nixos-facter
];
# Disable cache.nixos.org to speed up tests
nix.settings.substituters = [ ];
nix.settings.trusted-public-keys = [ ];
virtualisation.emptyDiskImages = [ 512 ];
virtualisation.diskSize = 8 * 1024;
virtualisation.rootDevice = "/dev/vdb";
# both installer and target need to use the same diskImage
virtualisation.diskImage = "./target.qcow2";
virtualisation.memorySize = 3048;
users.users.nonrootuser = {
isNormalUser = true;
openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
extraGroups = [ "wheel" ];
};
users.users.root.openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
# Allow users to manage their own SSH keys
services.openssh.authorizedKeysFiles = [
"/root/.ssh/authorized_keys"
"/home/%u/.ssh/authorized_keys"
"/etc/ssh/authorized_keys.d/%u"
];
security.sudo.wheelNeedsPassword = false;
};
# Common base test machine configuration
baseTestMachine =
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
self.clanLib.test.minifyModule
];
# Enable SSH and add authorized key for testing
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
users.users.nonrootuser = {
isNormalUser = true;
openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
extraGroups = [ "wheel" ];
home = "/home/nonrootuser";
createHome = true;
};
users.users.root.openssh.authorizedKeys.keys = [ (builtins.readFile ../assets/ssh/pubkey) ];
# Allow users to manage their own SSH keys
services.openssh.authorizedKeysFiles = [
"/root/.ssh/authorized_keys"
"/home/%u/.ssh/authorized_keys"
"/etc/ssh/authorized_keys.d/%u"
];
security.sudo.wheelNeedsPassword = false;
boot.consoleLogLevel = lib.mkForce 100;
boot.kernelParams = [ "boot.shell_on_fail" ];
# disko config
boot.loader.grub.efiSupport = lib.mkDefault true;
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
clan.core.vars.settings.secretStore = "vm";
clan.core.vars.generators.test = {
files.test.neededFor = "partitioning";
script = ''
echo "notok" > "$out"/test
'';
};
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/vda";
preCreateHook = ''
test -e /run/partitioning-secrets/test/test
'';
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for grub MBR
priority = 1;
};
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
# NixOS test library combining port utils and clan VM test utilities
nixosTestLib = pkgs.python3Packages.buildPythonPackage {
pname = "nixos-test-lib";
version = "1.0.0";
format = "pyproject";
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./pyproject.toml
./nixos_test_lib
];
};
nativeBuildInputs = with pkgs.python3Packages; [
setuptools
wheel
];
doCheck = false;
};
# Common closure info
closureInfo = pkgs.closureInfo {
rootPaths = [
self.checks.x86_64-linux.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.toplevel
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.initialRamdisk
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.build.diskoScript
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-install-machine-with-system.config.system.clan.deployment.file
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
};
in
{
inherit
target
baseTestMachine
nixosTestLib
closureInfo
;
}

View File

@@ -35,7 +35,6 @@
pkgs.stdenv.drvPath
pkgs.stdenvNoCC
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
self.nixosConfigurations.test-morph-machine.config.system.clan.deployment.file
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in

View File

@@ -23,14 +23,14 @@ nixosLib.runTest (
clan.test.fromFlake = ./.;
extraPythonPackages = _p: [
clan-core.legacyPackages.${hostPkgs.system}.setupNixInNixPythonPackage
clan-core.legacyPackages.${hostPkgs.system}.nixosTestLib
];
testScript =
{ nodes, ... }:
''
from setup_nix_in_nix import setup_nix_in_nix # type: ignore[import-untyped]
setup_nix_in_nix()
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

View File

@@ -185,7 +185,6 @@ in
];
clan.core.vars.generators.borgbackup = {
files."borgbackup.ssh.pub".secret = false;
files."borgbackup.ssh" = { };
files."borgbackup.repokey" = { };
@@ -197,7 +196,7 @@ in
pkgs.xkcdpass
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/borgbackup.ssh
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/borgbackup.ssh
xkcdpass -n 4 -d - > "$out"/borgbackup.repokey
'';
};

View File

@@ -33,6 +33,7 @@ in
root-password = ./root-password;
single-disk = ./single-disk;
sshd = ./sshd;
state-version = ./state-version;
static-hosts = ./static-hosts;
sunshine = ./sunshine;
syncthing = ./syncthing;

View File

@@ -7,7 +7,7 @@ The importer module allows users to configure importing modules in a flexible an
It exposes the `extraModules` functionality of the inventory, without any added configuration.
## Usage:
## Usage
```nix
inventory.services = {

View File

@@ -54,7 +54,7 @@ in
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/ssh.id_ed25519
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
'';
};
@@ -74,7 +74,7 @@ in
pkgs.openssh
];
script = ''
ssh-keygen -t rsa -b 4096 -N "" -f "$out"/ssh.id_rsa
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
'';
};

View File

@@ -36,7 +36,7 @@
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/id_ed25519
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
'';
};

View File

@@ -0,0 +1,18 @@
---
description = "Automatically generate the state version of the nixos installation."
features = [ "inventory", "deprecated" ]
---
This module generates the `system.stateVersion` of the nixos installation automatically.
Options: [system.stateVersion](https://search.nixos.org/options?channel=unstable&show=system.stateVersion&from=0&size=50&sort=relevance&type=packages&query=stateVersion)
Migration:
If you are already setting `system.stateVersion`, then import the module and then either let the automatic generation happen, or trigger the generation manually for the machine. The module will take the specified version, if one is already supplied through the config.
To manually generate the version for a specified machine run:
```
clan vars generate [MACHINE]
```
If the setting was already set you can then remove `system.stateVersion` from your machine configuration. For new machines, just import the module.

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/default.nix ];
}

View File

@@ -0,0 +1,28 @@
{ config, lib, ... }:
let
var = config.clan.core.vars.generators.state-version.files.version or { };
in
{
warnings = [
''
The clan.state-version service is deprecated and will be
removed on 2025-07-15 in favor of a nix option.
Please migrate your configuration to use `clan.core.settings.state-version.enable = true` instead.
''
];
system.stateVersion = lib.mkDefault (lib.removeSuffix "\n" var.value);
clan.core.vars.generators.state-version = {
files.version = {
secret = false;
value = lib.mkDefault config.system.nixos.release;
};
runtimeInputs = [ ];
script = ''
echo -n ${config.system.stateVersion} > "$out"/version
'';
};
}

View File

@@ -256,7 +256,7 @@
pkgs.xkcdpass
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/borgbackup.ssh
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/borgbackup.ssh
xkcdpass -n 4 -d - > "$out"/borgbackup.repokey
'';
};

View File

@@ -41,14 +41,6 @@
clan-core,
...
}:
let
dependencies = [
clan-core
pkgs.stdenv.drvPath
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{
services.openssh.enable = true;
@@ -59,15 +51,6 @@
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
environment.etc.install-closure.source = "${closureInfo}/store-paths";
nix.settings = {
substituters = pkgs.lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = pkgs.lib.mkForce 3;
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
};
system.extraDependencies = dependencies;
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
};

View File

@@ -23,7 +23,13 @@ in
unit-test-module = (
self.clanLib.test.flakeModules.makeEvalChecks {
inherit module;
inherit self inputs;
inherit inputs;
fileset = lib.fileset.unions [
# The hello-world service being tested
../../clanServices/hello-world
# Required modules
../../nixosModules/clanCore
];
testName = "hello-world";
tests = ./tests/eval-tests.nix;
# Optional arguments passed to the test

View File

@@ -1,7 +1,7 @@
The importer module allows users to configure importing modules in a flexible and structured way.
It exposes the `extraModules` functionality of the inventory, without any added configuration.
## Usage:
## Usage
```nix
inventory.instances = {

View File

@@ -0,0 +1,36 @@
The `sshd` Clan service manages SSH to make it easy to securely access your machines over the internet. The service uses `vars` to store the SSH host keys for each machine to ensure they remain stable across deployments.
`sshd` also generates SSH certificates for both servers and clients allowing for certificate-based authentication for SSH.
The service also disables password-based authentication over SSH, to access your machines you'll need to use public key authentication or certificate-based authentication.
## Usage
```nix
{
inventory.instances = {
# By default this service only generates ed25519 host keys
sshd-basic = {
module = {
name = "sshd";
input = "clan-core";
};
roles.server.tags.all = { };
roles.client.tags.all = { };
};
# Also generate RSA host keys for all servers
sshd-with-rsa = {
module = {
name = "sshd";
input = "clan-core";
};
roles.server.tags.all = { };
roles.server.settings = {
hostKeys.rsa.enable = true;
};
roles.client.tags.all = { };
};
};
}
```

View File

@@ -2,7 +2,7 @@
{
_class = "clan.service";
manifest.name = "clan-core/sshd";
manifest.description = "Enables secure remote access to the machine over ssh.";
manifest.description = "Enables secure remote access to the machine over SSH";
manifest.categories = [
"System"
"Network"
@@ -49,7 +49,7 @@
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/id_ed25519
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
'';
};
@@ -109,7 +109,7 @@
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/id_ed25519
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519
'';
};
@@ -151,7 +151,7 @@
pkgs.openssh
];
script = ''
ssh-keygen -t rsa -b 4096 -N "" -f "$out"/ssh.id_rsa
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
'';
};
@@ -164,7 +164,7 @@
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f "$out"/ssh.id_ed25519
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
'';
};
};

View File

@@ -0,0 +1,37 @@
This service generates the `system.stateVersion` of the nixos installation
automatically.
Possible values:
[system.stateVersion](https://search.nixos.org/options?channel=unstable&show=system.stateVersion&from=0&size=50&sort=relevance&type=packages&query=stateVersion)
## Usage
The following configuration will set `stateVersion` for all machines:
```
inventory.instances = {
state-version = {
module = {
name = "state-version";
input = "clan";
};
roles.default.tags.all = { };
};
```
## Migration
If you are already setting `system.stateVersion`, either let the automatic
generation happen, or trigger the generation manually for the machine. The
service will take the specified version, if one is already supplied through the
config.
To manually generate the version for a specified machine run:
```
clan vars generate [MACHINE]
```
If the setting was already set, you can then remove `system.stateVersion` from
your machine configuration. For new machines, just import the service as shown
above.

View File

@@ -0,0 +1,49 @@
{ ... }:
{
_class = "clan.service";
manifest.name = "clan-core/state-version";
manifest.description = "Automatically generate the state version of the nixos installation.";
manifest.categories = [ "System" ];
roles.default = {
perInstance =
{ ... }:
{
nixosModule =
{
config,
lib,
...
}:
let
var = config.clan.core.vars.generators.state-version.files.version or { };
in
{
warnings = [
''
The clan.state-version service is deprecated and will be
removed on 2025-07-15 in favor of a nix option.
Please migrate your configuration to use `clan.core.settings.state-version.enable = true` instead.
''
];
system.stateVersion = lib.mkDefault (lib.removeSuffix "\n" var.value);
clan.core.vars.generators.state-version = {
files.version = {
secret = false;
value = lib.mkDefault config.system.nixos.release;
};
runtimeInputs = [ ];
script = ''
echo -n ${config.system.stateVersion} > "$out"/version
'';
};
};
};
};
}

View File

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

View File

@@ -0,0 +1,22 @@
{ lib, ... }:
{
name = "service-state-version";
clan = {
directory = ./.;
inventory = {
machines.server = { };
instances.default = {
module.name = "@clan/state-version";
module.input = "self";
roles.default.machines."server" = { };
};
};
};
nodes.server = { };
testScript = lib.mkDefault ''
start_all()
'';
}

View File

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

View File

@@ -1,30 +1,31 @@
## Usage
```
inventory.instances = {
# Deploy user alice on all machines. Don't prompt for password (will be
# auto-generated).
user-alice = {
module = {
name = "users";
input = "clan";
```nix
{
inventory.instances = {
# Deploy user alice on all machines. Don't prompt for password (will be
# auto-generated).
user-alice = {
module = {
name = "users";
input = "clan";
};
roles.default.tags.all = { };
roles.default.settings = {
user = "alice";
prompt = false;
};
};
roles.default.tags.all = { };
roles.default.settings = {
user = "alice";
prompt = false;
# Deploy user bob only on his laptop. Prompt for a password.
user-bob = {
module = {
name = "users";
input = "clan";
};
roles.default.machines.bobs-laptop = { };
roles.default.settings.user = "bob";
};
};
# Deploy user bob only on his laptop. Prompt for a password.
user-bob = {
module = {
name = "users";
input = "clan";
};
roles.default.machines.bobs-laptop = { };
roles.default.settings.user = "bob";
};
}
```

View File

@@ -73,9 +73,10 @@ in
];
networking.networkmanager.ensureProfiles.profiles = flip mapAttrs settings.networks (
name: _network: {
name: networkCfg: {
connection.id = "$ssid_${name}";
connection.type = "wifi";
connection.autoconnect = networkCfg.autoConnect;
wifi.mode = "infrastructure";
wifi.ssid = "$ssid_${name}";
wifi-security.psk = "$pw_${name}";
@@ -102,7 +103,7 @@ in
# Generate the secrets file
echo "Generating wifi secrets file: $env_file"
${flip (concatMapAttrsStringSep "\n") settings.networks (
name: _network: ''
name: _networkCfg: ''
echo "ssid_${name}=\"$(cat "${ssid_path name}")\"" >> /run/secrets/NetworkManager/wifi-secrets
echo "pw_${name}=\"$(cat "${password_path name}")\"" >> /run/secrets/NetworkManager/wifi-secrets
''

View File

@@ -15,7 +15,15 @@ in
unit-test-module = (
self.clanLib.test.flakeModules.makeEvalChecks {
inherit module;
inherit self inputs;
inherit inputs;
fileset = lib.fileset.unions [
# The zerotier service being tested
../../clanServices/zerotier
# Required modules
../../nixosModules/clanCore
# Dependencies like clan-cli
../../pkgs/clan-cli
];
testName = "zerotier";
tests = ./tests/eval-tests.nix;
testArgs = { };

1
devFlake/private.narHash Normal file
View File

@@ -0,0 +1 @@
sha256-pFUj3KhQ4FkzZT19t+FHBru8u8Lspax0rS2cv7nXIgM=

165
devFlake/private/flake.lock generated Normal file
View File

@@ -0,0 +1,165 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"ixx": {
"inputs": {
"flake-utils": [
"nuschtos",
"flake-utils"
],
"nixpkgs": [
"nuschtos",
"nixpkgs"
]
},
"locked": {
"lastModified": 1748294338,
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
"owner": "NuschtOS",
"repo": "ixx",
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"ref": "v0.0.8",
"repo": "ixx",
"type": "github"
}
},
"nixpkgs-dev": {
"locked": {
"lastModified": 1751867001,
"narHash": "sha256-3I49W0s3WVEDBO5S1RxYr74E2LLG7X8Wuvj9AmU0RDk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "73feb5e20ec7259e280ca6f424ba165059b3bb6b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nuschtos": {
"inputs": {
"flake-utils": "flake-utils_2",
"ixx": "ixx",
"nixpkgs": [
"nixpkgs-dev"
]
},
"locked": {
"lastModified": 1749730855,
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
"owner": "NuschtOS",
"repo": "search",
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"repo": "search",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs-dev": "nixpkgs-dev",
"nuschtos": "nuschtos",
"systems": "systems_2",
"treefmt-nix": "treefmt-nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": []
},
"locked": {
"lastModified": 1750931469,
"narHash": "sha256-0IEdQB1nS+uViQw4k3VGUXntjkDp7aAlqcxdewb/hAc=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "ac8e6f32e11e9c7f153823abc3ab007f2a65d3e1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,19 @@
{
description = "private dev inputs";
# Dev dependencies
inputs.nixpkgs-dev.url = "github:NixOS/nixpkgs/nixos-unstable-small";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.flake-utils.inputs.systems.follows = "systems";
inputs.nuschtos.url = "github:NuschtOS/search";
inputs.nuschtos.inputs.nixpkgs.follows = "nixpkgs-dev";
inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
inputs.treefmt-nix.inputs.nixpkgs.follows = "";
inputs.systems.url = "github:nix-systems/default";
outputs = _: { };
}

12
devFlake/update-private-narhash Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Used to update the private dev flake hash reference.
set -euo pipefail
cd "$(dirname "$0")"
echo "Updating $PWD/private.narHash" >&2
nix --extra-experimental-features 'flakes nix-command' flake lock ./private
nix --extra-experimental-features 'flakes nix-command' hash path ./private >./private.narHash
echo OK

1
docs/.gitignore vendored
View File

@@ -1,4 +1,5 @@
/site/reference
/site/static
/site/options-page
/site/openapi.json
!/site/static/extra.css

View File

@@ -48,13 +48,13 @@ nav:
- Home: index.md
- Guides:
- Getting Started:
- Creating Your First Clan: guides/getting-started/index.md
- Create USB Installer (optional): guides/getting-started/installer.md
- Add Machines: guides/getting-started/add-machines.md
- Add Services: guides/getting-started/add-services.md
- Secrets & Facts: guides/getting-started/secrets.md
- Deploy Machine: guides/getting-started/deploy.md
- Continuous Integration: guides/getting-started/check.md
- 🚀 Creating Your First Clan: guides/getting-started/index.md
- 📀 Create USB Installer (optional): guides/getting-started/installer.md
- ⚙️ Add Machines: guides/getting-started/add-machines.md
- ⚙️ Add Services: guides/getting-started/add-services.md
- 🔐 Secrets & Facts: guides/getting-started/secrets.md
- 🚢 Deploy Machine: guides/getting-started/deploy.md
- 🧪 Continuous Integration: guides/getting-started/check.md
- clanServices: guides/clanServices.md
- Disk Encryption: guides/disk-encryption.md
- Mesh VPN: guides/mesh-vpn.md
@@ -62,6 +62,7 @@ nav:
- Vars Backend: guides/vars-backend.md
- Facts Backend: guides/secrets.md
- Adding more machines: guides/more-machines.md
- Target Host: guides/target-host.md
- Inventory:
- Inventory: guides/inventory.md
- Secure Boot: guides/secure-boot.md
@@ -92,6 +93,7 @@ nav:
- reference/clanServices/mycelium.md
- reference/clanServices/packages.md
- reference/clanServices/sshd.md
- reference/clanServices/state-version.md
- reference/clanServices/trusted-nix-caches.md
- reference/clanServices/users.md
- reference/clanServices/wifi.md
@@ -126,6 +128,7 @@ nav:
- reference/clanModules/root-password.md
- reference/clanModules/single-disk.md
- reference/clanModules/sshd.md
- reference/clanModules/state-version.md
- reference/clanModules/static-hosts.md
- reference/clanModules/sunshine.md
- reference/clanModules/syncthing-static-peers.md
@@ -152,6 +155,7 @@ nav:
- reference/cli/show.md
- reference/cli/ssh.md
- reference/cli/state.md
- reference/cli/templates.md
- reference/cli/vars.md
- reference/cli/vms.md
- NixOS Modules:
@@ -179,6 +183,9 @@ nav:
- 05-deployment-parameters: decisions/05-deployment-parameters.md
- Template: decisions/_template.md
- Options: options.md
- Developer:
- Introduction: intern/index.md
- API: intern/api.md
docs_dir: site
site_dir: out
@@ -192,7 +199,6 @@ theme:
- navigation.instant
- navigation.tabs
- navigation.tabs.sticky
- navigation.footer
- content.code.annotate
- content.code.copy
- content.tabs.link
@@ -236,3 +242,4 @@ extra:
plugins:
- search
- macros
- redoc-tag

View File

@@ -1,8 +1,8 @@
{
clan-core,
pkgs,
module-docs,
clan-cli-docs,
clan-lib-openapi,
asciinema-player-js,
asciinema-player-css,
roboto,
@@ -18,7 +18,17 @@ pkgs.stdenv.mkDerivation {
# Points to repository root.
# so that we can access directories outside of docs to include code snippets
src = clan-core;
src = pkgs.lib.fileset.toSource {
root = ../..;
fileset = pkgs.lib.fileset.unions [
# Docs directory
../../docs
# Icons needed for the build
../../pkgs/clan-app/ui/icons
# Any other directories that might be referenced for code snippets
# Add them here as needed based on what mkdocs actually uses
];
};
nativeBuildInputs =
[
@@ -29,6 +39,7 @@ pkgs.stdenv.mkDerivation {
mkdocs
mkdocs-material
mkdocs-macros
mkdocs-redoc-tag
]);
configurePhase = ''
pushd docs
@@ -36,6 +47,10 @@ pkgs.stdenv.mkDerivation {
mkdir -p ./site/reference/cli
cp -af ${module-docs}/* ./site/reference/
cp -af ${clan-cli-docs}/* ./site/reference/cli/
mkdir -p ./site/reference/internal
cp -af ${clan-lib-openapi} ./site/openapi.json
chmod -R +w ./site/reference
echo "Generated API documentation in './site/reference/' "

View File

@@ -82,10 +82,9 @@
}
''
export CLAN_CORE_PATH=${
self.filter {
include = [
"clanModules"
];
inputs.nixpkgs.lib.fileset.toSource {
root = ../..;
fileset = ../../clanModules;
}
}
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
@@ -126,8 +125,12 @@
});
packages = {
docs = pkgs.python3.pkgs.callPackage ./default.nix {
clan-core = self;
inherit (self'.packages) clan-cli-docs docs-options inventory-api-docs;
inherit (self'.packages)
clan-cli-docs
docs-options
inventory-api-docs
clan-lib-openapi
;
inherit (inputs) nixpkgs;
inherit module-docs;
inherit asciinema-player-js;

View File

@@ -1,9 +1,15 @@
{ self, config, ... }:
{
self,
config,
inputs,
privateInputs ? { },
...
}:
{
perSystem =
{
inputs',
lib,
pkgs,
...
}:
let
@@ -157,11 +163,16 @@
};
in
{
packages.docs-options = inputs'.nuschtos.packages.mkMultiSearch {
inherit baseHref;
title = "Clan Options";
# scopes = mapAttrsToList mkScope serviceModules;
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
packages = lib.optionalAttrs ((privateInputs ? nuschtos) || (inputs ? nuschtos)) {
docs-options =
(privateInputs.nuschtos or inputs.nuschtos)
.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch
{
inherit baseHref;
title = "Clan Options";
# scopes = mapAttrsToList mkScope serviceModules;
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
};
};
};
}

View File

@@ -452,7 +452,6 @@ Each `clanService`:
* Is a module of class **`clan.service`**
* Can define **roles** (e.g., `client`, `server`)
* Uses **`inventory.instances`** to configure where and how it is deployed
* Replaces the legacy `clanModules` and `inventory.services` system altogether
!!! Note
`clanServices` are part of Clan's next-generation service model and are intended to replace `clanModules`.

View File

@@ -28,7 +28,7 @@ Benefits:
* Caching mechanism is very simple.
### Method 2: Direct access:
### Method 2: Direct access
Directly calling the evaluator / build sandbox via `nix build` and `nix eval`within the Python code

View File

@@ -52,6 +52,7 @@ clanModules/borgbackup
```nix title="flake.nix"
# ...
# Sometimes this attribute set is defined in clan.nix
clan-core.lib.clan {
# 1. Add the module to the available clanModules with inventory support
inventory.modules = {
@@ -175,6 +176,7 @@ The following shows how to add options to your module.
Configuration can be set as follows.
```nix title="flake.nix"
# Sometimes this attribute set is defined in clan.nix
clan-core.lib.clan {
inventory.services = {
custom-module.instance_1 = {

View File

@@ -27,6 +27,7 @@ i.e. `@hsjobeki/customNetworking`
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } ({
imports = [ inputs.clan-core.flakeModules.default ];
# ...
# Sometimes this attribute set is defined in clan.nix
clan = {
# If needed: Exporting the module for other people
modules."@hsjobeki/customNetworking" = import ./service-modules/networking.nix;
@@ -218,6 +219,7 @@ To import the module use `importApply`
outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({self, lib, ...}: {
imports = [ inputs.clan-core.flakeModules.default ];
# ...
# Sometimes this attribute set is defined in clan.nix
clan = {
# Register the module
modules."@hsjobeki/messaging" = lib.importApply ./service-modules/messaging.nix { inherit self; };
@@ -244,6 +246,7 @@ Then wrap the module and forward the variable `self` from the outer context into
outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } ({self, lib, ...}: {
imports = [ inputs.clan-core.flakeModules.default ];
# ...
# Sometimes this attribute set is defined in clan.nix
clan = {
# Register the module
modules."@hsjobeki/messaging" = {

View File

@@ -17,8 +17,10 @@ For example:
```nix
inventory.instances = {
borgbackup = {
roles.client.machines = [ "laptop" "server1" ];
roles.server.machines = [ "backup-box" ];
roles.client.machines."laptop" = {};
roles.client.machines."server1" = {};
roles.server.machines."backup-box" = {};
};
}
```
@@ -40,7 +42,8 @@ Example of instantiating a `borgbackup` service using `clan-core`:
```nix
inventory.instances = {
# Instance Name: Different name for this 'borgbackup' instance
borgbackup-example = {
borgbackup = {
# Since this is instances."borgbackup" the whole `module = { ... }` below is equivalent and optional.
module = {
name = "borgbackup"; # <-- Name of the module (optional)
input = "clan-core"; # <-- The flake input where the service is defined (optional)

View File

@@ -105,7 +105,7 @@ git+file:///home/lhebendanz/Projects/clan-core
│ ├───editor omitted (use '--all-systems' to show)
└───templates
├───default: template: Initialize a new clan flake
└───new-clan: template: Initialize a new clan flake
└───default: template: Initialize a new clan flake
```
You can execute every test separately by following the tree path `nix run .#checks.x86_64-linux.clan-pytest -L` for example.

View File

@@ -63,8 +63,7 @@ Replace `kernelModules` with the ethernet module loaded one on your target machi
}
```
### Step 1: Copying SSH Public Key
## Copying SSH Public Key
Before starting the installation process, ensure that the SSH public key is copied to the NixOS installer.
@@ -74,7 +73,7 @@ Before starting the installation process, ensure that the SSH public key is copi
ssh-copy-id -o PreferredAuthentications=password -o PubkeyAuthentication=no root@nixos-installer.local
```
### Step 1.5: Prepare Secret Key and Partition Disks
## Prepare Secret Key and Partition Disks
1. Access the installer using SSH:
@@ -100,7 +99,7 @@ blkdiscard /dev/disk/by-id/<installdisk>
clan machines install gchq-local --target-host root@nixos-installer --phases kexec,disko
```
### Step 2: ZFS Pool Import and System Installation
## ZFS Pool Import and System Installation
1. SSH into the installer once again:
@@ -123,8 +122,8 @@ CTRL+D
4. Locally generate ssh host keys. You only need to generate ones for the algorithms you're using in `authorizedKeys`.
```bash
ssh-keygen -q -N "" -t ed25519 -f ./initrd_host_ed25519_key
ssh-keygen -q -N "" -t rsa -b 4096 -f ./initrd_host_rsa_key
ssh-keygen -q -N "" -C "" -t ed25519 -f ./initrd_host_ed25519_key
ssh-keygen -q -N "" -C "" -t rsa -b 4096 -f ./initrd_host_rsa_key
```
5. Securely copy your local initrd ssh host keys to the installer's `/mnt` directory:
@@ -151,7 +150,7 @@ zpool export zroot
8. Perform a reboot of the machine and remove the USB installer.
### Step 3: Accessing the Initial Ramdisk (initrd) Environment
## Accessing the Initial Ramdisk (initrd) Environment
1. SSH into the initrd environment using the `initrd_rsa_key` and provided port:

View File

@@ -3,7 +3,7 @@ Clan supports integration with [flake-parts](https://flake.parts/), a framework
To construct your Clan using flake-parts, follow these steps:
## 1. Update Your Flake Inputs
## Update Your Flake Inputs
To begin, you'll need to add `flake-parts` as a new dependency in your flake's inputs. This is alongside the already existing dependencies, such as `clan-core` and `nixpkgs`. Here's how you can update your `flake.nix` file:
@@ -25,7 +25,7 @@ inputs = {
}
```
## 2. Import the Clan flake-parts Module
## Import the Clan flake-parts Module
After updating your flake inputs, the next step is to import the Clan flake-parts module. This will make the [Clan options](../reference/nix-api/clan.md) available within `mkFlake`.
@@ -43,7 +43,7 @@ After updating your flake inputs, the next step is to import the Clan flake-part
}
```
### 3. Configure Clan Settings and Define Machines
## Configure Clan Settings and Define Machines
Next you'll need to configure Clan wide settings and define machines, here's an example of how `flake.nix` should look:
@@ -91,6 +91,6 @@ Next you'll need to configure Clan wide settings and define machines, here's an
```
For detailed information about configuring `flake-parts` and the available options within Clan,
refer to the Clan module documentation located [here](https://git.clan.lol/clan/clan-core/src/branch/main/flakeModules/clan.nix).
refer to the [Clan module](https://git.clan.lol/clan/clan-core/src/branch/main/flakeModules/clan.nix) documentation.
---

View File

@@ -90,6 +90,7 @@ See the complete [list](../../guides/more-machines.md#automatic-registration) of
The option: `machines.<name>` is used to add extra *nixosConfiguration* to a machine
```{.nix .annotate title="flake.nix" hl_lines="3-13 18-22"}
# Sometimes this attribute set is defined in clan.nix
clan = {
inventory.machines = {
jon = {
@@ -119,26 +120,34 @@ clan = {
1. It is required to define a *targetHost* for each machine before deploying. Best practice has been, to use the zerotier ip/hostname or the ip from the from overlay network you decided to use.
2. Add your *ssh key* here - That will ensure you can always login to your machine via *ssh* in case something goes wrong.
### (Optional): Renaming Machine
### (Optional) Renaming a Machine
For renaming jon to your own machine name, you can use the following command:
Older templates included static machine folders like `jon` and `sara`.
If your setup still uses such static machines, you can rename a machine folder to match your own machine name:
```
git mv ./machines/jon ./machines/newname
```bash
git mv ./machines/jon ./machines/<your-machine-name>
```
Note that our clan lives inside a git repository.
Only files that have been added with `git add` are recognized by `nix`.
So for every file that you add or rename you also need to run:
Since your Clan configuration lives inside a Git repository, remember:
```
git add ./path/to/my/file
* Only files tracked by Git (`git add`) are recognized.
* Whenever you add, rename, or remove files, run:
```bash
git add ./machines/<your-machine-name>
```
### (Optional): Removing a Machine
to stage the changes.
If you only want to setup a single machine at this point, you can delete `sara` from `flake.nix` as well as from the machines directory:
---
```
### (Optional) Removing a Machine
If you want to work with a single machine for now, you can remove other machine entries both from your `flake.nix` and from the `machines` directory. For example, to remove the machine `sara`:
```bash
git rm -rf ./machines/sara
```
Make sure to also remove or update any references to that machine in your `nix files` or `inventory.json` if you have any of that

View File

@@ -28,6 +28,7 @@ To learn more: [Guide about clanService](../clanServices.md)
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.clan-core.flakeModules.default ];
# Sometimes this attribute set is defined in clan.nix
clan = {
inventory.machines = {
jon = {
@@ -76,6 +77,7 @@ Adding the following services is recommended for most users:
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.clan-core.flakeModules.default ];
# Sometimes this attribute set is defined in clan.nix
clan = {
inventory.machines = {
jon = {

View File

@@ -27,7 +27,7 @@ Now that you have created a new machine, we will walk through how to install it.
!!! Warning "NixOS can cause strange issues when booting in certain cloud environments."
If on Linode: Make sure that the system uses Direct Disk boot kernel (found in the configuration pannel)
### Step 1. Setting `targetHost`
## Setting `targetHost`
=== "flake.nix (flake-parts)"
@@ -98,7 +98,7 @@ Now that you have created a new machine, we will walk through how to install it.
The use of `root@` in the target address implies SSH access as the `root` user.
Ensure that the root login is secured and only used when necessary.
### Step 2. Identify the Target Disk
## Identify the Target Disk
On the setup computer, SSH into the target:
@@ -129,7 +129,7 @@ In this example we would copy `nvme-eui.e8238fa6bf530001001b448b4aec2929`
!!! tip
For advanced partitioning, see [Disko templates](https://github.com/nix-community/disko-templates) or [Disko examples](https://github.com/nix-community/disko/tree/master/example).
### Step 3. Fill in hardware specific machine configuration
## Fill in hardware specific machine configuration
Edit the following fields inside the `./machines/<machine_name>/configuration.nix`
@@ -164,7 +164,7 @@ Edit the following fields inside the `./machines/<machine_name>/configuration.ni
!!! Info "Replace `__CHANGE_ME__` with the appropriate `ID-LINK` identifier, such as `nvme-eui.e8238fa6bf530001001b448b4aec2929`"
!!! Info "Replace `__YOUR_SSH_KEY__` with your personal key, like `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoMI0NC5eT9pHlQExrvR5ASV3iW9+BXwhfchq0smXUJ jon@jon-desktop`"
### Step 4. Deploy the machine
## Deploy the machine
**Finally deployment time!** Use the following command to build and deploy the image via SSH onto your machine.
@@ -227,7 +227,7 @@ Edit the following fields inside the `./machines/<machine_name>/configuration.ni
```
2. The root password for the installer medium.
This password is autogenerated and meant to be easily typeable.
3. See how to connect the installer medium to wlan [here](./installer.md#optional-connect-to-wifi-manually).
3. See [how to connect to wlan](./installer.md#optional-connect-to-wifi-manually).
!!! tip
Use [KDE Connect](https://apps.kde.org/de/kdeconnect/) for easyily sharing QR codes from phone to desktop
@@ -236,21 +236,21 @@ Edit the following fields inside the `./machines/<machine_name>/configuration.ni
Just run the command **Option B: Cloud VM** below
#### Deployment Commands
### Deployment Commands
##### Using password auth
#### Using password auth
```bash
clan machines install [MACHINE] --target-host <IP> --update-hardware-config nixos-facter
```
##### Using QR JSON
#### Using QR JSON
```bash
clan machines install [MACHINE] --json "[JSON]" --update-hardware-config nixos-facter
```
##### Using QR image file
#### Using QR image file
```bash
clan machines install [MACHINE] --png [PATH] --update-hardware-config nixos-facter

View File

@@ -4,8 +4,7 @@ Ready to create your own Clan and manage a fleet of machines? Follow these simpl
By the end of this guide, you'll have a fresh NixOS configuration ready to push to one or more machines. You'll create a new Git repository and a flake, and all you need is at least one machine to push to. This is the easiest way to begin, and we recommend you to copy your existing configuration into this new setup!
### Prerequisites
## Prerequisites
=== "**Linux**"
@@ -37,22 +36,23 @@ By the end of this guide, you'll have a fresh NixOS configuration ready to push
If you have previously installed Nix, make sure `experimental-features = nix-command flakes` is present in `~/.config/nix/nix.conf` or `/etc/nix/nix.conf`. If this is not the case, please add it to `~/.config/nix/nix.conf`.
### Step 1: Add Clan CLI to Your Shell
## Add Clan CLI to Your Shell
Add the Clan CLI into your development workflow:
Add the Clan CLI into your environment:
```bash
nix shell git+https://git.clan.lol/clan/clan-core#clan-cli --refresh
```
You can find reference documentation for the `clan` CLI program [here](../../reference/cli/index.md).
Alternatively you can check out the help pages directly:
```terminalSession
clan --help
```
### Step 2: Initialize Your Project
Should print the avilable commands.
Also checkout the [cli-reference documentation](../../reference/cli/index.md).
## Initialize Your Project
If you want to migrate an existing project, follow this [guide](../migrations/migration-guide.md).
@@ -62,36 +62,29 @@ Set the foundation of your Clan project by initializing it by running:
clan flakes create my-clan
```
This command creates the `flake.nix` and `.clan-flake` files for your project.
It will also generate files from a default template, to help show general clan usage patterns.
This command creates a `flake.nix` and some other files for your project.
### Step 3: Verify the Project Structure
## Explore the Project Structure
Ensure that all project files exist by running:
Take a lookg at all project files:
```bash
cd my-clan
tree
```
This should yield the following:
For example, you might see something like:
``` { .console .no-copy }
.
├── flake.nix
├── machines
│   ├── jon
│   │   ├── configuration.nix
│   │   └── hardware-configuration.nix
│   └── sara
│   ├── configuration.nix
│   └── hardware-configuration.nix
└── modules
└── shared.nix
5 directories, 9 files
├── machines/
├── modules/
└── README.md
```
Dont worry if your output looks different—the template evolves over time.
??? info "Recommended way of sourcing the `clan` CLI tool"
The default template adds the `clan` CLI tool to the development shell.
@@ -109,17 +102,28 @@ This should yield the following:
To automatically add the `clan` CLI tool to your environment without having to
run `nix develop` every time, we recommend setting up [direnv](https://direnv.net/).
```bash
clan machines list
```
clan show
```
``` { .console .no-copy }
jon
sara
You should see something like this:
```terminal-session
Name: my-clan
Description: None
```
!!! success
---
You just successfully bootstrapped your first Clan.
## Next Steps
You can continue with **any** of the following steps at your own pace:
- [x] [Install Nix & Clan CLI](./index.md)
- [x] [Initialize Clan](./index.md#initialize-your-project)
- [ ] [Create USB Installer (optional)](./installer.md)
- [ ] [Add Machines](./add-machines.md)
- [ ] [Add Services](./add-services.md)
- [ ] [Configure Secrets](./secrets.md)
- [ ] [Deploy](./deploy.md) - Requires configured secrets
- [ ] [Setup CI (optional)](./check.md)

View File

@@ -11,13 +11,12 @@ To install Clan on physical machines, you need to use our custom installer image
??? info "Reasons for a Custom Install Image"
Our custom install images are built to include essential tools like [nixos-facter](https://github.com/nix-community/nixos-facter) and support for [ZFS](https://wiki.archlinux.org/title/ZFS). They're also optimized to run on systems with as little as 1 GB of RAM, ensuring efficient performance even on lower-end hardware.
### Step 0. Prerequisites
## Prerequisites
- [x] A free USB Drive with at least 1.5GB (All data on it will be lost)
- [x] Linux/NixOS Machine with Internet
### Step 1. Identify the USB Flash Drive
## Identify the USB Flash Drive
1. Insert your USB flash drive into your computer.
@@ -45,7 +44,7 @@ To install Clan on physical machines, you need to use our custom installer image
sudo umount /dev/sdb1
```
### Step 2. Installer
## Installer
=== "**Linux OS**"
**Create a Custom Installer**
@@ -118,7 +117,7 @@ sudo umount /dev/sdb1
!!! Note
If you don't have `wget` installed, you can use `curl --progress-bar -OL <url>` instead.
### Step 2.5 Flash the Installer to the USB Drive
## Flash the Installer to the USB Drive
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
@@ -151,11 +150,10 @@ sudo umount /dev/sdb1
If you need to configure Wi-Fi first, refer to the next section.
If Multicast-DNS (Avahi) is enabled on your own machine, you can also access the installer using the `nixos-installer.local` address.
## Boot From USB Stick
### Step 3: Boot From USB Stick
- To use, boot from the Clan USB drive with **secure boot turned off**. For step by step instructions go to [Disabling Secure Boot](../../guides/secure-boot.md)
## (Optional) Connect to Wifi Manually
If you don't have access via LAN the Installer offers support for connecting via Wifi.
@@ -203,4 +201,3 @@ Press ++ctrl+d++ to exit `IWD`.
Press ++ctrl+d++ **again** to update the displayed QR code and connection information.
You're all set up

View File

@@ -52,65 +52,6 @@ For more information see the [SOPS] guide on [encrypting with age].
!!! note
It's safe to add any secrets created by the clan CLI and placed in your repository to version control systems like `git`.
### Using Age Plugins
If you wish to use a key generated using an [age plugin] as your admin key, extra care is needed.
You must **precede your secret key with a comment that contains its corresponding recipient**.
This is usually output as part of the generation process
and is only required because there is no unified mechanism for recovering a recipient from a plugin secret key.
Here is an example:
```title="~/.config/sops/age/keys.txt"
# public key: age1zdy49ek6z60q9r34vf5mmzkx6u43pr9haqdh5lqdg7fh5tpwlfwqea356l
AGE-PLUGIN-FIDO2-HMAC-1QQPQZRFR7ZZ2WCV...
```
!!! note
The comment that precedes the plugin secret key need only contain the recipient.
Any other text is ignored.
In the example above, you can specify `# recipient: age1zdy...`, `# public: age1zdy....` or even
just `# age1zdy....`
You will need to add an entry into your `flake.nix` to ensure that the necessary `age` plugins
are loaded when using Clan:
```nix title="flake.nix"
{
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
inputs.nixpkgs.follows = "clan-core/nixpkgs";
outputs =
{ self, clan-core, ... }:
let
clan = clan-core.clanLib.clan {
inherit self;
meta.name = "myclan";
# Add Yubikey and FIDO2 HMAC plugins
# Note: the plugins listed here must be available in nixpkgs.
secrets.age.plugins = [
"age-plugin-yubikey"
"age-plugin-fido2-hmac"
];
machines = {
# elided for brevity
};
};
in
{
inherit (clan) nixosConfigurations nixosModules clanInternals;
# elided for brevity
};
}
```
### Add Your Public Key(s)
```console
@@ -176,3 +117,63 @@ clan secrets users remove-key $USER --age-key <your_public_key>
[age plugin]: https://github.com/FiloSottile/awesome-age?tab=readme-ov-file#plugins
[sops]: https://github.com/getsops/sops
[encrypting with age]: https://github.com/getsops/sops?tab=readme-ov-file#encrypting-using-age
## Further: Using Age Plugins
If you wish to use a key generated using an [age plugin] as your admin key, extra care is needed.
You must **precede your secret key with a comment that contains its corresponding recipient**.
This is usually output as part of the generation process
and is only required because there is no unified mechanism for recovering a recipient from a plugin secret key.
Here is an example:
```title="~/.config/sops/age/keys.txt"
# public key: age1zdy49ek6z60q9r34vf5mmzkx6u43pr9haqdh5lqdg7fh5tpwlfwqea356l
AGE-PLUGIN-FIDO2-HMAC-1QQPQZRFR7ZZ2WCV...
```
!!! note
The comment that precedes the plugin secret key need only contain the recipient.
Any other text is ignored.
In the example above, you can specify `# recipient: age1zdy...`, `# public: age1zdy....` or even
just `# age1zdy....`
You will need to add an entry into your `flake.nix` to ensure that the necessary `age` plugins
are loaded when using Clan:
```nix title="flake.nix"
{
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
inputs.nixpkgs.follows = "clan-core/nixpkgs";
outputs =
{ self, clan-core, ... }:
let
# Sometimes this attribute set is defined in clan.nix
clan = clan-core.lib.clan {
inherit self;
meta.name = "myclan";
# Add Yubikey and FIDO2 HMAC plugins
# Note: the plugins listed here must be available in nixpkgs.
secrets.age.plugins = [
"age-plugin-yubikey"
"age-plugin-fido2-hmac"
];
machines = {
# elided for brevity
};
};
in
{
inherit (clan) nixosConfigurations nixosModules clanInternals;
# elided for brevity
};
}
```

View File

@@ -121,16 +121,3 @@ It is possible to add services to multiple machines via tags as shown
};
}
```
### API specification
**The complete schema specification is available [here](../reference/nix-api/inventory.md)**
Or it can build anytime via:
```sh
nix build git+https://git.clan.lol/clan/clan-core#schemas.inventory
> result
> ├── schema.cue
> └── schema.json
```

View File

@@ -9,7 +9,7 @@ Currently, Clan supports the following features for macOS:
- `clan machines update` for existing [nix-darwin](https://github.com/nix-darwin/nix-darwin) installations
- Support for [vars](../guides/vars-backend.md)
## Step 1: Add Your Machine to Your Clan Flake
## Add Your Machine to Your Clan Flake
In this example, we'll name the machine `yourmachine`. Replace this with your preferred machine name.
@@ -35,7 +35,7 @@ clan-core.lib.clan {
}
```
## Step 2: Add a `configuration.nix` for Your Machine
## Add a `configuration.nix` for Your Machine
Create the file `./machines/yourmachine/configuration.nix` with the following content (replace `yourmachine` with your chosen machine name):
@@ -48,7 +48,7 @@ Create the file `./machines/yourmachine/configuration.nix` with the following co
After creating the file, run `git add` to ensure Nix recognizes it.
## Step 3: Generate Vars (If Needed)
## Generate Vars (If Needed)
If your machine uses vars, generate them with:
@@ -58,12 +58,12 @@ clan vars generate yourmachine
Replace `yourmachine` with your chosen machine name.
## Step 4: Install Nix
## Install Nix
Install Nix on your macOS machine using one of the methods described in the [nix-darwin prerequisites](https://github.com/nix-darwin/nix-darwin?tab=readme-ov-file#prerequisites).
## Step 5: Install nix-darwin
## Install nix-darwin
Upload your Clan flake to the macOS machine. Then, from within your flake directory, run:
@@ -73,7 +73,7 @@ sudo nix run nix-darwin/master#darwin-rebuild -- switch --flake .#yourmachine
Replace `yourmachine` with your chosen machine name.
## Step 6: Manage Your Machine with Clan
## Manage Your Machine with Clan
Once all the steps above are complete, you can start managing your machine with:

View File

@@ -15,140 +15,87 @@ Clan
Node B
```
If you select multiple network technologies at the same time. e.g. (zerotier + yggdrassil)
You must choose one of them as primary network and the machines are always connected via the primary network.
This guide shows you how to configure `zerotier` through clan's `Inventory` System.
This guide shows you how to configure `zerotier` either through `NixOS Options` directly, or Clan's `Inventory` System.
## The Controller
The controller is the initial entrypoint for new machines into the vpn.
It will sign the id's of new machines.
Once id's are signed, the controller's continuous operation is not essential.
A good controller choice is nevertheless a machine that can always be reached for updates - so that new peers can be added to the network.
=== "**Inventory**"
## 1. Choose the Controller
For the purpose of this guide we have two machines:
The controller is the initial entrypoint for new machines into the vpn.
It will sign the id's of new machines.
Once id's are signed, the controller's continuous operation is not essential.
A good controller choice is nevertheless a machine that can always be reached for updates - so that new peers can be added to the network.
- The `controller` machine, which will be the zerotier controller.
- The `new_machine` machine, which is the machine we want to add to the vpn network.
For the purpose of this guide we have two machines:
## Configure the Service
- The `controller` machine, which will be the zerotier controller.
- The `new_machine` machine, which is the machine we want to add to the vpn network.
```nix {.nix title="flake.nix" hl_lines="19-25"}
{
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
inputs.nixpkgs.follows = "clan-core/nixpkgs";
## 2. Configure the Inventory
outputs =
{ self, clan-core, ... }:
let
# Sometimes this attribute set is defined in clan.nix
clan = clan-core.lib.clan {
inherit self;
Note: consider picking a more descriptive name for the VPN than "default".
It will be added as an altname for the Zerotier virtual ethernet interface, and
will also be visible in the Zerotier app.
meta.name = "myclan";
```nix
clan.inventory = {
services.zerotier.default = {
roles.controller.machines = [
"controller"
];
roles.peer.machines = [
"new_machine"
];
inventory.machines = {
controller = {};
new_machine = {};
};
inventory.instances = {
zerotier = {
# Assign the controller machine to the role "controller"
roles.controller.machines."controller" = {};
# All clan machines are zerotier peers
roles.peer.tags."all" = {};
};
};
};
in
{
inherit (clan) nixosConfigurations nixosModules clanInternals;
# elided for brevity
};
```
}
```
## 3. Apply the Configuration
Update the `controller` machine:
## Apply the Configuration
```bash
clan machines update controller
```
Update the `controller` machine first:
```bash
clan machines update controller
```
=== "**NixOS Options**"
## 1. Set-Up the VPN Controller
Then update all other peers:
The VPN controller is initially essential for providing configuration to new
peers. Once addresses are allocated, the controller's continuous operation is not essential.
```bash
clan machines update
```
1. **Designate a Machine**: Label a machine as the VPN controller in the clan,
referred to as `<CONTROLLER>` henceforth in this guide.
2. **Add Configuration**: Input the following configuration to the NixOS
configuration of the controller machine:
```nix
clan.core.networking.zerotier.controller = {
enable = true;
public = true;
};
```
3. **Update the Controller Machine**: Execute the following:
```bash
clan machines update <CONTROLLER>
```
Your machine is now operational as the VPN controller.
### Verify Connection
## 2. Add Machines to the VPN
On the `new_machine` run:
To introduce a new machine to the VPN, adhere to the following steps:
```bash
$ sudo zerotier-cli info
```
1. **Update Configuration**: On the new machine, incorporate the following to its
configuration, substituting `<CONTROLLER>` with the controller machine name:
```nix
{ config, ... }: {
clan.core.networking.zerotier.networkId = builtins.readFile ../../vars/per-machine/<CONTROLLER>/zerotier/zerotier-network-id/value;
}
```
1. **Update the New Machine**: Execute:
```bash
$ clan machines update <NEW_MACHINE>
```
Replace `<NEW_MACHINE>` with the designated new machine name.
The status should be "ONLINE":
!!! Note "For Private Networks"
1. **Retrieve Zerotier Metadata**
=== "From the repo"
**Retrieve the ZeroTier IP**: In the clan repo, execute:
```console
$ clan facts list <NEW_MACHINE> | jq -r '.["zerotier-ip"]'
```
The returned address is the Zerotier IP address of the machine.
=== "On the new machine"
**Retrieve the ZeroTier ID**: On the `new_machine`, execute:
```bash
$ sudo zerotier-cli info
```
Example Output:
```{.console, .no-copy}
200 info d2c71971db 1.12.1 OFFLINE
```
, where `d2c71971db` is the ZeroTier ID.
2. **Authorize the New Machine on the Controller**: On the controller machine,
execute:
=== "with ZerotierIP"
```bash
$ sudo zerotier-members allow --member-ip <IP>
```
Substitute `<IP>` with the ZeroTier IP obtained previously.
=== "with ZerotierID"
```bash
$ sudo zerotier-members allow <ID>
```
Substitute `<ID>` with the ZeroTier ID obtained previously.
2. **Verify Connection**: On the `new_machine`, re-execute:
```bash
$ sudo zerotier-cli info
```
The status should now be "ONLINE":
```{.console, .no-copy}
200 info d2c71971db 1.12.1 ONLINE
```
!!! success "Congratulations!"
The new machine is now part of the VPN, and the ZeroTier
configuration on NixOS within the Clan project is complete.
```{.console, .no-copy}
200 info d2c71971db 1.12.1 ONLINE
```
## Further
@@ -158,3 +105,45 @@ In the future we plan to add additional network technologies like tinc, head/tai
We chose zerotier because in our tests it was a straight forwards solution to bootstrap.
It allows you to selfhost a controller and the controller doesn't need to be globally reachable.
Which made it a good fit for starting the project.
## Debugging
### Retrieve the ZeroTier ID
In the repo:
```console
$ clan vars list <machineName>
```
```{.console, .no-copy}
$ clan vars list controller
# ... elided
zerotier/zerotier-identity-secret: ********
zerotier/zerotier-ip: fd0a:b849:2928:1234:c99:930a:a959:2928
zerotier/zerotier-network-id: 0aa959282834000c
```
On the machine:
```bash
$ sudo zerotier-cli info
```
#### Manually Authorize a Machine on the Controller
=== "with ZerotierIP"
```bash
$ sudo zerotier-members allow --member-ip <IP>
```
Substitute `<IP>` with the ZeroTier IP obtained previously.
=== "with ZerotierID"
```bash
$ sudo zerotier-members allow <ID>
```
Substitute `<ID>` with the ZeroTier ID obtained previously.

View File

@@ -74,9 +74,7 @@ instances = {
## Steps to Migrate
### 1. Move `services` entries to `instances`
### Move `services` entries to `instances`
Check if a service that you use has been migrated [In our reference](../../reference/clanServices/index.md)
@@ -96,7 +94,7 @@ Each nested service-instance-pair becomes a flat key, like `borgbackup.simple
---
### 2. Add `module.name` and `module.input`
### Add `module.name` and `module.input`
Each instance must declare the module name and flake input it comes from:
@@ -117,7 +115,7 @@ Then refer to it as `input = "clan-core"`.
---
### 3. Move role and machine config under `roles`
### Move role and machine config under `roles`
In the new system:

View File

@@ -1,9 +1,11 @@
At the moment, NixOS/Clan does not support [Secure Boot](https://wiki.gentoo.org/wiki/Secure_Boot). Therefore, you need to disable it in the BIOS. You can watch this [video guide](https://www.youtube.com/watch?v=BKVShiMUePc) or follow the instructions below:
### Step 1: Insert the USB Stick
## Insert the USB Stick
- Begin by inserting the USB stick into a USB port on your computer.
### Step 2: Access the UEFI/BIOS Menu
## Access the UEFI/BIOS Menu
- Restart your computer.
- As your computer restarts, press the appropriate key to enter the UEFI/BIOS settings.
??? tip "The key depends on your laptop or motherboard manufacturer. Click to see a reference list:"
@@ -32,18 +34,22 @@ At the moment, NixOS/Clan does not support [Secure Boot](https://wiki.gentoo.org
!!! Note
Pressing the key quickly and repeatedly is sometimes necessary to access the UEFI/BIOS menu, as the window to enter this mode is brief.
### Step 3: Access Advanced Mode (Optional)
## Access Advanced Mode (Optional)
- If your UEFI/BIOS has a `Simple` or `Easy` mode interface, look for an option labeled `Advanced Mode` (often found in the lower right corner).
- Click on `Advanced Mode` to access more settings. This step is optional, as your boot settings might be available in the basic view.
### Step 4: Disable Secure Boot
## Disable Secure Boot
- Locate the `Secure Boot` option in your UEFI/BIOS settings. This is typically found under a `Security` tab, `Boot` tab, or a similarly named section.
- Set the `Secure Boot` option to `Disabled`.
### Step 5: Change Boot Order
## Change Boot Order
- Find the option to adjust the boot order—often labeled `Boot Order`, `Boot Sequence`, or `Boot Priority`.
- Ensure that your USB device is set as the first boot option. This allows your computer to boot from the USB stick.
### Step 6: Save and Exit
## Save and Exit
- Save your changes before exiting the UEFI/BIOS menu. Look for a `Save & Exit` option or press the corresponding function key (often `F10`).
- Your computer should now restart and boot from the USB stick.

View File

@@ -0,0 +1,84 @@
# How to Set `targetHost` for a Machine
The `targetHost` defines where the machine can be reached for operations like SSH or deployment. You can set it in two ways, depending on your use case.
---
## ✅ Option 1: Use the Inventory (Recommended for Static Hosts)
If the hostname is **static**, like `server.example.com`, set it in the **inventory**:
```{.nix title="flake.nix" hl_lines="8"}
{
# edlided
outputs =
{ self, clan-core, ... }:
let
# Sometimes this attribute set is defined in clan.nix
clan = clan-core.lib.clan {
inventory.machines.jon = {
deploy.targetHost = "root@server.example.com";
};
};
in
{
inherit (clan.config) nixosConfigurations nixosModules clanInternals;
# elided
};
}
```
This is fast, simple and explicit, and doesnt require evaluating the NixOS config. We can also displayed it in the clan-cli or clan-app.
---
## ✅ Option 2: Use NixOS (Only for Dynamic Hosts)
If your target host depends on a **dynamic expression** (like using the machines evaluated FQDN), set it inside the NixOS module:
```{.nix title="flake.nix" hl_lines="8"}
{
# edlided
outputs =
{ self, clan-core, ... }:
let
# Sometimes this attribute set is defined in clan.nix
clan = clan-core.lib.clan {
machines.jon = {config, ...}: {
clan.core.networking.targetHost = "jon@${config.networking.fqdn}";
};
};
in
{
inherit (clan.config) nixosConfigurations nixosModules clanInternals;
# elided
};
}
```
Use this **only if the value cannot be made static**, because its slower and won't be displayed in the clan-cli or clan-app yet.
---
## 📝 TL;DR
| Use Case | Use Inventory? | Example |
| ------------------------- | -------------- | -------------------------------- |
| Static hostname | ✅ Yes | `root@server.example.com` |
| Dynamic config expression | ❌ No | `jon@${config.networking.fqdn}` |
---
## 🚀 Coming Soon: Unified Networking Module
Were working on a new networking module that will automatically do all of this for you.
- Easier to use
- Sane defaults: Youll always be able to reach the machine — no need to worry about hostnames.
- ✨ Migration from **either method** will be supported and simple.
## Summary
- Ask: *Does this hostname dynamically change based on NixOS config?*
- If **no**, use the inventory.
- If **yes**, then use NixOS config.

7
docs/site/intern/api.md Normal file
View File

@@ -0,0 +1,7 @@
---
template: options.html
hide:
- navigation
- toc
---
<redoc src="/openapi.json" />

25
docs/site/intern/index.md Normal file
View File

@@ -0,0 +1,25 @@
# Developer Documentation
!!! Danger
This documentation is **not** intended for external users. It may contain low-level details and internal-only interfaces.*
Welcome to the internal developer documentation.
This section is intended for contributors, engineers, and internal stakeholders working directly with our system, tooling, and APIs. It provides a technical overview of core components, internal APIs, conventions, and patterns that support the platform.
Our goal is to make the internal workings of the system **transparent, discoverable, and consistent** — helping you contribute confidently, troubleshoot effectively, and build faster.
## What's Here?
!!! note "docs migration ongoing"
- [ ] **API Reference**: 🚧🚧🚧 Detailed documentation of internal API functions, inputs, and expected outputs. 🚧🚧🚧
- [ ] **System Concepts**: Architectural overviews and domain-specific guides.
- [ ] **Development Guides**: How to test, extend, or integrate with key components.
- [ ] **Design Notes**: Rationales behind major design decisions or patterns.
## Who is This For?
* Developers contributing to the platform
* Engineers debugging or extending internal systems
* Anyone needing to understand **how** and **why** things work under the hood

104
flake.lock generated
View File

@@ -16,11 +16,11 @@
]
},
"locked": {
"lastModified": 1751241706,
"narHash": "sha256-T3hOK/yQexsrgTfkSceRVpWOtkMqbbKYWUCPwQnrUl0=",
"rev": "97d8e88ec1d43b52f9886a722c013af2db15bb47",
"lastModified": 1751846468,
"narHash": "sha256-h0mpWZIOIAKj4fmLNyI2HDG+c0YOkbYmyJXSj/bQ9s0=",
"rev": "a2166c13b0cb3febdaf36391cd2019aa2ccf4366",
"type": "tarball",
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/97d8e88ec1d43b52f9886a722c013af2db15bb47.tar.gz"
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/a2166c13b0cb3febdaf36391cd2019aa2ccf4366.tar.gz"
},
"original": {
"type": "tarball",
@@ -34,11 +34,11 @@
]
},
"locked": {
"lastModified": 1750903843,
"narHash": "sha256-Ng9+f0H5/dW+mq/XOKvB9uwvGbsuiiO6HrPdAcVglCs=",
"lastModified": 1751854533,
"narHash": "sha256-U/OQFplExOR1jazZY4KkaQkJqOl59xlh21HP9mI79Vc=",
"owner": "nix-community",
"repo": "disko",
"rev": "83c4da299c1d7d300f8c6fd3a72ac46cb0d59aae",
"rev": "16b74a1e304197248a1bc663280f2548dbfcae3c",
"type": "github"
},
"original": {
@@ -54,11 +54,11 @@
]
},
"locked": {
"lastModified": 1749398372,
"narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
@@ -67,52 +67,6 @@
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"ixx": {
"inputs": {
"flake-utils": [
"nuschtos",
"flake-utils"
],
"nixpkgs": [
"nuschtos",
"nixpkgs"
]
},
"locked": {
"lastModified": 1748294338,
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
"owner": "NuschtOS",
"repo": "ixx",
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"ref": "v0.0.8",
"repo": "ixx",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
@@ -164,51 +118,25 @@
"nixpkgs": {
"locked": {
"lastModified": 315532800,
"narHash": "sha256-VgDAFPxHNhCfC7rI5I5wFqdiVJBH43zUefVo8hwo7cI=",
"rev": "41da1e3ea8e23e094e5e3eeb1e6b830468a7399e",
"narHash": "sha256-mUlYenGbsUFP0A3EhfKJXmUl5+MQGJLhoEop2t3g5p4=",
"rev": "ceb24d94c6feaa4e8737a8e2bd3cf71c3a7eaaa0",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre814815.41da1e3ea8e2/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre826033.ceb24d94c6fe/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz"
}
},
"nuschtos": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"ixx": "ixx",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1749730855,
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
"owner": "NuschtOS",
"repo": "search",
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"repo": "search",
"type": "github"
}
},
"root": {
"inputs": {
"data-mesher": "data-mesher",
"disko": "disko",
"flake-parts": "flake-parts",
"flake-utils": "flake-utils",
"nix-darwin": "nix-darwin",
"nix-select": "nix-select",
"nixos-facter-modules": "nixos-facter-modules",
"nixpkgs": "nixpkgs",
"nuschtos": "nuschtos",
"sops-nix": "sops-nix",
"systems": "systems",
"treefmt-nix": "treefmt-nix"
@@ -221,11 +149,11 @@
]
},
"locked": {
"lastModified": 1750119275,
"narHash": "sha256-Rr7Pooz9zQbhdVxux16h7URa6mA80Pb/G07T4lHvh0M=",
"lastModified": 1751606940,
"narHash": "sha256-KrDPXobG7DFKTOteqdSVeL1bMVitDcy7otpVZWDE6MA=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "77c423a03b9b2b79709ea2cb63336312e78b72e2",
"rev": "3633fc4acf03f43b260244d94c71e9e14a2f6e0d",
"type": "github"
},
"original": {

View File

@@ -35,19 +35,13 @@
};
};
# dependencies needed for nuschtos
flake-utils.url = "github:numtide/flake-utils";
flake-utils.inputs.systems.follows = "systems";
nuschtos.url = "github:NuschtOS/search";
nuschtos.inputs.nixpkgs.follows = "nixpkgs";
nuschtos.inputs.flake-utils.follows = "flake-utils";
};
outputs =
inputs@{
flake-parts,
nixpkgs,
systems,
flake-parts,
...
}:
let
@@ -56,10 +50,25 @@
optional
pathExists
;
loadDevFlake =
path:
let
flakeHash = nixpkgs.lib.fileContents "${toString path}.narHash";
flakePath = "path:${toString path}?narHash=${flakeHash}";
in
builtins.getFlake (builtins.unsafeDiscardStringContext flakePath);
devFlake = builtins.tryEval (loadDevFlake ./devFlake/private);
privateInputs = if devFlake.success then devFlake.value.inputs else { };
in
flake-parts.lib.mkFlake { inherit inputs; } (
{ ... }:
{
_module.args = {
inherit privateInputs;
};
clan = {
meta.name = "clan-core";
inventory = {

View File

@@ -4,7 +4,7 @@
perSystem =
{ self', pkgs, ... }:
{
treefmt.projectRootFile = ".git/config";
treefmt.projectRootFile = "LICENSE.md";
treefmt.programs.shellcheck.enable = true;
treefmt.programs.mypy.enable = true;

View File

@@ -37,6 +37,7 @@ lib.fix (
inventory = clanLib.callLib ./modules/inventory { };
modules = clanLib.callLib ./modules/inventory/frontmatter { };
test = clanLib.callLib ./test { };
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
# Custom types
types = clanLib.callLib ./types { };

18
lib/flake-inputs.nix Normal file
View File

@@ -0,0 +1,18 @@
{ ... }:
{
/**
Generate nix-unit input overrides for tests
# Example
```nix
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
```
*/
getOverrides =
inputs:
builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (
builtins.filter (name: name != "self") (builtins.attrNames inputs)
)
);
}

View File

@@ -1,8 +1,6 @@
{ self, inputs, ... }:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
perSystem =

View File

@@ -67,6 +67,44 @@ in
'';
};
# TODO: make this writable by moving the options from inventoryClass into clan.
exports = lib.mkOption {
readOnly = true;
visible = false;
internal = true;
};
exportsModule = lib.mkOption {
internal = true;
visible = false;
type = types.deferredModule;
default = { };
description = ''
A module that is used to define the module of flake level exports -
such as 'exports.machines.<name>' and 'exports.instances.<name>'
Example:
```nix
{
options.vars.generators = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submoduleWith {
modules = [
{
options.script = lib.mkOption { type = lib.types.str; };
}
];
}
);
default = { };
};
}
```
'';
};
specialArgs = lib.mkOption {
type = types.attrsOf types.raw;
default = { };

View File

@@ -224,11 +224,11 @@ in
inherit nixosConfigurations;
inherit darwinConfigurations;
exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports;
clanInternals = {
inventoryClass =
let
localModuleSet =
lib.filterAttrs (n: _: !inventory._legacyModules ? ${n}) inventory.modules // config.modules;
flakeInputs = config.self.inputs;
in
{
@@ -238,16 +238,19 @@ in
imports = [
../inventoryClass/builder/default.nix
(lib.modules.importApply ../inventoryClass/service-list-from-inputs.nix {
inherit flakeInputs clanLib localModuleSet;
inherit flakeInputs clanLib;
})
{
inherit inventory directory;
}
(
let
clanConfig = config;
in
{ config, ... }:
{
distributedServices = clanLib.inventory.mapInstances {
inherit (config) inventory;
inherit (clanConfig) inventory exportsModule;
inherit flakeInputs;
clanCoreModules = clan-core.clan.modules;
prefix = [ "distributedServices" ];

View File

@@ -4,9 +4,7 @@
...
}:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
imports = [

View File

@@ -0,0 +1,75 @@
# Wraps all services in one fixed point module
{
lib,
config,
specialArgs,
_ctx,
...
}:
let
inherit (lib) mkOption types;
inherit (types) attrsWith submoduleWith;
in
{
# TODO: merge these options into clan options
options = {
exportsModule = mkOption {
type = types.deferredModule;
readOnly = true;
};
mappedServices = mkOption {
visible = false;
type = attrsWith {
placeholder = "mappedServiceName";
elemType = submoduleWith {
modules = [
(
{ name, ... }:
{
_module.args._ctx = [ name ];
_module.args.exports' = config.exports;
}
)
./service-module.nix
# feature modules
(lib.modules.importApply ./api-feature.nix {
inherit (specialArgs) clanLib;
prefix = _ctx;
})
];
};
};
default = { };
};
exports = mkOption {
type = submoduleWith {
modules = [
{
options = {
instances = lib.mkOption {
# instances.<instanceName>...
type = types.attrsOf (submoduleWith {
modules = [
config.exportsModule
];
});
};
# instances.<machineName>...
machines = lib.mkOption {
type = types.attrsOf (submoduleWith {
modules = [
config.exportsModule
];
});
};
};
}
] ++ lib.mapAttrsToList (_: service: service.exports) config.mappedServices;
};
default = { };
};
debug = mkOption {
default = lib.mapAttrsToList (_: service: service.exports) config.mappedServices;
};
};
}

View File

@@ -1,8 +1,6 @@
{ self, inputs, ... }:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
perSystem =
@@ -12,6 +10,23 @@ in
system,
...
}:
let
# Common filtered source for inventory tests
inventoryTestsSrc = lib.fileset.toSource {
root = ../../../..;
fileset = lib.fileset.unions [
../../../../flake.nix
../../../../flake.lock
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../../..)
../../../../flakeModules
../../../../lib
../../../../nixosModules/clanCore
../../../../clanModules/borgbackup
../../../../machines
../../../../inventory.json
];
};
in
{
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.<attrName>
legacyPackages.evalTests-distributedServices = import ./tests {
@@ -29,7 +44,7 @@ in
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${self}#legacyPackages.${system}.evalTests-distributedServices
--flake ${inventoryTestsSrc}#legacyPackages.${system}.evalTests-distributedServices
touch $out
'';
@@ -39,7 +54,7 @@ in
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${self}#legacyPackages.${system}.eval-tests-resolve-module
--flake ${inventoryTestsSrc}#legacyPackages.${system}.eval-tests-resolve-module
touch $out
'';

View File

@@ -26,6 +26,7 @@ in
inventory,
clanCoreModules,
prefix ? [ ],
exportsModule,
}:
let
# machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags;
@@ -89,23 +90,6 @@ in
}
) inventory.instances or { };
# TODO: Eagerly check the _class of the resolved module
importedModulesEvaluated = lib.mapAttrs (
module_ident: instances:
clanLib.evalService {
prefix = prefix ++ [ module_ident ];
modules =
[
# Import the resolved module.
# i.e. clan.modules.admin
(builtins.head instances).instance.resolvedModule
] # Include all the instances that correlate to the resolved module
++ (builtins.map (v: {
instances.${v.instanceName}.roles = v.instance.instanceRoles;
}) instances);
}
) grouped;
# Group the instances by the module they resolve to
# This is necessary to evaluate the module in a single pass
# :: { <module.input>_<module.name> :: [ { name, value } ] }
@@ -126,16 +110,52 @@ in
}
) { } importedModuleWithInstances;
# servicesEval.config.mappedServices.self-A.result.final.jon.nixosModule
allMachines = lib.mapAttrs (machineName: _: {
# This is the list of nixosModules for each machine
machineImports = lib.foldlAttrs (
acc: _module_ident: eval:
acc ++ [ eval.config.result.final.${machineName}.nixosModule or { } ]
) [ ] importedModulesEvaluated;
acc: _module_ident: serviceModule:
acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ]
) [ ] servicesEval.config.mappedServices;
}) inventory.machines or { };
evalServices =
{ modules, prefix }:
lib.evalModules {
specialArgs = {
inherit clanLib;
_ctx = prefix;
};
modules = [
./all-services-wrapper.nix
] ++ modules;
};
servicesEval = evalServices {
inherit prefix;
modules = [
{
inherit exportsModule;
mappedServices = lib.mapAttrs (_module_ident: instances: {
imports =
[
# Import the resolved module.
# i.e. clan.modules.admin
(builtins.head instances).instance.resolvedModule
] # Include all the instances that correlate to the resolved module
++ (builtins.map (v: {
instances.${v.instanceName}.roles = v.instance.instanceRoles;
}) instances);
}) grouped;
}
];
};
importedModulesEvaluated = servicesEval.config.mappedServices;
in
{
inherit
servicesEval
importedModuleWithInstances
grouped
allMachines

View File

@@ -104,6 +104,13 @@ let
in
{
options = {
# Option to disable some behavior during docs rendering
_docs_rendering = mkOption {
default = false;
visible = false;
type = types.bool;
};
instances = mkOption {
visible = false;
defaultText = "Throws: 'The service must define its instances' when not defined";
@@ -384,6 +391,33 @@ in
type = types.deferredModuleWith {
staticModules = [
({
options.exports = mkOption {
type = types.deferredModule;
default = { };
description = ''
export modules defined in 'perInstance'
mapped to their instance name
Example
with instances:
```nix
instances.A = { ... };
instances.B= { ... };
roles.peer.perInstance = { instanceName, machine, ... }:
{
exports.foo = 1;
}
This yields all other services can access these exports
=>
exports.instances.A.foo = 1;
exports.instances.B.foo = 1;
```
'';
};
options.nixosModule = mkOption {
type = types.deferredModule;
default = { };
@@ -412,27 +446,6 @@ in
```
'';
};
options.services = mkOption {
visible = false;
type = attrsWith {
placeholder = "serviceName";
elemType = submoduleWith {
modules = [
{
_module.args._ctx = _ctx ++ [
config.manifest.name
"roles"
roleName
"perInstance"
"services"
];
}
./service-module.nix
];
};
};
default = { };
};
})
];
};
@@ -514,6 +527,32 @@ in
type = types.deferredModuleWith {
staticModules = [
({
options.exports = mkOption {
type = types.deferredModule;
default = { };
description = ''
export modules defined in 'perMachine'
mapped to their machine name
Example
with machines:
```nix
instances.A = { roles.peer.machines.jon = ... };
instances.B = { roles.peer.machines.jon = ... };
perMachine = { machine, ... }:
{
exports.foo = 1;
}
This yields all other services can access these exports
=>
exports.machines.jon.foo = 1;
exports.machines.sara.foo = 1;
```
'';
};
options.nixosModule = mkOption {
type = types.deferredModule;
default = { };
@@ -537,25 +576,6 @@ in
```
'';
};
options.services = mkOption {
visible = false;
type = attrsWith {
placeholder = "serviceName";
elemType = submoduleWith {
modules = [
{
_module.args._ctx = _ctx ++ [
config.manifest.name
"perMachine"
"services"
];
}
./service-module.nix
];
};
};
default = { };
};
})
];
};
@@ -608,6 +628,96 @@ in
modules = [ v ];
}).config;
};
exports = mkOption {
description = ''
This services exports.
Gets merged with all other services exports
Final value (merged and evaluated with other services) available as `exports'` in the arguments of this module.
```nix
{ exports', ... }: {
_class = "clan.service";
# ...
}
```
'';
default = { };
type = types.submoduleWith {
# Static modules
modules = [
{
options.instances = mkOption {
type = types.attrsOf types.deferredModule;
description = ''
export modules defined in 'perInstance'
mapped to their instance name
Example
with instances:
```nix
instances.A = { ... };
instances.B= { ... };
roles.peer.perInstance = { instanceName, machine, ... }:
{
exports.foo = 1;
}
This yields all other services can access these exports
=>
exports.instances.A.foo = 1;
exports.instances.B.foo = 1;
```
'';
};
options.machines = mkOption {
type = types.attrsOf types.deferredModule;
description = ''
export modules defined in 'perMachine'
mapped to their machine name
Example
with machines:
```nix
instances.A = { roles.peer.machines.jon = ... };
instances.B = { roles.peer.machines.jon = ... };
perMachine = { machine, ... }:
{
exports.foo = 1;
}
This yields all other services can access these exports
=>
exports.machines.jon.foo = 1;
exports.machines.sara.foo = 1;
```
'';
};
# Lazy default via imports
# should probably be moved to deferredModuleWith { staticModules = [ ]; }
imports =
if config._docs_rendering then
[ ]
else
lib.mapAttrsToList (_roleName: role: {
instances = lib.mapAttrs (_instanceName: instance: {
imports = lib.mapAttrsToList (_machineName: v: v.exports) instance.allMachines;
}) role.allInstances;
}) config.result.allRoles
++ lib.mapAttrsToList (machineName: machine: {
machines.${machineName} = machine.exports;
}) config.result.allMachines;
}
];
};
};
# ---
# Place the result in _module.result to mark them as "internal" and discourage usage/overrides
#
@@ -727,40 +837,18 @@ in
instanceAcc: instanceName: instance:
instanceAcc
// {
nixosModules =
(
(lib.mapAttrsToList (
nestedServiceName: serviceModule:
let
unmatchedMachines = lib.attrNames (
lib.removeAttrs serviceModule.result.final (lib.attrNames config.result.allMachines)
);
in
if unmatchedMachines != [ ] then
throw ''
The following machines are not part of the parent service: ${builtins.toJSON unmatchedMachines}
Either remove the machines, or include them into the parent via a role.
(Added via roles.${roleName}.perInstance.services.${nestedServiceName})
${errorContext}
''
else
serviceModule.result.final.${machineName}.nixosModule
) instance.allMachines.${machineName}.services or { })
)
++ (
if instance.allMachines.${machineName}.nixosModule or { } != { } then
instanceAcc.nixosModules
++ [
(lib.setDefaultModuleLocation
"Via instances.${instanceName}.roles.${roleName}.machines.${machineName}"
instance.allMachines.${machineName}.nixosModule
)
]
else
instanceAcc.nixosModules
);
nixosModules = (
if instance.allMachines.${machineName}.nixosModule or { } != { } then
instanceAcc.nixosModules
++ [
(lib.setDefaultModuleLocation
"Via instances.${instanceName}.roles.${roleName}.machines.${machineName}"
instance.allMachines.${machineName}.nixosModule
)
]
else
instanceAcc.nixosModules
);
}
) roleAcc role.allInstances
)
@@ -773,38 +861,18 @@ in
{
inherit instanceResults machineResult;
nixosModule = {
imports =
[
# include service assertions:
(
let
failedAssertions = (lib.filterAttrs (_: v: !v.assertion) config.result.assertions);
in
{
assertions = lib.attrValues failedAssertions;
}
)
(lib.setDefaultModuleLocation "Via ${config.manifest.name}.perMachine - machine='${machineName}';" machineResult.nixosModule)
]
++ (lib.mapAttrsToList (
nestedServiceName: serviceModule:
imports = [
# include service assertions:
(
let
unmatchedMachines = lib.attrNames (
lib.removeAttrs serviceModule.result.final (lib.attrNames config.result.allMachines)
);
failedAssertions = (lib.filterAttrs (_: v: !v.assertion) config.result.assertions);
in
if unmatchedMachines != [ ] then
throw ''
The following machines are not part of the parent service: ${builtins.toJSON unmatchedMachines}
Either remove the machines, or include them into the parent via a role.
(Added via perMachine.services.${nestedServiceName})
${errorContext}
''
else
serviceModule.result.final.${machineName}.nixosModule
) machineResult.services)
++ instanceResults.nixosModules;
{
assertions = lib.attrValues failedAssertions;
}
)
(lib.setDefaultModuleLocation "Via ${config.manifest.name}.perMachine - machine='${machineName}';" machineResult.nixosModule)
] ++ instanceResults.nixosModules;
};
}
) config.result.allMachines;

View File

@@ -48,9 +48,11 @@ let
clanCoreModules = { };
flakeInputs = flakeInputsFixture;
inherit inventory;
exportsModule = { };
};
in
{
exports = import ./exports.nix { inherit lib clanLib; };
resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; };
test_simple =
let
@@ -171,7 +173,7 @@ in
{
# Test that the module is mapped into the output
# We might change the attribute name in the future
expr = lib.attrNames res.importedModulesEvaluated.self-A.config.instances;
expr = lib.attrNames res.importedModulesEvaluated.self-A.instances;
expected = [
"instance_bar"
"instance_foo"
@@ -227,7 +229,7 @@ in
{
# Test that the module is mapped into the output
# We might change the attribute name in the future
expr = lib.attrNames res.importedModulesEvaluated.self-A.config.result.allMachines;
expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines;
expected = [
"jon"
"sara"
@@ -279,14 +281,14 @@ in
{
# Test that the module is mapped into the output
# We might change the attribute name in the future
expr = lib.attrNames res.importedModulesEvaluated.self-A.config.result.allMachines;
expr = lib.attrNames res.importedModulesEvaluated.self-A.result.allMachines;
expected = [
"jon"
"sara"
];
};
machine_imports = import ./machine_imports.nix { inherit lib clanLib; };
per_machine_args = import ./per_machine_args.nix { inherit lib callInventoryAdapter; };
per_instance_args = import ./per_instance_args.nix { inherit lib callInventoryAdapter; };
nested = import ./nested_services { inherit lib clanLib; };
}

View File

@@ -0,0 +1,170 @@
{ lib, clanLib }:
let
clan = clanLib.clan {
self = { };
directory = ./.;
exportsModule = {
options.vars.generators = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submoduleWith {
# TODO: import the vars submodule here
modules = [
{
options.script = lib.mkOption { type = lib.types.str; };
}
];
}
);
default = { };
};
};
machines.jon = { };
machines.sara = { };
# A module that adds exports perMachine
modules.A =
{ exports', ... }:
{
manifest.name = "A";
roles.peer.perInstance =
{ machine, ... }:
{
# Cross reference a perMachine exports
exports.vars.generators."${machine.name}-network-ip".script =
"A:" + exports'.machines.${machine.name}.vars.generators.key.script;
# Cross reference a perInstance exports from a different service
exports.vars.generators."${machine.name}-full-hostname".script =
"A:" + exports'.instances."B-1".vars.generators.hostname.script;
};
roles.server = { };
perMachine =
{ machine, ... }:
{
exports = {
vars.generators.key.script = machine.name;
};
};
};
# A module that adds exports perInstance
modules.B = {
manifest.name = "B";
roles.peer.perInstance =
{ instanceName, ... }:
{
exports = {
vars.generators.hostname.script = instanceName;
};
};
};
inventory = {
instances.B-1 = {
module.name = "B";
module.input = "self";
roles.peer.tags.all = { };
};
instances.B-2 = {
module.name = "B";
module.input = "self";
roles.peer.tags.all = { };
};
instances.A-1 = {
module.name = "A";
module.input = "self";
roles.peer.tags.all = { };
roles.server.tags.all = { };
};
instances.A-2 = {
module.name = "A";
module.input = "self";
roles.peer.tags.all = { };
roles.server.tags.all = { };
};
};
};
in
{
test_1 = {
inherit clan;
expr = clan.config.exports;
expected = {
instances = {
A-1 = {
vars = {
generators = {
jon-full-hostname = {
script = "A:B-1";
};
jon-network-ip = {
script = "A:jon";
};
sara-full-hostname = {
script = "A:B-1";
};
sara-network-ip = {
script = "A:sara";
};
};
};
};
A-2 = {
vars = {
generators = {
jon-full-hostname = {
script = "A:B-1";
};
jon-network-ip = {
script = "A:jon";
};
sara-full-hostname = {
script = "A:B-1";
};
sara-network-ip = {
script = "A:sara";
};
};
};
};
B-1 = {
vars = {
generators = {
hostname = {
script = "B-1";
};
};
};
};
B-2 = {
vars = {
generators = {
hostname = {
script = "B-2";
};
};
};
};
};
machines = {
jon = {
vars = {
generators = {
key = {
script = "jon";
};
};
};
};
sara = {
vars = {
generators = {
key = {
script = "sara";
};
};
};
};
};
};
};
}

View File

@@ -0,0 +1,49 @@
{ lib, clanLib }:
let
clan = clanLib.clan {
self = { };
directory = ./.;
machines.jon = { };
machines.sara = { };
# A module that adds exports perMachine
modules.A =
{ ... }:
{
manifest.name = "A";
roles.peer.perInstance =
{ ... }:
{
nixosModule = {
options.bar = lib.mkOption {
default = 1;
};
};
};
roles.server = { };
perMachine =
{ ... }:
{
nixosModule = {
options.foo = lib.mkOption {
default = 1;
};
};
};
};
inventory.instances.A = {
module.input = "self";
roles.peer.tags.all = { };
};
};
in
{
test_1 = {
inherit clan;
expr = { inherit (clan.config.clanInternals.machines.x86_64-linux.jon.config) bar foo; };
expected = {
foo = 1;
bar = 1;
};
};
}

View File

@@ -1,8 +0,0 @@
{ clanLib, lib, ... }:
{
test_simple = import ./simple.nix { inherit clanLib lib; };
test_multi_machine = import ./multi_machine.nix { inherit clanLib lib; };
test_multi_import_duplication = import ./multi_import_duplication.nix { inherit clanLib lib; };
}

View File

@@ -1,125 +0,0 @@
{ clanLib, lib, ... }:
let
# Potentially imported many times
# To add the ssh key
example-admin = (
{ lib, ... }:
{
manifest.name = "example-admin";
roles.client.interface = {
options.keys = lib.mkOption { };
};
roles.client.perInstance =
{ settings, ... }:
{
nixosModule = {
inherit (settings) keys;
};
};
}
);
consumer-A =
{ ... }:
{
manifest.name = "consumer-A";
instances.foo = {
roles.server.machines."jon" = { };
};
instances.bar = {
roles.server.machines."jon" = { };
};
roles.server = {
perInstance =
{ machine, instanceName, ... }:
{
services."example-admin" = {
imports = [
example-admin
];
instances."${instanceName}" = {
roles.client.machines.${machine.name} = {
settings.keys = [ "pubkey-1" ];
};
};
};
};
};
};
consumer-B =
{ ... }:
{
manifest.name = "consumer-A";
instances.foo = {
roles.server.machines."jon" = { };
};
instances.bar = {
roles.server.machines."jon" = { };
};
roles.server = {
perInstance =
{ machine, instanceName, ... }:
{
services."example-admin" = {
imports = [
example-admin
];
instances."${instanceName}" = {
roles.client.machines.${machine.name} = {
settings.keys = [
"pubkey-1"
];
};
};
};
};
};
};
eval = clanLib.evalService {
modules = [
(consumer-A)
];
prefix = [ ];
};
eval2 = clanLib.evalService {
modules = [
(consumer-B)
];
prefix = [ ];
};
evalNixos = lib.evalModules {
modules = [
{
options.assertions = lib.mkOption { };
# This is suboptimal
options.keys = lib.mkOption { };
}
eval.config.result.final.jon.nixosModule
eval2.config.result.final.jon.nixosModule
];
};
in
{
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
inherit eval;
expr = evalNixos.config;
expected = {
assertions = [ ];
# TODO: Some deduplication mechanism is nice
# Could add types.set or do 'apply = unique', or something else ?
keys = [
"pubkey-1"
"pubkey-1"
"pubkey-1"
"pubkey-1"
];
};
}

View File

@@ -1,108 +0,0 @@
{ clanLib, lib, ... }:
let
service-B = (
{ lib, ... }:
{
manifest.name = "service-B";
roles.client.interface = {
options.user = lib.mkOption { };
options.host = lib.mkOption { };
};
roles.client.perInstance =
{ settings, instanceName, ... }:
{
nixosModule = {
units.${instanceName} = {
script = settings.user + "@" + settings.host;
};
};
};
perMachine =
{ ... }:
{
nixosModule = {
ssh.enable = true;
};
};
}
);
service-A =
{ ... }:
{
manifest.name = "service-A";
instances.foo = {
roles.server.machines."jon" = { };
roles.server.machines."sara" = { };
};
roles.server = {
perInstance =
{ machine, instanceName, ... }:
{
services."B" = {
imports = [
service-B
];
instances."A-${instanceName}-B" = {
roles.client.machines.${machine.name} = {
settings.user = "johnny";
settings.host = machine.name;
};
};
};
};
};
};
eval = clanLib.evalService {
modules = [
(service-A)
];
prefix = [ ];
};
evalNixos = lib.mapAttrs (
_n: v:
(lib.evalModules {
modules = [
{
options.assertions = lib.mkOption { };
options.units = lib.mkOption { };
options.ssh = lib.mkOption { };
}
v.nixosModule
];
}).config
) eval.config.result.final;
in
{
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
inherit eval;
expr = evalNixos;
expected = {
jon = {
assertions = [ ];
ssh = {
enable = true;
};
units = {
A-foo-B = {
script = "johnny@jon";
};
};
};
sara = {
assertions = [ ];
ssh = {
enable = true;
};
units = {
A-foo-B = {
script = "johnny@sara";
};
};
};
};
}

View File

@@ -1,117 +0,0 @@
/*
service-B :: Service
exports a nixosModule which set "address" and "hostname"
Note: How we use null together with mkIf to create optional values.
This is a method, to create mergable modules
service-A :: Service
service-A.roles.server.perInstance.services."B"
imports service-B
configures a client with hostname = "johnny"
service-A.perMachine.services."B"
imports service-B
configures a client with address = "root"
*/
{ clanLib, lib, ... }:
let
service-B = (
{ lib, ... }:
{
manifest.name = "service-B";
roles.client.interface = {
options.hostname = lib.mkOption { default = null; };
options.address = lib.mkOption { default = null; };
};
roles.client.perInstance =
{ settings, ... }:
{
nixosModule = {
imports = [
# Only export the value that is actually set.
(lib.mkIf (settings.hostname != null) {
hostname = settings.hostname;
})
(lib.mkIf (settings.address != null) {
address = settings.address;
})
];
};
};
}
);
service-A =
{ ... }:
{
manifest.name = "service-A";
instances.foo = {
roles.server.machines."jon" = { };
};
instances.bar = {
roles.server.machines."jon" = { };
};
roles.server = {
perInstance =
{ machine, instanceName, ... }:
{
services."B" = {
imports = [
service-B
];
instances."B-for-A" = {
roles.client.machines.${machine.name} = {
settings.hostname = instanceName + "+johnny";
};
};
};
};
};
perMachine =
{ machine, ... }:
{
services."B" = {
imports = [
service-B
];
instances."B-for-A" = {
roles.client.machines.${machine.name} = {
settings.address = "root";
};
};
};
};
};
eval = clanLib.evalService {
modules = [
(service-A)
];
prefix = [ ];
};
evalNixos = lib.evalModules {
modules = [
{
options.assertions = lib.mkOption { };
options.hostname = lib.mkOption { type = lib.types.separatedString " "; };
options.address = lib.mkOption { type = lib.types.str; };
}
eval.config.result.final."jon".nixosModule
];
};
in
{
# Check that the nixos system has the settings from the nested module, as well as those from the "perMachine" and "perInstance"
inherit eval;
expr = evalNixos.config;
expected = {
address = "root";
assertions = [ ];
# Concatenates hostnames from both instances
hostname = "bar+johnny foo+johnny";
};
}

View File

@@ -106,7 +106,7 @@ in
test_per_instance_arguments = {
expr = {
instanceName =
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
# settings are specific.
# Below we access:
@@ -114,11 +114,11 @@ in
# roles = peer
# machines = jon
settings =
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
machine =
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
roles =
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
};
expected = {
instanceName = "instance_foo";
@@ -161,9 +161,9 @@ in
# TODO: Cannot be tested like this anymore
test_per_instance_settings_vendoring = {
x = res.importedModulesEvaluated.self-A.config;
x = res.importedModulesEvaluated.self-A;
expr =
res.importedModulesEvaluated.self-A.config.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
expected = {
timeout = "config.thing";
};

View File

@@ -81,7 +81,7 @@ in
inherit res;
expr = {
hasMachineSettings =
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
? settings;
# settings are specific.
@@ -89,10 +89,10 @@ in
# instance = instance_foo
# roles = peer
# machines = jon
specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
specificMachineSettings = filterInternals res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
hasRoleSettings =
res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
? settings;
# settings are specific.
@@ -100,7 +100,7 @@ in
# instance = instance_foo
# roles = peer
# machines = *
specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.config.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.settings;
specificRoleSettings = filterInternals res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.settings;
};
expected = {
hasMachineSettings = true;

View File

@@ -5,9 +5,7 @@
...
}:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
imports = [
@@ -47,7 +45,7 @@ in
(pkgs.nixosOptionsDoc {
options =
(self.clanLib.evalService {
modules = [ ];
modules = [ { _docs_rendering = true; } ];
prefix = [ ];
}).options;
warningsAreErrors = true;
@@ -70,12 +68,18 @@ in
--show-trace \
${inputOverrides} \
--flake ${
self.filter {
include = [
"flakeModules"
"lib"
"clanModules/flake-module.nix"
"clanModules/borgbackup"
lib.fileset.toSource {
root = ../../..;
fileset = lib.fileset.unions [
../../../flake.nix
../../../flake.lock
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
../../../flakeModules
../../../lib
../../../nixosModules/clanCore
../../../clanModules/borgbackup
../../../machines
../../../inventory.json
];
}
}#legacyPackages.${system}.evalTests-inventory

View File

@@ -394,6 +394,7 @@ in
options = {
# ModuleSpec
module = lib.mkOption {
default = { };
type = types.submodule {
options.input = lib.mkOption {
type = types.nullOr types.str;

View File

@@ -1,12 +1,9 @@
{
flakeInputs,
clanLib,
localModuleSet,
}:
{ lib, config, ... }:
let
inspectModule =
inputName: moduleName: module:
let
@@ -28,16 +25,30 @@ in
{
options.modulesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithModules = lib.filterAttrs (_inputName: v: v ? clan.modules) flakeInputs;
in
lib.mapAttrs (
inputName: v: lib.mapAttrs (inspectModule inputName) v.clan.modules
) inputsWithModules;
};
options.localModules = lib.mkOption {
default = lib.mapAttrs (inspectModule "self") localModuleSet;
readOnly = true;
type = lib.types.raw;
default = config.modulesPerSource.self;
};
options.templatesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithTemplates = lib.filterAttrs (_inputName: v: v ? clan.templates) flakeInputs;
in
lib.mapAttrs (_inputName: v: lib.mapAttrs (_n: t: t) v.clan.templates) inputsWithTemplates;
};
}

View File

@@ -15,7 +15,7 @@ find = {}
[tool.setuptools.package-data]
test_driver = ["py.typed"]
[tool.mypy]
python_version = "3.12"
python_version = "3.13"
warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true

View File

@@ -16,7 +16,7 @@
*/
makeEvalChecks =
{
self,
fileset,
inputs,
testName,
tests,
@@ -24,9 +24,7 @@
testArgs ? { },
}:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
attrName = "eval-tests-${testName}";
in
{
@@ -41,16 +39,44 @@
}
// testArgs
);
checks.${attrName} = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
checks.${attrName} =
let
# The root is two directories up from where this file is located
root = ../..;
nix-unit --eval-store "$HOME" \
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${self}#legacyPackages.${system}.${attrName}
touch $out
'';
# Combine the user-provided fileset with all flake-module.nix files
# and other essential files
src = lib.fileset.toSource {
inherit root;
fileset = lib.fileset.unions [
# Core flake files
(root + "/flake.nix")
(root + "/flake.lock")
# All flake-module.nix files anywhere in the tree
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") root)
# The flakeModules/clan.nix if it exists
(lib.fileset.maybeMissing (root + "/flakeModules/clan.nix"))
# Core libraries
(root + "/lib")
# User-provided fileset
fileset
];
};
in
pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
nix-unit --eval-store "$HOME" \
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${src}#legacyPackages.${system}.${attrName}
touch $out
'';
};
}

View File

@@ -1,4 +1,9 @@
{ self, inputs, ... }:
{
self,
inputs,
lib,
...
}:
{
perSystem =
{ ... }:
@@ -10,7 +15,11 @@
test-types-module = (
self.clanLib.test.flakeModules.makeEvalChecks {
module = throw "";
inherit self inputs;
inherit inputs;
fileset = lib.fileset.unions [
# Only lib is needed for type tests
../../lib
];
testName = "types";
tests = ./tests.nix;
# Optional arguments passed to the test

View File

@@ -1,7 +1,6 @@
{
config,
lib,
pkgs,
...
}:
{
@@ -24,6 +23,14 @@
description = ''
the location of the deployment.json file
'';
default = throw ''
deployment.json file generation has been removed in favor of direct selectors.
Please upgrade your clan-cli to the latest version.
The deployment data is now accessed directly from the configuration
instead of being written to a separate JSON file.
'';
};
deployment.buildHost = lib.mkOption {
type = lib.types.nullOr lib.types.str;
@@ -83,8 +90,5 @@
inherit (config.system.clan.deployment) nixosMobileWorkaround;
inherit (config.clan.deployment) requireExplicitUpdate;
};
system.clan.deployment.file = pkgs.writeText "deployment.json" (
builtins.toJSON config.system.clan.deployment.data
);
};
}

View File

@@ -11,8 +11,7 @@ in
enable = lib.mkEnableOption "automatic state-version generation.
The option will take the specified version, if one is already supplied through
the config or generate one if not.
";
the config or generate one if not";
};
config = lib.mkIf (config.clan.core.settings.state-version.enable) {

View File

@@ -40,6 +40,18 @@ in
};
config = {
# Check for removed passBackend option usage
assertions = [
{
assertion = config.clan.core.vars.settings.passBackend == null;
message = ''
The option `clan.core.vars.settings.passBackend' has been removed.
Use clan.core.vars.password-store.passPackage instead.
Set it to pkgs.pass for GPG or pkgs.passage for age encryption.
'';
}
];
# check all that all non-secret files have no owner/group/mode set
warnings = lib.foldl' (
warnings: generator:
@@ -73,10 +85,5 @@ in
) [ ] (lib.attrValues generator.files)
) [ ] (lib.attrValues config.clan.core.vars.generators);
system.clan.deployment.data = {
vars = config.clan.core.vars._serialized;
inherit (config.clan.core.networking) targetHost buildHost;
inherit (config.clan.core.deployment) requireExplicitUpdate;
};
};
}

View File

@@ -1,4 +1,4 @@
{ lib, pkgs, ... }:
{ lib, pkgs }:
let
eval =
module:

View File

@@ -5,18 +5,14 @@
...
}:
let
inputOverrides = builtins.concatStringsSep " " (
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
);
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
perSystem =
{ system, pkgs, ... }:
{
legacyPackages.evalTests-module-clan-vars = import ./eval-tests {
inherit lib;
clan-core = self;
pkgs = inputs.nixpkgs.legacyPackages.${system};
inherit lib pkgs;
};
checks.eval-module-clan-vars = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
@@ -26,11 +22,15 @@ in
--show-trace \
${inputOverrides} \
--flake ${
self.filter {
include = [
"flakeModules"
"nixosModules"
"lib"
lib.fileset.toSource {
root = ../../..;
fileset = lib.fileset.unions [
../../../flake.nix
../../../flake.lock
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
../../../flakeModules/clan.nix
../../../lib
../../../nixosModules/clanCore/vars
];
}
}#legacyPackages.${system}.evalTests-module-clan-vars

View File

@@ -34,50 +34,6 @@ let
in
{
options = {
_serialized = lib.mkOption {
readOnly = true;
internal = true;
description = ''
JSON serialization of the generators.
This is read from the python client to generate the specified resources.
'';
default = {
# TODO: We don't support per-machine choice of backends
# Configuring different backend doesn't work, this information should be made read only and configured
# Via clan.settings instead.
inherit (config.settings) secretModule publicModule;
# Serialize generators, so that we can use them in the python client
# This need to be done because we have some non-serializable values in the generators
# Like the finalScript (derivation) or pkgs.
generators = lib.flip lib.mapAttrs config.generators (
_name: generator: {
inherit (generator)
name
dependencies
validationHash
migrateFact
share
prompts
;
files = lib.flip lib.mapAttrs generator.files (
_name: file: {
inherit (file)
name
owner
group
mode
deploy
secret
neededFor
;
}
);
}
);
};
};
settings = import ./settings-opts.nix { inherit lib; };
generators = lib.mkOption {
description = ''

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