Compare commits

...

205 Commits

Author SHA1 Message Date
9a05d2a072 Drop macOS-specific remote-program param from nix copy command 2025-11-04 01:53:58 +08:00
clan-bot
62b64c3b3e Merge pull request 'Update nixpkgs-dev in devFlake' (#5728) from update-devFlake-nixpkgs-dev into main 2025-11-03 15:07:53 +00:00
clan-bot
19a1ad6081 Update nixpkgs-dev in devFlake 2025-11-03 15:01:50 +00:00
Kenji Berthold
a2df5db3d6 Merge pull request 'docs/testing: Document requirements for our container testing system' (#5693) from ke-docs-testing-container into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5693
2025-11-03 13:13:53 +00:00
Kenji Berthold
ac46f890ea Merge branch 'main' into ke-docs-testing-container 2025-11-03 13:06:14 +00:00
clan-bot
19abf8d288 Merge pull request 'Update nixpkgs-dev in devFlake' (#5726) from update-devFlake-nixpkgs-dev into main 2025-11-03 10:06:31 +00:00
clan-bot
e5105e31c4 Update nixpkgs-dev in devFlake 2025-11-03 10:01:47 +00:00
clan-bot
0f847b4799 Merge pull request 'Update nixpkgs-dev in devFlake' (#5724) from update-devFlake-nixpkgs-dev into main 2025-11-02 20:06:24 +00:00
clan-bot
40a8a823b8 Update nixpkgs-dev in devFlake 2025-11-02 20:01:50 +00:00
Mic92
e3adb3fc71 Merge pull request 'Fix vars upload for public vars with neededFor activation/partitioning' (#5723) from vars into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5723
2025-11-02 16:00:13 +00:00
Jörg Thalheim
a569a1d147 Fix vars upload for public vars with neededFor activation/partitioning
When vars are marked with neededFor="activation" or "partitioning", they
need to be available early in the boot process. However, the populate_dir
methods in both sops and password_store secret backends were only calling
self.get() which only retrieves secret vars from the .../secret path.

This caused public vars (stored at .../value) to fail with "Secret does
not exist" errors when trying to upload them.

The fix uses file.value property instead, which properly delegates to the
correct store (SecretStore or FactStore) based on whether the file is
marked as secret or public.

Fixes affected all neededFor phases in both backends:
- sops: activation and partitioning phases
- password_store: activation and partitioning phases
2025-11-02 16:49:49 +01:00
Mic92
64718b77ca Merge pull request 'readme fix' (#5722) from i18n/clan-core:main into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5722
2025-11-02 15:49:16 +00:00
i18n
7b34c39736 Merge pull request '更新 docs/site/getting-started/creating-your-first-clan.md' (#1) from i18n-patch-1 into main
Reviewed-on: https://git.clan.lol/i18n/clan-core/pulls/1
2025-11-02 13:24:05 +00:00
i18n
4d6ab60793 更新 docs/site/getting-started/creating-your-first-clan.md 2025-11-02 13:23:04 +00:00
clan-bot
35bffee544 Merge pull request 'Update nixpkgs-dev in devFlake' (#5721) from update-devFlake-nixpkgs-dev into main 2025-11-02 10:05:51 +00:00
clan-bot
16917fd79b Update nixpkgs-dev in devFlake 2025-11-02 10:01:50 +00:00
clan-bot
895c116c01 Merge pull request 'Update nix-darwin' (#5720) from update-nix-darwin into main 2025-11-02 05:06:00 +00:00
clan-bot
e67151f7b9 Merge pull request 'Update flake-parts' (#5719) from update-flake-parts into main 2025-11-02 05:05:06 +00:00
clan-bot
8d26ec1760 Update nix-darwin 2025-11-02 05:01:04 +00:00
clan-bot
7a9062b629 Update flake-parts 2025-11-02 05:01:01 +00:00
clan-bot
de07454a0a Merge pull request 'Update nix-darwin' (#5718) from update-nix-darwin into main 2025-11-01 20:06:16 +00:00
clan-bot
6fe60f61cf Update nix-darwin 2025-11-01 20:00:58 +00:00
clan-bot
3fa74847e4 Merge pull request 'Update nixpkgs-dev in devFlake' (#5716) from update-devFlake-nixpkgs-dev into main 2025-11-01 15:06:01 +00:00
clan-bot
fc37140b52 Update nixpkgs-dev in devFlake 2025-11-01 15:01:52 +00:00
hsjobeki
83406c61f3 Merge pull request 'services: update hello-world readme and tests' (#5714) from update-docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5714
2025-11-01 11:37:39 +00:00
hsjobeki
6d736e7e80 Merge pull request 'docs: update experimental notes as planned in release-notes' (#5715) from ex-docs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5715
2025-11-01 11:35:29 +00:00
Johannes Kirschbauer
7b6cec4100 services: update hello-world readme and tests 2025-11-01 12:33:15 +01:00
Johannes Kirschbauer
e21a6516b5 docs: update experimental notes as planned in release-notes 2025-11-01 12:30:01 +01:00
Mic92
6ffe8ea5f6 Merge pull request 'treewide: replace pkgs.hostPlatform with pkgs.stdenv.hostPlatform' (#5713) from fix-eval into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5713
2025-10-31 17:57:38 +00:00
Jörg Thalheim
0a2fefd141 treewide: replace pkgs.hostPlatform with pkgs.stdenv.hostPlatform
nixpkgs now throws an error for this, the other variant in stdenv also
exists in the previous release
2025-10-31 18:52:31 +01:00
Luis Hebendanz
0c885d05b6 Merge pull request 'clan_lib/flake: Improve select error message' (#5711) from Qubasa/clan-core:improve_clan_select_error_message into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5711
2025-10-31 15:11:29 +00:00
Qubasa
58d85b117a clan_lib/flake: Improve select error message 2025-10-31 16:05:54 +01:00
clan-bot
ad58d7b6e9 Merge pull request 'Update nixpkgs-dev in devFlake' (#5710) from update-devFlake-nixpkgs-dev into main 2025-10-31 15:05:13 +00:00
clan-bot
7a63cb9642 Update nixpkgs-dev in devFlake 2025-10-31 15:01:50 +00:00
clan-bot
196b98da36 Merge pull request 'Update disko' (#5707) from update-disko into main 2025-10-31 10:10:34 +00:00
clan-bot
42acbe95b8 Update disko 2025-10-31 10:00:58 +00:00
clan-bot
b6b065e365 Merge pull request 'Update nixpkgs-dev in devFlake' (#5706) from update-devFlake-nixpkgs-dev into main 2025-10-31 00:08:41 +00:00
clan-bot
4b1955b189 Update nixpkgs-dev in devFlake 2025-10-31 00:02:00 +00:00
clan-bot
ef7ef8b843 Merge pull request 'Update nixpkgs-dev in devFlake' (#5704) from update-devFlake-nixpkgs-dev into main 2025-10-30 20:05:49 +00:00
clan-bot
38c1367322 Update nixpkgs-dev in devFlake 2025-10-30 20:01:49 +00:00
clan-bot
8e72c086fd Merge pull request 'Update nixpkgs-dev in devFlake' (#5702) from update-devFlake-nixpkgs-dev into main 2025-10-30 15:06:22 +00:00
clan-bot
c454b1339d Update nixpkgs-dev in devFlake 2025-10-30 15:01:51 +00:00
hsjobeki
d1b2d43e5b Merge pull request 'services: move into clan submodule' (#5701) from unify-clan into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5701
2025-10-30 13:00:14 +00:00
Johannes Kirschbauer
da98ca0f1c clanLib: remove unused mapInstances 2025-10-30 13:54:05 +01:00
Johannes Kirschbauer
1953540d08 tests: update inventory tests to use whole clan modules 2025-10-30 13:54:05 +01:00
Johannes Kirschbauer
be31b9ce21 docs: remove service options from nuschtSearch
These hacks are blocking the flake level vars and exports
Maybe we bring this back later
So far nobody seemed using nuschtSearch
2025-10-30 13:54:05 +01:00
Johannes Kirschbauer
169b4016e6 docs: set self to clan-core for docs 2025-10-30 13:53:49 +01:00
Johannes Kirschbauer
2e55028a1b services: move into clan submodule 2025-10-30 13:53:49 +01:00
hsjobeki
1d228231f2 Merge pull request 'clan/services: Reduce surface of services wrapper function' (#5700) from unify-clan into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5700
2025-10-30 09:49:56 +00:00
Johannes Kirschbauer
affb926450 services: remove duplicate module args 2025-10-30 10:10:55 +01:00
Johannes Kirschbauer
c7f65e929f inventoryAdapter: replace importedModulesEvaluated by equivalent config 2025-10-30 10:10:31 +01:00
hsjobeki
ba4ff493e8 Merge pull request 'revert: uniqueStrings' (#5699) from hsjobeki-patch-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5699
2025-10-30 08:34:59 +00:00
hsjobeki
eb08803e2a revert bfb30251e6
revert lib: replace uniqueStrings after upstreamed

TODO: Reapply after 25.11 release
2025-10-30 08:29:43 +00:00
clan-bot
bbc9486f0e Merge pull request 'Update nixpkgs-dev in devFlake' (#5697) from update-devFlake-nixpkgs-dev into main 2025-10-29 20:06:16 +00:00
clan-bot
999d709350 Update nixpkgs-dev in devFlake 2025-10-29 20:01:48 +00:00
clan-bot
0b1a330cc2 Merge pull request 'Update nixpkgs-dev in devFlake' (#5696) from update-devFlake-nixpkgs-dev into main 2025-10-29 15:06:14 +00:00
clan-bot
995b7cf50d Update nixpkgs-dev in devFlake 2025-10-29 15:01:49 +00:00
a-kenji
bc290fe59f docs/testing: Document requirements for our container testing system
Document the requirements for our container testing system:
- uid-range
- auto-allocate-uids

Further document that the container tests are used by default and how to
switch to the more traditional and more supported / featureful VM
testing framework.
2025-10-29 13:47:26 +01:00
clan-bot
5477b13233 Merge pull request 'Update nuschtos in devFlake' (#5690) from update-devFlake-nuschtos into main 2025-10-29 10:08:23 +00:00
clan-bot
d6170e5efb Update nuschtos in devFlake 2025-10-29 10:01:53 +00:00
clan-bot
18fe117363 Merge pull request 'Update nixpkgs-dev in devFlake' (#5689) from update-devFlake-nixpkgs-dev into main 2025-10-29 00:07:47 +00:00
clan-bot
33a868acc2 Update nixpkgs-dev in devFlake 2025-10-29 00:03:27 +00:00
hsjobeki
11372d35e1 Merge pull request 'clan/checks: fix clanLib not checking' (#5685) from check into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5685
2025-10-28 10:51:17 +00:00
Johannes Kirschbauer
b7508b2b43 clan/checks: fix clanLib not checking 2025-10-28 11:46:49 +01:00
clan-bot
183817b769 Merge pull request 'Update nixpkgs-dev in devFlake' (#5684) from update-devFlake-nixpkgs-dev into main 2025-10-28 10:08:10 +00:00
clan-bot
591e53e9be Update nixpkgs-dev in devFlake 2025-10-28 10:01:54 +00:00
hsjobeki
a6a6415e31 Merge pull request 'clan/checks: move into lib function; add tests' (#5683) from role-settings into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5683
2025-10-28 08:46:41 +00:00
Johannes Kirschbauer
0060ead876 clan/checks: move into lib function; add tests 2025-10-28 09:40:31 +01:00
hsjobeki
224e41d3ad Merge pull request 'modules: clean up clan module' (#5679) from role-settings into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5679
2025-10-28 08:22:46 +00:00
Johannes Kirschbauer
b3323007b2 test: update test filesets 2025-10-28 09:04:50 +01:00
Johannes Kirschbauer
3e950bc66f docs: add doc-comment for template submodule 2025-10-28 09:04:50 +01:00
Johannes Kirschbauer
9503b46b21 modules: rename arbitrary interface.nix to 'top-level-interface' 2025-10-28 09:04:50 +01:00
Johannes Kirschbauer
a2cec323a2 modules: move nixos modules into nixosModules folder 2025-10-28 09:04:50 +01:00
Johannes Kirschbauer
4239f4d27f clan/module: explain throw 2025-10-28 09:04:50 +01:00
clan-bot
8ac8264997 Merge pull request 'Update nixpkgs-dev in devFlake' (#5681) from update-devFlake-nixpkgs-dev into main 2025-10-27 20:06:57 +00:00
clan-bot
544a53ae9c Update nixpkgs-dev in devFlake 2025-10-27 20:01:46 +00:00
Luis Hebendanz
89e18482ed Merge pull request 'checks: Fix flakey llm test, improve performance' (#5678) from Qubasa/clan-core:fix_slow_llm into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5678
2025-10-27 16:34:21 +00:00
Qubasa
a8217b5a32 llm checks: Skip parts of the test on aarch64 for performance 2025-10-27 17:25:06 +01:00
Qubasa
bdd5de5628 checks: Fix flakey llm test, improve performance 2025-10-27 17:12:12 +01:00
Mic92
61d8bfd0d1 Merge pull request 'fix: respect directory parameter in machines_dir' (#5677) from fix-custom-directory into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5677
Reviewed-by: hsjobeki <hsjobeki@gmail.com>
2025-10-27 15:42:44 +00:00
Jörg Thalheim
b8d79c7fc2 fix: respect directory parameter in machines_dir
The machines_dir() function was hardcoding "machines" without considering
the directory parameter from buildClan/clan configuration. This caused
update-hardware-config and other commands to write files to the wrong
location when a custom directory was specified (e.g., directory = ./clan).

Solution:
1. Added relativeDirectory to inventoryClass in Nix, computed where both
   self and directory have consistent store paths during evaluation
2. Updated machines_dir() to use this pre-computed relative path from Nix
   via flake.select("clanInternals.inventoryClass.relativeDirectory")

Fixes: https://git.clan.lol/clan/clan-core/issues/2906
2025-10-27 16:37:07 +01:00
hsjobeki
fb25ab028b Merge pull request 'services: add role settings with explicit warning' (#5676) from role-settings into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5676
2025-10-27 15:33:39 +00:00
clan-bot
5b136ecaf0 Merge pull request 'Update nixpkgs-dev in devFlake' (#5675) from update-devFlake-nixpkgs-dev into main 2025-10-27 15:08:23 +00:00
clan-bot
d4733dbb0a Update nixpkgs-dev in devFlake 2025-10-27 15:01:55 +00:00
Johannes Kirschbauer
bfb30251e6 lib: replace uniqueStrings after upstreamed 2025-10-27 14:00:46 +01:00
Johannes Kirschbauer
33115f76b7 services: add role settings with explicit warning 2025-10-27 13:31:44 +01:00
pinpox
9e9208e699 Merge pull request 'yggdrasil: read peers from exports' (#5657) from yggdrasil-export-peers into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5657
2025-10-27 12:13:59 +00:00
hsjobeki
6b3fd57174 Merge pull request 'extraModules: soft deprecation for string extraModules' (#5656) from inline into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5656
2025-10-27 12:12:17 +00:00
clan-bot
3be5237cf6 Merge pull request 'Update nixpkgs-dev in devFlake' (#5674) from update-devFlake-nixpkgs-dev into main 2025-10-26 20:06:09 +00:00
clan-bot
368f80eaae Merge pull request 'Update nix-darwin' (#5665) from update-nix-darwin into main 2025-10-26 20:04:12 +00:00
clan-bot
4d7079534c Update nixpkgs-dev in devFlake 2025-10-26 20:01:51 +00:00
clan-bot
7d4cf1c551 Update nix-darwin 2025-10-26 20:00:59 +00:00
pinpox
ea088b95e9 yggdrasil: read peers from exports 2025-10-26 12:07:52 +01:00
pinpox
a7a37f5320 Update readme 2025-10-26 12:02:17 +01:00
clan-bot
8bda4880a7 Merge pull request 'Update nixpkgs-dev in devFlake' (#5673) from update-devFlake-nixpkgs-dev into main 2025-10-26 10:06:09 +00:00
clan-bot
6eb83618c0 Update nixpkgs-dev in devFlake 2025-10-26 10:01:55 +00:00
pinpox
1fe3833779 Merge pull request 'clanServices/internet: set default for host export' (#5672) from issue-5671 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5672
2025-10-25 20:44:00 +00:00
pinpox
e63f5c966e clanServices/internet: set default for host export 2025-10-25 22:37:17 +02:00
pinpox
69241183ac Merge pull request 'Fix meta.tld option' (#5670) from issue-5669 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5670
2025-10-25 20:18:28 +00:00
pinpox
0a7c65cd27 Fix meta.tld option
The option curretly keeps it default value, even if set. This fixes the
value being passed through correctly, so that the user-specified value
is actually used.

Fixes: #5669
2025-10-25 22:08:44 +02:00
clan-bot
bd13eb3e23 Merge pull request 'Update treefmt-nix' (#5660) from update-treefmt-nix into main 2025-10-25 20:06:14 +00:00
clan-bot
1e5191a16c Update treefmt-nix 2025-10-25 20:01:34 +00:00
clan-bot
1e4bf0dd4e Merge pull request 'Update nixpkgs-dev in devFlake' (#5667) from update-devFlake-nixpkgs-dev into main 2025-10-25 10:07:40 +00:00
clan-bot
4d66dc59aa Update nixpkgs-dev in devFlake 2025-10-25 10:01:59 +00:00
hsjobeki
9a442c15e9 Merge pull request 'docs: add experimental note to 'exports'' (#5662) from exports into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5662
2025-10-24 15:11:27 +00:00
clan-bot
157af90a56 Merge pull request 'Update treefmt-nix in devFlake' (#5661) from update-devFlake-treefmt-nix into main 2025-10-24 15:08:50 +00:00
Johannes Kirschbauer
24b94965d8 docs: add experimental note to 'exports' 2025-10-24 17:05:47 +02:00
clan-bot
46bcad9267 Update treefmt-nix in devFlake 2025-10-24 15:01:55 +00:00
Johannes Kirschbauer
1aba0577dc schemas: filter 'extraModules' from python classes and derived schemas 2025-10-24 16:57:26 +02:00
Luis Hebendanz
383088af2d Merge pull request 'clan_lib/llm: get_llm_turn uses state transitions instead of callback function' (#5659) from Qubasa/clan-core:llm_no_callback2 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5659
2025-10-24 14:37:25 +00:00
Qubasa
c3456c1f0c clan_lib/llm: get_llm_turn uses state transitions instead of callback function 2025-10-24 16:31:09 +02:00
lassulus
183de9209f Merge pull request 'clan_lib select: fix maybe select storing miss as {}' (#5655) from select_cache_fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5655
2025-10-24 12:51:33 +00:00
hsjobeki
1df5c5ff60 Merge pull request 'clanTest: throw when usage of 'self' is triggered' (#5658) from specialArgs into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5658
2025-10-24 12:16:34 +00:00
Johannes Kirschbauer
e55a3b4fc6 clanTest: throw when usage of 'self' is triggered
That means we rely on a certain structure of the user flake
these assumptions cannot be made. Their flake can have any shape and is not controlled by us
2025-10-24 14:10:17 +02:00
Johannes Kirschbauer
6ee4657da3 extraModules: soft deprecation for string extraModules 2025-10-24 12:54:17 +02:00
lassulus
7294d8bcbe clan_lib select: fix maybe select storing miss as {} 2025-10-24 12:46:48 +02:00
hsjobeki
3fec5aa5b3 Merge pull request 'api: services simplify update services; update tests' (#5654) from inline into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5654
2025-10-24 10:41:26 +00:00
Johannes Kirschbauer
63e55b8631 api: services simplify update services; update tests 2025-10-24 12:19:26 +02:00
clan-bot
dd771f8dd9 Merge pull request 'Update nixpkgs-dev in devFlake' (#5653) from update-devFlake-nixpkgs-dev into main 2025-10-24 10:05:38 +00:00
clan-bot
d31a4cc7d8 Update nixpkgs-dev in devFlake 2025-10-24 10:01:53 +00:00
hsjobeki
73d4cf51af Merge pull request 'services: allow inline modules' (#5652) from inline into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5652
2025-10-23 16:48:06 +00:00
Johannes Kirschbauer
d015218226 api: update models 2025-10-23 18:43:07 +02:00
Johannes Kirschbauer
f50475fcfd services: allow inline modules 2025-10-23 18:43:07 +02:00
Johannes Kirschbauer
ae5efd9e2f inventory: fix path filter wrong length 2025-10-23 18:43:07 +02:00
hsjobeki
c2c2874e82 Merge pull request 'modules: move clan docs into clan-module' (#5651) from lib-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5651
2025-10-23 15:57:38 +00:00
Johannes Kirschbauer
1f8c2a3722 modules: move clan docs into clan-module 2025-10-23 17:52:39 +02:00
hgl
50aa7eb0cf Merge pull request 'ui: use storybook-solidjs-vite for storybook' (#5649) from hgl-sb-vite into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5649
2025-10-23 15:11:01 +00:00
clan-bot
920b39a124 Merge pull request 'Update nixpkgs-dev in devFlake' (#5635) from update-devFlake-nixpkgs-dev into main 2025-10-23 15:06:59 +00:00
Glen Huang
c159ef79d2 ui: use storybook-solidjs-vite for storybook 2025-10-23 23:04:09 +08:00
clan-bot
7a95b169c1 Update nixpkgs-dev in devFlake 2025-10-23 15:02:00 +00:00
hsjobeki
0c3fd40120 Merge pull request 'inventory: clean up unused modules and submodule names' (#5638) from lib-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5638
2025-10-23 13:14:37 +00:00
hgl
7268697dc5 Merge pull request 'ui: not using wasm node' (#5642) from hgl-cleanup into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5642
2025-10-23 12:52:26 +00:00
Glen Huang
ec395bada4 ui: not using wasm node
Tested both on arm64 darwin and linux that build can succeed without them
2025-10-23 20:48:59 +08:00
hsjobeki
757552671c Merge pull request 'API: init delete instance' (#5641) from instance-delete into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5641
2025-10-23 12:46:24 +00:00
Johannes Kirschbauer
04b52d5bee inventory: clean up unused modules and submodule names 2025-10-23 14:44:38 +02:00
Johannes Kirschbauer
ffeb8b892a API: init delete instance 2025-10-23 14:40:52 +02:00
hgl
26806b5750 Merge pull request 'ui: clean up using knip' (#5633) from hgl-storybook into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5633
2025-10-23 12:39:46 +00:00
Glen Huang
6e70054566 ui: clean up using knip 2025-10-23 20:25:47 +08:00
hsjobeki
93268e8592 Merge pull request 'modules/inventory: use filtered serialization of inventory instead' (#5634) from modules-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5634
2025-10-23 12:14:48 +00:00
Johannes Kirschbauer
a420c6ca25 inventory/tests: fixup test fixture 2025-10-23 13:51:37 +02:00
Johannes Kirschbauer
69fd13a76f clan-core-for-checks: update 2025-10-23 13:31:47 +02:00
Johannes Kirschbauer
ab3f262c22 modules/inventory: use filtered serialization of inventory
Opens up inventory for partially non-serializable parts
2025-10-23 10:37:14 +02:00
hgl
aabbe0dfac Merge pull request 'run storybook in nix derivation' (#5589) from hgl-storybook into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5589
2025-10-23 07:23:26 +00:00
Glen Huang
35cb99a3a2 app: fix typo 2025-10-23 14:26:12 +08:00
Glen Huang
90e6d77e26 ui: fix running storybook tests in ci 2025-10-23 14:26:12 +08:00
Glen Huang
5fb4751bd8 ui: fail storybook tests if playwright version mismatch 2025-10-23 14:26:12 +08:00
Glen Huang
03640e44a4 ui: make ci run storybook tests 2025-10-23 14:26:12 +08:00
Glen Huang
51fd60917e ui: fix createInstaller.tsx fmt 2025-10-23 14:26:12 +08:00
Glen Huang
2d7e659953 ui: run storybook tests with nix 2025-10-23 14:26:12 +08:00
Brian McGee
c638df8ed9 wip(storybook): run storybook in nix derivation 2025-10-23 14:26:12 +08:00
Brian McGee
ec269a48f3 wip(storybook): run storybook in nix derivation 2025-10-23 14:26:12 +08:00
clan-bot
fc4c9287cb Merge pull request 'Update nixpkgs-dev in devFlake' (#5632) from update-devFlake-nixpkgs-dev into main 2025-10-23 05:07:25 +00:00
clan-bot
fee62373a9 Update nixpkgs-dev in devFlake 2025-10-23 05:02:06 +00:00
pinpox
f075b339b5 Merge pull request 'Add library function to read public vars' (#5628) from lib-vars-helper into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5628
Reviewed-by: Kenji Berthold <aks.kenji@protonmail.com>
2025-10-22 22:52:57 +00:00
pinpox
872a622f71 Add tests 2025-10-23 00:42:32 +02:00
pinpox
4a41c4cefb clanServices/data-mesher: use clanLib.getPublicValue 2025-10-22 23:26:04 +02:00
pinpox
a5cd36e845 clanServices/zerotier: use clanLib.getPublicValue 2025-10-22 23:18:00 +02:00
pinpox
0dd6c08e33 clanServices/wireguard: use clanLib.getPublicValue 2025-10-22 22:48:56 +02:00
pinpox
dc0b7fc3bf Add library function to read public vars 2025-10-22 19:56:10 +02:00
hsjobeki
0d4bbbd17e Merge pull request 'inventory: filter extraModules and remove unneded top-level options' (#5603) from modules-1 into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5603
2025-10-22 17:20:20 +00:00
Johannes Kirschbauer
90797ffa7d inventory: filter extraModules and remove unneded top-level options 2025-10-22 19:13:31 +02:00
hsjobeki
7f2bd809d6 Merge pull request 'modules: simplify inventoryClass module' (#5627) from lib-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5627
2025-10-22 16:56:29 +00:00
Johannes Kirschbauer
da7ff9a40a modules/inventory: distributed services output reduce lexical scope 2025-10-22 18:47:30 +02:00
Johannes Kirschbauer
410d0d0532 modules: move input-mapping into inventoryClass submodule 2025-10-22 18:46:10 +02:00
Johannes Kirschbauer
a25d983c87 modules: move introspection module into inventoryClass submodule 2025-10-22 18:28:09 +02:00
clan-bot
3953fa4047 Merge pull request 'Update nixpkgs-dev in devFlake' (#5630) from update-devFlake-nixpkgs-dev into main 2025-10-22 15:08:26 +00:00
clan-bot
ea93cb9987 Merge pull request 'Update nixos-facter-modules' (#5629) from update-nixos-facter-modules into main 2025-10-22 15:08:16 +00:00
clan-bot
c13278f3c2 Update nixpkgs-dev in devFlake 2025-10-22 15:02:13 +00:00
clan-bot
66fdf937e3 Update nixos-facter-modules 2025-10-22 15:01:13 +00:00
Johannes Kirschbauer
843f55f844 modules: simplify inventoryClass module 2025-10-22 16:29:33 +02:00
pinpox
5a5633d779 Merge pull request 'Add meta.tld clan option' (#5619) from add-tld into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5619
2025-10-22 14:19:16 +00:00
Luis Hebendanz
8310433342 Merge pull request 'clan-lib: Add llm API for clan service selection' (#5626) from Qubasa/clan-core:llm_api into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5626
2025-10-22 13:39:35 +00:00
Qubasa
51141772b3 clan_lib: Add llm integration tests 2025-10-22 15:36:11 +02:00
Qubasa
58b88e874f clan_lib: Add llm unit tests 2025-10-22 15:36:11 +02:00
Qubasa
5fb616efb4 clan_lib: Add llm container test data 2025-10-22 15:36:11 +02:00
Qubasa
6c6afd6f4b clan_lib: Move llm.py out of clan_lib/services towards it's own clan_lib/llm folder 2025-10-22 15:36:11 +02:00
Qubasa
4010953041 clan_lib: Add llm api 2025-10-22 15:36:11 +02:00
Luis Hebendanz
6b74c66292 Merge pull request 'clan-cli: Make select query reproducible, by sorting select keys' (#5625) from Qubasa/clan-core:repro_select into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5625
2025-10-22 13:04:01 +00:00
Luis Hebendanz
fd35adbc3e Merge pull request 'clan-cli: Add support for ForwardRef type in type_to_jsonschema and tests' (#5624) from Qubasa/clan-core:forward_ref_add into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5624
2025-10-22 12:58:34 +00:00
Qubasa
f86b0ec3da clan-cli: Make select query reproducible, by sorting select keys 2025-10-22 14:56:07 +02:00
Luis Hebendanz
5f6e0540cd Merge pull request 'clan-cli: Add passthru.sourceWithTest for container_test.py' (#5623) from Qubasa/clan-core:passthru_test into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5623
2025-10-22 12:54:37 +00:00
Qubasa
c7ec9a9715 clan-cli: Add support for ForwardRef type in type_to_jsonschema and tests 2025-10-22 14:45:04 +02:00
Qubasa
841e9135fe clan-cli: Add passthru.sourceWithTest for container_test.py 2025-10-22 14:36:16 +02:00
pinpox
9299cd9666 Add tld 2025-10-22 12:40:09 +02:00
Kenji Berthold
9851993b82 Merge pull request 'pkgs/cli: Autocomplete --build-host' (#5609) from ke-cli-complete-buildhost into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5609
2025-10-22 07:51:50 +00:00
Kenji Berthold
55d1807f07 Merge branch 'main' into ke-cli-complete-buildhost 2025-10-22 07:41:08 +00:00
clan-bot
ee0abdc7f4 Merge pull request 'Update nixpkgs-dev in devFlake' (#5616) from update-devFlake-nixpkgs-dev into main 2025-10-22 00:07:16 +00:00
clan-bot
6c9ab63842 Update nixpkgs-dev in devFlake 2025-10-22 00:02:06 +00:00
a-kenji
d70db5af79 pkgs/cli: Autocomplete buildhost 2025-10-21 22:40:37 +02:00
clan-bot
eae858dec6 Merge pull request 'Update nixos-facter-modules' (#5607) from update-nixos-facter-modules into main 2025-10-21 20:08:56 +00:00
clan-bot
2ec035a1cb Update nixos-facter-modules 2025-10-21 20:01:12 +00:00
hsjobeki
bd6c227bbe Merge pull request 'lib: move modules into a toplevel folder' (#5610) from lib-modules into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5610
2025-10-21 19:06:45 +00:00
Johannes Kirschbauer
02f3474a58 modules: add source to eval tests 2025-10-21 21:01:06 +02:00
Johannes Kirschbauer
c838e08d77 lib/modules: fixup paths 2025-10-21 20:41:28 +02:00
Johannes Kirschbauer
346e3d816a lib/modules: move modules out of lib 2025-10-21 19:35:50 +02:00
Mic92
a15959fad2 Merge pull request 'pkgs/cli: Fix dynamic shell completions' (#5599) from ke-cli-completion-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5599
2025-10-21 14:36:10 +00:00
clan-bot
0e0c2ead1f Merge pull request 'Update sops-nix' (#5601) from update-sops-nix into main 2025-10-21 14:25:13 +00:00
a-kenji
c42381d810 pkgs/cli: Fix dynamic shell completions
Fix dynamic shell completions by correcting parameter names

Dynamic completions for the CLI were not working because completion
functions used `_prefix` as the first parameter name instead of `prefix`.
This is incompatible with how `argcomplete` passes it's arguments.

Add regression test for the prefix parameter as well as some basic shell
completion testing.
2025-10-21 14:19:30 +00:00
clan-bot
587ce7258a Update sops-nix 2025-10-21 14:19:11 +00:00
Mic92
d0bb804843 Merge pull request 'sshd: check searchDomains before accessing openssh-cert generator' (#5519) from fix-sshd-searchdomains-graceful-degrade into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5519
Reviewed-by: Luis Hebendanz <consulting@qube.email>
2025-10-21 14:18:58 +00:00
Jörg Thalheim
410eecb988 monitoring: regenerate test certificates with 100-year expiration
Regenerated telegraf test certificates to use the new 100-year
expiration period instead of the default 30-day expiration.

Related to #5605
2025-10-21 15:28:41 +02:00
Jörg Thalheim
98136142b4 monitoring: extend telegraf certificate expiration to 100 years
The default 30-day expiration was causing certificates to expire
frequently, breaking monitoring. Setting to 100 years provides a
temporary solution until automated certificate rotation is implemented.

Fixes #5605
2025-10-21 15:28:41 +02:00
Jörg Thalheim
37da9fb3e4 sshd: client role inherits searchDomains from all servers
The client role now automatically collects and merges searchDomains from
ALL servers in the instance when not explicitly configured. This eliminates
redundant configuration and ensures clients trust certificates from all
servers.

Also uses lib.mkIf with .exists check to safely handle the openssh-cert
generator access, checking searchDomains first to enable lazy evaluation.
2025-10-21 15:28:41 +02:00
clan-bot
4566ad9789 Merge pull request 'Update nixpkgs-dev in devFlake' (#5602) from update-devFlake-nixpkgs-dev into main 2025-10-21 12:47:33 +00:00
clan-bot
6faacc7dde Update nixpkgs-dev in devFlake 2025-10-21 10:02:01 +00:00
239 changed files with 9703 additions and 3621 deletions

View File

@@ -1,8 +1,10 @@
clanServices/.* @pinpox @kenji
lib/test/container-test-driver/.* @DavHau @mic92
lib/modules/inventory/.* @hsjobeki
lib/modules/inventoryClass/.* @hsjobeki
lib/inventory/.* @hsjobeki
lib/inventoryClass/.* @hsjobeki
modules/.* @hsjobeki
pkgs/clan-app/ui/.* @hsjobeki @brianmcgee
pkgs/clan-app/clan_app/.* @qubasa @hsjobeki

View File

@@ -87,6 +87,7 @@ in
# Container Tests
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
nixos-systemd-abstraction = self.clanLib.test.containerTest ./systemd-abstraction nixosTestArgs;
nixos-llm-test = self.clanLib.test.containerTest ./llm nixosTestArgs;
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
nixos-test-extra-python-packages = self.clanLib.test.containerTest ./test-extra-python-packages nixosTestArgs;

View File

@@ -58,51 +58,53 @@
pkgs.buildPackages.xorg.lndir
pkgs.glibcLocales
pkgs.kbd.out
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".pkgs.perlPackages.FileSlurp
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.ConfigIniFiles
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".pkgs.perlPackages.FileSlurp
pkgs.bubblewrap
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript
self.nixosConfigurations."test-flash-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript.drvPath
]
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
{
# Skip flash test on aarch64-linux for now as it's too slow
checks = lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.hostPlatform.system != "aarch64-linux") {
nixos-test-flash = self.clanLib.test.baseTest {
name = "flash";
nodes.target = {
virtualisation.emptyDiskImages = [ 4096 ];
virtualisation.memorySize = 4096;
checks =
lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.stdenv.hostPlatform.system != "aarch64-linux")
{
nixos-test-flash = self.clanLib.test.baseTest {
name = "flash";
nodes.target = {
virtualisation.emptyDiskImages = [ 4096 ];
virtualisation.memorySize = 4096;
virtualisation.useNixStoreImage = true;
virtualisation.writableStore = true;
virtualisation.useNixStoreImage = true;
virtualisation.writableStore = true;
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
environment.etc."install-closure".source = "${closureInfo}/store-paths";
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
environment.etc."install-closure".source = "${closureInfo}/store-paths";
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = lib.mkForce 3;
flake-registry = "";
experimental-features = [
"nix-command"
"flakes"
];
};
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = lib.mkForce 3;
flake-registry = "";
experimental-features = [
"nix-command"
"flakes"
];
};
};
testScript = ''
start_all()
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
# Some distros like to automount disks with spaces
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.stdenv.hostPlatform.system}")
'';
} { inherit pkgs self; };
};
testScript = ''
start_all()
machine.succeed("echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRWUusawhlIorx7VFeQJHmMkhl9X3QpnvOdhnV/bQNG root@target' > ./test_id_ed25519.pub")
# Some distros like to automount disks with spaces
machine.succeed('mkdir -p "/mnt/with spaces" && mkfs.ext4 /dev/vdc && mount /dev/vdc "/mnt/with spaces"')
machine.succeed("clan flash write --ssh-pubkey ./test_id_ed25519.pub --keymap de --language de_DE.UTF-8 --debug --flake ${self.checks.x86_64-linux.clan-core-for-checks} --yes --disk main /dev/vdc test-flash-machine-${pkgs.hostPlatform.system}")
'';
} { inherit pkgs self; };
};
};
}

View File

@@ -160,9 +160,9 @@
closureInfo = pkgs.closureInfo {
rootPaths = [
privateInputs.clan-core-for-checks
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.initialRamdisk
self.nixosConfigurations."test-install-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.toplevel
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.initialRamdisk
self.nixosConfigurations."test-install-machine-${pkgs.stdenv.hostPlatform.system}".config.system.build.diskoScript
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir
@@ -215,7 +215,7 @@
# Prepare test flake and Nix store
flake_dir = prepare_test_flake(
temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
"${closureInfo}"
)
@@ -296,7 +296,7 @@
# Prepare test flake and Nix store
flake_dir = prepare_test_flake(
temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
"${closureInfo}"
)

82
checks/llm/default.nix Normal file
View File

@@ -0,0 +1,82 @@
{ self, pkgs, ... }:
let
cli = self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full;
ollama-model = pkgs.callPackage ./qwen3-4b-instruct.nix { };
in
{
name = "llm";
nodes = {
peer1 =
{ pkgs, ... }:
{
users.users.text-user = {
isNormalUser = true;
linger = true;
uid = 1000;
extraGroups = [ "systemd-journal" ];
};
# Set environment variables for user systemd
environment.extraInit = ''
if [ "$(id -u)" = "1000" ]; then
export XDG_RUNTIME_DIR="/run/user/1000"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
ollama_dir="$HOME/.ollama"
mkdir -p "$ollama_dir"
ln -sf ${ollama-model}/models "$ollama_dir"/models
fi
'';
# Enable PAM for user systemd sessions
security.pam.services.systemd-user = {
startSession = true;
# Workaround for containers - use pam_permit to avoid helper binary issues
text = pkgs.lib.mkForce ''
account required pam_permit.so
session required pam_permit.so
session required pam_env.so conffile=/etc/pam/environment readenv=0
session required ${pkgs.systemd}/lib/security/pam_systemd.so
'';
};
environment.systemPackages = [
cli
pkgs.ollama
(cli.pythonRuntime.withPackages (
ps: with ps; [
pytest
pytest-xdist
(cli.pythonRuntime.pkgs.toPythonModule cli)
self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixosTestLib
]
))
];
};
};
testScript =
{ ... }:
''
start_all()
peer1.wait_for_unit("multi-user.target")
peer1.wait_for_unit("user@1000.service")
# Fix user journal permissions so text-user can read their own logs
peer1.succeed("chown text-user:systemd-journal /var/log/journal/*/user-1000.journal*")
peer1.succeed("chmod 640 /var/log/journal/*/user-1000.journal*")
# the -o adopts="" is needed to overwrite any args coming from pyproject.toml
# -p no:cacheprovider disables pytest's cacheprovider which tries to write to the nix store in this case
cmd = "su - text-user -c 'pytest -s -n0 -m service_runner -p no:cacheprovider -o addopts="" ${cli.passthru.sourceWithTests}/clan_lib/llm'"
print("Running tests with command: " + cmd)
# Run tests as text-user (environment variables are set automatically)
peer1.succeed(cmd)
'';
}

View File

@@ -0,0 +1,70 @@
{ pkgs }:
let
# Got them from https://github.com/Gholamrezadar/ollama-direct-downloader
# Download manifest
manifest = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/manifests/4b-instruct";
# You'll need to calculate this hash - run the derivation once and it will tell you the correct hash
hash = "sha256-Dtze80WT6sGqK+nH0GxDLc+BlFrcpeyi8nZiwY8Wi6A=";
};
# Download blobs
blob1 = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:b72accf9724e93698c57cbd3b1af2d3341b3d05ec2089d86d273d97964853cd2";
hash = "sha256-tyrM+XJOk2mMV8vTsa8tM0Gz0F7CCJ2G0nPZeWSFPNI=";
};
blob2 = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:85e4a5b7b8ef0e48af0e8658f5aaab9c2324c76c1641493f4d1e25fce54b18b9";
hash = "sha256-heSlt7jvDkivDoZY9aqrnCMkx2wWQUk/TR4l/OVLGLk=";
};
blob3 = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:eade0a07cac7712787bbce23d12f9306adb4781d873d1df6e16f7840fa37afec";
hash = "sha256-6t4KB8rHcSeHu84j0S+TBq20eB2HPR324W94QPo3r+w=";
};
blob4 = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:d18a5cc71b84bc4af394a31116bd3932b42241de70c77d2b76d69a314ec8aa12";
hash = "sha256-0YpcxxuEvErzlKMRFr05MrQiQd5wx30rdtaaMU7IqhI=";
};
blob5 = pkgs.fetchurl {
url = "https://registry.ollama.ai/v2/library/qwen3/blobs/sha256:0914c7781e001948488d937994217538375b4fd8c1466c5e7a625221abd3ea7a";
hash = "sha256-CRTHeB4AGUhIjZN5lCF1ODdbT9jBRmxeemJSIavT6no=";
};
in
pkgs.stdenv.mkDerivation {
pname = "ollama-qwen3-4b-instruct";
version = "1.0";
dontUnpack = true;
buildPhase = ''
mkdir -p $out/models/manifests/registry.ollama.ai/library/qwen3
mkdir -p $out/models/blobs
# Copy manifest
cp ${manifest} $out/models/manifests/registry.ollama.ai/library/qwen3/4b-instruct
# Copy blobs with correct names
cp ${blob1} $out/models/blobs/sha256-b72accf9724e93698c57cbd3b1af2d3341b3d05ec2089d86d273d97964853cd2
cp ${blob2} $out/models/blobs/sha256-85e4a5b7b8ef0e48af0e8658f5aaab9c2324c76c1641493f4d1e25fce54b18b9
cp ${blob3} $out/models/blobs/sha256-eade0a07cac7712787bbce23d12f9306adb4781d873d1df6e16f7840fa37afec
cp ${blob4} $out/models/blobs/sha256-d18a5cc71b84bc4af394a31116bd3932b42241de70c77d2b76d69a314ec8aa12
cp ${blob5} $out/models/blobs/sha256-0914c7781e001948488d937994217538375b4fd8c1466c5e7a625221abd3ea7a
'';
installPhase = ''
# buildPhase already created everything in $out
:
'';
meta = with pkgs.lib; {
description = "Qwen3 4B Instruct model for Ollama";
license = "apache-2.0";
platforms = platforms.all;
};
}

View File

@@ -2,7 +2,7 @@
let
cli = self.packages.${pkgs.hostPlatform.system}.clan-cli-full;
cli = self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full;
in
{
name = "systemd-abstraction";
@@ -62,6 +62,6 @@ in
peer1.succeed("chmod 640 /var/log/journal/*/user-1000.journal*")
# Run tests as text-user (environment variables are set automatically)
peer1.succeed("su - text-user -c 'pytest -s -n0 ${cli}/${cli.pythonRuntime.sitePackages}/clan_lib/service_runner'")
peer1.succeed("su - text-user -c 'pytest -p no:cacheprovider -o addopts="" -s -n0 ${cli.passthru.sourceWithTests}/clan_lib/service_runner'")
'';
}

View File

@@ -115,9 +115,9 @@
let
closureInfo = pkgs.closureInfo {
rootPaths = [
self.packages.${pkgs.hostPlatform.system}.clan-cli
self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks
self.clanInternals.machines.${pkgs.hostPlatform.system}.test-update-machine.config.system.build.toplevel
self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli
self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks
self.clanInternals.machines.${pkgs.stdenv.hostPlatform.system}.test-update-machine.config.system.build.toplevel
pkgs.stdenv.drvPath
pkgs.bash.drvPath
pkgs.buildPackages.xorg.lndir
@@ -132,7 +132,7 @@
imports = [ self.nixosModules.test-update-machine ];
};
extraPythonPackages = _p: [
self.legacyPackages.${pkgs.hostPlatform.system}.nixosTestLib
self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.nixosTestLib
];
testScript = ''
@@ -154,7 +154,7 @@
# Prepare test flake and Nix store
flake_dir = prepare_test_flake(
temp_dir,
"${self.checks.${pkgs.hostPlatform.system}.clan-core-for-checks}",
"${self.checks.${pkgs.stdenv.hostPlatform.system}.clan-core-for-checks}",
"${closureInfo}"
)
(flake_dir / ".clan-flake").write_text("") # Ensure .clan-flake exists
@@ -226,7 +226,7 @@
"--to",
"ssh://root@192.168.1.1",
"--no-check-sigs",
f"${self.packages.${pkgs.hostPlatform.system}.clan-cli}",
f"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}",
"--extra-experimental-features", "nix-command flakes",
],
check=True,
@@ -242,7 +242,7 @@
"-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no",
f"root@192.168.1.1",
"${self.packages.${pkgs.hostPlatform.system}.clan-cli}/bin/clan",
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli}/bin/clan",
"machines",
"update",
"--debug",
@@ -270,7 +270,7 @@
# Run clan update command
subprocess.run([
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan",
"machines",
"update",
"--debug",
@@ -297,7 +297,7 @@
# Run clan update command with --build-host
subprocess.run([
"${self.packages.${pkgs.hostPlatform.system}.clan-cli-full}/bin/clan",
"${self.packages.${pkgs.stdenv.hostPlatform.system}.clan-cli-full}/bin/clan",
"machines",
"update",
"--debug",

View File

@@ -1,3 +1,6 @@
!!! Danger "Experimental"
This service is experimental and will change in the future.
This service sets up a certificate authority (CA) that can issue certificates to
other machines in your clan. For this the `ca` role is used.
It additionally provides a `default` role, that can be applied to all machines

View File

@@ -1,3 +1,6 @@
!!! Danger "Experimental"
This service is experimental and will change in the future.
This module enables hosting clan-internal services easily, which can be resolved
inside your VPN. This allows defining a custom top-level domain (e.g. `.clan`)
and exposing endpoints from a machine to others, which will be

View File

@@ -1,4 +1,7 @@
{ ... }:
{
clanLib,
...
}:
let
sharedInterface =
{ lib, ... }:
@@ -51,15 +54,15 @@ let
builtins.foldl' (
urls: name:
let
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
ip = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "zerotier";
file = "zerotier-ip";
default = null;
};
in
if builtins.pathExists ipPath then
let
ip = builtins.readFile ipPath;
in
urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ]
else
urls
if ip != null then urls ++ [ "[${ip}]:${builtins.toString settings.network.port}" ] else urls
) [ ] (builtins.attrNames ((roles.admin.machines or { }) // (roles.signer.machines or { })))
);
@@ -156,9 +159,14 @@ in
readHostKey =
machine:
let
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
publicKey = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
inherit machine;
generator = "data-mesher-host-key";
file = "public_key";
};
in
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
builtins.elemAt (lib.splitString "\n" publicKey) 1;
in
{
enable = true;

View File

@@ -1 +1,83 @@
This a test README just to appease the eval warnings if we don't have one
!!! Danger "Experimental"
This service is for demonstration purpose only and may change in the future.
The Hello-World Clan Service is a minimal example showing how to build and register your own service.
It serves as a reference implementation and is used in clan-core CI tests to ensure compatibility.
## What it demonstrates
- How to define a basic Clan-compatible service.
- How to structure your service for discovery and configuration.
- How Clan services interact with nixos.
## Testing
This service demonstrates two levels of testing to ensure quality and stability across releases:
1. **Unit & Integration Testing** — via [`nix-unit`](https://github.com/nix-community/nix-unit)
2. **End-to-End Testing** — via **NixOS VM tests**, which we extended to support **container virtualization** for better performance.
We highly advocate following the [Practical Testing Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html):
* Write **unit tests** for core logic and invariants.
* Add **one or two end-to-end (E2E)** tests to confirm your service starts and behaves correctly in a real NixOS environment.
NixOS is **untyped** and frequently changes; tests are the safest way to ensure long-term stability of services.
```
/ \
/ \
/ E2E \
/-------\
/ \
/Integration\
/-------------\
/ \
/ Unit Tests \
-------------------
```
### nix-unit
We highly advocate the usage of
[nix-unit](https://github.com/nix-community/nix-unit)
Example in: tests/eval-tests.nix
If you use flake-parts you can use the [native integration](https://flake.parts/options/nix-unit.html)
If nix-unit succeeds you'r nixos evaluation should be mostly correct.
!!! Tip
- Ensure most used 'settings' and variants are tested.
- Think about some important edge-cases your system should handle.
### NixOS VM / Container Test
!!! Warning "Early Vars & clanTest"
The testing system around vars is experimental
`clanTest` is still experimental and enables container virtualization by default.
This is still early and might have some limitations.
Some minimal boilerplate is needed to use `clanTest`
```nix
nixosLib = import (inputs.nixpkgs + "/nixos/lib") { }
nixosLib.runTest (
{ ... }:
{
imports = [
self.modules.nixosTest.clanTest
# Example in tests/vm/default.nix
testModule
];
hostPkgs = pkgs;
# Uncomment if you don't want or cannot use containers
# test.useContainers = false;
}
)
```

View File

@@ -8,7 +8,7 @@
{
_class = "clan.service";
manifest.name = "clan-core/hello-word";
manifest.description = "This is a test";
manifest.description = "Minimal example clan service that greets the world";
manifest.readme = builtins.readFile ./README.md;
# This service provides two roles: "morning" and "evening". Roles can be

View File

@@ -26,7 +26,7 @@ in
# The hello-world service being tested
../../clanServices/hello-world
# Required modules
../../nixosModules/clanCore
../../nixosModules
];
testName = "hello-world";
tests = ./tests/eval-tests.nix;

View File

@@ -4,7 +4,7 @@
...
}:
let
testFlake = clanLib.clan {
testClan = clanLib.clan {
self = { };
# Point to the folder of the module
# TODO: make this optional
@@ -33,10 +33,20 @@ let
};
in
{
test_simple = {
config = testFlake.config;
/**
We highly advocate the usage of:
https://github.com/nix-community/nix-unit
expr = { };
expected = { };
If you use flake-parts you can use the native integration: https://flake.parts/options/nix-unit.html
*/
test_simple = {
# Allows inspection via the nix-repl
# Ignored by nix-unit; it only looks at 'expr' and 'expected'
inherit testClan;
# Assert that jon has the
# configured greeting in 'environment.etc.hello.text'
expr = testClan.config.nixosConfigurations.jon.config.environment.etc."hello".text;
expected = "Good evening World!";
};
}

View File

@@ -1,8 +1,5 @@
🚧🚧🚧 Experimental 🚧🚧🚧
Use at your own risk.
We are still refining its interfaces, instability and breakages are expected.
!!! Danger "Experimental"
This service is experimental and will change in the future.
---

View File

@@ -16,6 +16,7 @@
options = {
host = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
ip address or hostname (domain) of the machine
'';

View File

@@ -1,3 +1,6 @@
!!! Danger "Experimental"
This service is experimental and will change in the future.
## Usage
```

View File

@@ -44,8 +44,10 @@
pkgs.openssl
];
# TODO: Implement automated certificate rotation instead of using a 100-year expiration
script = ''
openssl req -x509 -nodes -newkey rsa:4096 \
-days 36500 \
-keyout "$out"/key \
-out "$out"/crt \
-subj "/C=US/ST=CA/L=San Francisco/O=Example Corp/OU=IT/CN=example.com"

View File

@@ -1,33 +1,33 @@
-----BEGIN CERTIFICATE-----
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
k1PNWTkgLsXO8DNkCudxcvPElXfmaw6zwaLrZys=
VQQDDAtleGFtcGxlLmNvbTAgFw0yNTEwMjExMzE3MTZaGA8yMTI1MDkyNzEzMTcx
NlowbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
bmNpc2NvMRUwEwYDVQQKDAxFeGFtcGxlIENvcnAxCzAJBgNVBAsMAklUMRQwEgYD
VQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AMbUCTs38JdEFlz+fiEwsEb9OV+6u4P5pkKkRFIJ04sTW9/NIeUJx5xOcAPn6B8K
mi+d6vHln2WDCNJHqthGHQDS250x8Qs+JrmtIvDPko+oDOlbWMPiT4Lv6p134+lV
obkiEMKSKz1gHuhlnHXFjkU+xTjxvEtGuq1+JPem4oJ9HUhSk1F6cftigzrYqUuk
JRROiUrbKiFp/TLedmAqQg/7wOrJKSKX91pQwNZhjB2/1REt0HP92W8uZIrzvLqq
JkrGfK9Y6e87DwXoTT0lvMAT7jbMsMWdGoCw/BQV8CwciUUG4ggI/jb+2TTktB3f
kMN/qRTKZ3zv/rn68RJfecAXYCQ2VfvO/Mr9nml2/cM7nrUBcs12YAHcm3766VWJ
pq6qBLcz/pHzMdt+/23nbO7bH2PL6r69VCSYvsDDnqpVL+LnYhgYUE0lPjuWuGmp
oKjggS6p4p1PXEQMOcj9UWdOyjefSzJsOp+25Of9SQzxHkBsVw0iArRFUYP6G15k
kNjYpuinFTw1XVDCFGPRIAhySnERlkv6WNyQQC87QTVJITKkz3R5cv4gwFG0kjAi
Va4nIJs2CctcizuEaPlwnEFrZ99gcB7RYPSUQVGAbfkqt2bhy/xGr+Jlp4kqPfS5
iPomwfcDwEnDbmcM8S2adPWtZ+oHskxZQmJ6+jhGgM73AgMBAAGjUzBRMB0GA1Ud
DgQWBBRHz2QAo1z8r9BewZro+HYv18AxTzAfBgNVHSMEGDAWgBRHz2QAo1z8r9Be
wZro+HYv18AxTzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCz
BTuZI7VymDWerWLfHMWyogoJWOkFB2yEpQe7J+LjS8yZmJg4CYpA4JJ+uM2sBm2Q
yL6M57ZmSY6EFoYeYw3gRfwGC32qJHirhsWvrjUpRC5+4YT9P6fNmgm5aD27JZao
bjyNA9Vy9SCL4JMeWET2w9VGNDaYQCs0x57HZioxYRMSD5vMVbirvCtqX7H3F/X+
r/VHEqEae7tVtuAB2D2GdcFzslCRb9uomuVfLJNqR6Nz1Tw+2adyySijRMCDdpRl
Pg9MBv4sevL6F4C1vUqUG1LXzcfHLFtrV1oUIEpJ0frxAgpdhSbnHiQa64cKX3N0
CsS6VALipGFmxj01+jD0Vhhf4rjjTT5C3Ag4WTqI98Fu4RMW35eBstnt6UUWyJQO
Q1skk+hg0ynfb3lO8OIZ4sDkmxDqAOQXeMMo1tU2YMgNA5Lv1FyO9Silc0VlkOiO
ft1RC8UbECqYyTvz7SNrv8aQP6EUoNSpxQHyBHOQy65dyOLOdP4S+PccUwsdxv/N
O5eN9ndMWqNvnyPKyQ3M+MLVvkCR1vDb6ABgPhH17BLkj8fWQgy5lhjJy5a8VHlO
1VDzV1Xeezy/MYCpS+TamaWTXscbhLMzWWiiAiDT8dltKw4G6U+g7DiF80kM59L5
D1hOs4gOQ853+83L/Ej4ESTj0B04NLVMlzMGtl3qcA==
-----END CERTIFICATE-----

View File

@@ -1,19 +1,18 @@
{
"data": "ENC[AES256_GCM,data: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,iv:q3j8EAokyyxiszf+wyRqxEr2igaD1bX7YnFx/NbsGg8=,tag:HKKYWRJEUwW2/TxL+5dSng==,type:str]",
"data": "ENC[AES256_GCM,data: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,iv:dGSmyDNBdVyF54bYS/Zxm2NNXZyGtLjkyYlrI9/nKvc=,tag:ip2fy76NjObWbW20HyuZUA==,type:str]",
"sops": {
"age": [
{
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaeXRjU214aWk5ajl1aW9E\naGJlb1ViaVRmMTBHdkFDQUNDZS94WFZiNUNvCllmWTJBck9hR3U3V09VWDZwQ2xI\nd3ZEQnBIUG5ZSTVIdS8rQ2FMYVhyNk0KLS0tIEE1UG8rSzFyU01sVXhGVHpoaE9i\nSis4Qi9tMGFqbTNMTDZUVk1ZdXkrM28Km4VkfaOsZ69ckjvrg+os43H/O1IoWHzC\nt4LqZRz1Tk7/d1aLWavSPPjVYrCOMZeNBqGbQpGfjjuXrafClRNQdQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQS96MUFubVdOR2VCc2xO\ncTc5QnNHNTFpdURnSnF3dVhBQXQ3bnBuRW1RCngzSVlhSW9rNUxoSWdKcEtKVXc3\nQitLZ2NDUXBSUmxtVWpYRUlvOHVXcW8KLS0tIGZaWlRVak9NYmt2elpwYStYenRE\nanlkT3BET1FjQ2lFZkp3SXFMSkJSaVkKKkr+MNNqs6Ve3K5OrZfBEGlnc7OAthqf\nOZrP9NYOTMgkvhFsZTVpUS0zskry0iwmTNt+KeluYf0Tko8K53Kx2A==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3R1RHTGViTnRLVVkyM3J0\nbm96cGVPTlo4NXBNL0g1eEVSNG9DUkgwVFRBCmRKVTlMRmV3Tmg2RTZIclBlWlcr\ndzI5MUxhcllzbE1IMDNxa08zVkpITmsKLS0tIG01Y2dyQkY3UmRudFk2d0p6bThn\nemlaWnZoS3p4VHhMTFFwTm9VN0ttYzQKVbLFgtK6NIRIiryWHeeOPD45iwUds4QD\n7b8xYYoxlo+DETggxK6Vz3IdT/BSK5bFtgAxl864b5gW+Aw4c6AO5w==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXd2dVYmJIbUVVeXk5Nk1E\nekFiUldVVUhRTmE4dHRiTHNDdEMyS1pRV1RrCkNScGdXVSs4UU5id29DV0pZWDQr\nenV1QmpnOFk5aFpTTUxmb0hDVHZDdFkKLS0tIHpmalJtRC94bEhaUStmeUlHT21w\nd3o3UzJHZklxK0RCYUUxc2c3aG1XclkKEPq1ZgyGiAK/Hy4zT7wfdDfPEE3vMHpR\nzwQV5y3M3DmlnKQEvJu0DpQ334CyAcubZC7cswQdUrM8TPqJhb/TuA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-09-18T14:33:37Z",
"mac": "ENC[AES256_GCM,data:XKCnd0QrAlOCECSeSvbLYHMLbmUh4fMRnLaTb5ARoP4Zc9joWGsCaRZxokc2/sG4BXA/6pkbQXHyIOudKbcBpVjjvs9E+6Mnzt53nfRoH/iOkYPbN2EO49okVZJXW0M1rlBxrxvGuiDlz2p2p6L7neKLy4EB482pYea5+dUr2Yw=,iv:oj/MkZCfkvCmAb79uzEvKwEAm1bKtWhS4rPRAWSgRgw=,tag:h5TPPILXkhJplnDT2Gqtfw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.10.2"
"lastmodified": "2025-10-21T13:17:17Z",
"mac": "ENC[AES256_GCM,data:wdAFURkJZvclbz3UFPSPV9fma7zrZVEhMhsRqylGQMLepX/WohEAr8nJgeHl05be1Q8M8biPXCCoL0vfwg4BRZOkhD8PusJh8iBI3+STNQe/S1qoIK1ByfBFhJD+tIsVsgduLp6G32e6SRNvkuX3UpJqyViuRUavfQd3b8LRU4I=,iv:S3sMNTz5Kg4TxHj1tnk/ayiFuO74dR4aPnnomtkGByo=,tag:uive2bYe42s6VtPd03jTMw==,type:str]",
"version": "3.11.0"
}
}

View File

@@ -29,7 +29,7 @@
};
perInstance =
{ settings, ... }:
{ settings, roles, ... }:
{
nixosModule =
{
@@ -38,8 +38,19 @@
pkgs,
...
}:
let
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
# Collect searchDomains from all servers in this instance
allServerSearchDomains = lib.flatten (
lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) (
roles.server.machines or { }
)
);
# Merge client's searchDomains with all servers' searchDomains
searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains);
in
{
clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) {
share = true;
files.id_ed25519.deploy = false;
files."id_ed25519.pub" = {
@@ -54,9 +65,9 @@
'';
};
programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) {
programs.ssh.knownHosts.ssh-ca = lib.mkIf (searchDomains != [ ]) {
certAuthority = true;
extraHostNames = builtins.map (domain: "*.${domain}") settings.certificate.searchDomains;
extraHostNames = builtins.map (domain: "*.${domain}") searchDomains;
publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value;
};
};

View File

@@ -22,6 +22,7 @@ in
../../clanServices/syncthing
# Required modules
../../nixosModules/clanCore
../../nixosModules/machineModules
# Dependencies like clan-cli
../../pkgs/clan-cli
];

View File

@@ -1,8 +1,5 @@
🚧🚧🚧 Experimental 🚧🚧🚧
Use at your own risk.
We are still refining its interfaces, instability and breakages are expected.
!!! Danger "Experimental"
This service is experimental and will change in the future.
---

View File

@@ -41,14 +41,14 @@ let
# In this case it is 'self-zerotier-redux'
# This is usually only used internally, but we can use it to test the evaluation of service module in isolation
# evaluatedService =
# testFlake.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-zerotier-redux.config;
# testFlake.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-zerotier-redux.config;
in
{
test_simple = {
inherit testFlake;
expr =
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.self-wifi.config;
testFlake.config.clan.clanInternals.inventoryClass.distributedServices.servicesEval.config.mappedServices.self-wifi.config;
expected = 1;
# expr = {

View File

@@ -54,7 +54,10 @@
- For other controllers: The controller's /56 subnet
*/
{ ... }:
{
clanLib,
...
}:
let
# Shared module for extraHosts configuration
extraHostsModule =
@@ -74,10 +77,12 @@ let
controllerHosts = lib.mapAttrsToList (
name: _value:
let
prefix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
);
prefix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-network-${instanceName}";
file = "prefix";
};
# Controller IP is always ::1 in their subnet
ip = prefix + "::1";
in
@@ -88,20 +93,24 @@ let
peerHosts = lib.mapAttrsToList (
peerName: peerValue:
let
peerSuffix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${peerName}/wireguard-network-${instanceName}/suffix/value"
);
peerSuffix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = peerName;
generator = "wireguard-network-${instanceName}";
file = "suffix";
};
# Determine designated controller
designatedController =
if (builtins.length (builtins.attrNames roles.controller.machines) == 1) then
(builtins.head (builtins.attrNames roles.controller.machines))
else
peerValue.settings.controller;
controllerPrefix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${designatedController}/wireguard-network-${instanceName}/prefix/value"
);
controllerPrefix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = designatedController;
generator = "wireguard-network-${instanceName}";
file = "prefix";
};
peerIP = controllerPrefix + ":" + peerSuffix;
in
"${peerIP} ${peerName}.${domain}"
@@ -220,10 +229,12 @@ in
lib.mapAttrsToList (
ctrlName: _:
let
controllerPrefix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
);
controllerPrefix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = ctrlName;
generator = "wireguard-network-${instanceName}";
file = "prefix";
};
peerIP = controllerPrefix + ":" + peerSuffix;
in
"${peerIP}/56"
@@ -234,20 +245,22 @@ in
# Connect to all controllers
peers = lib.mapAttrsToList (name: value: {
publicKey = (
builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
)
);
publicKey = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-keys-${instanceName}";
file = "publickey";
};
# Allow each controller's /56 subnet
allowedIPs = [
"${
builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
)
clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-network-${instanceName}";
file = "prefix";
}
}::/56"
];
@@ -349,25 +362,29 @@ in
if allPeers ? ${name} then
# For peers: they now have our entire /56 subnet
{
publicKey = (
builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
)
);
publicKey = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-keys-${instanceName}";
file = "publickey";
};
# Allow the peer's /96 range in ALL controller subnets
allowedIPs = lib.mapAttrsToList (
ctrlName: _:
let
controllerPrefix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
);
peerSuffix = builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/suffix/value"
);
controllerPrefix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = ctrlName;
generator = "wireguard-network-${instanceName}";
file = "prefix";
};
peerSuffix = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-network-${instanceName}";
file = "suffix";
};
in
"${controllerPrefix}:${peerSuffix}/96"
) roles.controller.machines;
@@ -377,19 +394,21 @@ in
else
# For other controllers: use their /56 subnet
{
publicKey = (
builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
)
);
publicKey = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-keys-${instanceName}";
file = "publickey";
};
allowedIPs = [
"${
builtins.readFile (
config.clan.core.settings.directory
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
)
clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "wireguard-network-${instanceName}";
file = "prefix";
}
}::/56"
];

View File

@@ -1,7 +1,20 @@
!!! Danger "Experimental"
This service is experimental and will change in the future.
---
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan.
Yggdrasil is designed to be a future-proof and decentralised alternative to
the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines.
Yggdrasil is designed to be a future-proof and decentralised alternative to the
structured routing protocols commonly used today on the internet. Inside your
clan, it will allow you to reach all of your machines.
If you have other services in your inventory which export peers (e.g. the
`internet` or the services) as [service
exports](https://docs.clan.lol/reference/options/clan_service/#exports), they
will be added as yggdrasil peers automatically. This allows using the stable
yggdrasil IPv6 address to refer to other hosts and letting yggdrasil decide on
the best routing based on available connections.
## Example Usage

View File

@@ -29,12 +29,13 @@
];
};
options.peers = lib.mkOption {
options.extraPeers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Static peers to configure for this host.
If not set, local peers will be auto-discovered
Additional static peers to configure for this host. If you use a
VPN clan service, it will automatically be added as peers to other hosts.
Local peers are also auto-discovered and don't need to be added.
'';
example = [
"tcp://192.168.1.1:6443"
@@ -45,16 +46,67 @@
};
};
perInstance =
{ settings, ... }:
{
settings,
roles,
exports,
...
}:
{
nixosModule =
{
config,
pkgs,
lib,
clan-core,
...
}:
let
mkPeers = ip: [
# "tcp://${ip}:6443"
"quic://${ip}:6443"
"ws://${ip}:6443"
"tls://${ip}:6443"
];
select' = clan-core.inputs.nix-select.lib.select;
# TODO make it nicer @lassulus, @picnoir wants microlens
# Get a list of all exported IPs from all VPN modules
exportedPeerIPs = builtins.foldl' (
acc: e:
if e == { } then
acc
else
acc ++ (lib.flatten (builtins.filter (s: s != "") (lib.attrValues (select' "peers.*.plain" e))))
) [ ] (lib.attrValues (select' "instances.*.networking.?peers.*.host.?plain" exports));
# Construct a list of peers in yggdrasil format
exportedPeers = lib.flatten (map mkPeers exportedPeerIPs);
in
{
# Set <yggdrasil ip> <hostname>.<tld> for all hosts.
# Networking modules will then add themselves as peers, so we can
# always use this to resolve a host via the best possible route,
# doing fail-over if needed.
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.filter (n: n != "") (
map (
name:
let
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/yggdrasil/address/value";
in
if builtins.pathExists ipPath then
"${builtins.readFile ipPath} ${name}.${config.clan.core.settings.tld}"
else
""
) (lib.attrNames roles.default.machines)
)
);
clan.core.vars.generators.yggdrasil = {
files.privateKey = { };
@@ -99,7 +151,7 @@
settings = {
PrivateKeyPath = "/key";
IfName = "ygg";
Peers = settings.peers;
Peers = lib.lists.unique (exportedPeers ++ settings.extraPeers);
MulticastInterfaces = [
# Ethernet is preferred over WIFI
{

View File

@@ -17,6 +17,13 @@
roles.default.machines.peer1 = { };
roles.default.machines.peer2 = { };
};
# Peers are set form exports of the internet service
instances."internet" = {
module.name = "internet";
roles.default.machines.peer1.settings.host = "peer1";
roles.default.machines.peer2.settings.host = "peer2";
};
};
};

View File

@@ -1,4 +1,7 @@
{ ... }:
{
clanLib,
...
}:
{
_class = "clan.service";
manifest.name = "clan-core/zerotier";
@@ -39,6 +42,7 @@
imports = [
(import ./shared.nix {
inherit
clanLib
instanceName
roles
config
@@ -90,6 +94,7 @@
imports = [
(import ./shared.nix {
inherit
clanLib
instanceName
roles
config
@@ -142,6 +147,7 @@
imports = [
(import ./shared.nix {
inherit
clanLib
instanceName
roles
config
@@ -160,15 +166,16 @@
);
networkIps = builtins.foldl' (
ips: name:
if
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
then
ips
++ [
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
]
else
ips
let
ztIp = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "zerotier";
file = "zerotier-ip";
default = null;
};
in
if ztIp != null then ips ++ [ ztIp ] else ips
) [ ] machines;
allHostIPs = settings.allowedIps ++ networkIps;
in

View File

@@ -21,6 +21,7 @@ in
../../clanServices/zerotier
# Required modules
../../nixosModules/clanCore
../../nixosModules/machineModules
# Dependencies like clan-cli
../../pkgs/clan-cli
];

View File

@@ -1,4 +1,5 @@
{
clanLib,
lib,
config,
pkgs,
@@ -8,20 +9,26 @@
}:
let
controllerMachine = builtins.head (lib.attrNames roles.controller.machines or { });
networkIdPath = "${config.clan.core.settings.directory}/vars/per-machine/${controllerMachine}/zerotier/zerotier-network-id/value";
networkId = if builtins.pathExists networkIdPath then builtins.readFile networkIdPath else null;
networkId = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = controllerMachine;
generator = "zerotier";
file = "zerotier-network-id";
default = null;
};
moons = lib.attrNames (roles.moon.machines or { });
moonIps = builtins.foldl' (
ips: name:
if
builtins.pathExists "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value"
then
ips
++ [
(builtins.readFile "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value")
]
else
ips
let
moonIp = clanLib.vars.getPublicValue {
flake = config.clan.core.settings.directory;
machine = name;
generator = "zerotier";
file = "zerotier-ip";
default = null;
};
in
if moonIp != null then ips ++ [ moonIp ] else ips
) [ ] moons;
in
{

24
devFlake/flake.lock generated
View File

@@ -3,10 +3,10 @@
"clan-core-for-checks": {
"flake": false,
"locked": {
"lastModified": 1760361585,
"narHash": "sha256-v4PnSmt1hXW4dSgVWxcd1ZeEBlhO7NksNRC5cX7L5iw=",
"lastModified": 1761204206,
"narHash": "sha256-A4KDudGblln1yh8c95OVow2NRlHtbGZXr/pgNenyrNc=",
"ref": "main",
"rev": "7e7e58eb64ef61beb0a938a6622ec0122382131b",
"rev": "aabbe0dfac47b7cfbe2210bcb27fb7ecce93350f",
"shallow": true,
"type": "git",
"url": "https://git.clan.lol/clan/clan-core"
@@ -105,11 +105,11 @@
},
"nixpkgs-dev": {
"locked": {
"lastModified": 1760965023,
"narHash": "sha256-cpcgkeLApMGFCdp4jFqeIxTwlcGaSI+Zwmv8z2E85pY=",
"lastModified": 1762168314,
"narHash": "sha256-+DX6mIF47gRGoK0mqkTg1Jmcjcup0CAXJFHVkdUx8YA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "40ef6b9aa73f70b265c29df083fafae66b9df351",
"rev": "94fc102d2c15d9c1a861e59de550807c65358e1b",
"type": "github"
},
"original": {
@@ -128,11 +128,11 @@
]
},
"locked": {
"lastModified": 1760652422,
"narHash": "sha256-C88Pgz38QIl9JxQceexqL2G7sw9vodHWx1Uaq+NRJrw=",
"lastModified": 1761730856,
"narHash": "sha256-t1i5p/vSWwueZSC0Z2BImxx3BjoUDNKyC2mk24krcMY=",
"owner": "NuschtOS",
"repo": "search",
"rev": "3ebeebe8b6a49dfb11f771f761e0310f7c48d726",
"rev": "e29de6db0cb3182e9aee75a3b1fd1919d995d85b",
"type": "github"
},
"original": {
@@ -208,11 +208,11 @@
"nixpkgs": []
},
"locked": {
"lastModified": 1760945191,
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"lastModified": 1761311587,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
"type": "github"
},
"original": {

View File

@@ -88,6 +88,7 @@ For the provide flake example, your flake should now look like this:
self = self; # this needs to point at the repository root
specialArgs = {};
meta.name = throw "Change me to something unique";
meta.tld = throw "Change me to something unique";
machines = {
berlin = {

View File

@@ -51,7 +51,7 @@ Make sure you have the following:
**Note:** This creates a new directory in your current location
```shellSession
nix run https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli --refresh -- flakes create
nix run "https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli" --refresh -- flakes create
```
3. Enter a **name** in the prompt:
@@ -137,12 +137,13 @@ Description: None
This confirms your setup is working correctly.
You can now change the default name by editing the `meta.name` field in your `clan.nix` file.
You can now change the default name and tld by editing the `meta.name` and `meta.tld` fields in your `clan.nix` file.
```{.nix title="clan.nix" hl_lines="3"}
```{.nix title="clan.nix" hl_lines="3 4"}
{
# Ensure this is unique among all clans you want to use.
meta.name = "__CHANGE_ME__";
meta.tld = "changeme";
# ...
# elided

View File

@@ -10,10 +10,11 @@ and how to define a remote builder for your machine closures.
Set the machines `targetHost` to the reachable IP address of the new machine.
This eliminates the need to specify `--target-host` in CLI commands.
```{.nix title="clan.nix" hl_lines="9"}
```{.nix title="clan.nix" hl_lines="10"}
{
# Ensure this is unique among all clans you want to use.
meta.name = "my-clan";
meta.tld = "ccc";
inventory.machines = {
# Define machines here.

View File

@@ -150,10 +150,61 @@ Those are very similar to NixOS VM tests, as in they run virtualized nixos machi
As of now the container test driver is a downstream development in clan-core.
Basically everything stated under the NixOS VM tests sections applies here, except some limitations.
Limitations:
### Using Container Tests vs VM Tests
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the container.
- setuid binaries don't work
Container tests are **enabled by default** for all tests using the clan testing framework.
They offer significant performance advantages over VM tests:
- **Faster startup**
- **Lower resource usage**: No full kernel boot or hardware emulation overhead
To control whether a test uses containers or VMs, use the `clan.test.useContainers` option:
```nix
{
clan = {
directory = ./.;
test.useContainers = true; # Use containers (default)
# test.useContainers = false; # Use VMs instead
};
}
```
**When to use VM tests instead of container tests:**
- Testing kernel features, modules, or boot processes
- Testing hardware-specific features
- When you need full system isolation
### System Requirements for Container Tests
Container tests require the **`uid-range`** system feature** in the Nix sandbox.
This feature allows Nix to allocate a range of UIDs for containers to use, enabling `systemd-nspawn` containers to run properly inside the Nix build sandbox.
**Configuration:**
The `uid-range` feature requires the `auto-allocate-uids` setting to be enabled in your Nix configuration.
To verify or enable it, add to your `/etc/nix/nix.conf` or NixOS configuration:
```nix
settings.experimental-features = [
"auto-allocate-uids"
];
nix.settings.auto-allocate-uids = true;
nix.settings.system-features = [ "uid-range" ];
```
**Technical details:**
- Container tests set `requiredSystemFeatures = [ "uid-range" ];` in their derivation (see `lib/test/container-test-driver/driver-module.nix:98`)
- Without this feature, containers cannot properly manage user namespaces and will fail to start
### Limitations
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the containers.
- Early implementation and limited by features.
### Where to find examples for NixOS container tests

View File

@@ -60,6 +60,7 @@ Configure Clan-wide settings and define machines. Here's an example `flake.nix`:
# Define your Clan
clan = {
meta.name = ""; # Required and must be unique
meta.tld = ""; # Required and must be unique
machines = {
jon = {

View File

@@ -43,6 +43,7 @@ For the purpose of this guide we have two machines:
inherit self;
meta.name = "myclan";
meta.tld = "ccc";
inventory.machines = {
controller = {};

View File

@@ -63,6 +63,7 @@ To use `age` plugins with Clan, you need to configure them in your `flake.nix` f
inherit self;
meta.name = "myclan";
meta.tld = "ccc";
# Add YubiKey and FIDO2 HMAC plugins
# Note: Plugins must be available in nixpkgs.

42
flake.lock generated
View File

@@ -31,11 +31,11 @@
]
},
"locked": {
"lastModified": 1760701190,
"narHash": "sha256-y7UhnWlER8r776JsySqsbTUh2Txf7K30smfHlqdaIQw=",
"lastModified": 1761899396,
"narHash": "sha256-XOpKBp6HLzzMCbzW50TEuXN35zN5WGQREC7n34DcNMM=",
"owner": "nix-community",
"repo": "disko",
"rev": "3a9450b26e69dcb6f8de6e2b07b3fc1c288d85f5",
"rev": "6f4cf5abbe318e4cd1e879506f6eeafd83f7b998",
"type": "github"
},
"original": {
@@ -51,11 +51,11 @@
]
},
"locked": {
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"lastModified": 1762040540,
"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"rev": "0010412d62a25d959151790968765a70c436598b",
"type": "github"
},
"original": {
@@ -71,11 +71,11 @@
]
},
"locked": {
"lastModified": 1760721282,
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
"lastModified": 1762039661,
"narHash": "sha256-oM5BwAGE78IBLZn+AqxwH/saqwq3e926rNq5HmOulkc=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
"rev": "c3c8c9f2a5ed43175ac4dc030308756620e6e4e4",
"type": "github"
},
"original": {
@@ -99,11 +99,11 @@
},
"nixos-facter-modules": {
"locked": {
"lastModified": 1756491981,
"narHash": "sha256-lXyDAWPw/UngVtQfgQ8/nrubs2r+waGEYIba5UX62+k=",
"lastModified": 1761137276,
"narHash": "sha256-4lDjGnWRBLwqKQ4UWSUq6Mvxu9r8DSqCCydodW/Jsi8=",
"owner": "nix-community",
"repo": "nixos-facter-modules",
"rev": "c1b29520945d3e148cd96618c8a0d1f850965d8c",
"rev": "70bcd64225d167c7af9b475c4df7b5abba5c7de8",
"type": "github"
},
"original": {
@@ -115,10 +115,10 @@
"nixpkgs": {
"locked": {
"lastModified": 315532800,
"narHash": "sha256-yDxtm0PESdgNetiJN5+MFxgubBcLDTiuSjjrJiyvsvM=",
"rev": "d7f52a7a640bc54c7bb414cca603835bf8dd4b10",
"narHash": "sha256-LDT9wuUZtjPfmviCcVWif5+7j4kBI2mWaZwjNNeg4eg=",
"rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre871443.d7f52a7a640b/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre887438.a7fc11be66bd/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@@ -146,11 +146,11 @@
]
},
"locked": {
"lastModified": 1760845571,
"narHash": "sha256-PwGzU3EOU65Ef1VvuNnVLie+l+P0g/fzf/PGUG82KbM=",
"lastModified": 1760998189,
"narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "9c9a9798be331ed3f4b2902933d7677d0659ee61",
"rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3",
"type": "github"
},
"original": {
@@ -181,11 +181,11 @@
]
},
"locked": {
"lastModified": 1760945191,
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"lastModified": 1761311587,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
"type": "github"
},
"original": {

View File

@@ -68,6 +68,7 @@
(
{ ... }:
{
debug = true;
clan = {
meta.name = "clan-core";
inventory = {
@@ -98,6 +99,7 @@
./lib/filter-clan-core/flake-module.nix
./lib/flake-module.nix
./lib/flake-parts/clan-nixos-test.nix
./modules/flake-module.nix
./nixosModules/clanCore/vars/flake-module.nix
./nixosModules/flake-module.nix
./pkgs/clan-cli/clan_cli/tests/flake-module.nix

View File

@@ -39,32 +39,10 @@ in
};
modules = [
clan-core.modules.clan.default
{
checks.minNixpkgsVersion = {
assertion = lib.versionAtLeast nixpkgs.lib.version "25.11";
message = ''
Nixpkgs version: ${nixpkgs.lib.version} is incompatible with clan-core. (>= 25.11 is recommended)
---
Your version of 'nixpkgs' seems too old for clan-core.
Please read: https://docs.clan.lol/guides/nixpkgs-flake-input
You can ignore this check by setting:
clan.checks.minNixpkgsVersion.ignore = true;
---
'';
};
}
];
};
apply =
config:
lib.deepSeq (lib.mapAttrs (
id: check:
if check.ignore || check.assertion then
null
else
throw "clan.checks.${id} failed with message\n${check.message}"
) config.checks) config;
# Important: !This logic needs to be kept in sync with lib.clan function!
apply = config: clan-core.lib.checkConfig config.checks config;
};
# Mapped flake toplevel outputs

19
lib/clan/checkConfig.nix Normal file
View File

@@ -0,0 +1,19 @@
{ lib, ... }:
/**
Function to assert clan configuration checks.
Arguments:
- 'checks' attribute of clan configuration
- Any: the returned configuration (can be anything, is just passed through)
*/
checks:
lib.deepSeq (
lib.mapAttrs (
id: check:
if check.ignore || check.assertion then
null
else
throw "clan.checks.${id} failed with message\n${check.message}"
) checks
)

View File

@@ -33,20 +33,23 @@
let
nixpkgs = self.inputs.nixpkgs or clan-core.inputs.nixpkgs;
nix-darwin = self.inputs.nix-darwin or clan-core.inputs.nix-darwin;
configuration = (
lib.evalModules {
class = "clan";
specialArgs = {
inherit
self
;
inherit
nixpkgs
nix-darwin
;
};
modules = [
clan-core.modules.clan.default
m
];
}
);
in
lib.evalModules {
class = "clan";
specialArgs = {
inherit
self
;
inherit
nixpkgs
nix-darwin
;
};
modules = [
clan-core.modules.clan.default
m
];
}
clan-core.clanLib.checkConfig configuration.config.checks configuration

View File

@@ -137,6 +137,12 @@ in
default = { };
type = types.submoduleWith {
specialArgs = {
self = throw ''
'self' is banned in the use of clan.services
Use 'exports' instead: https://docs.clan.lol/reference/options/clan_service/#exports
---
If you really need to used 'self' here, that makes the module less portable
'';
inherit (config.clanSettings)
clan-core
nixpkgs

View File

@@ -16,10 +16,12 @@ lib.fix (
*/
callLib = file: args: import file ({ inherit lib clanLib; } // args);
evalService = clanLib.callLib ./modules/inventory/distributed-service/evalService.nix { };
checkConfig = clanLib.callLib ./clan/checkConfig.nix { };
evalService = clanLib.callLib ./evalService.nix { };
# ------------------------------------
# ClanLib functions
inventory = clanLib.callLib ./modules/inventory { };
inventory = clanLib.callLib ./inventory { };
test = clanLib.callLib ./test { };
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
# Custom types
@@ -30,6 +32,8 @@ lib.fix (
jsonschema = import ./jsonschema { inherit lib; };
docs = import ./docs.nix { inherit lib; };
vars = import ./vars.nix { inherit lib; };
# flakes
flakes = clanLib.callLib ./flakes.nix { };

View File

@@ -2,7 +2,7 @@
lib ? import <nixpkgs/lib>,
}:
let
clanLibOrig = (import ./.. { inherit lib; }).__unfix__;
clanLibOrig = (import ./. { inherit lib; }).__unfix__;
clanLibWithFs =
{ virtual_fs }:
lib.fix (
@@ -11,19 +11,19 @@ let
let
clan-core = {
clanLib = final;
modules.clan.default = lib.modules.importApply ./clan { inherit clan-core; };
modules.clan.default = lib.modules.importApply ../modules/clan { inherit clan-core; };
# Note: Can add other things to "clan-core"
# ... Not needed for this test
};
in
{
clan = import ../clan {
clan = import ./clan {
inherit lib clan-core;
};
# Override clanLib.fs for unit-testing against a virtual filesystem
fs = import ../clanTest/virtual-fs.nix { inherit lib; } {
fs = import ./clanTest/virtual-fs.nix { inherit lib; } {
inherit rootPath virtual_fs;
# Example of a passthru
# passthru = [
@@ -53,7 +53,12 @@ in
};
};
}).clan
{ config.directory = rootPath; };
{
directory = rootPath;
self = {
inputs.nixpkgs.lib.version = "25.11";
};
};
in
{
inherit vclan;
@@ -94,7 +99,12 @@ in
};
};
}).clan
{ config.directory = rootPath; };
{
directory = rootPath;
self = {
inputs.nixpkgs.lib.version = "25.11";
};
};
in
{
inherit vclan;

View File

@@ -17,9 +17,9 @@ lib.evalModules {
specialArgs._ctx = prefix;
modules = [
# Base module
./service-module.nix
./inventory/distributed-service/service-module.nix
# Feature modules
(lib.modules.importApply ./api-feature.nix {
(lib.modules.importApply ./inventory/distributed-service/api-feature.nix {
inherit clanLib prefix;
})
]

View File

@@ -10,12 +10,11 @@ in
rec {
# TODO: automatically generate this from the directory conventions
imports = [
./modules/flake-module.nix
./clanTest/flake-module.nix
./introspection/flake-module.nix
./modules/inventory/flake-module.nix
./jsonschema/flake-module.nix
./types/flake-module.nix
./inventory/flake-module.nix
];
flake.clanLib =
let
@@ -78,9 +77,6 @@ rec {
../lib
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../.)
../flakeModules
# ../../nixosModules/clanCore
# ../../machines
# ../../inventory.json
];
};
in
@@ -101,6 +97,36 @@ rec {
touch $out
'';
};
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests-build-clan
legacyPackages.evalTests-build-clan = import ./tests.nix {
inherit lib;
clan-core = self;
};
checks = {
eval-lib-build-clan = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
nix-unit --eval-store "$HOME" \
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${
self.filter {
include = [
"flakeModules"
"inventory.json"
"lib"
"machines"
"nixosModules"
"modules"
];
}
}#legacyPackages.${system}.evalTests-build-clan
touch $out
'';
};
};
}

View File

@@ -2,15 +2,11 @@
lib,
clanLib,
}:
let
services = clanLib.callLib ./distributed-service/inventory-adapter.nix { };
in
{
inherit (services) mapInstances;
inventoryModule = {
_file = "clanLib.inventory.module";
imports = [
../inventoryClass/inventory.nix
../../modules/inventoryClass/inventory.nix
];
_module.args = { inherit clanLib; };
};

View File

@@ -28,9 +28,9 @@ in
elemType = submoduleWith {
class = "clan.service";
specialArgs = {
exports = config.exports;
directory = directory;
clanLib = specialArgs.clanLib;
exports = config.exports;
};
modules = [
(

View File

@@ -13,16 +13,18 @@ in
let
# Common filtered source for inventory tests
inventoryTestsSrc = lib.fileset.toSource {
root = ../../../..;
root = ../../..;
fileset = lib.fileset.unions [
../../../../flake.nix
../../../../flake.lock
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../../..)
../../../../flakeModules
../../../../lib
../../../../nixosModules/clanCore
../../../../machines
../../../../inventory.json
../../../flake.nix
../../../flake.lock
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
../../../flakeModules
../../../lib
../../../nixosModules/clanCore
../../../nixosModules/machineModules
../../../machines
../../../inventory.json
../../../modules
];
};
in

View File

@@ -81,6 +81,7 @@ let
applySettings =
instanceName: instance:
lib.mapAttrs (roleName: role: {
settings = config.instances.${instanceName}.roles.${roleName}.finalSettings.config;
machines = lib.mapAttrs (machineName: _v: {
settings =
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config;
@@ -158,6 +159,29 @@ in
(
{ name, ... }@role:
{
options.finalSettings = mkOption {
default = evalMachineSettings instance.name role.name null role.config.settings { };
type = types.raw;
description = ''
Final evaluated settings of the curent-machine
This contains the merged and evaluated settings of the role interface,
the role settings and the machine settings.
Type: 'configuration' as returned by 'lib.evalModules'
'';
apply = lib.warn ''
=== WANRING ===
'roles.<roleName>.settings' do not contain machine specific settings.
Prefer `machines.<machineName>.settings` instead. (i.e `perInstance: roles.<roleName>.machines.<machineName>.settings`)
If you have a use-case that requires access to the original role settings without machine overrides.
Contact us via matrix (https://matrix.to/#/#clan:clan.lol) or file an issue: https://git.clan.lol
This feature will be removed in the next release
'';
};
# instances.{instanceName}.roles.{roleName}.machines
options.machines = mkOption {
description = ''
@@ -216,7 +240,7 @@ in
options.extraModules = lib.mkOption {
default = [ ];
type = types.listOf (types.either types.deferredModule types.str);
type = types.listOf types.deferredModule;
};
}
)
@@ -483,6 +507,9 @@ in
type = types.deferredModule;
default = { };
description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
export modules defined in 'perInstance'
mapped to their instance name
@@ -611,6 +638,9 @@ in
type = types.deferredModule;
default = { };
description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
export modules defined in 'perMachine'
mapped to their machine name
@@ -712,6 +742,9 @@ in
exports = mkOption {
description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
This services exports.
Gets merged with all other services exports.
@@ -850,7 +883,11 @@ in
instanceRes.nixosModule
]
++ (map (
s: if builtins.typeOf s == "string" then "${directory}/${s}" else s
s:
if builtins.typeOf s == "string" then
lib.warn "String types for 'extraModules' will be deprecated - ${s}" "${directory}/${s}"
else
lib.setDefaultModuleLocation "via inventory.instances.${instanceName}.roles.${roleName}" s
) instanceCfg.roles.${roleName}.extraModules);
};
}

View File

@@ -4,63 +4,53 @@
...
}:
let
inherit (lib)
evalModules
;
evalInventory =
m:
(evalModules {
# Static modules
modules = [
clanLib.inventory.inventoryModule
{
_file = "test file";
tags.all = [ ];
tags.nixos = [ ];
tags.darwin = [ ];
}
{
modules.test = { };
}
m
];
}).config;
callInventoryAdapter =
inventoryModule:
let
inventory = evalInventory inventoryModule;
flakeInputsFixture = {
self.clan.modules = inventoryModule.modules or { };
# Example upstream module
upstream.clan.modules = {
uzzi = {
_class = "clan.service";
manifest = {
name = "uzzi-from-upstream";
};
};
flakeInputsFixture = {
upstream.clan.modules = {
uzzi = {
_class = "clan.service";
manifest = {
name = "uzzi-from-upstream";
};
};
in
clanLib.inventory.mapInstances {
directory = ./.;
clanCoreModules = { };
flakeInputs = flakeInputsFixture;
inherit inventory;
exportsModule = { };
};
};
createTestClan =
testClan:
let
res = clanLib.clan ({
# Static / mocked
specialArgs = {
clan-core = {
clan.modules = { };
};
};
self.inputs = flakeInputsFixture // {
self.clan = res.config;
};
directory = ./.;
exportsModule = { };
imports = [
testClan
];
});
in
res;
in
{
extraModules = import ./extraModules.nix { inherit clanLib; };
exports = import ./exports.nix { inherit lib clanLib; };
settings = import ./settings.nix { inherit lib callInventoryAdapter; };
specialArgs = import ./specialArgs.nix { inherit lib callInventoryAdapter; };
resolve_module_spec = import ./import_module_spec.nix { inherit lib callInventoryAdapter; };
settings = import ./settings.nix { inherit lib createTestClan; };
specialArgs = import ./specialArgs.nix { inherit lib createTestClan; };
resolve_module_spec = import ./import_module_spec.nix {
inherit lib createTestClan;
};
test_simple =
let
res = callInventoryAdapter {
res = createTestClan {
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
@@ -71,7 +61,7 @@ in
};
};
# User config
instances."instance_foo" = {
inventory.instances."instance_foo" = {
module = {
name = "simple-module";
};
@@ -81,7 +71,7 @@ in
{
# Test that the module is mapped into the output
# We might change the attribute name in the future
expr = res.importedModulesEvaluated ? "<clan-core>-simple-module";
expr = res.config._services.mappedServices ? "<clan-core>-simple-module";
expected = true;
inherit res;
};
@@ -92,7 +82,7 @@ in
# All instances should be included within one evaluation to make all of them available
test_module_grouping =
let
res = callInventoryAdapter {
res = createTestClan {
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
@@ -112,18 +102,19 @@ in
perMachine = { }: { };
};
# User config
instances."instance_foo" = {
inventory.instances."instance_foo" = {
module = {
name = "A";
};
};
instances."instance_bar" = {
inventory.instances."instance_bar" = {
module = {
name = "B";
};
};
instances."instance_baz" = {
inventory.instances."instance_baz" = {
module = {
name = "A";
};
@@ -133,16 +124,16 @@ in
{
# Test that the module is mapped into the output
# We might change the attribute name in the future
expr = lib.mapAttrs (_n: v: builtins.length v) res.grouped;
expected = {
"<clan-core>-A" = 2;
"<clan-core>-B" = 1;
};
expr = lib.attrNames res.config._services.mappedServices;
expected = [
"<clan-core>-A"
"<clan-core>-B"
];
};
test_creates_all_instances =
let
res = callInventoryAdapter {
res = createTestClan {
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
@@ -154,22 +145,24 @@ in
perMachine = { }: { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
inventory = {
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
};
};
instances."instance_bar" = {
module = {
name = "A";
input = "self";
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
};
};
instances."instance_zaza" = {
module = {
name = "B";
input = null;
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
};
};
};
@@ -177,7 +170,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.instances;
expr = lib.attrNames res.config._services.mappedServices.self-A.instances;
expected = [
"instance_bar"
"instance_foo"
@@ -187,7 +180,7 @@ in
# Membership via roles
test_add_machines_directly =
let
res = callInventoryAdapter {
res = createTestClan {
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
@@ -202,38 +195,40 @@ in
# perMachine = {}: {};
};
machines = {
jon = { };
sara = { };
hxi = { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
inventory = {
machines = {
jon = { };
sara = { };
hxi = { };
};
roles.peer.machines.jon = { };
};
instances."instance_bar" = {
module = {
name = "A";
input = "self";
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = { };
};
roles.peer.machines.sara = { };
};
instances."instance_zaza" = {
module = {
name = "B";
input = null;
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.sara = { };
};
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
roles.peer.tags.all = { };
};
};
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.result.allMachines;
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
expected = [
"jon"
"sara"
@@ -243,7 +238,7 @@ in
# Membership via tags
test_add_machines_via_tags =
let
res = callInventoryAdapter {
res = createTestClan {
# Authored module
# A minimal module looks like this
# It isn't exactly doing anything but it's a valid module that produces an output
@@ -257,35 +252,37 @@ in
# perMachine = {}: {};
};
machines = {
jon = {
tags = [ "foo" ];
inventory = {
machines = {
jon = {
tags = [ "foo" ];
};
sara = {
tags = [ "foo" ];
};
hxi = { };
};
sara = {
tags = [ "foo" ];
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
roles.peer.tags.foo = { };
};
hxi = { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
roles.peer.tags.foo = { };
};
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
};
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.result.allMachines;
expr = lib.attrNames res.config._services.mappedServices.self-A.result.allMachines;
expected = [
"jon"
"sara"
@@ -293,6 +290,9 @@ in
};
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; };
per_machine_args = import ./per_machine_args.nix { inherit lib createTestClan; };
per_instance_args = import ./per_instance_args.nix {
inherit lib;
callInventoryAdapter = createTestClan;
};
}

View File

@@ -1,4 +1,4 @@
{ callInventoryAdapter, ... }:
{ createTestClan, ... }:
let
# Authored module
# A minimal module looks like this
@@ -23,10 +23,13 @@ let
resolve =
spec:
callInventoryAdapter {
inherit modules machines;
instances."instance_foo" = {
module = spec;
createTestClan {
inherit modules;
inventory = {
inherit machines;
instances."instance_foo" = {
module = spec;
};
};
};
in
@@ -36,25 +39,16 @@ in
(resolve {
name = "A";
input = "self";
}).importedModuleWithInstances.instance_foo.resolvedModule;
expected = {
_class = "clan.service";
manifest = {
name = "network";
};
};
}).config._services.mappedServices.self-A.manifest.name;
expected = "network";
};
test_import_remote_module_by_name = {
expr =
(resolve {
name = "uzzi";
input = "upstream";
}).importedModuleWithInstances.instance_foo.resolvedModule;
expected = {
_class = "clan.service";
manifest = {
name = "uzzi-from-upstream";
};
};
}).config._services.mappedServices.upstream-uzzi.manifest.name;
expected = "uzzi-from-upstream";
};
}

View File

@@ -58,39 +58,43 @@ let
sara = { };
};
res = callInventoryAdapter {
inherit modules machines;
instances."instance_foo" = {
module = {
name = "A";
input = "self";
inherit modules;
inventory = {
inherit machines;
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = lib.mkForce "foo-peer-jon";
};
roles.peer = {
settings.timeout = "foo-peer";
};
roles.controller.machines.jon = { };
};
roles.peer.machines.jon = {
settings.timeout = lib.mkForce "foo-peer-jon";
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = "bar-peer-jon";
};
};
roles.peer = {
settings.timeout = "foo-peer";
# TODO: move this into a seperate test.
# Seperate out the check that this module is never imported
# import the module "B" (undefined)
# All machines have this instance
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
roles.controller.machines.jon = { };
};
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = "bar-peer-jon";
};
};
# TODO: move this into a seperate test.
# Seperate out the check that this module is never imported
# import the module "B" (undefined)
# All machines have this instance
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
};
@@ -105,9 +109,10 @@ in
{
# settings should evaluate
test_per_instance_arguments = {
inherit res;
expr = {
instanceName =
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances."instance_foo".allMachines.jon.passthru.instanceName;
# settings are specific.
# Below we access:
@@ -115,11 +120,11 @@ in
# roles = peer
# machines = jon
settings =
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.settings;
machine =
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.machine;
roles =
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.roles;
};
expected = {
instanceName = "instance_foo";
@@ -137,6 +142,7 @@ in
settings = { };
};
};
settings = { };
};
peer = {
machines = {
@@ -146,6 +152,9 @@ in
};
};
};
settings = {
timeout = "foo-peer";
};
};
};
settings = {
@@ -156,9 +165,9 @@ in
# TODO: Cannot be tested like this anymore
test_per_instance_settings_vendoring = {
x = res.importedModulesEvaluated.self-A;
x = res.config._services.mappedServices.self-A;
expr =
res.importedModulesEvaluated.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
res.config._services.mappedServices.self-A.result.allRoles.peer.allInstances.instance_foo.allMachines.jon.passthru.vendoredSettings;
expected = {
timeout = "config.thing";
};

View File

@@ -1,4 +1,4 @@
{ lib, callInventoryAdapter }:
{ lib, createTestClan }:
let
# Authored module
# A minimal module looks like this
@@ -39,36 +39,40 @@ let
jon = { };
sara = { };
};
res = callInventoryAdapter {
inherit modules machines;
instances."instance_foo" = {
module = {
name = "A";
input = "self";
res = createTestClan {
inherit modules;
inventory = {
inherit machines;
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = lib.mkForce "foo-peer-jon";
};
roles.peer = {
settings.timeout = "foo-peer";
};
};
roles.peer.machines.jon = {
settings.timeout = lib.mkForce "foo-peer-jon";
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = "bar-peer-jon";
};
};
roles.peer = {
settings.timeout = "foo-peer";
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
};
instances."instance_bar" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = {
settings.timeout = "bar-peer-jon";
};
};
instances."instance_zaza" = {
module = {
name = "B";
input = null;
};
roles.peer.tags.all = { };
};
};
in
@@ -79,7 +83,7 @@ in
inherit res;
expr = {
hasMachineSettings =
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon
? settings;
# settings are specific.
@@ -88,10 +92,10 @@ in
# roles = peer
# machines = jon
specificMachineSettings =
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer.machines.jon.settings;
hasRoleSettings =
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer
? settings;
# settings are specific.
@@ -100,20 +104,25 @@ in
# roles = peer
# machines = *
specificRoleSettings =
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
res.config._services.mappedServices.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
};
expected = rec {
expected = {
hasMachineSettings = true;
hasRoleSettings = false;
hasRoleSettings = true;
specificMachineSettings = {
timeout = "foo-peer-jon";
};
specificRoleSettings = {
machines = {
jon = {
settings = specificMachineSettings;
settings = {
timeout = "foo-peer-jon";
};
};
};
settings = {
timeout = "foo-peer";
};
};
};
};

View File

@@ -1,6 +1,6 @@
{ callInventoryAdapter, lib, ... }:
{ createTestClan, lib, ... }:
let
res = callInventoryAdapter {
res = createTestClan {
modules."A" = {
_class = "clan.service";
manifest = {
@@ -21,28 +21,31 @@ let
};
};
};
machines = {
jon = { };
sara = { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
inventory = {
machines = {
jon = { };
sara = { };
};
# Settings for both jon and sara
roles.peer.settings = {
timeout = 40;
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
# Settings for both jon and sara
roles.peer.settings = {
timeout = 40;
};
# Jon overrides timeout
roles.peer.machines.jon = {
settings.timeout = lib.mkForce 42;
};
roles.peer.machines.sara = { };
};
# Jon overrides timeout
roles.peer.machines.jon = {
settings.timeout = lib.mkForce 42;
};
roles.peer.machines.sara = { };
};
};
config = res.servicesEval.config.mappedServices.self-A;
config = res.config._services.mappedServices.self-A;
#
applySettings =

View File

@@ -1,6 +1,6 @@
{ callInventoryAdapter, lib, ... }:
{ createTestClan, lib, ... }:
let
res = callInventoryAdapter {
res = createTestClan {
modules."A" = m: {
_class = "clan.service";
config = {
@@ -14,19 +14,21 @@ let
default = m;
};
};
machines = {
jon = { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
inventory = {
machines = {
jon = { };
};
instances."instance_foo" = {
module = {
name = "A";
input = "self";
};
roles.peer.machines.jon = { };
};
roles.peer.machines.jon = { };
};
};
specialArgs = lib.attrNames res.servicesEval.config.mappedServices.self-A.test.specialArgs;
specialArgs = lib.attrNames res.config._services.mappedServices.self-A.test.specialArgs;
in
{
test_simple = {

View File

@@ -1,12 +0,0 @@
{ clan-core }:
{
_class = "clan";
_module.args = {
inherit clan-core;
inherit (clan-core) clanLib;
};
imports = [
./module.nix
./interface.nix
];
}

View File

@@ -1,4 +0,0 @@
{ self, lib, ... }:
{
flake.modules.clan.default = lib.modules.importApply ./default.nix { clan-core = self; };
}

View File

@@ -1,58 +0,0 @@
{
self,
inputs,
...
}:
let
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
in
{
imports = [
./clan/flake-module.nix
];
perSystem =
{
pkgs,
lib,
system,
...
}:
let
jsonDocs = import ./eval-docs.nix {
inherit pkgs lib;
clan-core = self;
};
in
{
legacyPackages.clan-options = jsonDocs.optionsJSON;
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.evalTests-build-clan
legacyPackages.evalTests-build-clan = import ./tests.nix {
inherit lib;
clan-core = self;
};
checks = {
eval-lib-build-clan = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
export HOME="$(realpath .)"
nix-unit --eval-store "$HOME" \
--extra-experimental-features flakes \
--show-trace \
${inputOverrides} \
--flake ${
self.filter {
include = [
"flakeModules"
"inventory.json"
"lib"
"machines"
"nixosModules"
];
}
}#legacyPackages.${system}.evalTests-build-clan
touch $out
'';
};
};
}

View File

@@ -1,171 +0,0 @@
# Adapter function between the inventory.instances and the clan.service module
#
# Data flow:
# - inventory.instances -> Adapter -> clan.service module -> Service Resources (i.e. NixosModules per Machine, Vars per Service, etc.)
#
# What this file does:
#
# - Resolves the [Module] to an actual module-path and imports it.
# - Groups together all the same modules into a single import and creates all instances for it.
# - Resolves the inventory tags into machines. Tags don't exist at the service level.
# Also combines the settings for 'machines' and 'tags'.
{
lib,
clanLib,
...
}:
{
mapInstances =
{
# This is used to resolve the module imports from 'flake.inputs'
flakeInputs,
# The clan inventory
inventory,
directory,
clanCoreModules,
prefix ? [ ],
exportsModule,
}:
let
# machineHasTag = machineName: tagName: lib.elem tagName inventory.machines.${machineName}.tags;
# map the instances into the module
importedModuleWithInstances = lib.mapAttrs (
instanceName: instance:
let
resolvedModule = clanLib.resolveModule {
moduleSpec = instance.module;
inherit flakeInputs clanCoreModules;
};
# Every instance includes machines via roles
# :: { client :: ... }
instanceRoles = lib.mapAttrs (
roleName: role:
let
resolvedMachines = clanLib.inventory.resolveTags {
members = {
# Explicit members
machines = lib.attrNames role.machines;
# Resolved Members
tags = lib.attrNames role.tags;
};
inherit (inventory) machines;
inherit instanceName roleName;
};
in
# instances.<instanceName>.roles.<roleName> =
# Remove "tags", they are resolved into "machines"
(removeAttrs role [ "tags" ])
// {
machines = lib.genAttrs resolvedMachines.machines (
machineName:
let
machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { };
in
# TODO: tag settings
# Wait for this feature until option introspection for 'settings' is done.
# This might get too complex to handle otherwise.
# settingsViaTags = lib.filterAttrs (
# tagName: _: machineHasTag machineName tagName
# ) instance.roles.${roleName}.tags;
{
# TODO: Do we want to wrap settings with
# setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}";
settings = {
imports = [
machineSettings
]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags);
};
}
);
}
) instance.roles;
in
{
inherit (instance) module;
inherit resolvedModule instanceRoles;
}
) inventory.instances or { };
# 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 } ] }
# Since 'perMachine' needs access to all the instances we should include them as a whole
grouped = lib.foldlAttrs (
acc: instanceName: instance:
let
inputName = if instance.module.input == null then "<clan-core>" else instance.module.input;
id = inputName + "-" + instance.module.name;
in
acc
// {
${id} = acc.${id} or [ ] ++ [
{
inherit instanceName instance;
}
];
}
) { } 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: serviceModule:
acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ]
) [ ] servicesEval.config.mappedServices;
}) inventory.machines or { };
evalServices =
{ modules, prefix }:
lib.evalModules {
class = "clan";
specialArgs = {
inherit clanLib;
_ctx = prefix;
};
modules = [
(import ./all-services-wrapper.nix { inherit directory; })
]
++ modules;
};
servicesEval = evalServices {
inherit prefix;
modules = [
{
inherit exportsModule;
mappedServices = lib.mapAttrs (_module_ident: instances: {
imports = [
# Import the resolved module.
# i.e. clan.modules.admin
{
options.module = lib.mkOption {
type = lib.types.raw;
default = (builtins.head instances).instance.module;
};
}
(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
# Exposed for testing
grouped
allMachines
importedModulesEvaluated
;
};
}

View File

@@ -1,5 +0,0 @@
{
imports = [
./interface.nix
];
}

View File

@@ -1,28 +0,0 @@
{ lib, ... }:
let
inherit (lib) types mkOption;
submodule = m: types.submoduleWith { modules = [ m ]; };
in
{
options = {
directory = mkOption {
type = types.path;
};
distributedServices = mkOption {
type = types.raw;
};
inventory = mkOption {
type = types.raw;
};
machines = mkOption {
type = types.attrsOf (submodule ({
options = {
machineImports = mkOption {
type = types.listOf types.raw;
};
};
}));
};
};
}

View File

@@ -1,20 +0,0 @@
{
config,
lib,
clanLib,
...
}:
{
options.introspection = lib.mkOption {
readOnly = true;
# TODO: use options.inventory instead of the evaluate config attribute
default =
builtins.removeAttrs (clanLib.introspection.getPrios { options = config.inventory.options; })
# tags are freeformType which is not supported yet.
# services is removed and throws an error if accessed.
[
"tags"
"services"
];
};
}

View File

@@ -1,70 +0,0 @@
{
flakeInputs,
clanLib,
}:
{ lib, config, ... }:
let
inspectModule =
inputName: moduleName: module:
let
eval = clanLib.evalService {
modules = [ module ];
prefix = [
inputName
"clan"
"modules"
moduleName
];
};
in
{
manifest = eval.config.manifest;
roles = lib.mapAttrs (_n: v: { inherit (v) description; }) eval.config.roles;
};
in
{
options.staticModules = lib.mkOption {
readOnly = true;
type = lib.types.raw;
apply = moduleSet: lib.mapAttrs (inspectModule "<clan-core>") moduleSet;
};
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.moduleSchemas = lib.mkOption {
# { sourceName :: { moduleName :: { roleName :: Schema }}}
readOnly = true;
type = lib.types.raw;
default = lib.mapAttrs (
_inputName: moduleSet:
lib.mapAttrs (
_moduleName: module:
(clanLib.evalService {
modules = [ module ];
prefix = [ ];
}).config.result.api.schema
) moduleSet
) config.modulesPerSource;
};
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

@@ -62,6 +62,9 @@
# Core libraries
(root + "/lib")
# modules directory
(root + "/modules")
# User-provided fileset
fileset
];

View File

@@ -81,6 +81,7 @@ in
description = null;
icon = null;
name = "test";
tld = "clan";
};
};
@@ -105,7 +106,7 @@ in
self = {
inputs = { };
};
directory = ../../.;
directory = ../.;
meta.name = "test-clan-core";
};
in
@@ -123,7 +124,7 @@ in
self = {
inputs = { };
};
directory = ../../.;
directory = ../.;
meta.name = "test-clan-core";
};
in
@@ -211,6 +212,87 @@ in
};
};
test_clan_check_simple_fail =
let
eval = clan {
checks.constFail = {
assertion = false;
message = "This is a constant failure";
};
};
in
{
result = eval;
expr = eval.config;
expectedError.type = "ThrownError";
expectedError.msg = "This is a constant failure";
};
test_clan_check_simple_pass =
let
eval = clan {
checks.constFail = {
assertion = true;
message = "This is a constant success";
};
};
in
{
result = eval;
expr = lib.seq eval.config 42;
expected = 42;
};
test_get_var_machine =
let
varsLib = import ./vars.nix { };
in
{
expr = varsLib.getPublicValue {
backend = "in_repo";
default = "test";
shared = false;
generator = "test-generator";
machine = "test-machine";
file = "test-file";
flake = ./vars-test-flake;
};
expected = "foo-machine";
};
test_get_var_shared =
let
varsLib = import ./vars.nix { };
in
{
expr = varsLib.getPublicValue {
backend = "in_repo";
default = "test";
shared = true;
generator = "test-generator";
machine = "test-machine";
file = "test-file";
flake = ./vars-test-flake;
};
expected = "foo-shared";
};
test_get_var_default =
let
varsLib = import ./vars.nix { };
in
{
expr = varsLib.getPublicValue {
backend = "in_repo";
default = "test-default";
shared = true;
generator = "test-generator-wrong";
machine = "test-machine";
file = "test-file";
flake = ./vars-test-flake;
};
expected = "test-default";
};
test_clan_all_machines_laziness =
let
eval = clan {

View File

@@ -0,0 +1 @@
foo-shared

25
lib/vars.nix Normal file
View File

@@ -0,0 +1,25 @@
_: {
getPublicValue =
{
backend ? "in_repo",
default ? throw "getPublicValue: Public value ${machine}/${generator}/${file} not found!",
shared ? false,
generator,
machine,
file,
flake,
}:
if backend == "in_repo" then
let
path =
if shared then
"${flake}/vars/shared/${generator}/${file}/value"
else
"${flake}/vars/per-machine/${machine}/${generator}/${file}/value";
in
if builtins.pathExists path then builtins.readFile path else default
else
throw "backend ${backend} does not implement getPublicValue";
}

3
lib/vars_test.nix Normal file
View File

@@ -0,0 +1,3 @@
{
}

16
modules/clan/checks.nix Normal file
View File

@@ -0,0 +1,16 @@
{ lib, nixpkgs, ... }:
{
checks.minNixpkgsVersion = {
assertion = lib.versionAtLeast nixpkgs.lib.version "25.11";
message = ''
Nixpkgs version: ${nixpkgs.lib.version} is incompatible with clan-core. (>= 25.11 is recommended)
---
Your version of 'nixpkgs' seems too old for clan-core.
Please read: https://docs.clan.lol/guides/nixpkgs-flake-input
You can ignore this check by setting:
clan.checks.minNixpkgsVersion.ignore = true;
---
'';
};
}

25
modules/clan/default.nix Normal file
View File

@@ -0,0 +1,25 @@
/**
Root 'clan' Module
Defines lib.clan and flake-parts.clan options
and all common logic for the 'clan' module.
- has Class _class = "clan"
- _module.args.clan-core: reference to clan-core flake
- _module.args.clanLib: reference to lib.clan function
*/
{ clan-core }:
{
_class = "clan";
_module.args = {
inherit clan-core;
inherit (clan-core) clanLib;
};
imports = [
./top-level-interface.nix
./module.nix
./distributed-services.nix
./checks.nix
];
}

View File

@@ -0,0 +1,163 @@
{
lib,
clanLib,
config,
clan-core,
...
}:
let
inherit (lib) mkOption types;
# Keep a reference to top-level
clanConfig = config;
inventory = clanConfig.inventory;
flakeInputs = clanConfig.self.inputs;
clanCoreModules = clan-core.clan.modules;
grouped = lib.foldlAttrs (
acc: instanceName: instance:
let
inputName = if instance.module.input == null then "<clan-core>" else instance.module.input;
id = inputName + "-" + instance.module.name;
in
acc
// {
${id} = acc.${id} or [ ] ++ [
{
inherit instanceName instance;
}
];
}
) { } importedModuleWithInstances;
importedModuleWithInstances = lib.mapAttrs (
instanceName: instance:
let
resolvedModule = clanLib.resolveModule {
moduleSpec = instance.module;
inherit flakeInputs clanCoreModules;
};
# Every instance includes machines via roles
# :: { client :: ... }
instanceRoles = lib.mapAttrs (
roleName: role:
let
resolvedMachines = clanLib.inventory.resolveTags {
members = {
# Explicit members
machines = lib.attrNames role.machines;
# Resolved Members
tags = lib.attrNames role.tags;
};
inherit (inventory) machines;
inherit instanceName roleName;
};
in
# instances.<instanceName>.roles.<roleName> =
# Remove "tags", they are resolved into "machines"
(removeAttrs role [ "tags" ])
// {
machines = lib.genAttrs resolvedMachines.machines (
machineName:
let
machineSettings = instance.roles.${roleName}.machines.${machineName}.settings or { };
in
# TODO: tag settings
# Wait for this feature until option introspection for 'settings' is done.
# This might get too complex to handle otherwise.
# settingsViaTags = lib.filterAttrs (
# tagName: _: machineHasTag machineName tagName
# ) instance.roles.${roleName}.tags;
{
# TODO: Do we want to wrap settings with
# setDefaultModuleLocation "inventory.instances.${instanceName}.roles.${roleName}.tags.${tagName}";
settings = {
imports = [
machineSettings
]; # ++ lib.attrValues (lib.mapAttrs (_tagName: v: v.settings) settingsViaTags);
};
}
);
}
) instance.roles;
in
{
inherit (instance) module;
inherit resolvedModule instanceRoles;
}
) inventory.instances or { };
in
{
_class = "clan";
options._services = mkOption {
visible = false;
description = ''
All service instances
!!! Danger "Internal API"
Do not rely on this API yet.
- Will be renamed to just 'services' in the future.
Once the name can be claimed again.
- Structure will change.
API will be declared as public after beeing simplified.
'';
type = types.submoduleWith {
# TODO: Remove specialArgs
specialArgs = {
inherit clanLib;
};
modules = [
(import ../../lib/inventory/distributed-service/all-services-wrapper.nix {
inherit (clanConfig) directory;
})
# Dependencies
{
exportsModule = clanConfig.exportsModule;
}
{
# TODO: Rename to "allServices"
# All services
mappedServices = lib.mapAttrs (_module_ident: instances: {
imports = [
# Import the resolved module.
# i.e. clan.modules.admin
{
options.module = lib.mkOption {
type = lib.types.raw;
default = (builtins.head instances).instance.module;
};
}
(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;
}
];
};
default = { };
};
options._allMachines = mkOption {
internal = true;
type = types.raw;
default = lib.mapAttrs (machineName: _: {
# This is the list of nixosModules for each machine
machineImports = lib.foldlAttrs (
acc: _module_ident: serviceModule:
acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ]
) [ ] config._services.mappedServices;
}) inventory.machines or { };
};
config = {
clanInternals.inventoryClass.machines = config._allMachines;
# clanInternals.inventoryClass.distributedServices = config._services;
# Exports from distributed services
exports = config._services.exports;
};
}

View File

@@ -1,19 +1,24 @@
{
pkgs,
lib,
clanModule,
clanLib,
clan-core,
}:
let
eval = lib.evalModules {
modules = [
clan-core.modules.clan.default
clanModule
];
specialArgs = {
self = clan-core;
};
};
evalDocs = pkgs.nixosOptionsDoc {
options = eval.options;
warningsAreErrors = false;
transformOptions = clan-core.clanLib.docs.stripStorePathsFromDeclarations;
transformOptions = clanLib.docs.stripStorePathsFromDeclarations;
};
in
{

View File

@@ -0,0 +1,27 @@
{ self, lib, ... }:
let
clanModule = lib.modules.importApply ./default.nix { clan-core = self; };
in
{
flake.modules.clan.default = clanModule;
perSystem =
{
pkgs,
lib,
...
}:
let
jsonDocs = import ./eval-docs.nix {
clan-core = self;
inherit
pkgs
lib
clanModule
;
clanLib = self.clanLib;
};
in
{
legacyPackages.clan-options = jsonDocs.optionsJSON;
};
}

View File

@@ -100,7 +100,7 @@ let
_: machine:
machine.extendModules {
modules = [
(lib.modules.importApply ../machineModules/overridePkgs.nix {
(lib.modules.importApply ../../nixosModules/machineModules/overridePkgs.nix {
pkgs = pkgsFor.${system};
})
];
@@ -167,6 +167,9 @@ in
{ ... }@args:
let
_class =
# _class was added in https://github.com/NixOS/nixpkgs/pull/395141
# Clan relies on it to determine which modules to load
# people need to use at least that version of nixpkgs
args._class or (throw ''
Your version of nixpkgs is incompatible with the latest clan.
Please update nixpkgs input to the latest nixos-unstable or nixpkgs-unstable.
@@ -176,7 +179,7 @@ in
in
{
imports = [
(lib.modules.importApply ../machineModules/forName.nix {
(lib.modules.importApply ../../nixosModules/machineModules/forName.nix {
inherit (config.inventory) meta;
inherit
name
@@ -216,43 +219,44 @@ in
inherit nixosConfigurations;
inherit darwinConfigurations;
exports = config.clanInternals.inventoryClass.distributedServices.servicesEval.config.exports;
clanInternals = {
inventoryClass =
let
flakeInputs = config.self.inputs;
# Compute the relative directory path
selfStr = toString config.self;
dirStr = toString directory;
relativeDirectory =
if selfStr == dirStr then
""
else if lib.hasPrefix selfStr dirStr then
lib.removePrefix (selfStr + "/") dirStr
else
# This shouldn't happen in normal usage, but can occur when
# the flake is copied (e.g., in tests). Fall back to empty string.
"";
in
{
_module.args = {
inherit clanLib;
};
imports = [
../inventoryClass/builder/default.nix
(lib.modules.importApply ../inventoryClass/service-list-from-inputs.nix {
inherit flakeInputs clanLib;
})
../inventoryClass/default.nix
{
inherit inventory directory;
inherit
inventory
directory
flakeInputs
relativeDirectory
;
exportsModule = config.exportsModule;
}
(
let
clanConfig = config;
in
{ config, ... }:
{ ... }:
{
staticModules = clan-core.clan.modules;
distributedServices = clanLib.inventory.mapInstances {
inherit (clanConfig) inventory exportsModule;
inherit flakeInputs directory;
clanCoreModules = clan-core.clan.modules;
prefix = [ "distributedServices" ];
};
machines = config.distributedServices.allMachines;
}
)
../inventoryClass/inventory-introspection.nix
];
};

View File

@@ -1,3 +1,28 @@
/**
The templates submodule
'clan.templates'
Different kinds supported:
- clan templates: 'clan.templates.clan'
- disko templates: 'clan.templates.disko'
- machine templates: 'clan.templates.machine'
A template has the form:
```nix
{
description: string; # short summary what the template contains
path: path; # path to the template
}
```
The clan API copies the template from the given 'path'
into a target folder. For example,
`./machines/<machine-name>` for 'machine' templates.
*/
{
lib,
...

View File

@@ -288,7 +288,7 @@ in
Global information about the clan.
'';
type = types.deferredModuleWith {
staticModules = [ ../inventoryClass/meta-interface.nix ];
staticModules = [ ../inventoryClass/meta.nix ];
};
default = { };
};

5
modules/flake-module.nix Normal file
View File

@@ -0,0 +1,5 @@
{
imports = [
./clan/flake-module.nix
];
}

View File

@@ -0,0 +1,156 @@
{
lib,
clanLib,
config,
...
}:
let
inherit (lib) types mkOption;
submodule = m: types.submoduleWith { modules = [ m ]; };
inspectModule =
inputName: moduleName: module:
let
eval = clanLib.evalService {
modules = [ module ];
prefix = [
inputName
"clan"
"modules"
moduleName
];
};
in
{
manifest = eval.config.manifest;
roles = lib.mapAttrs (_n: v: { inherit (v) description; }) eval.config.roles;
};
exposedInventory = lib.intersectAttrs {
meta = null;
machines = null;
instances = null;
tags = null;
} config.inventory;
filterAttrsRecursive' =
path: pred: set:
lib.listToAttrs (
lib.concatMap (
name:
let
v = set.${name};
loc = path ++ [ name ];
in
if pred loc v then
[
(lib.nameValuePair name (if lib.isAttrs v then filterAttrsRecursive' loc pred v else v))
]
else
[ ]
) (lib.attrNames set)
);
filteredInventory = filterAttrsRecursive' [ ] (
# Remove extraModules from serialization,
# identified by: prefix + pathLength + name
# inventory.instances.*.roles.*.extraModules
path: _value: !(lib.length path == 5 && ((lib.last path)) == "extraModules")
) exposedInventory;
in
{
options = {
flakeInputs = mkOption {
type = types.raw;
};
exportsModule = mkOption {
type = types.raw;
};
inventory = mkOption {
type = types.raw;
};
inventorySerialization = mkOption {
type = types.raw;
readOnly = true;
default = filteredInventory;
};
directory = mkOption {
type = types.path;
};
relativeDirectory = mkOption {
type = types.str;
internal = true;
description = ''
The relative directory path from the flake root to the clan directory.
Empty string if directory equals the flake root.
'';
};
machines = mkOption {
type = types.attrsOf (submodule ({
options = {
machineImports = mkOption {
type = types.listOf types.raw;
};
};
}));
};
introspection = lib.mkOption {
readOnly = true;
# TODO: use options.inventory instead of the evaluate config attribute
default =
builtins.removeAttrs (clanLib.introspection.getPrios { options = config.inventory.options; })
# tags are freeformType which is not supported yet.
# services is removed and throws an error if accessed.
[
"tags"
"services"
];
};
staticModules = lib.mkOption {
readOnly = true;
type = lib.types.raw;
apply = moduleSet: lib.mapAttrs (inspectModule "<clan-core>") moduleSet;
};
modulesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithModules = lib.filterAttrs (_inputName: v: v ? clan.modules) config.flakeInputs;
in
lib.mapAttrs (
inputName: v: lib.mapAttrs (inspectModule inputName) v.clan.modules
) inputsWithModules;
};
moduleSchemas = lib.mkOption {
# { sourceName :: { moduleName :: { roleName :: Schema }}}
readOnly = true;
type = lib.types.raw;
default = lib.mapAttrs (
_inputName: moduleSet:
lib.mapAttrs (
_moduleName: module:
(clanLib.evalService {
modules = [ module ];
prefix = [ ];
}).config.result.api.schema
) moduleSet
) config.modulesPerSource;
};
templatesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithTemplates = lib.filterAttrs (_inputName: v: v ? clan.templates) config.flakeInputs;
in
lib.mapAttrs (_inputName: v: lib.mapAttrs (_n: t: t) v.clan.templates) inputsWithTemplates;
};
};
}

View File

@@ -115,7 +115,7 @@ in
meta = lib.mkOption {
type = lib.types.submoduleWith {
modules = [
./meta-interface.nix
./meta.nix
];
};
};
@@ -167,7 +167,7 @@ in
'';
type = types.submoduleWith {
specialArgs = {
inherit (config) machines;
inherit (config) machines clanLib;
};
modules = [
{
@@ -359,7 +359,7 @@ in
inherit clanLib;
};
}
(import ./roles-interface.nix { })
(import ./role.nix { })
];
}
);

View File

@@ -31,6 +31,20 @@ let
Under construction, will be used for the UI
'';
};
tld = lib.mkOption {
type = types.strMatching "[a-z]+";
default = "clan";
example = "ccc";
description = ''
Top level domain (TLD) of the clan. It should be set to a valid, but
not already existing TLD.
It will be used to provide clan-internal services and resolve each host of the
clan with:
<hostname>.<tld>
'';
};
};
in
{

View File

@@ -44,12 +44,6 @@ in
description = ''
List of additionally imported `.nix` expressions.
Supported types:
- **Strings**: Interpreted relative to the 'directory' passed to `lib.clan`.
- **Paths**: should be relative to the current file.
- **Any**: Nix expression must be serializable to JSON.
!!! Note
**The import only happens if the machine is part of the service or role.**
@@ -73,15 +67,8 @@ in
}
```
'';
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
default = [ ];
type = types.listOf (
types.oneOf [
types.str
types.path
(types.attrsOf types.anything)
]
);
type = types.listOf types.raw;
};
};
}

View File

@@ -106,6 +106,14 @@ in
# Set by the flake, so it's read-only in the machine
readOnly = true;
};
tld = lib.mkOption {
type = types.strMatching "[a-z]+";
description = ''
the TLD for the clan
'';
# Set by the flake, so it's read-only in the machine
readOnly = true;
};
machine = mkOption {
description = ''
Settings of the machine.

View File

@@ -18,7 +18,7 @@ let
inputs.data-mesher.nixosModules.data-mesher
];
config = {
clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.hostPlatform.system};
clan.core.clanPkgs = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system};
};
};
in

View File

@@ -30,5 +30,5 @@ let
in
{
# Note this might jump back and worth as kernel get added or removed.
boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.hostPlatform pkgs.zfs) latestKernelPackage;
boot.kernelPackages = lib.mkIf (lib.meta.availableOn pkgs.stdenv.hostPlatform pkgs.zfs) latestKernelPackage;
}

View File

@@ -3,6 +3,7 @@
directory,
meta,
}:
# The following is a nixos/darwin module
{
_class,
lib,
@@ -20,7 +21,7 @@
);
clan.core.settings = {
inherit (meta) name icon;
inherit (meta) name icon tld;
inherit directory;
machine = {
inherit name;

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