diff --git a/nix/modules/spindle.nix b/nix/modules/spindle.nix index e4bf62b9..5aad22e8 100644 --- a/nix/modules/spindle.nix +++ b/nix/modules/spindle.nix @@ -378,6 +378,12 @@ in description = "File containing this executor's token."; }; + generateToken = mkOption { + type = types.bool; + default = false; + description = "Generate the token file once at a persistent path."; + }; + labels = mkOption { type = types.listOf types.str; default = []; @@ -580,8 +586,20 @@ in ]; registrationCommands = concatStringsSep "\n" (mapAttrsToList ( - name: executor: - escapeShellArgs ( + name: executor: let + tokenFile = escapeShellArg executor.tokenFile; + tokenDir = escapeShellArg (builtins.dirOf executor.tokenFile); + tokenTemplate = escapeShellArg "${executor.tokenFile}.tmp.XXXXXX"; + generateCommand = escapeShellArgs [ + "${cfg.package}/bin/spindle" + "mill" + "executor" + "token" + "generate" + "--db" + (toString cfg.server.dbPath) + ]; + registerCommand = escapeShellArgs ( [ "${cfg.package}/bin/spindle" "mill" @@ -594,7 +612,34 @@ in ] ++ concatMap (label: ["--label" label]) executor.labels ++ [name] - ) + ); + emptyGeneratedToken = escapeShellArg "generated token for ${name} is empty"; + missingToken = escapeShellArg "token file for ${name} is missing or empty: ${executor.tokenFile}"; + in '' + ${optionalString executor.generateToken '' + if [ ! -e ${tokenFile} ]; then + if [ ! -d ${tokenDir} ]; then + ${pkgs.coreutils}/bin/install -d -m 0700 ${tokenDir} + fi + umask 0077 + tmp="$(${pkgs.coreutils}/bin/mktemp ${tokenTemplate})" + trap '${pkgs.coreutils}/bin/rm -f "$tmp"' EXIT + ${generateCommand} > "$tmp" + if [ ! -s "$tmp" ]; then + printf '%s\n' ${emptyGeneratedToken} >&2 + exit 1 + fi + ${pkgs.coreutils}/bin/chmod 0600 "$tmp" + ${pkgs.coreutils}/bin/mv "$tmp" ${tokenFile} + trap - EXIT + fi + ''} + if [ ! -f ${tokenFile} ] || [ ! -s ${tokenFile} ]; then + printf '%s\n' ${missingToken} >&2 + exit 1 + fi + ${registerCommand} + '' ) cfg.mill.executors); @@ -651,6 +696,13 @@ in message = "services.tangled.spindle.mill.executors.${name}.tokenFile must be an absolute path"; }) cfg.mill.executors + ++ mapAttrsToList (name: executor: { + assertion = + !executor.generateToken + || (!hasPrefix "/run/" executor.tokenFile && !hasPrefix "/tmp/" executor.tokenFile); + message = "services.tangled.spindle.mill.executors.${name}.tokenFile must persist across reboots when generateToken is enabled"; + }) + cfg.mill.executors ++ [ { assertion = length enabledAgentPorts == length (unique enabledAgentPorts); @@ -719,9 +771,9 @@ in { spindle-mill-executors = mkIf hasExecutors { description = "register spindle mill executors"; - before = - ["spindle.service"] - ++ mapAttrsToList (name: _: "${executorServiceName name}.service") enabledExecutors; + after = ["spindle.service"]; + wants = ["spindle.service"]; + before = mapAttrsToList (name: _: "${executorServiceName name}.service") enabledExecutors; wantedBy = ["multi-user.target"]; serviceConfig = { Type = "oneshot";