lol

nixos/github-runners: fix format of service file

+196 -194
+196 -194
nixos/modules/services/continuous-integration/github-runner/service.nix
··· 45 45 config.nix.package 46 46 ] ++ cfg.extraPackages; 47 47 48 - serviceConfig = mkMerge [{ 49 - ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service"; 48 + serviceConfig = mkMerge [ 49 + { 50 + ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service"; 50 51 51 - # Does the following, sequentially: 52 - # - If the module configuration or the token has changed, purge the state directory, 53 - # and create the current and the new token file with the contents of the configured 54 - # token. While both files have the same content, only the later is accessible by 55 - # the service user. 56 - # - Configure the runner using the new token file. When finished, delete it. 57 - # - Set up the directory structure by creating the necessary symlinks. 58 - ExecStartPre = 59 - let 60 - # Wrapper script which expects the full path of the state, working and logs 61 - # directory as arguments. Overrides the respective systemd variables to provide 62 - # unambiguous directory names. This becomes relevant, for example, if the 63 - # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory= 64 - # to contain more than one directory. This causes systemd to set the respective 65 - # environment variables with the path of all of the given directories, separated 66 - # by a colon. 67 - writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" '' 68 - set -euo pipefail 52 + # Does the following, sequentially: 53 + # - If the module configuration or the token has changed, purge the state directory, 54 + # and create the current and the new token file with the contents of the configured 55 + # token. While both files have the same content, only the later is accessible by 56 + # the service user. 57 + # - Configure the runner using the new token file. When finished, delete it. 58 + # - Set up the directory structure by creating the necessary symlinks. 59 + ExecStartPre = 60 + let 61 + # Wrapper script which expects the full path of the state, working and logs 62 + # directory as arguments. Overrides the respective systemd variables to provide 63 + # unambiguous directory names. This becomes relevant, for example, if the 64 + # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory= 65 + # to contain more than one directory. This causes systemd to set the respective 66 + # environment variables with the path of all of the given directories, separated 67 + # by a colon. 68 + writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" '' 69 + set -euo pipefail 69 70 70 - STATE_DIRECTORY="$1" 71 - WORK_DIRECTORY="$2" 72 - LOGS_DIRECTORY="$3" 71 + STATE_DIRECTORY="$1" 72 + WORK_DIRECTORY="$2" 73 + LOGS_DIRECTORY="$3" 73 74 74 - ${lines} 75 - ''; 76 - runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" "workDir" ] cfg; 77 - newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig); 78 - currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json"; 79 - newConfigTokenPath= "$STATE_DIRECTORY/.new-token"; 80 - currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}"; 75 + ${lines} 76 + ''; 77 + runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" "workDir" ] cfg; 78 + newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig); 79 + currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json"; 80 + newConfigTokenPath = "$STATE_DIRECTORY/.new-token"; 81 + currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}"; 81 82 82 - runnerCredFiles = [ 83 - ".credentials" 84 - ".credentials_rsaparams" 85 - ".runner" 86 - ]; 87 - unconfigureRunner = writeScript "unconfigure" '' 88 - copy_tokens() { 89 - # Copy the configured token file to the state dir and allow the service user to read the file 90 - install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}" 91 - # Also copy current file to allow for a diff on the next start 92 - install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}" 93 - } 94 - clean_state() { 95 - find "$STATE_DIRECTORY/" -mindepth 1 -delete 96 - copy_tokens 97 - } 98 - diff_config() { 99 - changed=0 100 - # Check for module config changes 101 - [[ -f "${currentConfigPath}" ]] \ 102 - && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \ 103 - || changed=1 104 - # Also check the content of the token file 105 - [[ -f "${currentConfigTokenPath}" ]] \ 106 - && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \ 107 - || changed=1 108 - # If the config has changed, remove old state and copy tokens 109 - if [[ "$changed" -eq 1 ]]; then 110 - echo "Config has changed, removing old runner state." 111 - echo "The old runner will still appear in the GitHub Actions UI." \ 112 - "You have to remove it manually." 83 + runnerCredFiles = [ 84 + ".credentials" 85 + ".credentials_rsaparams" 86 + ".runner" 87 + ]; 88 + unconfigureRunner = writeScript "unconfigure" '' 89 + copy_tokens() { 90 + # Copy the configured token file to the state dir and allow the service user to read the file 91 + install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}" 92 + # Also copy current file to allow for a diff on the next start 93 + install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}" 94 + } 95 + clean_state() { 96 + find "$STATE_DIRECTORY/" -mindepth 1 -delete 97 + copy_tokens 98 + } 99 + diff_config() { 100 + changed=0 101 + # Check for module config changes 102 + [[ -f "${currentConfigPath}" ]] \ 103 + && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \ 104 + || changed=1 105 + # Also check the content of the token file 106 + [[ -f "${currentConfigTokenPath}" ]] \ 107 + && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \ 108 + || changed=1 109 + # If the config has changed, remove old state and copy tokens 110 + if [[ "$changed" -eq 1 ]]; then 111 + echo "Config has changed, removing old runner state." 112 + echo "The old runner will still appear in the GitHub Actions UI." \ 113 + "You have to remove it manually." 114 + clean_state 115 + fi 116 + } 117 + if [[ "${optionalString cfg.ephemeral "1"}" ]]; then 118 + # In ephemeral mode, we always want to start with a clean state 113 119 clean_state 114 - fi 115 - } 116 - if [[ "${optionalString cfg.ephemeral "1"}" ]]; then 117 - # In ephemeral mode, we always want to start with a clean state 118 - clean_state 119 - elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then 120 - # There are state files from a previous run; diff them to decide if we need a new registration 121 - diff_config 122 - else 123 - # The state directory is entirely empty which indicates a first start 124 - copy_tokens 125 - fi 126 - ''; 127 - configureRunner = writeScript "configure" '' 128 - if [[ -e "${newConfigTokenPath}" ]]; then 129 - echo "Configuring GitHub Actions Runner" 130 - args=( 131 - --unattended 132 - --disableupdate 133 - --work "$WORK_DIRECTORY" 134 - --url ${escapeShellArg cfg.url} 135 - --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)} 136 - --name ${escapeShellArg cfg.name} 137 - ${optionalString cfg.replace "--replace"} 138 - ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"} 139 - ${optionalString cfg.ephemeral "--ephemeral"} 140 - ) 141 - # If the token file contains a PAT (i.e., it starts with "ghp_" or "github_pat_"), we have to use the --pat option, 142 - # if it is not a PAT, we assume it contains a registration token and use the --token option 143 - token=$(<"${newConfigTokenPath}") 144 - if [[ "$token" =~ ^ghp_* ]] || [[ "$token" =~ ^github_pat_* ]]; then 145 - args+=(--pat "$token") 120 + elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then 121 + # There are state files from a previous run; diff them to decide if we need a new registration 122 + diff_config 146 123 else 147 - args+=(--token "$token") 124 + # The state directory is entirely empty which indicates a first start 125 + copy_tokens 148 126 fi 149 - ${cfg.package}/bin/config.sh "''${args[@]}" 150 - # Move the automatically created _diag dir to the logs dir 151 - mkdir -p "$STATE_DIRECTORY/_diag" 152 - cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/" 153 - rm -rf "$STATE_DIRECTORY/_diag/" 154 - # Cleanup token from config 155 - rm "${newConfigTokenPath}" 156 - # Symlink to new config 157 - ln -s '${newConfigPath}' "${currentConfigPath}" 158 - fi 159 - ''; 160 - setupWorkDir = writeScript "setup-work-dirs" '' 161 - # Cleanup previous service 162 - ${pkgs.findutils}/bin/find -H "$WORK_DIRECTORY" -mindepth 1 -delete 127 + ''; 128 + configureRunner = writeScript "configure" '' 129 + if [[ -e "${newConfigTokenPath}" ]]; then 130 + echo "Configuring GitHub Actions Runner" 131 + args=( 132 + --unattended 133 + --disableupdate 134 + --work "$WORK_DIRECTORY" 135 + --url ${escapeShellArg cfg.url} 136 + --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)} 137 + --name ${escapeShellArg cfg.name} 138 + ${optionalString cfg.replace "--replace"} 139 + ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"} 140 + ${optionalString cfg.ephemeral "--ephemeral"} 141 + ) 142 + # If the token file contains a PAT (i.e., it starts with "ghp_" or "github_pat_"), we have to use the --pat option, 143 + # if it is not a PAT, we assume it contains a registration token and use the --token option 144 + token=$(<"${newConfigTokenPath}") 145 + if [[ "$token" =~ ^ghp_* ]] || [[ "$token" =~ ^github_pat_* ]]; then 146 + args+=(--pat "$token") 147 + else 148 + args+=(--token "$token") 149 + fi 150 + ${cfg.package}/bin/config.sh "''${args[@]}" 151 + # Move the automatically created _diag dir to the logs dir 152 + mkdir -p "$STATE_DIRECTORY/_diag" 153 + cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/" 154 + rm -rf "$STATE_DIRECTORY/_diag/" 155 + # Cleanup token from config 156 + rm "${newConfigTokenPath}" 157 + # Symlink to new config 158 + ln -s '${newConfigPath}' "${currentConfigPath}" 159 + fi 160 + ''; 161 + setupWorkDir = writeScript "setup-work-dirs" '' 162 + # Cleanup previous service 163 + ${pkgs.findutils}/bin/find -H "$WORK_DIRECTORY" -mindepth 1 -delete 163 164 164 - # Link _diag dir 165 - ln -s "$LOGS_DIRECTORY" "$WORK_DIRECTORY/_diag" 165 + # Link _diag dir 166 + ln -s "$LOGS_DIRECTORY" "$WORK_DIRECTORY/_diag" 166 167 167 - # Link the runner credentials to the work dir 168 - ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$WORK_DIRECTORY/" 169 - ''; 170 - in 168 + # Link the runner credentials to the work dir 169 + ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$WORK_DIRECTORY/" 170 + ''; 171 + in 171 172 map (x: "${x} ${escapeShellArgs [ stateDir workDir logsDir ]}") [ 172 173 "+${unconfigureRunner}" # runs as root 173 174 configureRunner 174 175 setupWorkDir 175 176 ]; 176 177 177 - # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner) 178 - # to trigger a fresh registration. 179 - Restart = if cfg.ephemeral then "on-success" else "no"; 180 - # If the runner exits with `ReturnCode.RetryableError = 2`, always restart the service: 181 - # https://github.com/actions/runner/blob/40ed7f8/src/Runner.Common/Constants.cs#L146 182 - RestartForceExitStatus = [ 2 ]; 178 + # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner) 179 + # to trigger a fresh registration. 180 + Restart = if cfg.ephemeral then "on-success" else "no"; 181 + # If the runner exits with `ReturnCode.RetryableError = 2`, always restart the service: 182 + # https://github.com/actions/runner/blob/40ed7f8/src/Runner.Common/Constants.cs#L146 183 + RestartForceExitStatus = [ 2 ]; 183 184 184 - # Contains _diag 185 - LogsDirectory = [ systemdDir ]; 186 - # Default RUNNER_ROOT which contains ephemeral Runner data 187 - RuntimeDirectory = [ systemdDir ]; 188 - # Home of persistent runner data, e.g., credentials 189 - StateDirectory = [ systemdDir ]; 190 - StateDirectoryMode = "0700"; 191 - WorkingDirectory = workDir; 185 + # Contains _diag 186 + LogsDirectory = [ systemdDir ]; 187 + # Default RUNNER_ROOT which contains ephemeral Runner data 188 + RuntimeDirectory = [ systemdDir ]; 189 + # Home of persistent runner data, e.g., credentials 190 + StateDirectory = [ systemdDir ]; 191 + StateDirectoryMode = "0700"; 192 + WorkingDirectory = workDir; 192 193 193 - InaccessiblePaths = [ 194 - # Token file path given in the configuration, if visible to the service 195 - "-${cfg.tokenFile}" 196 - # Token file in the state directory 197 - "${stateDir}/${currentConfigTokenFilename}" 198 - ]; 194 + InaccessiblePaths = [ 195 + # Token file path given in the configuration, if visible to the service 196 + "-${cfg.tokenFile}" 197 + # Token file in the state directory 198 + "${stateDir}/${currentConfigTokenFilename}" 199 + ]; 199 200 200 - KillSignal = "SIGINT"; 201 + KillSignal = "SIGINT"; 201 202 202 - # Hardening (may overlap with DynamicUser=) 203 - # The following options are only for optimizing: 204 - # systemd-analyze security github-runner 205 - AmbientCapabilities = mkBefore [ "" ]; 206 - CapabilityBoundingSet = mkBefore [ "" ]; 207 - # ProtectClock= adds DeviceAllow=char-rtc r 208 - DeviceAllow = mkBefore [ "" ]; 209 - NoNewPrivileges = mkDefault true; 210 - PrivateDevices = mkDefault true; 211 - PrivateMounts = mkDefault true; 212 - PrivateTmp = mkDefault true; 213 - PrivateUsers = mkDefault true; 214 - ProtectClock = mkDefault true; 215 - ProtectControlGroups = mkDefault true; 216 - ProtectHome = mkDefault true; 217 - ProtectHostname = mkDefault true; 218 - ProtectKernelLogs = mkDefault true; 219 - ProtectKernelModules = mkDefault true; 220 - ProtectKernelTunables = mkDefault true; 221 - ProtectSystem = mkDefault "strict"; 222 - RemoveIPC = mkDefault true; 223 - RestrictNamespaces = mkDefault true; 224 - RestrictRealtime = mkDefault true; 225 - RestrictSUIDSGID = mkDefault true; 226 - UMask = mkDefault "0066"; 227 - ProtectProc = mkDefault "invisible"; 228 - SystemCallFilter = mkBefore [ 229 - "~@clock" 230 - "~@cpu-emulation" 231 - "~@module" 232 - "~@mount" 233 - "~@obsolete" 234 - "~@raw-io" 235 - "~@reboot" 236 - "~capset" 237 - "~setdomainname" 238 - "~sethostname" 239 - ]; 240 - RestrictAddressFamilies = mkBefore [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ]; 203 + # Hardening (may overlap with DynamicUser=) 204 + # The following options are only for optimizing: 205 + # systemd-analyze security github-runner 206 + AmbientCapabilities = mkBefore [ "" ]; 207 + CapabilityBoundingSet = mkBefore [ "" ]; 208 + # ProtectClock= adds DeviceAllow=char-rtc r 209 + DeviceAllow = mkBefore [ "" ]; 210 + NoNewPrivileges = mkDefault true; 211 + PrivateDevices = mkDefault true; 212 + PrivateMounts = mkDefault true; 213 + PrivateTmp = mkDefault true; 214 + PrivateUsers = mkDefault true; 215 + ProtectClock = mkDefault true; 216 + ProtectControlGroups = mkDefault true; 217 + ProtectHome = mkDefault true; 218 + ProtectHostname = mkDefault true; 219 + ProtectKernelLogs = mkDefault true; 220 + ProtectKernelModules = mkDefault true; 221 + ProtectKernelTunables = mkDefault true; 222 + ProtectSystem = mkDefault "strict"; 223 + RemoveIPC = mkDefault true; 224 + RestrictNamespaces = mkDefault true; 225 + RestrictRealtime = mkDefault true; 226 + RestrictSUIDSGID = mkDefault true; 227 + UMask = mkDefault "0066"; 228 + ProtectProc = mkDefault "invisible"; 229 + SystemCallFilter = mkBefore [ 230 + "~@clock" 231 + "~@cpu-emulation" 232 + "~@module" 233 + "~@mount" 234 + "~@obsolete" 235 + "~@raw-io" 236 + "~@reboot" 237 + "~capset" 238 + "~setdomainname" 239 + "~sethostname" 240 + ]; 241 + RestrictAddressFamilies = mkBefore [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ]; 241 242 242 - BindPaths = lib.optionals (cfg.workDir != null) [ cfg.workDir ]; 243 + BindPaths = lib.optionals (cfg.workDir != null) [ cfg.workDir ]; 243 244 244 - # Needs network access 245 - PrivateNetwork = mkDefault false; 246 - # Cannot be true due to Node 247 - MemoryDenyWriteExecute = mkDefault false; 245 + # Needs network access 246 + PrivateNetwork = mkDefault false; 247 + # Cannot be true due to Node 248 + MemoryDenyWriteExecute = mkDefault false; 248 249 249 - # The more restrictive "pid" option makes `nix` commands in CI emit 250 - # "GC Warning: Couldn't read /proc/stat" 251 - # You may want to set this to "pid" if not using `nix` commands 252 - ProcSubset = mkDefault "all"; 253 - # Coverage programs for compiled code such as `cargo-tarpaulin` disable 254 - # ASLR (address space layout randomization) which requires the 255 - # `personality` syscall 256 - # You may want to set this to `true` if not using coverage tooling on 257 - # compiled code 258 - LockPersonality = mkDefault false; 250 + # The more restrictive "pid" option makes `nix` commands in CI emit 251 + # "GC Warning: Couldn't read /proc/stat" 252 + # You may want to set this to "pid" if not using `nix` commands 253 + ProcSubset = mkDefault "all"; 254 + # Coverage programs for compiled code such as `cargo-tarpaulin` disable 255 + # ASLR (address space layout randomization) which requires the 256 + # `personality` syscall 257 + # You may want to set this to `true` if not using coverage tooling on 258 + # compiled code 259 + LockPersonality = mkDefault false; 259 260 260 - # Note that this has some interactions with the User setting; so you may 261 - # want to consult the systemd docs if using both. 262 - DynamicUser = mkDefault true; 263 - } 261 + # Note that this has some interactions with the User setting; so you may 262 + # want to consult the systemd docs if using both. 263 + DynamicUser = mkDefault true; 264 + } 264 265 (mkIf (cfg.user != null) { User = cfg.user; }) 265 - cfg.serviceOverrides]; 266 + cfg.serviceOverrides 267 + ]; 266 268 }