1{
2 lib,
3 pkgs,
4 ...
5}:
6{
7 options.services.github-runners = lib.mkOption {
8 description = ''
9 Multiple GitHub Runners.
10 '';
11 example = {
12 runner1 = {
13 enable = true;
14 url = "https://github.com/owner/repo";
15 name = "runner1";
16 tokenFile = "/secrets/token1";
17 };
18
19 runner2 = {
20 enable = true;
21 url = "https://github.com/owner/repo";
22 name = "runner2";
23 tokenFile = "/secrets/token2";
24 };
25 };
26 default = { };
27 type = lib.types.attrsOf (
28 lib.types.submodule (
29 { name, ... }:
30 {
31 options = {
32 enable = lib.mkOption {
33 default = false;
34 example = true;
35 description = ''
36 Whether to enable GitHub Actions runner.
37
38 Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
39 [About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners).
40 '';
41 type = lib.types.bool;
42 };
43
44 url = lib.mkOption {
45 type = lib.types.str;
46 description = ''
47 Repository to add the runner to.
48
49 Changing this option triggers a new runner registration.
50
51 IMPORTANT: If your token is org-wide (not per repository), you need to
52 provide a github org link, not a single repository, so do it like this
53 `https://github.com/nixos`, not like this
54 `https://github.com/nixos/nixpkgs`.
55 Otherwise, you are going to get a `404 NotFound`
56 from `POST https://api.github.com/actions/runner-registration`
57 in the configure script.
58 '';
59 example = "https://github.com/nixos/nixpkgs";
60 };
61
62 tokenFile = lib.mkOption {
63 type = lib.types.path;
64 description = ''
65 The full path to a file which contains either
66
67 * a fine-grained personal access token (PAT),
68 * a classic PAT
69 * or a runner registration token
70
71 Changing this option or the `tokenFile`’s content triggers a new runner registration.
72
73 We suggest using the fine-grained PATs. A runner registration token is valid
74 only for 1 hour after creation, so the next time the runner configuration changes
75 this will give you hard-to-debug HTTP 404 errors in the configure step.
76
77 The file should contain exactly one line with the token without any newline.
78 (Use `echo -n '…token…' > …token file…` to make sure no newlines sneak in.)
79
80 If the file contains a PAT, the service creates a new registration token
81 on startup as needed.
82 If a registration token is given, it can be used to re-register a runner of the same
83 name but is time-limited as noted above.
84
85 For fine-grained PATs:
86
87 Give it "Read and Write access to organization/repository self hosted runners",
88 depending on whether it is organization wide or per-repository. You might have to
89 experiment a little, fine-grained PATs are a `beta` Github feature and still subject
90 to change; nonetheless they are the best option at the moment.
91
92 For classic PATs:
93
94 Make sure the PAT has a scope of `admin:org` for organization-wide registrations
95 or a scope of `repo` for a single repository.
96
97 For runner registration tokens:
98
99 Nothing special needs to be done, but updating will break after one hour,
100 so these are not recommended.
101 '';
102 example = "/run/secrets/github-runner/nixos.token";
103 };
104
105 name = lib.mkOption {
106 type = lib.types.nullOr lib.types.str;
107 description = ''
108 Name of the runner to configure. If null, defaults to the hostname.
109
110 Changing this option triggers a new runner registration.
111 '';
112 example = "nixos";
113 default = name;
114 };
115
116 runnerGroup = lib.mkOption {
117 type = lib.types.nullOr lib.types.str;
118 description = ''
119 Name of the runner group to add this runner to (defaults to the default runner group).
120
121 Changing this option triggers a new runner registration.
122 '';
123 default = null;
124 };
125
126 extraLabels = lib.mkOption {
127 type = lib.types.listOf lib.types.str;
128 description = ''
129 Extra labels in addition to the default (unless disabled through the `noDefaultLabels` option).
130
131 Changing this option triggers a new runner registration.
132 '';
133 example = lib.literalExpression ''[ "nixos" ]'';
134 default = [ ];
135 };
136
137 noDefaultLabels = lib.mkOption {
138 type = lib.types.bool;
139 description = ''
140 Disables adding the default labels. Also see the `extraLabels` option.
141
142 Changing this option triggers a new runner registration.
143 '';
144 default = false;
145 };
146
147 replace = lib.mkOption {
148 type = lib.types.bool;
149 description = ''
150 Replace any existing runner with the same name.
151
152 Without this flag, registering a new runner with the same name fails.
153 '';
154 default = false;
155 };
156
157 extraPackages = lib.mkOption {
158 type = lib.types.listOf lib.types.package;
159 description = ''
160 Extra packages to add to `PATH` of the service to make them available to workflows.
161 '';
162 default = [ ];
163 };
164
165 extraEnvironment = lib.mkOption {
166 type = lib.types.attrs;
167 description = ''
168 Extra environment variables to set for the runner, as an attrset.
169 '';
170 example = {
171 GIT_CONFIG = "/path/to/git/config";
172 };
173 default = { };
174 };
175
176 serviceOverrides = lib.mkOption {
177 type = lib.types.attrs;
178 description = ''
179 Modify the systemd service. Can be used to, e.g., adjust the sandboxing options.
180 See {manpage}`systemd.exec(5)` for more options.
181 '';
182 example = {
183 ProtectHome = false;
184 RestrictAddressFamilies = [ "AF_PACKET" ];
185 };
186 default = { };
187 };
188
189 package = lib.mkPackageOption pkgs "github-runner" { };
190
191 ephemeral = lib.mkOption {
192 type = lib.types.bool;
193 description = ''
194 If enabled, causes the following behavior:
195
196 - Passes the `--ephemeral` flag to the runner configuration script
197 - De-registers and stops the runner with GitHub after it has processed one job
198 - On stop, systemd wipes the runtime directory (this always happens, even without using the ephemeral option)
199 - Restarts the service after its successful exit
200 - On start, wipes the state directory and configures a new runner
201
202 You should only enable this option if `tokenFile` points to a file which contains a
203 personal access token (PAT). If you're using the option with a registration token, restarting the
204 service will fail as soon as the registration token expired.
205
206 Changing this option triggers a new runner registration.
207 '';
208 default = false;
209 };
210
211 user = lib.mkOption {
212 type = lib.types.nullOr lib.types.str;
213 description = ''
214 User under which to run the service.
215
216 If this option and the `group` option is set to `null`,
217 the service runs as a dynamically allocated user.
218
219 Also see the `group` option for an overview on the effects of the `user` and `group` settings.
220 '';
221 default = null;
222 defaultText = lib.literalExpression "username";
223 };
224
225 group = lib.mkOption {
226 type = lib.types.nullOr lib.types.str;
227 description = ''
228 Group under which to run the service.
229
230 The effect of this option depends on the value of the `user` option:
231
232 - `group == null` and `user == null`:
233 The service runs with a dynamically allocated user and group.
234 - `group == null` and `user != null`:
235 The service runs as the given user and its default group.
236 - `group != null` and `user == null`:
237 This configuration is invalid. In this case, the service would use the given group
238 but run as root implicitly. If this is really what you want, set `user = "root"` explicitly.
239 '';
240 default = null;
241 defaultText = lib.literalExpression "groupname";
242 };
243
244 workDir = lib.mkOption {
245 type = with lib.types; nullOr str;
246 description = ''
247 Working directory, available as `$GITHUB_WORKSPACE` during workflow runs
248 and used as a default for [repository checkouts](https://github.com/actions/checkout).
249 The service cleans this directory on every service start.
250
251 A value of `null` will default to the systemd `RuntimeDirectory`.
252
253 Changing this option triggers a new runner registration.
254 '';
255 default = null;
256 };
257
258 nodeRuntimes = lib.mkOption {
259 type = with lib.types; nonEmptyListOf (enum [ "node20" ]);
260 default = [ "node20" ];
261 description = ''
262 List of Node.js runtimes the runner should support.
263 '';
264 };
265 };
266 }
267 )
268 );
269 };
270}