diff --git a/doc/builders/special.xml b/doc/builders/special.xml
index 8902ce5c81329..525eb71abfe7e 100644
--- a/doc/builders/special.xml
+++ b/doc/builders/special.xml
@@ -7,4 +7,5 @@
+
diff --git a/doc/builders/special/darwin-builder.section.md b/doc/builders/special/darwin-builder.section.md
new file mode 100644
index 0000000000000..96b1fcb507f25
--- /dev/null
+++ b/doc/builders/special/darwin-builder.section.md
@@ -0,0 +1,60 @@
+# darwin.builder {#sec-darwin-builder}
+
+`darwin.builder` provides a way to bootstrap a Linux builder on a macOS machine.
+
+This requires macOS version 12.4 or later.
+
+This also requires that port 22 on your machine is free (since Nix does not
+permit specifying a non-default SSH port for builders).
+
+You will also need to be a trusted user for your Nix installation. In other
+words, your `/etc/nix/nix.conf` should have something like:
+
+```
+extra-trusted-users =
+```
+
+To launch the builder, run the following flake:
+
+```ShellSession
+$ nix run nixpkgs#darwin.builder
+```
+
+That will prompt you to enter your `sudo` password:
+
+```
++ sudo --reset-timestamp /nix/store/…-install-credentials.sh ./keys
+Password:
+```
+
+… so that it can install a private key used to `ssh` into the build server.
+After that the script will launch the virtual machine:
+
+```
+<<< Welcome to NixOS 22.11.20220901.1bd8d11 (aarch64) - ttyAMA0 >>>
+
+Run 'nixos-help' for the NixOS manual.
+
+nixos login:
+```
+
+> Note: When you need to stop the VM, type `Ctrl`-`a` + `c` to open the `qemu`
+> prompt and then type `quit` followed by `Enter`
+
+To delegate builds to the remote builder, add the following options to your
+`nix.conf` file:
+
+```
+# - Replace ${ARCH} with either aarch64 or x86_64 to match your host machine
+# - Replace ${MAX_JOBS} with the maximum number of builds (pick 4 if you're not sure)
+builders = ssh-ng://builder@localhost ${ARCH}-linux /etc/nix/builder_ed25519 ${MAX_JOBS} - - - c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUpCV2N4Yi9CbGFxdDFhdU90RStGOFFVV3JVb3RpQzVxQkorVXVFV2RWQ2Igcm9vdEBuaXhvcwo='
+
+# Not strictly necessary, but this will reduce your disk utilization
+builders-use-substitutes = true
+```
+
+… and then restart your Nix daemon to apply the change:
+
+```ShellSession
+$ sudo launchctl kickstart -k system/org.nixos.nix-daemon
+```
diff --git a/nixos/modules/profiles/keys/ssh_host_ed25519_key b/nixos/modules/profiles/keys/ssh_host_ed25519_key
new file mode 100644
index 0000000000000..b18489795369e
--- /dev/null
+++ b/nixos/modules/profiles/keys/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmwAAAJASuMMnErjD
+JwAAAAtzc2gtZWQyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmw
+AAAEDIN2VWFyggtoSPXcAFy8dtG1uAig8sCuyE21eMDt2GgJBWcxb/Blaqt1auOtE+F8QU
+WrUotiC5qBJ+UuEWdVCbAAAACnJvb3RAbml4b3MBAgM=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000000000..2c45826715fc5
--- /dev/null
+++ b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBWcxb/Blaqt1auOtE+F8QUWrUotiC5qBJ+UuEWdVCb root@nixos
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
new file mode 100644
index 0000000000000..895dd04cb4852
--- /dev/null
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -0,0 +1,134 @@
+{ config, pkgs, ... }:
+
+let
+ keysDirectory = "/var/keys";
+
+ user = "builder";
+
+ keyType = "ed25519";
+
+in
+
+{ imports = [
+ ../virtualisation/qemu-vm.nix
+ ];
+
+ # 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" ];
+
+ nix.settings = {
+ auto-optimise-store = true;
+
+ min-free = 1024 * 1024 * 1024;
+
+ max-free = 3 * 1024 * 1024 * 1024;
+
+ trusted-users = [ "root" user ];
+ };
+
+ services.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 = pkgs.writeShellScript "install-credentials" ''
+ 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;
+
+ in
+ hostPkgs.writeShellScriptBin "create-builder" ''
+ 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="$(nix-store --add "$KEYS")" ${config.system.build.vm}/bin/run-nixos-vm
+ '';
+
+ system.stateVersion = "22.05";
+
+ users.users."${user}"= {
+ isNormalUser = true;
+ };
+
+ virtualisation = {
+ diskSize = 20 * 1024;
+
+ memorySize = 3 * 1024;
+
+ forwardPorts = [
+ { from = "host"; guest.port = 22; host.port = 22; }
+ ];
+
+ # 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;
+ };
+}
diff --git a/pkgs/top-level/darwin-packages.nix b/pkgs/top-level/darwin-packages.nix
index 8c1e259525044..c270dd2220ddf 100644
--- a/pkgs/top-level/darwin-packages.nix
+++ b/pkgs/top-level/darwin-packages.nix
@@ -205,4 +205,23 @@ impure-cmds // appleSourcePackages // chooseLibs // {
discrete-scroll = callPackage ../os-specific/darwin/discrete-scroll { };
+ # See doc/builders/special/darwin-builder.section.md
+ builder =
+ let
+ toGuest = builtins.replaceStrings [ "darwin" ] [ "linux" ];
+
+ nixos = import ../../nixos {
+ configuration = {
+ imports = [
+ ../../nixos/modules/profiles/macos-builder.nix
+ ];
+
+ virtualisation.host = { inherit pkgs; };
+ };
+
+ system = toGuest stdenv.hostPlatform.system;
+ };
+
+ in
+ nixos.config.system.build.macos-builder-installer;
})