From 55e343c43e4858a8f05be5f39fee1ecc7391db9e Mon Sep 17 00:00:00 2001 From: Yadunand Prem Date: Sat, 8 Nov 2025 11:48:34 +0800 Subject: [PATCH] some ai generated work to get services working for macos --- PLAN.md | 105 ++++++++++++++++++ clanServices/users/default.nix | 59 ++++++++++ .../distributed-service/service-module.nix | 42 +++++++ modules/clan/distributed-services.nix | 17 ++- modules/clan/module.nix | 2 +- 5 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 000000000..4c8910e63 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,105 @@ +Title: Add nix-darwin Support to Clan Services (clan.service) + +Summary +- Extend clan services so authors can ship a darwinModule alongside nixosModule. +- Wire service results into darwin machines the same way we already do for NixOS. +- Keep full backward compatibility: existing services that only export nixosModule continue to work unchanged. + +Goals +- Service authors can return perInstance/perMachine darwinModule similarly to nixosModule. +- Darwin machines import the correct aggregated service module outputs. +- Documentation describes the new result attribute and authoring pattern. + +Non-Goals (initial phase) +- No rework of service settings schema or UI beyond documenting darwinModule. +- No OS-specific extraModules handling (we will keep extraModules affecting only nixos aggregation initially to avoid breaking existing users). +- No sweeping updates of all services; we’ll add a concrete example (users) and leave others to be migrated incrementally. + +Design Overview +- Service result attributes gain darwinModule in both roles..perInstance and perMachine results. +- The service aggregator composes both nixosModule and darwinModule per machine. +- The machine wiring picks the correct module based on the machine’s class (nixos vs darwin). + +Changes By File (with anchors) +- lib/inventory/distributed-service/service-module.nix + - Add darwinModule to per-instance return type next to nixosModule. + - Where: lib/inventory/distributed-service/service-module.nix:536 (options.nixosModule = mkOption { … }) + - Action: Add sibling options.darwinModule = mkOption { type = types.deferredModule; default = { }; description = "A single nix-darwin module for the instance."; }. + - Add darwinModule to per-machine return type next to nixosModule. + - Where: lib/inventory/distributed-service/service-module.nix:666 (options.nixosModule = mkOption { … }) + - Action: Add sibling options.darwinModule = mkOption { type = types.deferredModule; default = { }; description = "A single nix-darwin module for the machine."; }. + - Compose darwinModule per (role, instance, machine) similarly to nixosModule. + - Where: lib/inventory/distributed-service/service-module.nix:878–893 (wrapper that builds nixosModule = { imports = [ instanceRes.nixosModule ] ++ extraModules … }) + - Action: Build darwinModule = { imports = [ instanceRes.darwinModule ]; }. + Note: Do NOT include roles.*.extraModules here for darwin initially to avoid importing nixos-specific modules into darwin eval. + - Aggregate darwinModules in final result. + - Where: lib/inventory/distributed-service/service-module.nix:958–993 (instanceResults builder and final nixosModule = { imports = [ machineResult.nixosModule ] ++ instanceResults.nixosModules; }) + - Actions: + - Track instanceResults.darwinModules in parallel to instanceResults.nixosModules. + - Add final darwinModule = { imports = [ machineResult.darwinModule ] ++ instanceResults.darwinModules; }. + +- modules/clan/distributed-services.nix + - Feed the right service module to each machine based on machineClass. + - Where: modules/clan/distributed-services.nix:147–152 + - Current: machineImports = fold over services, collecting serviceModule.result.final.${machineName}.nixosModule + - Change: If inventory.machines.${machineName}.machineClass == "darwin" then collect .darwinModule else .nixosModule. + +- modules/clan/module.nix + - Ensure machineImports are included for both nixos and darwin machines. + - Where: modules/clan/module.nix:195 (currently ++ lib.optionals (_class == "nixos") (v.machineImports or [ ])) + - Change: Include machineImports for darwin as well (or remove the conditional and always append v.machineImports). + +- docs/site/decisions/01-Clan-Modules.md + - Document darwinModule as a result attribute. + - Where: docs/site/decisions/01-Clan-Modules.md:129–146 (Result attributes and perMachine text mentioning only nixosModule) + - Change: Add “darwinModule” to the Result attributes list and examples, mirroring nixosModule. + +- Example service update: clanServices/users/default.nix + - Add perInstance.darwinModule and perMachine.darwinModule mirroring nixos behavior where feasible. + - Where: clanServices/users/default.nix:28–90 (roles.default.perInstance.nixosModule), 148–153 (perMachine.nixosModule) + - Change: Provide minimal darwinModule that sets users.users. (and any safe, cross-platform bits). If some nixos-only settings (e.g., systemd hooks) exist, keep them nixos-only. + +Implementation Steps +1) Service API extensions + - Add options.darwinModule to roles.*.perInstance and perMachine (see anchors above). + - Keep defaults to {} so services can omit it safely. + +2) Aggregation logic + - result.allRoles: emit darwinModule wrapper from instanceRes.darwinModule. + - result.final: + - Collect instanceResults.darwinModules alongside instanceResults.nixosModules. + - Produce final darwinModule with [ machineResult.darwinModule ] ++ instanceResults.darwinModules. + - Leave exports logic unchanged. + +3) Machine wiring + - modules/clan/distributed-services.nix: choose .darwinModule vs .nixosModule based on inventory.machines..machineClass. + - modules/clan/module.nix: include v.machineImports for both OS classes. + +4) Example migration (users) + - Add darwinModule in clanServices/users/default.nix. + - Validate that users service evaluates for a darwin machine and does not reference nixos-specific options. + +5) Documentation + - Update ADR docs to mention darwinModule in Result attributes and examples. + - Add a short “Authoring for Darwin” snippet showing perInstance/perMachine returning both modules. + +6) Tests and verification + - Unit-level: extend lib/inventory/distributed-service/tests to assert presence of result.final..darwinModule when perInstance/perMachine return it. + - Integration-level: evaluate a sample darwin machine (e.g., inventory.json has test-darwin-machine) and assert clan.darwinModules. includes the aggregated module. + - Sanity: ensure existing nixos-only services still evaluate unchanged. + +Backward Compatibility +- Existing services that only return nixosModule continue to work. +- Darwin machines won’t import service modules until services provide darwinModule, avoiding accidental breakage. +- extraModules remain applied only to nixos aggregation initially to prevent nixos-only modules from breaking darwin evaluation. We can add OS-specific extraModules in a follow-up (e.g., roles.*.extraModulesDarwin). + +Acceptance Criteria +- Services can return darwinModule in perInstance/perMachine without errors. +- Darwin machines import aggregated darwinModule outputs from all participating services. +- nixos behavior remains unchanged for existing services. +- Documentation updated to reflect the new attribute and example. + +Rollout Notes +- Start by updating clanServices/users as a working example. +- Encourage service authors to add darwinModule incrementally; no global migration is required. + diff --git a/clanServices/users/default.nix b/clanServices/users/default.nix index 6d720ee94..6c249975e 100644 --- a/clanServices/users/default.nix +++ b/clanServices/users/default.nix @@ -120,6 +120,63 @@ share = settings.share; + script = + ( + if settings.prompt then + '' + prompt_value=$(cat "$prompts"/user-password) + if [[ -n "''${prompt_value-}" ]]; then + echo "$prompt_value" | tr -d "\n" > "$out"/user-password + else + xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > "$out"/user-password + fi + '' + else + '' + xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > "$out"/user-password + '' + ) + + '' + mkpasswd -s -m sha-512 < "$out"/user-password | tr -d "\n" > "$out"/user-password-hash + ''; + }; + }; + darwinModule = + { + config, + pkgs, + lib, + ... + }: + { + # For darwin, we currently only generate and manage the password secret. + # Hooking into actual macOS account management may be added later. + clan.core.vars.generators."user-password-${settings.user}" = { + files.user-password-hash.neededFor = "users"; + files.user-password.deploy = false; + + prompts.user-password = lib.mkIf settings.prompt { + display = { + group = settings.user; + label = "password"; + required = false; + helperText = '' + Your password will be encrypted and stored securely using the secret store you've configured. + ''; + }; + type = "hidden"; + persist = true; + description = "Leave empty to generate automatically"; + }; + + runtimeInputs = [ + pkgs.coreutils + pkgs.xkcdpass + pkgs.mkpasswd + ]; + + share = settings.share; + script = ( if settings.prompt then @@ -149,5 +206,7 @@ # Immutable users to ensure that this module has exclusive control over the users. users.mutableUsers = false; }; + # No-op for darwin by default; can be extended later if needed. + darwinModule = { }; }; } diff --git a/lib/inventory/distributed-service/service-module.nix b/lib/inventory/distributed-service/service-module.nix index 63e3f1dce..d6efb2cf6 100644 --- a/lib/inventory/distributed-service/service-module.nix +++ b/lib/inventory/distributed-service/service-module.nix @@ -561,6 +561,15 @@ in ``` ''; }; + options.darwinModule = mkOption { + type = types.deferredModule; + default = { }; + description = '' + A single nix-darwin module for the instance. + + This mirrors `nixosModule` but targets darwin machines. + ''; + }; }) ]; }; @@ -686,6 +695,15 @@ in ``` ''; }; + options.darwinModule = mkOption { + type = types.deferredModule; + default = { }; + description = '' + A single nix-darwin module for the machine. + + This mirrors `nixosModule` but targets darwin machines. + ''; + }; }) ]; }; @@ -890,6 +908,11 @@ in lib.setDefaultModuleLocation "via inventory.instances.${instanceName}.roles.${roleName}" s ) instanceCfg.roles.${roleName}.extraModules); }; + darwinModule = { + imports = [ + instanceRes.darwinModule + ]; + }; } ) instanceCfg.roles.${roleName}.machines or { }; @@ -979,11 +1002,24 @@ in else instanceAcc.nixosModules ); + darwinModules = ( + if instance.allMachines.${machineName}.darwinModule or { } != { } then + instanceAcc.darwinModules + ++ [ + (lib.setDefaultModuleLocation + "Via instances.${instanceName}.roles.${roleName}.machines.${machineName}" + instance.allMachines.${machineName}.darwinModule + ) + ] + else + instanceAcc.darwinModules + ); } ) roleAcc role.allInstances ) { nixosModules = [ ]; + darwinModules = [ ]; # ... } config.result.allRoles; @@ -1021,6 +1057,12 @@ in ] ++ instanceResults.nixosModules; }; + darwinModule = { + imports = [ + (lib.setDefaultModuleLocation "Via ${config.manifest.name}.perMachine - machine='${machineName}';" machineResult.darwinModule) + ] + ++ instanceResults.darwinModules; + }; } ) config.result.allMachines; }; diff --git a/modules/clan/distributed-services.nix b/modules/clan/distributed-services.nix index adcff5cca..c9cc0d125 100644 --- a/modules/clan/distributed-services.nix +++ b/modules/clan/distributed-services.nix @@ -145,10 +145,23 @@ in internal = true; type = types.raw; default = lib.mapAttrs (machineName: _: { - # This is the list of nixosModules for each machine + # This is the list of service modules for each machine (nixos or darwin) machineImports = lib.foldlAttrs ( acc: _module_ident: serviceModule: - acc ++ [ serviceModule.result.final.${machineName}.nixosModule or { } ] + let + modName = + if inventory.machines.${machineName}.machineClass == "darwin" then + "darwinModule" + else + "nixosModule"; + finalForMachine = serviceModule.result.final.${machineName} or { }; + picked = + if builtins.hasAttr modName finalForMachine then + (builtins.getAttr modName finalForMachine) + else + { }; + in + acc ++ [ picked ] ) [ ] config._services.mappedServices; }) inventory.machines or { }; }; diff --git a/modules/clan/module.nix b/modules/clan/module.nix index 26739055c..483ffc719 100644 --- a/modules/clan/module.nix +++ b/modules/clan/module.nix @@ -192,7 +192,7 @@ in # - darwinModules (_class = darwin) (lib.optionalAttrs (clan-core ? "${_class}Modules") clan-core."${_class}Modules".clanCore) ] - ++ lib.optionals (_class == "nixos") (v.machineImports or [ ]); + ++ (v.machineImports or [ ]); # default hostname networking.hostName = lib.mkDefault name;