···1-{ config, pkgs, lib, ... }:
000002with lib;
03let
4 cfg = config.services.github-runner;
5- svcName = "github-runner";
6- systemdDir = "${svcName}/${cfg.name}";
7- # %t: Runtime directory root (usually /run); see systemd.unit(5)
8- runtimeDir = "%t/${systemdDir}";
9- # %S: State directory root (usually /var/lib); see systemd.unit(5)
10- stateDir = "%S/${systemdDir}";
11- # %L: Log directory root (usually /var/log); see systemd.unit(5)
12- logsDir = "%L/${systemdDir}";
13- # Name of file stored in service state directory
14- currentConfigTokenFilename = ".current-token";
15in
016{
17- options.services.github-runner = {
18- enable = mkOption {
19- default = false;
20- example = true;
21- description = lib.mdDoc ''
22- Whether to enable GitHub Actions runner.
23-24- Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
25- [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
26- '';
27- type = lib.types.bool;
28- };
29-30- url = mkOption {
31- type = types.str;
32- description = lib.mdDoc ''
33- Repository to add the runner to.
34-35- Changing this option triggers a new runner registration.
36-37- IMPORTANT: If your token is org-wide (not per repository), you need to
38- provide a github org link, not a single repository, so do it like this
39- `https://github.com/nixos`, not like this
40- `https://github.com/nixos/nixpkgs`.
41- Otherwise, you are going to get a `404 NotFound`
42- from `POST https://api.github.com/actions/runner-registration`
43- in the configure script.
44- '';
45- example = "https://github.com/nixos/nixpkgs";
46- };
47-48- tokenFile = mkOption {
49- type = types.path;
50- description = lib.mdDoc ''
51- The full path to a file which contains either a runner registration token or a
52- personal access token (PAT).
53- The file should contain exactly one line with the token without any newline.
54- If a registration token is given, it can be used to re-register a runner of the same
55- name but is time-limited. If the file contains a PAT, the service creates a new
56- registration token on startup as needed. Make sure the PAT has a scope of
57- `admin:org` for organization-wide registrations or a scope of
58- `repo` for a single repository.
59-60- Changing this option or the file's content triggers a new runner registration.
61- '';
62- example = "/run/secrets/github-runner/nixos.token";
63- };
64-65- name = mkOption {
66- # Same pattern as for `networking.hostName`
67- type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
68- description = lib.mdDoc ''
69- Name of the runner to configure. Defaults to the hostname.
70-71- Changing this option triggers a new runner registration.
72- '';
73- example = "nixos";
74- default = config.networking.hostName;
75- defaultText = literalExpression "config.networking.hostName";
76- };
77-78- runnerGroup = mkOption {
79- type = types.nullOr types.str;
80- description = lib.mdDoc ''
81- Name of the runner group to add this runner to (defaults to the default runner group).
82-83- Changing this option triggers a new runner registration.
84- '';
85- default = null;
86- };
87-88- extraLabels = mkOption {
89- type = types.listOf types.str;
90- description = lib.mdDoc ''
91- Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
92-93- Changing this option triggers a new runner registration.
94- '';
95- example = literalExpression ''[ "nixos" ]'';
96- default = [ ];
97- };
98-99- replace = mkOption {
100- type = types.bool;
101- description = lib.mdDoc ''
102- Replace any existing runner with the same name.
103-104- Without this flag, registering a new runner with the same name fails.
105- '';
106- default = false;
107- };
108-109- extraPackages = mkOption {
110- type = types.listOf types.package;
111- description = lib.mdDoc ''
112- Extra packages to add to `PATH` of the service to make them available to workflows.
113- '';
114- default = [ ];
115- };
116-117- package = mkOption {
118- type = types.package;
119- description = lib.mdDoc ''
120- Which github-runner derivation to use.
121- '';
122- default = pkgs.github-runner;
123- defaultText = literalExpression "pkgs.github-runner";
124- };
125-126- ephemeral = mkOption {
127- type = types.bool;
128- description = lib.mdDoc ''
129- If enabled, causes the following behavior:
130-131- - Passes the `--ephemeral` flag to the runner configuration script
132- - De-registers and stops the runner with GitHub after it has processed one job
133- - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
134- - Restarts the service after its successful exit
135- - On start, wipes the state directory and configures a new runner
136-137- You should only enable this option if `tokenFile` points to a file which contains a
138- personal access token (PAT). If you're using the option with a registration token, restarting the
139- service will fail as soon as the registration token expired.
140- '';
141- default = false;
142- };
143- };
144145 config = mkIf cfg.enable {
146- warnings = optionals (isStorePath cfg.tokenFile) [
147- ''
148- `services.github-runner.tokenFile` points to the Nix store and, therefore, is world-readable.
149- Consider using a path outside of the Nix store to keep the token private.
150- ''
151- ];
152-153- systemd.services.${svcName} = {
154- description = "GitHub Actions runner";
155-156- wantedBy = [ "multi-user.target" ];
157- wants = [ "network-online.target" ];
158- after = [ "network.target" "network-online.target" ];
159-160- environment = {
161- HOME = runtimeDir;
162- RUNNER_ROOT = stateDir;
163- };
164-165- path = (with pkgs; [
166- bash
167- coreutils
168- git
169- gnutar
170- gzip
171- ]) ++ [
172- config.nix.package
173- ] ++ cfg.extraPackages;
174-175- serviceConfig = rec {
176- ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
177-178- # Does the following, sequentially:
179- # - If the module configuration or the token has changed, purge the state directory,
180- # and create the current and the new token file with the contents of the configured
181- # token. While both files have the same content, only the later is accessible by
182- # the service user.
183- # - Configure the runner using the new token file. When finished, delete it.
184- # - Set up the directory structure by creating the necessary symlinks.
185- ExecStartPre =
186- let
187- # Wrapper script which expects the full path of the state, runtime and logs
188- # directory as arguments. Overrides the respective systemd variables to provide
189- # unambiguous directory names. This becomes relevant, for example, if the
190- # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
191- # to contain more than one directory. This causes systemd to set the respective
192- # environment variables with the path of all of the given directories, separated
193- # by a colon.
194- writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
195- set -euo pipefail
196-197- STATE_DIRECTORY="$1"
198- RUNTIME_DIRECTORY="$2"
199- LOGS_DIRECTORY="$3"
200-201- ${lines}
202- '';
203- runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] cfg;
204- newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
205- currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
206- newConfigTokenPath= "$STATE_DIRECTORY/.new-token";
207- currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}";
208-209- runnerCredFiles = [
210- ".credentials"
211- ".credentials_rsaparams"
212- ".runner"
213- ];
214- unconfigureRunner = writeScript "unconfigure" ''
215- copy_tokens() {
216- # Copy the configured token file to the state dir and allow the service user to read the file
217- install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}"
218- # Also copy current file to allow for a diff on the next start
219- install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}"
220- }
221-222- clean_state() {
223- find "$STATE_DIRECTORY/" -mindepth 1 -delete
224- copy_tokens
225- }
226-227- diff_config() {
228- changed=0
229-230- # Check for module config changes
231- [[ -f "${currentConfigPath}" ]] \
232- && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \
233- || changed=1
234-235- # Also check the content of the token file
236- [[ -f "${currentConfigTokenPath}" ]] \
237- && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \
238- || changed=1
239-240- # If the config has changed, remove old state and copy tokens
241- if [[ "$changed" -eq 1 ]]; then
242- echo "Config has changed, removing old runner state."
243- echo "The old runner will still appear in the GitHub Actions UI." \
244- "You have to remove it manually."
245- clean_state
246- fi
247- }
248-249- if [[ "${optionalString cfg.ephemeral "1"}" ]]; then
250- # In ephemeral mode, we always want to start with a clean state
251- clean_state
252- elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
253- # There are state files from a previous run; diff them to decide if we need a new registration
254- diff_config
255- else
256- # The state directory is entirely empty which indicates a first start
257- copy_tokens
258- fi
259- '';
260- configureRunner = writeScript "configure" ''
261- if [[ -e "${newConfigTokenPath}" ]]; then
262- echo "Configuring GitHub Actions Runner"
263-264- args=(
265- --unattended
266- --disableupdate
267- --work "$RUNTIME_DIRECTORY"
268- --url ${escapeShellArg cfg.url}
269- --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
270- --name ${escapeShellArg cfg.name}
271- ${optionalString cfg.replace "--replace"}
272- ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
273- ${optionalString cfg.ephemeral "--ephemeral"}
274- )
275-276- # If the token file contains a PAT (i.e., it starts with "ghp_"), we have to use the --pat option,
277- # if it is not a PAT, we assume it contains a registration token and use the --token option
278- token=$(<"${newConfigTokenPath}")
279- if [[ "$token" =~ ^ghp_* ]]; then
280- args+=(--pat "$token")
281- else
282- args+=(--token "$token")
283- fi
284-285- ${cfg.package}/bin/config.sh "''${args[@]}"
286-287- # Move the automatically created _diag dir to the logs dir
288- mkdir -p "$STATE_DIRECTORY/_diag"
289- cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
290- rm -rf "$STATE_DIRECTORY/_diag/"
291-292- # Cleanup token from config
293- rm "${newConfigTokenPath}"
294-295- # Symlink to new config
296- ln -s '${newConfigPath}' "${currentConfigPath}"
297- fi
298- '';
299- setupRuntimeDir = writeScript "setup-runtime-dirs" ''
300- # Link _diag dir
301- ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"
302-303- # Link the runner credentials to the runtime dir
304- ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
305- '';
306- in
307- map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
308- "+${unconfigureRunner}" # runs as root
309- configureRunner
310- setupRuntimeDir
311- ];
312-313- # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
314- # to trigger a fresh registration.
315- Restart = if cfg.ephemeral then "on-success" else "no";
316-317- # Contains _diag
318- LogsDirectory = [ systemdDir ];
319- # Default RUNNER_ROOT which contains ephemeral Runner data
320- RuntimeDirectory = [ systemdDir ];
321- # Home of persistent runner data, e.g., credentials
322- StateDirectory = [ systemdDir ];
323- StateDirectoryMode = "0700";
324- WorkingDirectory = runtimeDir;
325-326- InaccessiblePaths = [
327- # Token file path given in the configuration, if visible to the service
328- "-${cfg.tokenFile}"
329- # Token file in the state directory
330- "${stateDir}/${currentConfigTokenFilename}"
331- ];
332-333- # By default, use a dynamically allocated user
334- DynamicUser = true;
335-336- KillSignal = "SIGINT";
337-338- # Hardening (may overlap with DynamicUser=)
339- # The following options are only for optimizing:
340- # systemd-analyze security github-runner
341- AmbientCapabilities = "";
342- CapabilityBoundingSet = "";
343- # ProtectClock= adds DeviceAllow=char-rtc r
344- DeviceAllow = "";
345- NoNewPrivileges = true;
346- PrivateDevices = true;
347- PrivateMounts = true;
348- PrivateTmp = true;
349- PrivateUsers = true;
350- ProtectClock = true;
351- ProtectControlGroups = true;
352- ProtectHome = true;
353- ProtectHostname = true;
354- ProtectKernelLogs = true;
355- ProtectKernelModules = true;
356- ProtectKernelTunables = true;
357- ProtectSystem = "strict";
358- RemoveIPC = true;
359- RestrictNamespaces = true;
360- RestrictRealtime = true;
361- RestrictSUIDSGID = true;
362- UMask = "0066";
363- ProtectProc = "invisible";
364- SystemCallFilter = [
365- "~@clock"
366- "~@cpu-emulation"
367- "~@module"
368- "~@mount"
369- "~@obsolete"
370- "~@raw-io"
371- "~@reboot"
372- "~capset"
373- "~setdomainname"
374- "~sethostname"
375- ];
376- RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
377-378- # Needs network access
379- PrivateNetwork = false;
380- # Cannot be true due to Node
381- MemoryDenyWriteExecute = false;
382-383- # The more restrictive "pid" option makes `nix` commands in CI emit
384- # "GC Warning: Couldn't read /proc/stat"
385- # You may want to set this to "pid" if not using `nix` commands
386- ProcSubset = "all";
387- # Coverage programs for compiled code such as `cargo-tarpaulin` disable
388- # ASLR (address space layout randomization) which requires the
389- # `personality` syscall
390- # You may want to set this to `true` if not using coverage tooling on
391- # compiled code
392- LockPersonality = false;
393- };
394- };
395 };
396}
···1+{ config
2+, lib
3+, pkgs
4+, includeNameDefault
5+, ...
6+}:
7+8+with lib;
9+10+{
11+ enable = mkOption {
12+ default = false;
13+ example = true;
14+ description = lib.mdDoc ''
15+ Whether to enable GitHub Actions runner.
16+17+ Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
18+ [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
19+ '';
20+ type = lib.types.bool;
21+ };
22+23+ url = mkOption {
24+ type = types.str;
25+ description = lib.mdDoc ''
26+ Repository to add the runner to.
27+28+ Changing this option triggers a new runner registration.
29+30+ IMPORTANT: If your token is org-wide (not per repository), you need to
31+ provide a github org link, not a single repository, so do it like this
32+ `https://github.com/nixos`, not like this
33+ `https://github.com/nixos/nixpkgs`.
34+ Otherwise, you are going to get a `404 NotFound`
35+ from `POST https://api.github.com/actions/runner-registration`
36+ in the configure script.
37+ '';
38+ example = "https://github.com/nixos/nixpkgs";
39+ };
40+41+ tokenFile = mkOption {
42+ type = types.path;
43+ description = lib.mdDoc ''
44+ The full path to a file which contains either a runner registration token or a
45+ personal access token (PAT).
46+ The file should contain exactly one line with the token without any newline.
47+ If a registration token is given, it can be used to re-register a runner of the same
48+ name but is time-limited. If the file contains a PAT, the service creates a new
49+ registration token on startup as needed. Make sure the PAT has a scope of
50+ `admin:org` for organization-wide registrations or a scope of
51+ `repo` for a single repository.
52+53+ Changing this option or the file's content triggers a new runner registration.
54+ '';
55+ example = "/run/secrets/github-runner/nixos.token";
56+ };
57+58+ name = let
59+ # Same pattern as for `networking.hostName`
60+ baseType = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
61+ in mkOption {
62+ type = if includeNameDefault then baseType else types.nullOr baseType;
63+ description = lib.mdDoc ''
64+ Name of the runner to configure. Defaults to the hostname.
65+66+ Changing this option triggers a new runner registration.
67+ '';
68+ example = "nixos";
69+ } // (if includeNameDefault then {
70+ default = config.networking.hostName;
71+ defaultText = literalExpression "config.networking.hostName";
72+ } else {
73+ default = null;
74+ });
75+76+ runnerGroup = mkOption {
77+ type = types.nullOr types.str;
78+ description = lib.mdDoc ''
79+ Name of the runner group to add this runner to (defaults to the default runner group).
80+81+ Changing this option triggers a new runner registration.
82+ '';
83+ default = null;
84+ };
85+86+ extraLabels = mkOption {
87+ type = types.listOf types.str;
88+ description = lib.mdDoc ''
89+ Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
90+91+ Changing this option triggers a new runner registration.
92+ '';
93+ example = literalExpression ''[ "nixos" ]'';
94+ default = [ ];
95+ };
96+97+ replace = mkOption {
98+ type = types.bool;
99+ description = lib.mdDoc ''
100+ Replace any existing runner with the same name.
101+102+ Without this flag, registering a new runner with the same name fails.
103+ '';
104+ default = false;
105+ };
106+107+ extraPackages = mkOption {
108+ type = types.listOf types.package;
109+ description = lib.mdDoc ''
110+ Extra packages to add to `PATH` of the service to make them available to workflows.
111+ '';
112+ default = [ ];
113+ };
114+115+ extraEnvironment = mkOption {
116+ type = types.attrs;
117+ description = lib.mdDoc ''
118+ Extra environment variables to set for the runner, as an attrset.
119+ '';
120+ example = {
121+ GIT_CONFIG = "/path/to/git/config";
122+ };
123+ default = {};
124+ };
125+126+ serviceOverrides = mkOption {
127+ type = types.attrs;
128+ description = lib.mdDoc ''
129+ Overrides for the systemd service. Can be used to adjust the sandboxing options.
130+ '';
131+ example = {
132+ ProtectHome = false;
133+ };
134+ default = {};
135+ };
136+137+ package = mkOption {
138+ type = types.package;
139+ description = lib.mdDoc ''
140+ Which github-runner derivation to use.
141+ '';
142+ default = pkgs.github-runner;
143+ defaultText = literalExpression "pkgs.github-runner";
144+ };
145+146+ ephemeral = mkOption {
147+ type = types.bool;
148+ description = lib.mdDoc ''
149+ If enabled, causes the following behavior:
150+151+ - Passes the `--ephemeral` flag to the runner configuration script
152+ - De-registers and stops the runner with GitHub after it has processed one job
153+ - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
154+ - Restarts the service after its successful exit
155+ - On start, wipes the state directory and configures a new runner
156+157+ You should only enable this option if `tokenFile` points to a file which contains a
158+ personal access token (PAT). If you're using the option with a registration token, restarting the
159+ service will fail as soon as the registration token expired.
160+ '';
161+ default = false;
162+ };
163+164+ user = mkOption {
165+ type = types.nullOr types.str;
166+ description = lib.mdDoc ''
167+ User under which to run the service. If null, will use a systemd dynamic user.
168+ '';
169+ default = null;
170+ defaultText = literalExpression "username";
171+ };
172+}
···1+{ config
2+, lib
3+, pkgs
4+5+, cfg ? config.services.github-runner
6+, svcName
7+8+, systemdDir ? "${svcName}/${cfg.name}"
9+ # %t: Runtime directory root (usually /run); see systemd.unit(5)
10+, runtimeDir ? "%t/${systemdDir}"
11+ # %S: State directory root (usually /var/lib); see systemd.unit(5)
12+, stateDir ? "%S/${systemdDir}"
13+ # %L: Log directory root (usually /var/log); see systemd.unit(5)
14+, logsDir ? "%L/${systemdDir}"
15+ # Name of file stored in service state directory
16+, currentConfigTokenFilename ? ".current-token"
17+18+, ...
19+}:
20+21+with lib;
22+23+{
24+ description = "GitHub Actions runner";
25+26+ wantedBy = [ "multi-user.target" ];
27+ wants = [ "network-online.target" ];
28+ after = [ "network.target" "network-online.target" ];
29+30+ environment = {
31+ HOME = runtimeDir;
32+ RUNNER_ROOT = stateDir;
33+ } // cfg.extraEnvironment;
34+35+ path = (with pkgs; [
36+ bash
37+ coreutils
38+ git
39+ gnutar
40+ gzip
41+ ]) ++ [
42+ config.nix.package
43+ ] ++ cfg.extraPackages;
44+45+ serviceConfig = rec {
46+ ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
47+48+ # Does the following, sequentially:
49+ # - If the module configuration or the token has changed, purge the state directory,
50+ # and create the current and the new token file with the contents of the configured
51+ # token. While both files have the same content, only the later is accessible by
52+ # the service user.
53+ # - Configure the runner using the new token file. When finished, delete it.
54+ # - Set up the directory structure by creating the necessary symlinks.
55+ ExecStartPre =
56+ let
57+ # Wrapper script which expects the full path of the state, runtime and logs
58+ # directory as arguments. Overrides the respective systemd variables to provide
59+ # unambiguous directory names. This becomes relevant, for example, if the
60+ # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
61+ # to contain more than one directory. This causes systemd to set the respective
62+ # environment variables with the path of all of the given directories, separated
63+ # by a colon.
64+ writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
65+ set -euo pipefail
66+67+ STATE_DIRECTORY="$1"
68+ RUNTIME_DIRECTORY="$2"
69+ LOGS_DIRECTORY="$3"
70+71+ ${lines}
72+ '';
73+ runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] cfg;
74+ newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
75+ currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
76+ newConfigTokenPath= "$STATE_DIRECTORY/.new-token";
77+ currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}";
78+79+ runnerCredFiles = [
80+ ".credentials"
81+ ".credentials_rsaparams"
82+ ".runner"
83+ ];
84+ unconfigureRunner = writeScript "unconfigure" ''
85+ copy_tokens() {
86+ # Copy the configured token file to the state dir and allow the service user to read the file
87+ install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}"
88+ # Also copy current file to allow for a diff on the next start
89+ install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}"
90+ }
91+ clean_state() {
92+ find "$STATE_DIRECTORY/" -mindepth 1 -delete
93+ copy_tokens
94+ }
95+ diff_config() {
96+ changed=0
97+ # Check for module config changes
98+ [[ -f "${currentConfigPath}" ]] \
99+ && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \
100+ || changed=1
101+ # Also check the content of the token file
102+ [[ -f "${currentConfigTokenPath}" ]] \
103+ && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \
104+ || changed=1
105+ # If the config has changed, remove old state and copy tokens
106+ if [[ "$changed" -eq 1 ]]; then
107+ echo "Config has changed, removing old runner state."
108+ echo "The old runner will still appear in the GitHub Actions UI." \
109+ "You have to remove it manually."
110+ clean_state
111+ fi
112+ }
113+ if [[ "${optionalString cfg.ephemeral "1"}" ]]; then
114+ # In ephemeral mode, we always want to start with a clean state
115+ clean_state
116+ elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
117+ # There are state files from a previous run; diff them to decide if we need a new registration
118+ diff_config
119+ else
120+ # The state directory is entirely empty which indicates a first start
121+ copy_tokens
122+ fi '';
123+ configureRunner = writeScript "configure" ''
124+ if [[ -e "${newConfigTokenPath}" ]]; then
125+ echo "Configuring GitHub Actions Runner"
126+ args=(
127+ --unattended
128+ --disableupdate
129+ --work "$RUNTIME_DIRECTORY"
130+ --url ${escapeShellArg cfg.url}
131+ --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
132+ --name ${escapeShellArg cfg.name}
133+ ${optionalString cfg.replace "--replace"}
134+ ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
135+ ${optionalString cfg.ephemeral "--ephemeral"}
136+ )
137+ # If the token file contains a PAT (i.e., it starts with "ghp_"), we have to use the --pat option,
138+ # if it is not a PAT, we assume it contains a registration token and use the --token option
139+ token=$(<"${newConfigTokenPath}")
140+ if [[ "$token" =~ ^ghp_* ]]; then
141+ args+=(--pat "$token")
142+ else
143+ args+=(--token "$token")
144+ fi
145+ ${cfg.package}/bin/config.sh "''${args[@]}"
146+ # Move the automatically created _diag dir to the logs dir
147+ mkdir -p "$STATE_DIRECTORY/_diag"
148+ cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
149+ rm -rf "$STATE_DIRECTORY/_diag/"
150+ # Cleanup token from config
151+ rm "${newConfigTokenPath}"
152+ # Symlink to new config
153+ ln -s '${newConfigPath}' "${currentConfigPath}"
154+ fi
155+ '';
156+ setupRuntimeDir = writeScript "setup-runtime-dirs" ''
157+ # Link _diag dir
158+ ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"
159+160+ # Link the runner credentials to the runtime dir
161+ ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
162+ '';
163+ in
164+ map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
165+ "+${unconfigureRunner}" # runs as root
166+ configureRunner
167+ setupRuntimeDir
168+ ];
169+170+ # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
171+ # to trigger a fresh registration.
172+ Restart = if cfg.ephemeral then "on-success" else "no";
173+174+ # Contains _diag
175+ LogsDirectory = [ systemdDir ];
176+ # Default RUNNER_ROOT which contains ephemeral Runner data
177+ RuntimeDirectory = [ systemdDir ];
178+ # Home of persistent runner data, e.g., credentials
179+ StateDirectory = [ systemdDir ];
180+ StateDirectoryMode = "0700";
181+ WorkingDirectory = runtimeDir;
182+183+ InaccessiblePaths = [
184+ # Token file path given in the configuration, if visible to the service
185+ "-${cfg.tokenFile}"
186+ # Token file in the state directory
187+ "${stateDir}/${currentConfigTokenFilename}"
188+ ];
189+190+ KillSignal = "SIGINT";
191+192+ # Hardening (may overlap with DynamicUser=)
193+ # The following options are only for optimizing:
194+ # systemd-analyze security github-runner
195+ AmbientCapabilities = "";
196+ CapabilityBoundingSet = "";
197+ # ProtectClock= adds DeviceAllow=char-rtc r
198+ DeviceAllow = "";
199+ NoNewPrivileges = true;
200+ PrivateDevices = true;
201+ PrivateMounts = true;
202+ PrivateTmp = true;
203+ PrivateUsers = true;
204+ ProtectClock = true;
205+ ProtectControlGroups = true;
206+ ProtectHome = true;
207+ ProtectHostname = true;
208+ ProtectKernelLogs = true;
209+ ProtectKernelModules = true;
210+ ProtectKernelTunables = true;
211+ ProtectSystem = "strict";
212+ RemoveIPC = true;
213+ RestrictNamespaces = true;
214+ RestrictRealtime = true;
215+ RestrictSUIDSGID = true;
216+ UMask = "0066";
217+ ProtectProc = "invisible";
218+ SystemCallFilter = [
219+ "~@clock"
220+ "~@cpu-emulation"
221+ "~@module"
222+ "~@mount"
223+ "~@obsolete"
224+ "~@raw-io"
225+ "~@reboot"
226+ "~capset"
227+ "~setdomainname"
228+ "~sethostname"
229+ ];
230+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
231+232+ # Needs network access
233+ PrivateNetwork = false;
234+ # Cannot be true due to Node
235+ MemoryDenyWriteExecute = false;
236+237+ # The more restrictive "pid" option makes `nix` commands in CI emit
238+ # "GC Warning: Couldn't read /proc/stat"
239+ # You may want to set this to "pid" if not using `nix` commands
240+ ProcSubset = "all";
241+ # Coverage programs for compiled code such as `cargo-tarpaulin` disable
242+ # ASLR (address space layout randomization) which requires the
243+ # `personality` syscall
244+ # You may want to set this to `true` if not using coverage tooling on
245+ # compiled code
246+ LockPersonality = false;
247+248+ # Note that this has some interactions with the User setting; so you may
249+ # want to consult the systemd docs if using both.
250+ DynamicUser = true;
251+ } // (
252+ lib.optionalAttrs (cfg.user != null) { User = cfg.user; }
253+ ) // cfg.serviceOverrides;
254+}
···55 , stdenvOverride ? stdenv
56 , src ? (getCoreSrc core)
57 , broken ? false
58- , version ? "unstable-2022-10-01"
59 , platforms ? retroarch.meta.platforms
60 # The resulting core file is based on core name
61 # Setting `normalizeCore` to `true` will convert `-` to `_` on the core filename
···55 , stdenvOverride ? stdenv
56 , src ? (getCoreSrc core)
57 , broken ? false
58+ , version ? "unstable-2022-10-18"
59 , platforms ? retroarch.meta.platforms
60 # The resulting core file is based on core name
61 # Setting `normalizeCore` to `true` will convert `-` to `_` on the core filename
···23buildGoModule rec {
4 pname = "yggdrasil";
5- version = "0.4.4";
67 src = fetchFromGitHub {
8 owner = "yggdrasil-network";
9 repo = "yggdrasil-go";
10 rev = "v${version}";
11- sha256 = "sha256-uJFBboV0DhZHEir4+2VdTGMqxZsahnFRgr9btdMlW2M=";
12 };
1314- vendorSha256 = "sha256-qeyXUTcII0hMrOWIvsjaOXv/tKWBoUrTkCimRC/RnUw=";
1516 # Change the default location of the management socket on Linux
17 # systems so that the yggdrasil system service unit does not have to
···23buildGoModule rec {
4 pname = "yggdrasil";
5+ version = "0.4.5";
67 src = fetchFromGitHub {
8 owner = "yggdrasil-network";
9 repo = "yggdrasil-go";
10 rev = "v${version}";
11+ sha256 = "sha256-ehOvPFQtFgxVDOyF2MBbGO0IKwMWSb3aat+e+fJay1Q=";
12 };
1314+ vendorSha256 = "sha256-u1VrlTvmB2KSnlxcdCyfxw0xAMd+AeN5g/a7JehUV9U=";
1516 # Change the default location of the management socket on Linux
17 # systems so that the yggdrasil system service unit does not have to