at 23.11-pre 7.2 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 keysDirectory = "/var/keys"; 5 6 user = "builder"; 7 8 keyType = "ed25519"; 9 10 cfg = config.virtualisation.darwin-builder; 11 12in 13 14{ 15 imports = [ 16 ../virtualisation/qemu-vm.nix 17 18 # Avoid a dependency on stateVersion 19 { 20 disabledModules = [ 21 ../virtualisation/nixos-containers.nix 22 ../services/x11/desktop-managers/xterm.nix 23 ]; 24 config = { }; 25 options.boot.isContainer = lib.mkOption { default = false; internal = true; }; 26 } 27 ]; 28 29 options.virtualisation.darwin-builder = with lib; { 30 diskSize = mkOption { 31 default = 20 * 1024; 32 type = types.int; 33 example = 30720; 34 description = "The maximum disk space allocated to the runner in MB"; 35 }; 36 memorySize = mkOption { 37 default = 3 * 1024; 38 type = types.int; 39 example = 8192; 40 description = "The runner's memory in MB"; 41 }; 42 min-free = mkOption { 43 default = 1024 * 1024 * 1024; 44 type = types.int; 45 example = 1073741824; 46 description = '' 47 The threshold (in bytes) of free disk space left at which to 48 start garbage collection on the runner 49 ''; 50 }; 51 max-free = mkOption { 52 default = 3 * 1024 * 1024 * 1024; 53 type = types.int; 54 example = 3221225472; 55 description = '' 56 The threshold (in bytes) of free disk space left at which to 57 stop garbage collection on the runner 58 ''; 59 }; 60 workingDirectory = mkOption { 61 default = "."; 62 type = types.str; 63 example = "/var/lib/darwin-builder"; 64 description = '' 65 The working directory to use to run the script. When running 66 as part of a flake will need to be set to a non read-only filesystem. 67 ''; 68 }; 69 hostPort = mkOption { 70 default = 22; 71 type = types.int; 72 example = 31022; 73 description = '' 74 The localhost host port to forward TCP to the guest port. 75 ''; 76 }; 77 }; 78 79 config = { 80 # The builder is not intended to be used interactively 81 documentation.enable = false; 82 83 environment.etc = { 84 "ssh/ssh_host_ed25519_key" = { 85 mode = "0600"; 86 87 source = ./keys/ssh_host_ed25519_key; 88 }; 89 90 "ssh/ssh_host_ed25519_key.pub" = { 91 mode = "0644"; 92 93 source = ./keys/ssh_host_ed25519_key.pub; 94 }; 95 }; 96 97 # DNS fails for QEMU user networking (SLiRP) on macOS. See: 98 # 99 # https://github.com/utmapp/UTM/issues/2353 100 # 101 # This works around that by using a public DNS server other than the DNS 102 # server that QEMU provides (normally 10.0.2.3) 103 networking.nameservers = [ "8.8.8.8" ]; 104 105 nix.settings = { 106 auto-optimise-store = true; 107 108 min-free = cfg.min-free; 109 110 max-free = cfg.max-free; 111 112 trusted-users = [ "root" user ]; 113 }; 114 115 services = { 116 getty.autologinUser = user; 117 118 openssh = { 119 enable = true; 120 121 authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ]; 122 }; 123 }; 124 125 system.build.macos-builder-installer = 126 let 127 privateKey = "/etc/nix/${user}_${keyType}"; 128 129 publicKey = "${privateKey}.pub"; 130 131 # This installCredentials script is written so that it's as easy as 132 # possible for a user to audit before confirming the `sudo` 133 installCredentials = hostPkgs.writeShellScript "install-credentials" '' 134 KEYS="''${1}" 135 INSTALL=${hostPkgs.coreutils}/bin/install 136 "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey} 137 "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey} 138 ''; 139 140 hostPkgs = config.virtualisation.host.pkgs; 141 142 script = hostPkgs.writeShellScriptBin "create-builder" ( 143 # When running as non-interactively as part of a DarwinConfiguration the working directory 144 # must be set to a writeable directory. 145 (if cfg.workingDirectory != "." then '' 146 ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" 147 cd "${cfg.workingDirectory}" 148 '' else "") + '' 149 KEYS="''${KEYS:-./keys}" 150 ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" 151 PRIVATE_KEY="''${KEYS}/${user}_${keyType}" 152 PUBLIC_KEY="''${PRIVATE_KEY}.pub" 153 if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then 154 ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" 155 ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' 156 fi 157 if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then 158 (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") 159 fi 160 KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${config.system.build.vm}/bin/run-nixos-vm 161 ''); 162 163 in 164 script.overrideAttrs (old: { 165 meta = (old.meta or { }) // { 166 platforms = lib.platforms.darwin; 167 }; 168 }); 169 170 system = { 171 # To prevent gratuitous rebuilds on each change to Nixpkgs 172 nixos.revision = null; 173 174 stateVersion = lib.mkDefault (throw '' 175 The macOS linux builder should not need a stateVersion to be set, but a module 176 has accessed stateVersion nonetheless. 177 Please inspect the trace of the following command to figure out which module 178 has a dependency on stateVersion. 179 180 nix-instantiate --attr darwin.builder --show-trace 181 ''); 182 }; 183 184 users.users."${user}" = { 185 isNormalUser = true; 186 }; 187 188 security.polkit.enable = true; 189 190 security.polkit.extraConfig = '' 191 polkit.addRule(function(action, subject) { 192 if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") { 193 return "yes"; 194 } else { 195 return "no"; 196 } 197 }) 198 ''; 199 200 virtualisation = { 201 diskSize = cfg.diskSize; 202 203 memorySize = cfg.memorySize; 204 205 forwardPorts = [ 206 { from = "host"; guest.port = 22; host.port = cfg.hostPort; } 207 ]; 208 209 # Disable graphics for the builder since users will likely want to run it 210 # non-interactively in the background. 211 graphics = false; 212 213 sharedDirectories.keys = { 214 source = "\"$KEYS\""; 215 target = keysDirectory; 216 }; 217 218 # If we don't enable this option then the host will fail to delegate builds 219 # to the guest, because: 220 # 221 # - The host will lock the path to build 222 # - The host will delegate the build to the guest 223 # - The guest will attempt to lock the same path and fail because 224 # the lockfile on the host is visible on the guest 225 # 226 # Snapshotting the host's /nix/store as an image isolates the guest VM's 227 # /nix/store from the host's /nix/store, preventing this problem. 228 useNixStoreImage = true; 229 230 # Obviously the /nix/store needs to be writable on the guest in order for it 231 # to perform builds. 232 writableStore = true; 233 234 # This ensures that anything built on the guest isn't lost when the guest is 235 # restarted. 236 writableStoreUseTmpfs = false; 237 }; 238 }; 239}