···11-{ config, pkgs, lib, ... }:
11+{ config
22+, pkgs
33+, lib
44+, ...
55+}@args:
66+27with lib;
88+39let
410 cfg = config.services.github-runner;
55- svcName = "github-runner";
66- systemdDir = "${svcName}/${cfg.name}";
77- # %t: Runtime directory root (usually /run); see systemd.unit(5)
88- runtimeDir = "%t/${systemdDir}";
99- # %S: State directory root (usually /var/lib); see systemd.unit(5)
1010- stateDir = "%S/${systemdDir}";
1111- # %L: Log directory root (usually /var/log); see systemd.unit(5)
1212- logsDir = "%L/${systemdDir}";
1313- # Name of file stored in service state directory
1414- currentConfigTokenFilename = ".current-token";
1511in
1212+1613{
1717- options.services.github-runner = {
1818- enable = mkOption {
1919- default = false;
2020- example = true;
2121- description = lib.mdDoc ''
2222- Whether to enable GitHub Actions runner.
2323-2424- Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
2525- [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
2626- '';
2727- type = lib.types.bool;
2828- };
2929-3030- url = mkOption {
3131- type = types.str;
3232- description = lib.mdDoc ''
3333- Repository to add the runner to.
3434-3535- Changing this option triggers a new runner registration.
3636-3737- IMPORTANT: If your token is org-wide (not per repository), you need to
3838- provide a github org link, not a single repository, so do it like this
3939- `https://github.com/nixos`, not like this
4040- `https://github.com/nixos/nixpkgs`.
4141- Otherwise, you are going to get a `404 NotFound`
4242- from `POST https://api.github.com/actions/runner-registration`
4343- in the configure script.
4444- '';
4545- example = "https://github.com/nixos/nixpkgs";
4646- };
4747-4848- tokenFile = mkOption {
4949- type = types.path;
5050- description = lib.mdDoc ''
5151- The full path to a file which contains either a runner registration token or a
5252- personal access token (PAT).
5353- The file should contain exactly one line with the token without any newline.
5454- If a registration token is given, it can be used to re-register a runner of the same
5555- name but is time-limited. If the file contains a PAT, the service creates a new
5656- registration token on startup as needed. Make sure the PAT has a scope of
5757- `admin:org` for organization-wide registrations or a scope of
5858- `repo` for a single repository.
5959-6060- Changing this option or the file's content triggers a new runner registration.
6161- '';
6262- example = "/run/secrets/github-runner/nixos.token";
6363- };
6464-6565- name = mkOption {
6666- # Same pattern as for `networking.hostName`
6767- type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
6868- description = lib.mdDoc ''
6969- Name of the runner to configure. Defaults to the hostname.
7070-7171- Changing this option triggers a new runner registration.
7272- '';
7373- example = "nixos";
7474- default = config.networking.hostName;
7575- defaultText = literalExpression "config.networking.hostName";
7676- };
7777-7878- runnerGroup = mkOption {
7979- type = types.nullOr types.str;
8080- description = lib.mdDoc ''
8181- Name of the runner group to add this runner to (defaults to the default runner group).
8282-8383- Changing this option triggers a new runner registration.
8484- '';
8585- default = null;
8686- };
8787-8888- extraLabels = mkOption {
8989- type = types.listOf types.str;
9090- description = lib.mdDoc ''
9191- Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
9292-9393- Changing this option triggers a new runner registration.
9494- '';
9595- example = literalExpression ''[ "nixos" ]'';
9696- default = [ ];
9797- };
9898-9999- replace = mkOption {
100100- type = types.bool;
101101- description = lib.mdDoc ''
102102- Replace any existing runner with the same name.
103103-104104- Without this flag, registering a new runner with the same name fails.
105105- '';
106106- default = false;
107107- };
108108-109109- extraPackages = mkOption {
110110- type = types.listOf types.package;
111111- description = lib.mdDoc ''
112112- Extra packages to add to `PATH` of the service to make them available to workflows.
113113- '';
114114- default = [ ];
115115- };
116116-117117- package = mkOption {
118118- type = types.package;
119119- description = lib.mdDoc ''
120120- Which github-runner derivation to use.
121121- '';
122122- default = pkgs.github-runner;
123123- defaultText = literalExpression "pkgs.github-runner";
124124- };
125125-126126- ephemeral = mkOption {
127127- type = types.bool;
128128- description = lib.mdDoc ''
129129- If enabled, causes the following behavior:
130130-131131- - Passes the `--ephemeral` flag to the runner configuration script
132132- - De-registers and stops the runner with GitHub after it has processed one job
133133- - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
134134- - Restarts the service after its successful exit
135135- - On start, wipes the state directory and configures a new runner
136136-137137- You should only enable this option if `tokenFile` points to a file which contains a
138138- personal access token (PAT). If you're using the option with a registration token, restarting the
139139- service will fail as soon as the registration token expired.
140140- '';
141141- default = false;
142142- };
143143- };
1414+ options.services.github-runner = import ./github-runner/options.nix (args // {
1515+ # Users don't need to specify options.services.github-runner.name; it will default
1616+ # to the hostname.
1717+ includeNameDefault = true;
1818+ });
1441914520 config = mkIf cfg.enable {
146146- warnings = optionals (isStorePath cfg.tokenFile) [
147147- ''
148148- `services.github-runner.tokenFile` points to the Nix store and, therefore, is world-readable.
149149- Consider using a path outside of the Nix store to keep the token private.
150150- ''
151151- ];
152152-153153- systemd.services.${svcName} = {
154154- description = "GitHub Actions runner";
155155-156156- wantedBy = [ "multi-user.target" ];
157157- wants = [ "network-online.target" ];
158158- after = [ "network.target" "network-online.target" ];
159159-160160- environment = {
161161- HOME = runtimeDir;
162162- RUNNER_ROOT = stateDir;
163163- };
164164-165165- path = (with pkgs; [
166166- bash
167167- coreutils
168168- git
169169- gnutar
170170- gzip
171171- ]) ++ [
172172- config.nix.package
173173- ] ++ cfg.extraPackages;
174174-175175- serviceConfig = rec {
176176- ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
177177-178178- # Does the following, sequentially:
179179- # - If the module configuration or the token has changed, purge the state directory,
180180- # and create the current and the new token file with the contents of the configured
181181- # token. While both files have the same content, only the later is accessible by
182182- # the service user.
183183- # - Configure the runner using the new token file. When finished, delete it.
184184- # - Set up the directory structure by creating the necessary symlinks.
185185- ExecStartPre =
186186- let
187187- # Wrapper script which expects the full path of the state, runtime and logs
188188- # directory as arguments. Overrides the respective systemd variables to provide
189189- # unambiguous directory names. This becomes relevant, for example, if the
190190- # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
191191- # to contain more than one directory. This causes systemd to set the respective
192192- # environment variables with the path of all of the given directories, separated
193193- # by a colon.
194194- writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
195195- set -euo pipefail
196196-197197- STATE_DIRECTORY="$1"
198198- RUNTIME_DIRECTORY="$2"
199199- LOGS_DIRECTORY="$3"
200200-201201- ${lines}
202202- '';
203203- runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] cfg;
204204- newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
205205- currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
206206- newConfigTokenPath= "$STATE_DIRECTORY/.new-token";
207207- currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}";
208208-209209- runnerCredFiles = [
210210- ".credentials"
211211- ".credentials_rsaparams"
212212- ".runner"
213213- ];
214214- unconfigureRunner = writeScript "unconfigure" ''
215215- copy_tokens() {
216216- # Copy the configured token file to the state dir and allow the service user to read the file
217217- install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}"
218218- # Also copy current file to allow for a diff on the next start
219219- install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}"
220220- }
221221-222222- clean_state() {
223223- find "$STATE_DIRECTORY/" -mindepth 1 -delete
224224- copy_tokens
225225- }
226226-227227- diff_config() {
228228- changed=0
229229-230230- # Check for module config changes
231231- [[ -f "${currentConfigPath}" ]] \
232232- && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \
233233- || changed=1
234234-235235- # Also check the content of the token file
236236- [[ -f "${currentConfigTokenPath}" ]] \
237237- && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \
238238- || changed=1
239239-240240- # If the config has changed, remove old state and copy tokens
241241- if [[ "$changed" -eq 1 ]]; then
242242- echo "Config has changed, removing old runner state."
243243- echo "The old runner will still appear in the GitHub Actions UI." \
244244- "You have to remove it manually."
245245- clean_state
246246- fi
247247- }
248248-249249- if [[ "${optionalString cfg.ephemeral "1"}" ]]; then
250250- # In ephemeral mode, we always want to start with a clean state
251251- clean_state
252252- elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
253253- # There are state files from a previous run; diff them to decide if we need a new registration
254254- diff_config
255255- else
256256- # The state directory is entirely empty which indicates a first start
257257- copy_tokens
258258- fi
259259- '';
260260- configureRunner = writeScript "configure" ''
261261- if [[ -e "${newConfigTokenPath}" ]]; then
262262- echo "Configuring GitHub Actions Runner"
263263-264264- args=(
265265- --unattended
266266- --disableupdate
267267- --work "$RUNTIME_DIRECTORY"
268268- --url ${escapeShellArg cfg.url}
269269- --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
270270- --name ${escapeShellArg cfg.name}
271271- ${optionalString cfg.replace "--replace"}
272272- ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
273273- ${optionalString cfg.ephemeral "--ephemeral"}
274274- )
275275-276276- # If the token file contains a PAT (i.e., it starts with "ghp_"), we have to use the --pat option,
277277- # if it is not a PAT, we assume it contains a registration token and use the --token option
278278- token=$(<"${newConfigTokenPath}")
279279- if [[ "$token" =~ ^ghp_* ]]; then
280280- args+=(--pat "$token")
281281- else
282282- args+=(--token "$token")
283283- fi
284284-285285- ${cfg.package}/bin/config.sh "''${args[@]}"
286286-287287- # Move the automatically created _diag dir to the logs dir
288288- mkdir -p "$STATE_DIRECTORY/_diag"
289289- cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
290290- rm -rf "$STATE_DIRECTORY/_diag/"
291291-292292- # Cleanup token from config
293293- rm "${newConfigTokenPath}"
294294-295295- # Symlink to new config
296296- ln -s '${newConfigPath}' "${currentConfigPath}"
297297- fi
298298- '';
299299- setupRuntimeDir = writeScript "setup-runtime-dirs" ''
300300- # Link _diag dir
301301- ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"
302302-303303- # Link the runner credentials to the runtime dir
304304- ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
305305- '';
306306- in
307307- map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
308308- "+${unconfigureRunner}" # runs as root
309309- configureRunner
310310- setupRuntimeDir
311311- ];
312312-313313- # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
314314- # to trigger a fresh registration.
315315- Restart = if cfg.ephemeral then "on-success" else "no";
316316-317317- # Contains _diag
318318- LogsDirectory = [ systemdDir ];
319319- # Default RUNNER_ROOT which contains ephemeral Runner data
320320- RuntimeDirectory = [ systemdDir ];
321321- # Home of persistent runner data, e.g., credentials
322322- StateDirectory = [ systemdDir ];
323323- StateDirectoryMode = "0700";
324324- WorkingDirectory = runtimeDir;
325325-326326- InaccessiblePaths = [
327327- # Token file path given in the configuration, if visible to the service
328328- "-${cfg.tokenFile}"
329329- # Token file in the state directory
330330- "${stateDir}/${currentConfigTokenFilename}"
331331- ];
332332-333333- # By default, use a dynamically allocated user
334334- DynamicUser = true;
335335-336336- KillSignal = "SIGINT";
337337-338338- # Hardening (may overlap with DynamicUser=)
339339- # The following options are only for optimizing:
340340- # systemd-analyze security github-runner
341341- AmbientCapabilities = "";
342342- CapabilityBoundingSet = "";
343343- # ProtectClock= adds DeviceAllow=char-rtc r
344344- DeviceAllow = "";
345345- NoNewPrivileges = true;
346346- PrivateDevices = true;
347347- PrivateMounts = true;
348348- PrivateTmp = true;
349349- PrivateUsers = true;
350350- ProtectClock = true;
351351- ProtectControlGroups = true;
352352- ProtectHome = true;
353353- ProtectHostname = true;
354354- ProtectKernelLogs = true;
355355- ProtectKernelModules = true;
356356- ProtectKernelTunables = true;
357357- ProtectSystem = "strict";
358358- RemoveIPC = true;
359359- RestrictNamespaces = true;
360360- RestrictRealtime = true;
361361- RestrictSUIDSGID = true;
362362- UMask = "0066";
363363- ProtectProc = "invisible";
364364- SystemCallFilter = [
365365- "~@clock"
366366- "~@cpu-emulation"
367367- "~@module"
368368- "~@mount"
369369- "~@obsolete"
370370- "~@raw-io"
371371- "~@reboot"
372372- "~capset"
373373- "~setdomainname"
374374- "~sethostname"
375375- ];
376376- RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
377377-378378- # Needs network access
379379- PrivateNetwork = false;
380380- # Cannot be true due to Node
381381- MemoryDenyWriteExecute = false;
382382-383383- # The more restrictive "pid" option makes `nix` commands in CI emit
384384- # "GC Warning: Couldn't read /proc/stat"
385385- # You may want to set this to "pid" if not using `nix` commands
386386- ProcSubset = "all";
387387- # Coverage programs for compiled code such as `cargo-tarpaulin` disable
388388- # ASLR (address space layout randomization) which requires the
389389- # `personality` syscall
390390- # You may want to set this to `true` if not using coverage tooling on
391391- # compiled code
392392- LockPersonality = false;
393393- };
394394- };
2121+ services.github-runners.${cfg.name} = cfg;
39522 };
39623}
···11+{ config
22+, lib
33+, pkgs
44+, includeNameDefault
55+, ...
66+}:
77+88+with lib;
99+1010+{
1111+ enable = mkOption {
1212+ default = false;
1313+ example = true;
1414+ description = lib.mdDoc ''
1515+ Whether to enable GitHub Actions runner.
1616+1717+ Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
1818+ [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
1919+ '';
2020+ type = lib.types.bool;
2121+ };
2222+2323+ url = mkOption {
2424+ type = types.str;
2525+ description = lib.mdDoc ''
2626+ Repository to add the runner to.
2727+2828+ Changing this option triggers a new runner registration.
2929+3030+ IMPORTANT: If your token is org-wide (not per repository), you need to
3131+ provide a github org link, not a single repository, so do it like this
3232+ `https://github.com/nixos`, not like this
3333+ `https://github.com/nixos/nixpkgs`.
3434+ Otherwise, you are going to get a `404 NotFound`
3535+ from `POST https://api.github.com/actions/runner-registration`
3636+ in the configure script.
3737+ '';
3838+ example = "https://github.com/nixos/nixpkgs";
3939+ };
4040+4141+ tokenFile = mkOption {
4242+ type = types.path;
4343+ description = lib.mdDoc ''
4444+ The full path to a file which contains either a runner registration token or a
4545+ personal access token (PAT).
4646+ The file should contain exactly one line with the token without any newline.
4747+ If a registration token is given, it can be used to re-register a runner of the same
4848+ name but is time-limited. If the file contains a PAT, the service creates a new
4949+ registration token on startup as needed. Make sure the PAT has a scope of
5050+ `admin:org` for organization-wide registrations or a scope of
5151+ `repo` for a single repository.
5252+5353+ Changing this option or the file's content triggers a new runner registration.
5454+ '';
5555+ example = "/run/secrets/github-runner/nixos.token";
5656+ };
5757+5858+ name = let
5959+ # Same pattern as for `networking.hostName`
6060+ baseType = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
6161+ in mkOption {
6262+ type = if includeNameDefault then baseType else types.nullOr baseType;
6363+ description = lib.mdDoc ''
6464+ Name of the runner to configure. Defaults to the hostname.
6565+6666+ Changing this option triggers a new runner registration.
6767+ '';
6868+ example = "nixos";
6969+ } // (if includeNameDefault then {
7070+ default = config.networking.hostName;
7171+ defaultText = literalExpression "config.networking.hostName";
7272+ } else {
7373+ default = null;
7474+ });
7575+7676+ runnerGroup = mkOption {
7777+ type = types.nullOr types.str;
7878+ description = lib.mdDoc ''
7979+ Name of the runner group to add this runner to (defaults to the default runner group).
8080+8181+ Changing this option triggers a new runner registration.
8282+ '';
8383+ default = null;
8484+ };
8585+8686+ extraLabels = mkOption {
8787+ type = types.listOf types.str;
8888+ description = lib.mdDoc ''
8989+ Extra labels in addition to the default (`["self-hosted", "Linux", "X64"]`).
9090+9191+ Changing this option triggers a new runner registration.
9292+ '';
9393+ example = literalExpression ''[ "nixos" ]'';
9494+ default = [ ];
9595+ };
9696+9797+ replace = mkOption {
9898+ type = types.bool;
9999+ description = lib.mdDoc ''
100100+ Replace any existing runner with the same name.
101101+102102+ Without this flag, registering a new runner with the same name fails.
103103+ '';
104104+ default = false;
105105+ };
106106+107107+ extraPackages = mkOption {
108108+ type = types.listOf types.package;
109109+ description = lib.mdDoc ''
110110+ Extra packages to add to `PATH` of the service to make them available to workflows.
111111+ '';
112112+ default = [ ];
113113+ };
114114+115115+ extraEnvironment = mkOption {
116116+ type = types.attrs;
117117+ description = lib.mdDoc ''
118118+ Extra environment variables to set for the runner, as an attrset.
119119+ '';
120120+ example = {
121121+ GIT_CONFIG = "/path/to/git/config";
122122+ };
123123+ default = {};
124124+ };
125125+126126+ serviceOverrides = mkOption {
127127+ type = types.attrs;
128128+ description = lib.mdDoc ''
129129+ Overrides for the systemd service. Can be used to adjust the sandboxing options.
130130+ '';
131131+ example = {
132132+ ProtectHome = false;
133133+ };
134134+ default = {};
135135+ };
136136+137137+ package = mkOption {
138138+ type = types.package;
139139+ description = lib.mdDoc ''
140140+ Which github-runner derivation to use.
141141+ '';
142142+ default = pkgs.github-runner;
143143+ defaultText = literalExpression "pkgs.github-runner";
144144+ };
145145+146146+ ephemeral = mkOption {
147147+ type = types.bool;
148148+ description = lib.mdDoc ''
149149+ If enabled, causes the following behavior:
150150+151151+ - Passes the `--ephemeral` flag to the runner configuration script
152152+ - De-registers and stops the runner with GitHub after it has processed one job
153153+ - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
154154+ - Restarts the service after its successful exit
155155+ - On start, wipes the state directory and configures a new runner
156156+157157+ You should only enable this option if `tokenFile` points to a file which contains a
158158+ personal access token (PAT). If you're using the option with a registration token, restarting the
159159+ service will fail as soon as the registration token expired.
160160+ '';
161161+ default = false;
162162+ };
163163+164164+ user = mkOption {
165165+ type = types.nullOr types.str;
166166+ description = lib.mdDoc ''
167167+ User under which to run the service. If null, will use a systemd dynamic user.
168168+ '';
169169+ default = null;
170170+ defaultText = literalExpression "username";
171171+ };
172172+}
···11+{ config
22+, lib
33+, pkgs
44+55+, cfg ? config.services.github-runner
66+, svcName
77+88+, systemdDir ? "${svcName}/${cfg.name}"
99+ # %t: Runtime directory root (usually /run); see systemd.unit(5)
1010+, runtimeDir ? "%t/${systemdDir}"
1111+ # %S: State directory root (usually /var/lib); see systemd.unit(5)
1212+, stateDir ? "%S/${systemdDir}"
1313+ # %L: Log directory root (usually /var/log); see systemd.unit(5)
1414+, logsDir ? "%L/${systemdDir}"
1515+ # Name of file stored in service state directory
1616+, currentConfigTokenFilename ? ".current-token"
1717+1818+, ...
1919+}:
2020+2121+with lib;
2222+2323+{
2424+ description = "GitHub Actions runner";
2525+2626+ wantedBy = [ "multi-user.target" ];
2727+ wants = [ "network-online.target" ];
2828+ after = [ "network.target" "network-online.target" ];
2929+3030+ environment = {
3131+ HOME = runtimeDir;
3232+ RUNNER_ROOT = stateDir;
3333+ } // cfg.extraEnvironment;
3434+3535+ path = (with pkgs; [
3636+ bash
3737+ coreutils
3838+ git
3939+ gnutar
4040+ gzip
4141+ ]) ++ [
4242+ config.nix.package
4343+ ] ++ cfg.extraPackages;
4444+4545+ serviceConfig = rec {
4646+ ExecStart = "${cfg.package}/bin/Runner.Listener run --startuptype service";
4747+4848+ # Does the following, sequentially:
4949+ # - If the module configuration or the token has changed, purge the state directory,
5050+ # and create the current and the new token file with the contents of the configured
5151+ # token. While both files have the same content, only the later is accessible by
5252+ # the service user.
5353+ # - Configure the runner using the new token file. When finished, delete it.
5454+ # - Set up the directory structure by creating the necessary symlinks.
5555+ ExecStartPre =
5656+ let
5757+ # Wrapper script which expects the full path of the state, runtime and logs
5858+ # directory as arguments. Overrides the respective systemd variables to provide
5959+ # unambiguous directory names. This becomes relevant, for example, if the
6060+ # caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
6161+ # to contain more than one directory. This causes systemd to set the respective
6262+ # environment variables with the path of all of the given directories, separated
6363+ # by a colon.
6464+ writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
6565+ set -euo pipefail
6666+6767+ STATE_DIRECTORY="$1"
6868+ RUNTIME_DIRECTORY="$2"
6969+ LOGS_DIRECTORY="$3"
7070+7171+ ${lines}
7272+ '';
7373+ runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" "ephemeral" ] cfg;
7474+ newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
7575+ currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
7676+ newConfigTokenPath= "$STATE_DIRECTORY/.new-token";
7777+ currentConfigTokenPath = "$STATE_DIRECTORY/${currentConfigTokenFilename}";
7878+7979+ runnerCredFiles = [
8080+ ".credentials"
8181+ ".credentials_rsaparams"
8282+ ".runner"
8383+ ];
8484+ unconfigureRunner = writeScript "unconfigure" ''
8585+ copy_tokens() {
8686+ # Copy the configured token file to the state dir and allow the service user to read the file
8787+ install --mode=666 ${escapeShellArg cfg.tokenFile} "${newConfigTokenPath}"
8888+ # Also copy current file to allow for a diff on the next start
8989+ install --mode=600 ${escapeShellArg cfg.tokenFile} "${currentConfigTokenPath}"
9090+ }
9191+ clean_state() {
9292+ find "$STATE_DIRECTORY/" -mindepth 1 -delete
9393+ copy_tokens
9494+ }
9595+ diff_config() {
9696+ changed=0
9797+ # Check for module config changes
9898+ [[ -f "${currentConfigPath}" ]] \
9999+ && ${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 \
100100+ || changed=1
101101+ # Also check the content of the token file
102102+ [[ -f "${currentConfigTokenPath}" ]] \
103103+ && ${pkgs.diffutils}/bin/diff -q "${currentConfigTokenPath}" ${escapeShellArg cfg.tokenFile} >/dev/null 2>&1 \
104104+ || changed=1
105105+ # If the config has changed, remove old state and copy tokens
106106+ if [[ "$changed" -eq 1 ]]; then
107107+ echo "Config has changed, removing old runner state."
108108+ echo "The old runner will still appear in the GitHub Actions UI." \
109109+ "You have to remove it manually."
110110+ clean_state
111111+ fi
112112+ }
113113+ if [[ "${optionalString cfg.ephemeral "1"}" ]]; then
114114+ # In ephemeral mode, we always want to start with a clean state
115115+ clean_state
116116+ elif [[ "$(ls -A "$STATE_DIRECTORY")" ]]; then
117117+ # There are state files from a previous run; diff them to decide if we need a new registration
118118+ diff_config
119119+ else
120120+ # The state directory is entirely empty which indicates a first start
121121+ copy_tokens
122122+ fi '';
123123+ configureRunner = writeScript "configure" ''
124124+ if [[ -e "${newConfigTokenPath}" ]]; then
125125+ echo "Configuring GitHub Actions Runner"
126126+ args=(
127127+ --unattended
128128+ --disableupdate
129129+ --work "$RUNTIME_DIRECTORY"
130130+ --url ${escapeShellArg cfg.url}
131131+ --labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)}
132132+ --name ${escapeShellArg cfg.name}
133133+ ${optionalString cfg.replace "--replace"}
134134+ ${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}
135135+ ${optionalString cfg.ephemeral "--ephemeral"}
136136+ )
137137+ # If the token file contains a PAT (i.e., it starts with "ghp_"), we have to use the --pat option,
138138+ # if it is not a PAT, we assume it contains a registration token and use the --token option
139139+ token=$(<"${newConfigTokenPath}")
140140+ if [[ "$token" =~ ^ghp_* ]]; then
141141+ args+=(--pat "$token")
142142+ else
143143+ args+=(--token "$token")
144144+ fi
145145+ ${cfg.package}/bin/config.sh "''${args[@]}"
146146+ # Move the automatically created _diag dir to the logs dir
147147+ mkdir -p "$STATE_DIRECTORY/_diag"
148148+ cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
149149+ rm -rf "$STATE_DIRECTORY/_diag/"
150150+ # Cleanup token from config
151151+ rm "${newConfigTokenPath}"
152152+ # Symlink to new config
153153+ ln -s '${newConfigPath}' "${currentConfigPath}"
154154+ fi
155155+ '';
156156+ setupRuntimeDir = writeScript "setup-runtime-dirs" ''
157157+ # Link _diag dir
158158+ ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"
159159+160160+ # Link the runner credentials to the runtime dir
161161+ ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
162162+ '';
163163+ in
164164+ map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
165165+ "+${unconfigureRunner}" # runs as root
166166+ configureRunner
167167+ setupRuntimeDir
168168+ ];
169169+170170+ # If running in ephemeral mode, restart the service on-exit (i.e., successful de-registration of the runner)
171171+ # to trigger a fresh registration.
172172+ Restart = if cfg.ephemeral then "on-success" else "no";
173173+174174+ # Contains _diag
175175+ LogsDirectory = [ systemdDir ];
176176+ # Default RUNNER_ROOT which contains ephemeral Runner data
177177+ RuntimeDirectory = [ systemdDir ];
178178+ # Home of persistent runner data, e.g., credentials
179179+ StateDirectory = [ systemdDir ];
180180+ StateDirectoryMode = "0700";
181181+ WorkingDirectory = runtimeDir;
182182+183183+ InaccessiblePaths = [
184184+ # Token file path given in the configuration, if visible to the service
185185+ "-${cfg.tokenFile}"
186186+ # Token file in the state directory
187187+ "${stateDir}/${currentConfigTokenFilename}"
188188+ ];
189189+190190+ KillSignal = "SIGINT";
191191+192192+ # Hardening (may overlap with DynamicUser=)
193193+ # The following options are only for optimizing:
194194+ # systemd-analyze security github-runner
195195+ AmbientCapabilities = "";
196196+ CapabilityBoundingSet = "";
197197+ # ProtectClock= adds DeviceAllow=char-rtc r
198198+ DeviceAllow = "";
199199+ NoNewPrivileges = true;
200200+ PrivateDevices = true;
201201+ PrivateMounts = true;
202202+ PrivateTmp = true;
203203+ PrivateUsers = true;
204204+ ProtectClock = true;
205205+ ProtectControlGroups = true;
206206+ ProtectHome = true;
207207+ ProtectHostname = true;
208208+ ProtectKernelLogs = true;
209209+ ProtectKernelModules = true;
210210+ ProtectKernelTunables = true;
211211+ ProtectSystem = "strict";
212212+ RemoveIPC = true;
213213+ RestrictNamespaces = true;
214214+ RestrictRealtime = true;
215215+ RestrictSUIDSGID = true;
216216+ UMask = "0066";
217217+ ProtectProc = "invisible";
218218+ SystemCallFilter = [
219219+ "~@clock"
220220+ "~@cpu-emulation"
221221+ "~@module"
222222+ "~@mount"
223223+ "~@obsolete"
224224+ "~@raw-io"
225225+ "~@reboot"
226226+ "~capset"
227227+ "~setdomainname"
228228+ "~sethostname"
229229+ ];
230230+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
231231+232232+ # Needs network access
233233+ PrivateNetwork = false;
234234+ # Cannot be true due to Node
235235+ MemoryDenyWriteExecute = false;
236236+237237+ # The more restrictive "pid" option makes `nix` commands in CI emit
238238+ # "GC Warning: Couldn't read /proc/stat"
239239+ # You may want to set this to "pid" if not using `nix` commands
240240+ ProcSubset = "all";
241241+ # Coverage programs for compiled code such as `cargo-tarpaulin` disable
242242+ # ASLR (address space layout randomization) which requires the
243243+ # `personality` syscall
244244+ # You may want to set this to `true` if not using coverage tooling on
245245+ # compiled code
246246+ LockPersonality = false;
247247+248248+ # Note that this has some interactions with the User setting; so you may
249249+ # want to consult the systemd docs if using both.
250250+ DynamicUser = true;
251251+ } // (
252252+ lib.optionalAttrs (cfg.user != null) { User = cfg.user; }
253253+ ) // cfg.serviceOverrides;
254254+}
···5555 , stdenvOverride ? stdenv
5656 , src ? (getCoreSrc core)
5757 , broken ? false
5858- , version ? "unstable-2022-10-01"
5858+ , version ? "unstable-2022-10-18"
5959 , platforms ? retroarch.meta.platforms
6060 # The resulting core file is based on core name
6161 # Setting `normalizeCore` to `true` will convert `-` to `_` on the core filename
···2233buildGoModule rec {
44 pname = "yggdrasil";
55- version = "0.4.4";
55+ version = "0.4.5";
6677 src = fetchFromGitHub {
88 owner = "yggdrasil-network";
99 repo = "yggdrasil-go";
1010 rev = "v${version}";
1111- sha256 = "sha256-uJFBboV0DhZHEir4+2VdTGMqxZsahnFRgr9btdMlW2M=";
1111+ sha256 = "sha256-ehOvPFQtFgxVDOyF2MBbGO0IKwMWSb3aat+e+fJay1Q=";
1212 };
13131414- vendorSha256 = "sha256-qeyXUTcII0hMrOWIvsjaOXv/tKWBoUrTkCimRC/RnUw=";
1414+ vendorSha256 = "sha256-u1VrlTvmB2KSnlxcdCyfxw0xAMd+AeN5g/a7JehUV9U=";
15151616 # Change the default location of the management socket on Linux
1717 # systems so that the yggdrasil system service unit does not have to