diff --git a/doc/packages/darwin-builder.section.md b/doc/packages/darwin-builder.section.md index ca8519c5bf5f..06358c790165 100644 --- a/doc/packages/darwin-builder.section.md +++ b/doc/packages/darwin-builder.section.md @@ -100,7 +100,7 @@ $ sudo launchctl kickstart -k system/org.nixos.nix-daemon darwin-builder = nixpkgs.lib.nixosSystem { system = linuxSystem; modules = [ - "${nixpkgs}/nixos/modules/profiles/macos-builder.nix" + "${nixpkgs}/nixos/modules/profiles/nix-builder-vm.nix" { virtualisation = { host.pkgs = pkgs; darwin-builder.workingDirectory = "/var/lib/darwin-builder"; @@ -158,7 +158,7 @@ in the example below and rebuild. darwin-builder = nixpkgs.lib.nixosSystem { system = linuxSystem; modules = [ - "${nixpkgs}/nixos/modules/profiles/macos-builder.nix" + "${nixpkgs}/nixos/modules/profiles/nix-builder-vm.nix" { virtualisation.host.pkgs = pkgs; virtualisation.darwin-builder.diskSize = 5120; @@ -185,6 +185,6 @@ nix-repl> darwin.linux-builder.nixosConfig.nix.package «derivation /nix/store/...-nix-2.17.0.drv» nix-repl> :p darwin.linux-builder.nixosOptions.virtualisation.memorySize.definitionsWithLocations -[ { file = "/home/user/src/nixpkgs/nixos/modules/profiles/macos-builder.nix"; value = 3072; } ] +[ { file = "/home/user/src/nixpkgs/nixos/modules/profiles/nix-builder-vm.nix"; value = 3072; } ] ``` diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix index 08b2fce40011..896bb6bc9fbd 100644 --- a/nixos/modules/profiles/macos-builder.nix +++ b/nixos/modules/profiles/macos-builder.nix @@ -1,256 +1,5 @@ -{ config, lib, options, ... }: - -let - keysDirectory = "/var/keys"; - - user = "builder"; - - keyType = "ed25519"; - - cfg = config.virtualisation.darwin-builder; - +let lib = import ../../../lib; in - -{ - imports = [ - ../virtualisation/qemu-vm.nix - - # Avoid a dependency on stateVersion - { - disabledModules = [ - ../virtualisation/nixos-containers.nix - ../services/x11/desktop-managers/xterm.nix - ]; - # swraid's default depends on stateVersion - config.boot.swraid.enable = false; - options.boot.isContainer = lib.mkOption { default = false; internal = true; }; - } - ]; - - options.virtualisation.darwin-builder = with lib; { - diskSize = mkOption { - default = 20 * 1024; - type = types.int; - example = 30720; - description = "The maximum disk space allocated to the runner in MB"; - }; - memorySize = mkOption { - default = 3 * 1024; - type = types.int; - example = 8192; - description = "The runner's memory in MB"; - }; - min-free = mkOption { - default = 1024 * 1024 * 1024; - type = types.int; - example = 1073741824; - description = '' - The threshold (in bytes) of free disk space left at which to - start garbage collection on the runner - ''; - }; - max-free = mkOption { - default = 3 * 1024 * 1024 * 1024; - type = types.int; - example = 3221225472; - description = '' - The threshold (in bytes) of free disk space left at which to - stop garbage collection on the runner - ''; - }; - workingDirectory = mkOption { - default = "."; - type = types.str; - example = "/var/lib/darwin-builder"; - description = '' - The working directory to use to run the script. When running - as part of a flake will need to be set to a non read-only filesystem. - ''; - }; - hostPort = mkOption { - default = 31022; - type = types.int; - example = 22; - description = '' - The localhost host port to forward TCP to the guest port. - ''; - }; - }; - - config = { - # The builder is not intended to be used interactively - documentation.enable = false; - - environment.etc = { - "ssh/ssh_host_ed25519_key" = { - mode = "0600"; - - source = ./keys/ssh_host_ed25519_key; - }; - - "ssh/ssh_host_ed25519_key.pub" = { - mode = "0644"; - - source = ./keys/ssh_host_ed25519_key.pub; - }; - }; - - # DNS fails for QEMU user networking (SLiRP) on macOS. See: - # - # https://github.com/utmapp/UTM/issues/2353 - # - # This works around that by using a public DNS server other than the DNS - # server that QEMU provides (normally 10.0.2.3) - networking.nameservers = [ "8.8.8.8" ]; - - # The linux builder is a lightweight VM for remote building; not evaluation. - nix.channel.enable = false; - - # Deployment is by image. - # TODO system.switch.enable = false;? - system.disableInstallerTools = true; - - nix.settings = { - auto-optimise-store = true; - - min-free = cfg.min-free; - - max-free = cfg.max-free; - - trusted-users = [ user ]; - }; - - services = { - getty.autologinUser = user; - - openssh = { - enable = true; - - authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ]; - }; - }; - - system.build.macos-builder-installer = - let - privateKey = "/etc/nix/${user}_${keyType}"; - - publicKey = "${privateKey}.pub"; - - # This installCredentials script is written so that it's as easy as - # possible for a user to audit before confirming the `sudo` - installCredentials = hostPkgs.writeShellScript "install-credentials" '' - set -euo pipefail - - KEYS="''${1}" - INSTALL=${hostPkgs.coreutils}/bin/install - "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey} - "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey} - ''; - - hostPkgs = config.virtualisation.host.pkgs; - - script = hostPkgs.writeShellScriptBin "create-builder" ( - '' - set -euo pipefail - '' + - # When running as non-interactively as part of a DarwinConfiguration the working directory - # must be set to a writeable directory. - (if cfg.workingDirectory != "." then '' - ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" - cd "${cfg.workingDirectory}" - '' else "") + '' - KEYS="''${KEYS:-./keys}" - ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" - PRIVATE_KEY="''${KEYS}/${user}_${keyType}" - PUBLIC_KEY="''${PRIVATE_KEY}.pub" - if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then - ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" - ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' - fi - if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then - (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") - fi - KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} - ''); - - in - script.overrideAttrs (old: { - pos = __curPos; # sets meta.position to point here; see script binding above for package definition - meta = (old.meta or { }) // { - platforms = lib.platforms.darwin; - }; - passthru = (old.passthru or { }) // { - # Let users in the repl inspect the config - nixosConfig = config; - nixosOptions = options; - }; - }); - - system = { - # To prevent gratuitous rebuilds on each change to Nixpkgs - nixos.revision = null; - - # to be updated by module maintainers, see nixpkgs#325610 - stateVersion = "24.05"; - }; - - users.users."${user}" = { - isNormalUser = true; - }; - - security.polkit.enable = true; - - security.polkit.extraConfig = '' - polkit.addRule(function(action, subject) { - if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") { - return "yes"; - } else { - return "no"; - } - }) - ''; - - virtualisation = { - diskSize = cfg.diskSize; - - memorySize = cfg.memorySize; - - forwardPorts = [ - { from = "host"; guest.port = 22; host.port = cfg.hostPort; } - ]; - - # Disable graphics for the builder since users will likely want to run it - # non-interactively in the background. - graphics = false; - - sharedDirectories.keys = { - source = "\"$KEYS\""; - target = keysDirectory; - }; - - # If we don't enable this option then the host will fail to delegate builds - # to the guest, because: - # - # - The host will lock the path to build - # - The host will delegate the build to the guest - # - The guest will attempt to lock the same path and fail because - # the lockfile on the host is visible on the guest - # - # Snapshotting the host's /nix/store as an image isolates the guest VM's - # /nix/store from the host's /nix/store, preventing this problem. - useNixStoreImage = true; - - # Obviously the /nix/store needs to be writable on the guest in order for it - # to perform builds. - writableStore = true; - - # This ensures that anything built on the guest isn't lost when the guest is - # restarted. - writableStoreUseTmpfs = false; - - # Pass certificates from host to the guest otherwise when custom CA certificates - # are required we can't use the cached builder. - useHostCerts = true; - }; - }; -} + lib.warnIf (lib.isInOldestRelease 2411) + "nixos/modules/profiles/macos-builder.nix has moved to nixos/modules/profiles/nix-builder-vm.nix; please update your NixOS imports." + ./nix-builder-vm.nix diff --git a/nixos/modules/profiles/nix-builder-vm.nix b/nixos/modules/profiles/nix-builder-vm.nix new file mode 100644 index 000000000000..fcaca974f302 --- /dev/null +++ b/nixos/modules/profiles/nix-builder-vm.nix @@ -0,0 +1,284 @@ +/* + This profile uses NixOS to create a remote builder VM to build Linux packages, + which can be used to build packages for Linux on other operating systems; + primarily macOS. + + It contains both the relevant guest settings as well as an installer script + that manages it as a QEMU virtual machine on the host. +*/ +{ + config, + lib, + options, + ... +}: + +let + keysDirectory = "/var/keys"; + + user = "builder"; + + keyType = "ed25519"; + + cfg = config.virtualisation.darwin-builder; + +in + +{ + imports = [ + ../virtualisation/qemu-vm.nix + + # Avoid a dependency on stateVersion + { + disabledModules = [ + ../virtualisation/nixos-containers.nix + ../services/x11/desktop-managers/xterm.nix + ]; + # swraid's default depends on stateVersion + config.boot.swraid.enable = false; + options.boot.isContainer = lib.mkOption { + default = false; + internal = true; + }; + } + ]; + + options.virtualisation.darwin-builder = with lib; { + diskSize = mkOption { + default = 20 * 1024; + type = types.int; + example = 30720; + description = "The maximum disk space allocated to the runner in MB"; + }; + memorySize = mkOption { + default = 3 * 1024; + type = types.int; + example = 8192; + description = "The runner's memory in MB"; + }; + min-free = mkOption { + default = 1024 * 1024 * 1024; + type = types.int; + example = 1073741824; + description = '' + The threshold (in bytes) of free disk space left at which to + start garbage collection on the runner + ''; + }; + max-free = mkOption { + default = 3 * 1024 * 1024 * 1024; + type = types.int; + example = 3221225472; + description = '' + The threshold (in bytes) of free disk space left at which to + stop garbage collection on the runner + ''; + }; + workingDirectory = mkOption { + default = "."; + type = types.str; + example = "/var/lib/darwin-builder"; + description = '' + The working directory to use to run the script. When running + as part of a flake will need to be set to a non read-only filesystem. + ''; + }; + hostPort = mkOption { + default = 31022; + type = types.int; + example = 22; + description = '' + The localhost host port to forward TCP to the guest port. + ''; + }; + }; + + config = { + # The builder is not intended to be used interactively + documentation.enable = false; + + environment.etc = { + "ssh/ssh_host_ed25519_key" = { + mode = "0600"; + + source = ./keys/ssh_host_ed25519_key; + }; + + "ssh/ssh_host_ed25519_key.pub" = { + mode = "0644"; + + source = ./keys/ssh_host_ed25519_key.pub; + }; + }; + + # DNS fails for QEMU user networking (SLiRP) on macOS. See: + # + # https://github.com/utmapp/UTM/issues/2353 + # + # This works around that by using a public DNS server other than the DNS + # server that QEMU provides (normally 10.0.2.3) + networking.nameservers = [ "8.8.8.8" ]; + + # The linux builder is a lightweight VM for remote building; not evaluation. + nix.channel.enable = false; + + # Deployment is by image. + # TODO system.switch.enable = false;? + system.disableInstallerTools = true; + + nix.settings = { + auto-optimise-store = true; + + min-free = cfg.min-free; + + max-free = cfg.max-free; + + trusted-users = [ user ]; + }; + + services = { + getty.autologinUser = user; + + openssh = { + enable = true; + + authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ]; + }; + }; + + system.build.macos-builder-installer = + let + privateKey = "/etc/nix/${user}_${keyType}"; + + publicKey = "${privateKey}.pub"; + + # This installCredentials script is written so that it's as easy as + # possible for a user to audit before confirming the `sudo` + installCredentials = hostPkgs.writeShellScript "install-credentials" '' + set -euo pipefail + + KEYS="''${1}" + INSTALL=${hostPkgs.coreutils}/bin/install + "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey} + "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey} + ''; + + hostPkgs = config.virtualisation.host.pkgs; + + script = hostPkgs.writeShellScriptBin "create-builder" ( + '' + set -euo pipefail + '' + + + # When running as non-interactively as part of a DarwinConfiguration the working directory + # must be set to a writeable directory. + ( + if cfg.workingDirectory != "." then + '' + ${hostPkgs.coreutils}/bin/mkdir --parent "${cfg.workingDirectory}" + cd "${cfg.workingDirectory}" + '' + else + "" + ) + + '' + KEYS="''${KEYS:-./keys}" + ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}" + PRIVATE_KEY="''${KEYS}/${user}_${keyType}" + PUBLIC_KEY="''${PRIVATE_KEY}.pub" + if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then + ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}" + ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost' + fi + if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then + (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}") + fi + KEYS="$(${hostPkgs.nix}/bin/nix-store --add "$KEYS")" ${lib.getExe config.system.build.vm} + '' + ); + + in + script.overrideAttrs (old: { + pos = __curPos; # sets meta.position to point here; see script binding above for package definition + meta = (old.meta or { }) // { + platforms = lib.platforms.darwin; + }; + passthru = (old.passthru or { }) // { + # Let users in the repl inspect the config + nixosConfig = config; + nixosOptions = options; + }; + }); + + system = { + # To prevent gratuitous rebuilds on each change to Nixpkgs + nixos.revision = null; + + # to be updated by module maintainers, see nixpkgs#325610 + stateVersion = "24.05"; + }; + + users.users."${user}" = { + isNormalUser = true; + }; + + security.polkit.enable = true; + + security.polkit.extraConfig = '' + polkit.addRule(function(action, subject) { + if (action.id === "org.freedesktop.login1.power-off" && subject.user === "${user}") { + return "yes"; + } else { + return "no"; + } + }) + ''; + + virtualisation = { + diskSize = cfg.diskSize; + + memorySize = cfg.memorySize; + + forwardPorts = [ + { + from = "host"; + guest.port = 22; + host.port = cfg.hostPort; + } + ]; + + # Disable graphics for the builder since users will likely want to run it + # non-interactively in the background. + graphics = false; + + sharedDirectories.keys = { + source = "\"$KEYS\""; + target = keysDirectory; + }; + + # If we don't enable this option then the host will fail to delegate builds + # to the guest, because: + # + # - The host will lock the path to build + # - The host will delegate the build to the guest + # - The guest will attempt to lock the same path and fail because + # the lockfile on the host is visible on the guest + # + # Snapshotting the host's /nix/store as an image isolates the guest VM's + # /nix/store from the host's /nix/store, preventing this problem. + useNixStoreImage = true; + + # Obviously the /nix/store needs to be writable on the guest in order for it + # to perform builds. + writableStore = true; + + # This ensures that anything built on the guest isn't lost when the guest is + # restarted. + writableStoreUseTmpfs = false; + + # Pass certificates from host to the guest otherwise when custom CA certificates + # are required we can't use the cached builder. + useHostCerts = true; + }; + }; +} diff --git a/pkgs/top-level/darwin-packages.nix b/pkgs/top-level/darwin-packages.nix index f0bdad863e90..919e02702656 100644 --- a/pkgs/top-level/darwin-packages.nix +++ b/pkgs/top-level/darwin-packages.nix @@ -245,7 +245,7 @@ impure-cmds // appleSourcePackages // chooseLibs // { nixos = import ../../nixos { configuration = { imports = [ - ../../nixos/modules/profiles/macos-builder.nix + ../../nixos/modules/profiles/nix-builder-vm.nix ] ++ modules; # If you need to override this, consider starting with the right Nixpkgs