projects/guix·blob ruby-team · refs

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