1 { config, lib, options, ... }:
4 keysDirectory = "/var/keys";
10 cfg = config.virtualisation.darwin-builder;
16 ../virtualisation/qemu-vm.nix
18 # Avoid a dependency on stateVersion
21 ../virtualisation/nixos-containers.nix
22 ../services/x11/desktop-managers/xterm.nix
24 # swraid's default depends on stateVersion
25 config.boot.swraid.enable = false;
26 options.boot.isContainer = lib.mkOption { default = false; internal = true; };
30 options.virtualisation.darwin-builder = with lib; {
35 description = "The maximum disk space allocated to the runner in MB";
37 memorySize = mkOption {
41 description = "The runner's memory in MB";
44 default = 1024 * 1024 * 1024;
48 The threshold (in bytes) of free disk space left at which to
49 start garbage collection on the runner
53 default = 3 * 1024 * 1024 * 1024;
57 The threshold (in bytes) of free disk space left at which to
58 stop garbage collection on the runner
61 workingDirectory = mkOption {
64 example = "/var/lib/darwin-builder";
66 The working directory to use to run the script. When running
67 as part of a flake will need to be set to a non read-only filesystem.
75 The localhost host port to forward TCP to the guest port.
81 # The builder is not intended to be used interactively
82 documentation.enable = false;
85 "ssh/ssh_host_ed25519_key" = {
88 source = ./keys/ssh_host_ed25519_key;
91 "ssh/ssh_host_ed25519_key.pub" = {
94 source = ./keys/ssh_host_ed25519_key.pub;
98 # DNS fails for QEMU user networking (SLiRP) on macOS. See:
100 # https://github.com/utmapp/UTM/issues/2353
102 # This works around that by using a public DNS server other than the DNS
103 # server that QEMU provides (normally 10.0.2.3)
104 networking.nameservers = [ "8.8.8.8" ];
106 # The linux builder is a lightweight VM for remote building; not evaluation.
107 nix.channel.enable = false;
109 # Deployment is by image.
110 # TODO system.switch.enable = false;?
111 system.disableInstallerTools = true;
114 auto-optimise-store = true;
116 min-free = cfg.min-free;
118 max-free = cfg.max-free;
120 trusted-users = [ user ];
124 getty.autologinUser = user;
129 authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ];
133 system.build.macos-builder-installer =
135 privateKey = "/etc/nix/${user}_${keyType}";
137 publicKey = "${privateKey}.pub";
139 # This installCredentials script is written so that it's as easy as
140 # possible for a user to audit before confirming the `sudo`
141 installCredentials = hostPkgs.writeShellScript "install-credentials" ''
145 INSTALL=${hostPkgs.coreutils}/bin/install
146 "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey}
147 "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey}
150 hostPkgs = config.virtualisation.host.pkgs;
152 script = hostPkgs.writeShellScriptBin "create-builder" (
156 # When running as non-interactively as part of a DarwinConfiguration the working directory
157 # must be set to a writeable directory.
158 (if cfg.workingDirectory != "." then ''
159 ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}"
160 cd "${cfg.workingDirectory}"
162 KEYS="''${KEYS:-./keys}"
163 ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}"
164 PRIVATE_KEY="''${KEYS}/${user}_${keyType}"
165 PUBLIC_KEY="''${PRIVATE_KEY}.pub"
166 if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then
167 ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}"
168 ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost'
170 if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then
171 (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}")
173 KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm}
177 script.overrideAttrs (old: {
178 pos = __curPos; # sets meta.position to point here; see script binding above for package definition
179 meta = (old.meta or { }) // {
180 platforms = lib.platforms.darwin;
182 passthru = (old.passthru or { }) // {
183 # Let users in the repl inspect the config
184 nixosConfig = config;
185 nixosOptions = options;
190 # To prevent gratuitous rebuilds on each change to Nixpkgs
191 nixos.revision = null;
193 # to be updated by module maintainers, see nixpkgs#325610
194 stateVersion = "24.05";
197 users.users."${user}" = {
201 security.polkit.enable = true;
203 security.polkit.extraConfig = ''
204 polkit.addRule(function(action, subject) {
205 if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") {
214 diskSize = cfg.diskSize;
216 memorySize = cfg.memorySize;
219 { from = "host"; guest.port = 22; host.port = cfg.hostPort; }
222 # Disable graphics for the builder since users will likely want to run it
223 # non-interactively in the background.
226 sharedDirectories.keys = {
227 source = "\"$KEYS\"";
228 target = keysDirectory;
231 # If we don't enable this option then the host will fail to delegate builds
232 # to the guest, because:
234 # - The host will lock the path to build
235 # - The host will delegate the build to the guest
236 # - The guest will attempt to lock the same path and fail because
237 # the lockfile on the host is visible on the guest
239 # Snapshotting the host's /nix/store as an image isolates the guest VM's
240 # /nix/store from the host's /nix/store, preventing this problem.
241 useNixStoreImage = true;
243 # Obviously the /nix/store needs to be writable on the guest in order for it
245 writableStore = true;
247 # This ensures that anything built on the guest isn't lost when the guest is
249 writableStoreUseTmpfs = false;
251 # Pass certificates from host to the guest otherwise when custom CA certificates
252 # are required we can't use the cached builder.