Skip to content
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
merged 21 commits into from Apr 10, 2021
Merged
Show file tree
Hide file tree
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 Mar 18, 2021
a10f465
nixos/github-runner: initial version
veehaitch Mar 18, 2021
b4d9c31
nixos/github-runner: add warning if tokenFile in Nix store
veehaitch Mar 19, 2021
ac82082
github-runner: don't accept unexpected attrs
veehaitch Mar 19, 2021
fd1f0d2
github-runner: formatting nits
veehaitch Mar 19, 2021
a12d9d8
github-runner: add pre and post hooks to checkPhase
veehaitch Mar 19, 2021
320479b
nixos/github-runner: update ExecStartPre= comment
veehaitch Mar 20, 2021
9a51add
nixos/github-runner: adapt tokenFile option description
veehaitch Mar 20, 2021
08df6fa
nixos/github-runner: remove mkDefault for DynamicUser=
veehaitch Mar 20, 2021
91bf483
nixos/github-runner: create a parent for systemd dirs
veehaitch Mar 27, 2021
fcfa809
nixos/github-runner: use specifier to get abs runtime path
veehaitch Mar 27, 2021
9dcd2ec
nixos/github-runner: use hostname as default for option `name`
veehaitch Mar 28, 2021
193ac67
nixos/github-runner: use types.str for `name` option
veehaitch Mar 28, 2021
c4bd4d3
nixos/github-runner: pass dir paths explicitly for ExecStartPre=
veehaitch Mar 29, 2021
929aa7a
nixos/github-runner: update variable and script naming
veehaitch Mar 29, 2021
a37e84e
nixos/github-runner: let systemd choose the user/group
veehaitch Mar 29, 2021
6e222be
Revert "nixos/github-runner: use types.str for `name` option"
veehaitch Mar 29, 2021
095f377
nixos/github-runner: use types.path for `tokenFile` option
veehaitch Mar 29, 2021
64f4d4a
nixos/github-runner: escape options used as shell arguments
veehaitch Mar 29, 2021
a68dac1
nixos/github-runner: wait for network-online.target
veehaitch Apr 1, 2021
cd24f4f
github-runner: ignore additional online tests
veehaitch Apr 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Expand Up @@ -285,6 +285,7 @@
./services/continuous-integration/hail.nix
./services/continuous-integration/hercules-ci-agent/default.nix
./services/continuous-integration/hydra/default.nix
./services/continuous-integration/github-runner.nix
./services/continuous-integration/gitlab-runner.nix
./services/continuous-integration/gocd-agent/default.nix
./services/continuous-integration/gocd-server/default.nix
Expand Down
289 changes: 289 additions & 0 deletions nixos/modules/services/continuous-integration/github-runner.nix
@@ -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;
Copy link
Contributor

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.

error: cannot coerce null to a string

       at /nix/store/rfxj941pv6yf3p2rikhx8wsw3y1q7m34-source/nixos/modules/services/continuous-integration/github-runner.nix:6:18:

            5|   svcName = "github-runner";
            6|   systemdUser = "${svcName}-${cfg.name}";
             |                  ^
            7|   systemdDir = "${svcName}/${cfg.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.

Copy link
Member Author

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.

};

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;
};
};
};
}