New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nixos/github-runner: init at v2.277.1 #116775
Merged
+1,921
−0
Merged
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
67da734
github-runner: init at 2.277.1
veehaitch a10f465
nixos/github-runner: initial version
veehaitch b4d9c31
nixos/github-runner: add warning if tokenFile in Nix store
veehaitch ac82082
github-runner: don't accept unexpected attrs
veehaitch fd1f0d2
github-runner: formatting nits
veehaitch a12d9d8
github-runner: add pre and post hooks to checkPhase
veehaitch 320479b
nixos/github-runner: update ExecStartPre= comment
veehaitch 9a51add
nixos/github-runner: adapt tokenFile option description
veehaitch 08df6fa
nixos/github-runner: remove mkDefault for DynamicUser=
veehaitch 91bf483
nixos/github-runner: create a parent for systemd dirs
veehaitch fcfa809
nixos/github-runner: use specifier to get abs runtime path
veehaitch 9dcd2ec
nixos/github-runner: use hostname as default for option `name`
veehaitch 193ac67
nixos/github-runner: use types.str for `name` option
veehaitch c4bd4d3
nixos/github-runner: pass dir paths explicitly for ExecStartPre=
veehaitch 929aa7a
nixos/github-runner: update variable and script naming
veehaitch a37e84e
nixos/github-runner: let systemd choose the user/group
veehaitch 6e222be
Revert "nixos/github-runner: use types.str for `name` option"
veehaitch 095f377
nixos/github-runner: use types.path for `tokenFile` option
veehaitch 64f4d4a
nixos/github-runner: escape options used as shell arguments
veehaitch a68dac1
nixos/github-runner: wait for network-online.target
veehaitch cd24f4f
github-runner: ignore additional online tests
veehaitch File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
289 changes: 289 additions & 0 deletions
289
nixos/modules/services/continuous-integration/github-runner.nix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
{ config, pkgs, lib, ... }: | ||
with lib; | ||
let | ||
cfg = config.services.github-runner; | ||
svcName = "github-runner"; | ||
systemdUser = "${svcName}-${cfg.name}"; | ||
systemdDir = "${svcName}/${cfg.name}"; | ||
# %t: Runtime directory root (usually /run); see systemd.unit(5) | ||
runtimeDir = "%t/${systemdDir}"; | ||
in | ||
{ | ||
options.services.github-runner = { | ||
enable = mkOption { | ||
default = false; | ||
example = true; | ||
description = '' | ||
Whether to enable GitHub Actions runner. | ||
Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here: | ||
<link xlink:href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners" | ||
>About self-hosted runners</link>. | ||
''; | ||
type = lib.types.bool; | ||
}; | ||
|
||
url = mkOption { | ||
type = types.str; | ||
description = '' | ||
Repository to add the runner to. | ||
Changing this option triggers a new runner registration. | ||
''; | ||
example = "https://github.com/nixos/nixpkgs"; | ||
}; | ||
|
||
tokenFile = mkOption { | ||
type = types.str; | ||
description = '' | ||
The full path to a file which contains the runner registration token. | ||
The file should contain exactly one line with the token without any newline. | ||
The token can be used to re-register a runner of the same name but is time-limited. | ||
Changing this option or the file's content triggers a new runner registration. | ||
''; | ||
example = "/run/secrets/github-runner/nixos.token"; | ||
}; | ||
|
||
name = mkOption { | ||
type = types.nullOr types.str; | ||
description = '' | ||
Name of the runner to configure. Defaults to the hostname. | ||
Changing this option triggers a new runner registration. | ||
''; | ||
example = "nixos"; | ||
default = null; | ||
}; | ||
|
||
runnerGroup = mkOption { | ||
type = types.nullOr types.str; | ||
description = '' | ||
Name of the runner group to add this runner to (defaults to the default runner group). | ||
Changing this option triggers a new runner registration. | ||
''; | ||
default = null; | ||
}; | ||
|
||
extraLabels = mkOption { | ||
type = types.listOf types.str; | ||
description = '' | ||
Extra labels in addition to the default (<literal>["self-hosted", "Linux", "X64"]</literal>). | ||
Changing this option triggers a new runner registration. | ||
''; | ||
example = literalExample ''[ "nixos" ]''; | ||
default = [ ]; | ||
}; | ||
|
||
replace = mkOption { | ||
type = types.bool; | ||
description = '' | ||
Replace any existing runner with the same name. | ||
Without this flag, registering a new runner with the same name fails. | ||
''; | ||
default = false; | ||
}; | ||
|
||
extraPackages = mkOption { | ||
type = types.listOf types.package; | ||
description = '' | ||
Extra packages to add to <literal>PATH</literal> of the service to make them available to workflows. | ||
''; | ||
default = [ ]; | ||
}; | ||
}; | ||
|
||
config = mkIf cfg.enable { | ||
warnings = optionals (isStorePath cfg.tokenFile) [ | ||
'' | ||
`services.github-runner.tokenFile` points to the Nix store and, therefore, is world-readable. | ||
Consider using a path outside of the Nix store to keep the token private. | ||
'' | ||
]; | ||
|
||
systemd.services.${svcName} = { | ||
description = "GitHub Actions runner"; | ||
|
||
wantedBy = [ "multi-user.target" ]; | ||
|
||
after = [ "network.target" ]; | ||
|
||
environment = { | ||
HOME = runtimeDir; | ||
RUNNER_ROOT = runtimeDir; | ||
}; | ||
|
||
path = (with pkgs; [ | ||
bash | ||
coreutils | ||
git | ||
gnutar | ||
gzip | ||
]) ++ [ | ||
config.nix.package | ||
] ++ cfg.extraPackages; | ||
|
||
serviceConfig = rec { | ||
ExecStart = "${pkgs.github-runner}/bin/runsvc.sh"; | ||
|
||
# Does the following, sequentially: | ||
# - Copy the current and the previous `tokenFile` to the $RUNTIME_DIRECTORY | ||
# and make it accessible to the service user to allow for a content | ||
# comparison. | ||
# - If the module configuration or the token has changed, clear the state directory. | ||
# - Configure the runner. | ||
# - Copy the configured `tokenFile` to the $STATE_DIRECTORY and make it | ||
# inaccessible to the service user. | ||
# - Set up the directory structure by creating the necessary symlinks. | ||
ExecStartPre = | ||
let | ||
currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json"; | ||
runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" ] cfg; | ||
newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig); | ||
currentConfigTokenFilename = ".current-token"; | ||
newConfigTokenFilename = ".new-token"; | ||
runnerCredFiles = [ | ||
".credentials" | ||
".credentials_rsaparams" | ||
".runner" | ||
]; | ||
ownCreds = pkgs.writeShellScript "own-github-runner-credentials.sh" '' | ||
set -euo pipefail | ||
# Copy current and new token file to runtime dir and make it accessible to the service user | ||
cp "${cfg.tokenFile}" "$RUNTIME_DIRECTORY/${newConfigTokenFilename}" | ||
chmod 600 "$RUNTIME_DIRECTORY/${newConfigTokenFilename}" | ||
chown ${User}:${Group} "$RUNTIME_DIRECTORY/${newConfigTokenFilename}" | ||
if [[ -e "$STATE_DIRECTORY/${currentConfigTokenFilename}" ]]; then | ||
cp "$STATE_DIRECTORY/${currentConfigTokenFilename}" "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}" | ||
chmod 600 "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}" | ||
chown ${User}:${Group} "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}" | ||
fi | ||
''; | ||
disownCreds = pkgs.writeShellScript "disown-github-runner-credentials.sh" '' | ||
set -euo pipefail | ||
# Make the token inaccessible to the runner service user | ||
chmod 600 "$STATE_DIRECTORY/${currentConfigTokenFilename}" | ||
chown root:root "$STATE_DIRECTORY/${currentConfigTokenFilename}" | ||
''; | ||
unconfigureRunner = pkgs.writeShellScript "unconfigure-github-runner.sh" '' | ||
set -euo pipefail | ||
differs= | ||
# Set `differs = 1` if current and new runner config differ or if `currentConfigPath` does not exist | ||
${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 || differs=1 | ||
# Also trigger a registration if the token content changed | ||
${pkgs.diffutils}/bin/diff -q \ | ||
"$RUNTIME_DIRECTORY"/{${currentConfigTokenFilename},${newConfigTokenFilename}} \ | ||
>/dev/null 2>&1 || differs=1 | ||
if [[ -n "$differs" ]]; then | ||
echo "Config has changed, removing old runner state." | ||
echo "The old runner will still appear in the GitHub Actions UI." \ | ||
"You have to remove it manually." | ||
find "$STATE_DIRECTORY/" -mindepth 1 -delete | ||
fi | ||
''; | ||
configureRunner = pkgs.writeShellScript "configure-github-runner.sh" '' | ||
set -euo pipefail | ||
empty=$(ls -A "$STATE_DIRECTORY") | ||
if [[ -z "$empty" ]]; then | ||
echo "Configuring GitHub Actions Runner" | ||
token=$(< "$RUNTIME_DIRECTORY"/${newConfigTokenFilename}) | ||
RUNNER_ROOT="$STATE_DIRECTORY" ${pkgs.github-runner}/bin/config.sh \ | ||
--unattended \ | ||
--work "$RUNTIME_DIRECTORY" \ | ||
--url '${cfg.url}' \ | ||
--token "$token" \ | ||
--labels '${concatStringsSep "," cfg.extraLabels}' \ | ||
${optionalString (cfg.name != null) "--name '${cfg.name}'"} \ | ||
${optionalString cfg.replace "--replace"} \ | ||
${optionalString (cfg.runnerGroup != null) "--runnergroup '${cfg.runnerGroup}'"} | ||
# Move the automatically created _diag dir to the logs dir | ||
mkdir -p "$STATE_DIRECTORY/_diag" | ||
cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/" | ||
rm -rf "$STATE_DIRECTORY/_diag/" | ||
# Cleanup token from config | ||
rm -f "$RUNTIME_DIRECTORY"/${currentConfigTokenFilename} | ||
mv "$RUNTIME_DIRECTORY"/${newConfigTokenFilename} "$STATE_DIRECTORY/${currentConfigTokenFilename}" | ||
# Symlink to new config | ||
ln -s '${newConfigPath}' "${currentConfigPath}" | ||
fi | ||
''; | ||
setupRuntimeDir = pkgs.writeShellScript "setup-github-runner.sh" '' | ||
set -euo pipefail | ||
# Link _diag dir | ||
ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag" | ||
# Link the runner credentials to the runtime dir | ||
ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/" | ||
''; | ||
in | ||
[ | ||
"+${ownCreds}" # runs as root | ||
unconfigureRunner | ||
configureRunner | ||
"+${disownCreds}" # runs as root | ||
setupRuntimeDir | ||
]; | ||
|
||
# Contains _diag | ||
LogsDirectory = [ systemdDir ]; | ||
# Default RUNNER_ROOT which contains ephemeral Runner data | ||
RuntimeDirectory = [ systemdDir ]; | ||
# Home of persistent runner data, e.g., credentials | ||
StateDirectory = [ systemdDir ]; | ||
StateDirectoryMode = "0700"; | ||
WorkingDirectory = runtimeDir; | ||
|
||
# By default, use a dynamically allocated user | ||
DynamicUser = true; | ||
User = systemdUser; | ||
Group = User; | ||
|
||
KillMode = "process"; | ||
KillSignal = "SIGTERM"; | ||
|
||
# Hardening (may overlap with DynamicUser=) | ||
# The following options are only for optimizing: | ||
# systemd-analyze security github-runner | ||
AmbientCapabilities = ""; | ||
CapabilityBoundingSet = ""; | ||
# ProtectClock= adds DeviceAllow=char-rtc r | ||
DeviceAllow = ""; | ||
LockPersonality = true; | ||
NoNewPrivileges = true; | ||
PrivateDevices = true; | ||
PrivateMounts = true; | ||
PrivateTmp = true; | ||
PrivateUsers = true; | ||
ProtectClock = true; | ||
ProtectControlGroups = true; | ||
ProtectHome = true; | ||
ProtectHostname = true; | ||
ProtectKernelLogs = true; | ||
ProtectKernelModules = true; | ||
ProtectKernelTunables = true; | ||
ProtectSystem = "strict"; | ||
RemoveIPC = true; | ||
RestrictNamespaces = true; | ||
RestrictRealtime = true; | ||
RestrictSUIDSGID = true; | ||
UMask = "0066"; | ||
|
||
# Needs network access | ||
PrivateNetwork = false; | ||
# Cannot be true due to Node | ||
MemoryDenyWriteExecute = false; | ||
}; | ||
}; | ||
}; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have testet your PR, work fine so far :-) But get following error if I do not set option
name
.Looks like hostname is not set as default as described. But maybe I have done something wrong? But after setting name it works fine for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! Hopefully this is fixed in 9dcd2ec.