{ config, pkgs, lib, otherHosts, ... }: let cfg = config.sidonia; forEachOtherHost = f: map f otherHosts; in { options.sidonia.services.distributedBuilds = with lib; { host = { enable = mkEnableOption "Enable becoming a distributed build host"; user = mkOption { type = types.str; default = "builder"; }; signing = { pubKey = mkOption { description = "Public key for store binary cache signing"; type = types.str; }; privKeyPath = mkOption { description = "Path to private cache signing key"; type = types.path; }; }; ssh.pubKey = mkOption { description = "known_hosts public key"; type = types.str; }; hostNames = mkOption { description = "Hostnames or ip addresses of the build host"; type = types.listOf types.str; }; maxJobs = mkOption { description = "Maximum amount of jobs to accept from a single client"; type = types.either types.int (types.enum [ "auto" ]); default = config.nix.settings.max-jobs; }; }; client = { enable = mkEnableOption "Enable taking advantage of distributed builds"; ssh = { pubKey = mkOption { type = types.str; default = cfg.ssh.pubKey; }; privKeyPath = mkOption { type = types.path; default = cfg.ssh.privKeyPath; }; }; }; }; config = let host = cfg.services.distributedBuilds.host; client = cfg.services.distributedBuilds.client; in lib.mkMerge [ (lib.mkIf client.enable ( lib.mkMerge ( (forEachOtherHost ( remote: let remoteHost = remote.config.sidonia.services.distributedBuilds.host; builderName = "${remoteHost.user}_at_${remote.config.networking.hostName}"; substituterName = "substituter_at_${remote.config.networking.hostName}"; hostName = builtins.head remoteHost.hostNames; in lib.mkIf remoteHost.enable { programs.ssh = { knownHosts = { ${builderName} = { extraHostNames = remoteHost.hostNames; publicKey = remoteHost.ssh.pubKey; }; }; extraConfig = '' Host ${substituterName} User nix-ssh Hostname ${hostName} IdentitiesOnly yes IdentityFile ${client.ssh.privKeyPath} ConnectTimeout 3 ''; }; nix = { settings = { trusted-public-keys = [ remoteHost.signing.pubKey ]; substituters = [ "${remote.config.nix.sshServe.protocol}://${substituterName}" ]; }; buildMachines = [ ( { inherit hostName; sshUser = remoteHost.user; sshKey = client.ssh.privKeyPath; #publicHostKey = remoteHost.ssh.pubKey; # don't use this value as it wants base64, fall back to known_hosts instead systems = [ "x86_64-linux" "i686-linux" ]; protocol = "ssh-ng"; speedFactor = 2; supportedFeatures = remote.config.nix.settings.system-features; } // (if remoteHost.maxJobs != "auto" then { inherit (remoteHost) maxJobs; } else { }) ) ]; }; } )) ++ [ { nix.distributedBuilds = true; nix.settings.builders-use-substitutes = true; } ] ) )) (lib.mkIf host.enable ( lib.mkMerge ( (forEachOtherHost ( remote: let remoteCfg = remote.config.sidonia.services.distributedBuilds; in lib.mkIf remoteCfg.client.enable { users.users.${host.user}.openssh.authorizedKeys.keys = [ remoteCfg.client.ssh.pubKey ]; nix.sshServe.keys = [ remoteCfg.client.ssh.pubKey ]; } )) ++ [ { users.users.${host.user}.isNormalUser = true; services.openssh.settings.AllowUsers = [ host.user "nix-ssh" ]; nix.settings.secret-key-files = host.signing.privKeyPath; nix.settings.trusted-users = [ host.user ]; nix.sshServe = { enable = true; write = true; trusted = true; protocol = "ssh-ng"; }; } ] ) )) ]; }