{ config, lib, pkgs, ... }: # TODO: should max-builds be enforced on thisHost as well? let inherit (builtins) map fromTOML toString readFile elem attrNames attrValues ; # TODO: test ssh-ng # https://discourse.nixos.org/t/wrapper-to-restrict-builder-access-through-ssh-worth-upstreaming/25834 nix-ssh-wrapper = pkgs.writeShellScript "nix-ssh-wrapper" '' case $SSH_ORIGINAL_COMMAND in "nix-daemon --stdio") exec ${config.nix.package}/bin/nix-daemon --stdio ;; "nix-store --serve --write") exec ${config.nix.package}/bin/nix-store --serve --write ;; *) echo "Access only allowed for using the nix remote builder" 1>&2 exit esac ''; known-hosts = let known-hosts' = lib.importTOML ../hosts/known-hosts.toml; # TODO: eww in lib.pipe known-hosts' [ (lib.flip lib.removeAttrs ["__default__"]) (lib.mapAttrs (fqdn: host: lib.recursiveUpdate (known-hosts'."__default__" or {}) host )) (lib.mapAttrsToList (fqdn: host: let allHostnames = [ fqdn ] ++ host.aliases; in lib.forEach allHostnames (alias: lib.nameValuePair alias (host // { aliases = lib.remove alias allHostnames; isAlias = fqdn != alias; }) ) )) lib.flatten lib.listToAttrs ]; hostNames = attrNames known-hosts; thisHost = known-hosts.${config.networking.fqdn}; thisHostIsBuilder = thisHost.buildMachine.maxJobs > 0; thisHostIsBuildee = thisHost.ssh ? userPublicKey; thisHostIsHopHost = elem config.networking.fqdn (lib.forEach (attrValues known-hosts) (host: host.ssh.proxyJump or null)); mkRemoteConfig = fqdn: let thatHost = known-hosts.${fqdn}; thatJump = known-hosts.${thatHost.ssh.proxyJump}; buildMachine = thatHost.buildMachine // { hostName = fqdn; sshUser = thatHost.ssh.listenUser; }; thatHostIsBuilder = thatHost.buildMachine.maxJobs > 0; thatHostIsBuildee = thatHost.ssh ? userPublicKey && thisHostIsBuilder; thatHostIsThis = elem config.networking.fqdn ([ fqdn ] ++ thatHost.aliases); in lib.mkIf (!thatHostIsThis) ( lib.mkMerge [ # out (lib.mkIf (thisHostIsBuildee && thatHostIsBuilder) { # TODO: Allow setting speedFactor for local builds, as local is currently fixed to 0 # https://github.com/NixOS/nix/issues/2457 nix.distributedBuilds = true; # useful when the builder has a faster internet connection than i do nix.settings.builders-use-substitutes = true; nix.buildMachines = lib.mkIf (!thatHost.isAlias) [ buildMachine ]; }) # out or jump (lib.mkIf (thisHostIsBuildee && thatHost.ssh ? listenPublicKey) { programs.ssh.knownHosts.${fqdn}.publicKey = thatHost.ssh.listenPublicKey; # TODO: use nix.buildMachines.*.publicHostKey ? # timeouts are great when remote is unresponsive. nix doesn't care, lix is way and tests each remote only once programs.ssh.extraConfig = '' Host ${fqdn} ConnectTimeout ${toString thatHost.ssh.connectTimeout} Port ${toString thatHost.ssh.listenPort} ${lib.optionalString (thatHost.ssh ? proxyJump) '' ProxyJump ${thatJump.ssh.listenUser}@${thatHost.ssh.proxyJump}:${toString thatJump.ssh.listenPort} ''} ${lib.optionalString (thatHost.ssh ? userPrivateKey) '' IdentityFile ${thatHost.ssh.userPrivateKey} ''} ''; sops.secrets = lib.mkIf (lib.hasPrefix "/run/secrets/" (thatHost.ssh.userPrivateKey or "")) { "${lib.removePrefix "/run/secrets/" thatHost.ssh.userPrivateKey}" = { }; }; }) # in (lib.mkIf ((thisHostIsBuilder || thisHostIsHopHost) && thatHostIsBuildee && !thatHost.isAlias) { # TODO: ensure the user is "nixbld-remote"? users.groups.${thisHost.ssh.listenUser} = { }; users.users.${thisHost.ssh.listenUser} = { isSystemUser = lib.mkDefault (!config.users.users.${thisHost.ssh.listenUser}.isNormalUser); useDefaultShell = lib.mkDefault true; openssh.authorizedKeys.keys = [ # https://man.archlinux.org/man/core/openssh/sshd.8.en#AUTHORIZED_KEYS_FILE_FORMAT # TODO: lib.getExe ''restrict,pty,command="${nix-ssh-wrapper}" ${thatHost.ssh.userPublicKey}'' ]; group = lib.mkOverride 1499 "${thisHost.ssh.listenUser}"; # mkOptionDefault - 1, }; }) (lib.mkIf (thisHostIsBuilder && thatHostIsBuildee && !thatHost.isAlias) { nix.settings.allowed-users = [ thisHost.ssh.listenUser ]; nix.settings.trusted-users = [ thisHost.ssh.listenUser ]; }) ]); in { imports = lib.forEach hostNames mkRemoteConfig; }