some ai generated work to get services working for macos
This commit is contained in:
105
PLAN.md
Normal file
105
PLAN.md
Normal file
@@ -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.<name>.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.<name> (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.<name>.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.<machine>.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.<machine> 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.
|
||||||
|
|
||||||
@@ -120,6 +120,63 @@
|
|||||||
|
|
||||||
share = settings.share;
|
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 =
|
script =
|
||||||
(
|
(
|
||||||
if settings.prompt then
|
if settings.prompt then
|
||||||
@@ -149,5 +206,7 @@
|
|||||||
# Immutable users to ensure that this module has exclusive control over the users.
|
# Immutable users to ensure that this module has exclusive control over the users.
|
||||||
users.mutableUsers = false;
|
users.mutableUsers = false;
|
||||||
};
|
};
|
||||||
|
# No-op for darwin by default; can be extended later if needed.
|
||||||
|
darwinModule = { };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
lib.setDefaultModuleLocation "via inventory.instances.${instanceName}.roles.${roleName}" s
|
||||||
) instanceCfg.roles.${roleName}.extraModules);
|
) instanceCfg.roles.${roleName}.extraModules);
|
||||||
};
|
};
|
||||||
|
darwinModule = {
|
||||||
|
imports = [
|
||||||
|
instanceRes.darwinModule
|
||||||
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
) instanceCfg.roles.${roleName}.machines or { };
|
) instanceCfg.roles.${roleName}.machines or { };
|
||||||
@@ -979,11 +1002,24 @@ in
|
|||||||
else
|
else
|
||||||
instanceAcc.nixosModules
|
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
|
) roleAcc role.allInstances
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nixosModules = [ ];
|
nixosModules = [ ];
|
||||||
|
darwinModules = [ ];
|
||||||
# ...
|
# ...
|
||||||
}
|
}
|
||||||
config.result.allRoles;
|
config.result.allRoles;
|
||||||
@@ -1021,6 +1057,12 @@ in
|
|||||||
]
|
]
|
||||||
++ instanceResults.nixosModules;
|
++ instanceResults.nixosModules;
|
||||||
};
|
};
|
||||||
|
darwinModule = {
|
||||||
|
imports = [
|
||||||
|
(lib.setDefaultModuleLocation "Via ${config.manifest.name}.perMachine - machine='${machineName}';" machineResult.darwinModule)
|
||||||
|
]
|
||||||
|
++ instanceResults.darwinModules;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
) config.result.allMachines;
|
) config.result.allMachines;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -145,10 +145,23 @@ in
|
|||||||
internal = true;
|
internal = true;
|
||||||
type = types.raw;
|
type = types.raw;
|
||||||
default = lib.mapAttrs (machineName: _: {
|
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 (
|
machineImports = lib.foldlAttrs (
|
||||||
acc: _module_ident: serviceModule:
|
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;
|
) [ ] config._services.mappedServices;
|
||||||
}) inventory.machines or { };
|
}) inventory.machines or { };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ in
|
|||||||
# - darwinModules (_class = darwin)
|
# - darwinModules (_class = darwin)
|
||||||
(lib.optionalAttrs (clan-core ? "${_class}Modules") clan-core."${_class}Modules".clanCore)
|
(lib.optionalAttrs (clan-core ? "${_class}Modules") clan-core."${_class}Modules".clanCore)
|
||||||
]
|
]
|
||||||
++ lib.optionals (_class == "nixos") (v.machineImports or [ ]);
|
++ (v.machineImports or [ ]);
|
||||||
|
|
||||||
# default hostname
|
# default hostname
|
||||||
networking.hostName = lib.mkDefault name;
|
networking.hostName = lib.mkDefault name;
|
||||||
|
|||||||
Reference in New Issue
Block a user