projects/guix·blob master · refs

364 lines · 15.1 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2018, 2019, 2020, 2021 Christopher Baines <[email protected]>
3;;; Copyright © 2021, 2022 Arun Isaac <[email protected]>
4;;; Copyright © 2025 David Thompson <[email protected]>
5;;; Copyright © 2025 Ludovic Courtès <[email protected]>
6;;; Copyright © 2026 Igorj Gorjaĉev <[email protected]>
7;;; Copyright © 2026 Remco van 't Veer <[email protected]>
8;;;
9;;; This file is part of GNU Guix.
10;;;
11;;; GNU Guix is free software; you can redistribute it and/or modify
12;;; it under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation, either version 3 of the License, or
14;;; (at your option) any later version.
15;;;
16;;; GNU Guix is distributed in the hope that it will be useful,
17;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24;;;; Commentary:
25;;;
26;;; This module implements a service that to run instances of Laminar, a
27;;; continuous integration tool.
28;;;
29;;;; Code:
30
31(define-module (gnu services ci)
32 #:use-module (guix gexp)
33 #:use-module (guix records)
34 #:autoload (guix modules) (source-module-closure)
35 #:use-module (gnu packages admin)
36 #:use-module (gnu packages ci)
37 #:use-module (gnu services)
38 #:use-module (gnu services base)
39 #:use-module (gnu services shepherd)
40 #:use-module (gnu services admin)
41 #:use-module (gnu system shadow)
42 #:use-module (ice-9 match)
43 #:export (laminar-configuration
44 laminar-configuration?
45 laminar-configuration-home-directory
46 laminar-configuration-supplementary-groups
47 laminar-configuration-bind-http
48 laminar-configuration-bind-rpc
49 laminar-configuration-title
50 laminar-configuration-keep-rundirs
51 laminar-configuration-archive-url
52 laminar-configuration-base-url
53
54 laminar-service-type
55
56 forgejo-runner-configuration
57 forgejo-runner-configuration?
58 forgejo-runner-configuration-package
59 forgejo-runner-configuration-extra-shepherd-requirement
60 forgejo-runner-configuration-user
61 forgejo-runner-configuration-group
62 forgejo-runner-configuration-supplementary-groups
63 forgejo-runner-configuration-data-directory
64 forgejo-runner-configuration-run-directory
65 forgejo-runner-configuration-name
66 forgejo-runner-configuration-labels
67 forgejo-runner-configuration-capacity
68 forgejo-runner-configuration-timeout
69 forgejo-runner-configuration-fetch-timeout
70 forgejo-runner-configuration-fetch-interval
71 forgejo-runner-configuration-report-interval
72
73 forgejo-runner-service-type))
74
75(define-record-type* <laminar-configuration>
76 laminar-configuration make-laminar-configuration
77 laminar-configuration?
78 (laminar laminars-configuration-laminar
79 (default laminar))
80 (home-directory laminar-configuration-home-directory
81 (default "/var/lib/laminar"))
82 (supplementary-groups laminar-configuration-supplementary-groups
83 (default '()))
84 (bind-http laminar-configuration-bind-http
85 (default "*:8080"))
86 (bind-rpc laminar-configuration-bind-rpc
87 (default "unix-abstract:laminar"))
88 (title laminar-configuration-title
89 (default "Laminar"))
90 (keep-rundirs laminar-keep-rundirs
91 (default 0))
92 (archive-url laminar-archive-url
93 (default #f))
94 (base-url laminar-base-url
95 (default #f)))
96
97(define laminar-shepherd-service
98 (match-lambda
99 (($ <laminar-configuration> laminar home-directory supplementary-groups
100 bind-http bind-rpc
101 title keep-rundirs archive-url
102 base-url)
103 (list (shepherd-service
104 (documentation "Run Laminar.")
105 (provision '(laminar))
106 (requirement '(user-processes networking))
107 (start #~(make-forkexec-constructor
108 (list #$(file-append laminar "/sbin/laminard"))
109 #:environment-variables
110 `(,(string-append "LAMINAR_HOME="
111 #$home-directory)
112 ,(string-append "LAMINAR_BIND_HTTP="
113 #$bind-http)
114 ,(string-append "LAMINAR_BIND_RPC="
115 #$bind-rpc)
116 ,(string-append "LAMINAR_TITLE="
117 #$title)
118 ,(string-append "LAMINAR_KEEP_RUNDIRS="
119 #$(number->string
120 keep-rundirs))
121 ,@(if #$archive-url
122 (list
123 (string-append "LAMINAR_ARCHIVE_URL="
124 #$archive-url))
125 '())
126 ,@(if #$base-url
127 (list
128 (string-append "LAMINAR_BASE_URL="
129 #$base-url))
130 '()))
131 #:user "laminar"
132 #:group "laminar"
133 #:supplementary-groups '#$supplementary-groups))
134 (stop #~(make-kill-destructor)))))))
135
136(define (laminar-account config)
137 "Return the user accounts and user groups for CONFIG."
138 (list (user-group
139 (name "laminar")
140 (system? #t))
141 (user-account
142 (name "laminar")
143 (group "laminar")
144 (supplementary-groups
145 (laminar-configuration-supplementary-groups config))
146 (system? #t)
147 (comment "Laminar privilege separation user")
148 (home-directory (laminar-configuration-home-directory config))
149 (shell #~(string-append #$shadow "/sbin/nologin")))))
150
151(define (laminar-activation config)
152 (let ((bind-http (laminar-configuration-bind-http config)))
153 #~(begin
154 ;; If listen is a unix socket, create its parent directory.
155 (when (string-prefix? "unix:" #$bind-http)
156 (let ((run-directory
157 (dirname (substring #$bind-http (string-length "unix:"))))
158 (user (getpw "laminar")))
159 (mkdir-p run-directory)
160 (chown run-directory (passwd:uid user) (passwd:gid user)))))))
161
162(define laminar-service-type
163 (service-type
164 (name 'laminar)
165 (extensions
166 (list
167 (service-extension shepherd-root-service-type laminar-shepherd-service)
168 (service-extension account-service-type laminar-account)
169 (service-extension activation-service-type laminar-activation)))
170 (default-value (laminar-configuration))
171 (description
172 "Run the Laminar continuous integration service.")))
173
174
175;;;
176;;; Forgejo runner.
177;;;
178
179(define-record-type* <forgejo-runner-configuration>
180 forgejo-runner-configuration
181 make-forgejo-runner-configuration
182 forgejo-runner-configuration?
183 (package forgejo-runner-configuration-package
184 (default forgejo-runner))
185 (extra-shepherd-requirement
186 forgejo-runner-configuration-extra-shepherd-requirement
187 (default '(dockerd)))
188 (user forgejo-runner-configuration-user
189 (default "forgejo-runner"))
190 (group forgejo-runner-configuration-group
191 (default "forgejo-runner"))
192 (supplementary-groups
193 forgejo-runner-configuration-supplementary-groups
194 (default '("docker")))
195 (data-directory forgejo-runner-configuration-data-directory
196 (default "/var/lib/forgejo-runner"))
197 (run-directory forgejo-runner-configuration-run-directory
198 (default "/var/run/forgejo-runner"))
199 (log-file forgejo-runner-configuration-log-file
200 (default "/var/log/forgejo-runner.log"))
201
202 ;; Configuration options for the YAML config file:
203 ;; <https://forgejo.org/docs/latest/admin/runner-installation/#configuration>.
204 (name forgejo-runner-configuration-name
205 (default #~(gethostname)))
206 (labels forgejo-runner-configuration-labels
207 (default '("guix:host")))
208 (capacity forgejo-runner-configuration-job-capacity
209 (default 1))
210 (timeout forgejo-runner-configuration-timeout
211 (default (* 3 3600)))
212 (fetch-timeout forgejo-runner-configuration-fetch-timeout
213 (default 5))
214 (fetch-interval forgejo-runner-configuration-fetch-interval
215 (default 2))
216 (report-interval forgejo-runner-configuration-report-interval
217 (default 1)))
218
219(define (create-forgejo-runner-account config)
220 (list (user-account
221 (name (forgejo-runner-configuration-user config))
222 (group (forgejo-runner-configuration-group config))
223 (supplementary-groups
224 (forgejo-runner-configuration-supplementary-groups config))
225 (system? #t)
226 (comment "Forgejo Runner user")
227 (home-directory
228 (forgejo-runner-configuration-data-directory config)))
229 (user-group
230 (name (forgejo-runner-configuration-group config))
231 (system? #t))))
232
233(define (forgejo-runner-activation config)
234 (match-record config <forgejo-runner-configuration>
235 (user data-directory run-directory)
236 #~(let* ((user (getpwnam #$user)))
237 (mkdir-p #$run-directory)
238 (chown #$run-directory (passwd:uid user) (passwd:gid user)))))
239
240;; Very naive YAML writer that does just enough for our needs.
241(define* (write-yaml port exp depth)
242 (match exp
243 ((? string? str)
244 (write str port))
245 ((? number? n)
246 (display n port))
247 (('seconds (? number? n))
248 (format port "~as" n))
249 (#(values ...)
250 (display "[ " port)
251 (let ((strings
252 (map (lambda (value)
253 (call-with-output-string
254 (lambda (port)
255 (write-yaml port value depth))))
256 values)))
257 (display (string-join strings ", ") port))
258 (display " ]" port))
259 (() (values))
260 ((((? symbol? k) . v) . rest)
261 (do ((i 0 (1+ i)))
262 ((= i depth))
263 (display " " port))
264 (display k port)
265 (display ": " port)
266 (match v
267 (((k* . v*) . _) ; subtree
268 (newline port)
269 (write-yaml port v (1+ depth)))
270 (_ (write-yaml port v depth)))
271 (newline port)
272 (write-yaml port rest depth))))
273
274(define (yaml-file name exp)
275 (plain-file name
276 (call-with-output-string
277 (lambda (port)
278 (write-yaml port exp 0)))))
279
280(define (forgejo-runner-shepherd-service config)
281 (match-record config <forgejo-runner-configuration>
282 (package extra-shepherd-requirement user group supplementary-groups
283 data-directory run-directory log-file name capacity
284 timeout fetch-timeout fetch-interval report-interval
285 labels)
286 (define runner (file-append package "/bin/forgejo-runner"))
287 (define runner-file (string-append data-directory "/runner"))
288 (define config
289 (yaml-file
290 "forgejo-runner-config.yml"
291 `((runner . ((file . ,runner-file)
292 (capacity . ,capacity)
293 (timeout . (seconds ,timeout))
294 (fetch_timeout . (seconds ,fetch-timeout))
295 (fetch_interval . (seconds ,fetch-interval))
296 (report_interval . (seconds ,report-interval))
297 (labels . ,(list->vector labels))))
298 (cache . ((dir . ,(string-append run-directory "/cache"))))
299 (host . ((workdir_parent
300 . ,(string-append run-directory "/act")))))))
301
302 (list (shepherd-service
303 (provision '(forgejo-runner))
304 (requirement `(user-processes networking ,@extra-shepherd-requirement))
305 (start #~(make-forkexec-constructor
306 (list #$runner "daemon" "--config" #$config)
307 #:user #$user
308 #:group #$group
309 #:supplementary-groups '#$supplementary-groups
310 #:directory #$run-directory
311 #:log-file #$log-file
312 #:environment-variables
313 ;; Provide access to a fresh Guix obtained via 'guix
314 ;; pull'.
315 (cons* (string-append "PATH="
316 #$data-directory "/.config/guix/current/bin"
317 ":/run/current-system/profile/bin")
318 (string-append "HOME=" #$data-directory)
319 "GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt"
320 (default-environment-variables))))
321 (stop #~(make-kill-destructor))
322 (actions
323 (list
324 (shepherd-configuration-action config)
325 (shepherd-action
326 (procedure
327 #~(lambda (running instance token)
328 (define status
329 (spawn-command (list #$runner "register"
330 "--no-interactive"
331 "--config" #$config
332 "--name" #$name
333 "--instance" instance
334 "--token" token)
335 #:user #$user
336 #:group #$group))
337
338 (if (zero? status)
339 (format #t "Successfully registered runner \
340'~a' for '~a'.~%"
341 #$name instance)
342 (format #t "'~a register' failed with status ~a.~%"
343 #$runner status))
344 (zero? status)))
345 (name 'register)
346 (documentation "Register this runner with a Forgejo server.
347This action takes two arguments: the Forgejo server URL and an access
348token."))))
349 (documentation "Forgejo task runner")))))
350
351(define forgejo-runner-service-type
352 (service-type
353 (name 'forgejo-runner)
354 (extensions
355 (list (service-extension activation-service-type
356 forgejo-runner-activation)
357 (service-extension account-service-type
358 create-forgejo-runner-account)
359 (service-extension shepherd-root-service-type
360 forgejo-runner-shepherd-service)))
361 (default-value (forgejo-runner-configuration))
362 (description
363 "Run @command{forgejo-runner}, a daemon to run tasks for the Forgejo
364source code collaboration service.")))