diff options
author | Ludovic Courtès <ludo@gnu.org> | 2025-07-01 17:36:01 +0200 |
---|---|---|
committer | Ludovic Courtès <ludo@gnu.org> | 2025-07-04 18:41:43 +0200 |
commit | 6b42df3ad65e4aadb4e848eed8f36eea1974391a (patch) | |
tree | 7ff0fe65f2120916f40933e529bc632ed1371cb3 | |
parent | 821e517ea4cfb1a5b0e7ef16e756cfce3e6fa92d (diff) |
services: ci: Add Forgejo Runner service.
* gnu/services/ci.scm (<forgejo-runner-configuration>): New record type.
(create-forgejo-runner-account, forgejo-runner-activation)
(write-yaml, yaml-file, forgejo-runner-shepherd-service): New procedures.
(forgejo-runner-service-type): New variable.
* doc/guix.texi (Continuous Integration): Add “Forgejo Runner” heading.
Co-authored-by: David Thompson <davet@gnu.org>
Change-Id: Iba42d84da35812afa60e94773fbbadd68eca9813
-rw-r--r-- | doc/guix.texi | 107 | ||||
-rw-r--r-- | gnu/services/ci.scm | 195 |
2 files changed, 301 insertions, 1 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index a9f64bd9e4..d48dabfc10 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -37870,6 +37870,113 @@ Base URL to use for links to laminar itself. @end table @end deftp +@subsubheading Forgejo Runner + +@cindex continuous integration, Forgejo +@cindex Forgejo, continuous integration +The @code{(gnu services ci)} also provides a service for +@uref{https://code.forgejo.org/forgejo/runner, Forgejo Runner}, a daemon +that connects to an instance of the @uref{https://forgejo.org, Forgejo +code collaboration tool} and runs jobs for continuous integration. + +A minimal configuration mostly with default values that can be added to +the @code{services} field of your operating system looks like this: + +@lisp +(service forgejo-runner-service-type + (forgejo-runner-configuration + (name "my-runner") + (labels '("guix" "linux")))) +@end lisp + +This provides a @code{forgejo-runner} Shepherd service. That service +will initially fail to start; you will have to manually @dfn{register} +the runner against the Forgejo server by running a command like: + +@example +herd register forgejo-runner @var{url} @var{token} +@end example + +@noindent +... where the arguments are as follows: + +@table @var +@item url +the URL of the Forgejo server---e.g., +@indicateurl{https://codeberg.org}; +@item token +an access token +@uref{https://forgejo.org/docs/latest/admin/runner-installation/#standard-registration, +provided by the Forgejo server}. +@end table + +Once registration has succeeded, you can start the runner: + +@example +herd enable forgejo-runner +herd start forgejo-runner +@end example + +The runner then receives orders from the Forgejo server to execute +@dfn{actions}. Actions are commands and workflows specified by YAML +files in the @file{.forgejo/workflows} directory of source code +repositories---see @uref{https://forgejo.org/docs/v7.0/user/actions/, +the Forgejo Action documentation} for more info. + +Note that at the moment @code{forgejo-runner-service-type} lets you run +only one runner. Details about the configuration of this service +follow. + +@defvar forgejo-runner-service-type +This is the service type for Forgejo Runner. Its value must be a +@code{forgejo-runner-configuration} record, documented below. +@end defvar + +@deftp {Data Type} forgejo-runner-configuration +This data type represents the configuration of an instance of +@code{forgejo-runner-service-type}. It contains the following fields: + +@table @asis +@item @code{package} (default: @code{forgejo-runner}) +The Forgejo Runner package to use. + +@item @code{name} (default: @code{#~(gethostname)}) +Name of the runner as will be shown in the runner management interface +of Forgejo. + +@item @code{labels} (default: @code{'("guix")}) +List of +@uref{https://forgejo.org/docs/latest/admin/actions/#choosing-labels, +labels} representing the type of environment the runner provides and +that actions may refer to. + +@item @code{capacity} (default: @code{1}) +Number of tasks to be executed concurrently. + +@item @code{timeout} (default: @code{(* 3 3600)}) +Maximum duration of a job, in seconds. + +@item @code{fetch-timeout} (default: @code{5}) +Maximum duration for fetching the job from the Forgejo server, in +seconds. + +@item @code{fetch-interval} (default: @code{2}) +Interval (in seconds) for fetching the job from the Forgejo server. + +@item @code{report-interval} (default: @code{1}) +Interval (in seconds) for reporting the job status and log to the +Forgejo server. + +@item @code{data-directory} (default: @code{"/var/lib/forgejo-runner"}) +Directory where @command{forgejo-runner} will store persistent data such +as its configuration and access token. + +@item @code{run-directory} (default: @code{"/var/run/forgejo-runner"}) +Directory where @command{forgejo-runner} stores cached data. + +@end table +@end deftp + @node Power Management Services @subsection Power Management Services diff --git a/gnu/services/ci.scm b/gnu/services/ci.scm index d0363595a2..595cad347e 100644 --- a/gnu/services/ci.scm +++ b/gnu/services/ci.scm @@ -1,6 +1,8 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018, 2019, 2020, 2021 Christopher Baines <mail@cbaines.net> ;;; Copyright © 2021, 2022 Arun Isaac <arunisaac@systemreboot.net> +;;; Copyright © 2025 David Thompson <davet@gnu.org> +;;; Copyright © 2025 Ludovic Courtès <ludo@gnu.org> ;;; ;;; This file is part of GNU Guix. ;;; @@ -20,6 +22,7 @@ (define-module (gnu services ci) #:use-module (guix gexp) #:use-module (guix records) + #:autoload (guix modules) (source-module-closure) #:use-module (gnu packages admin) #:use-module (gnu packages ci) #:use-module (gnu services) @@ -39,7 +42,22 @@ laminar-configuration-archive-url laminar-configuration-base-url - laminar-service-type)) + laminar-service-type + + forgejo-runner-configuration + forgejo-runner-configuration? + forgejo-runner-configuration-package + forgejo-runner-configuration-data-directory + forgejo-runner-configuration-run-directory + forgejo-runner-configuration-name + forgejo-runner-configuration-labels + forgejo-runner-configuration-capacity + forgejo-runner-configuration-timeout + forgejo-runner-configuration-fetch-timeout + forgejo-runner-configuration-fetch-interval + forgejo-runner-configuration-report-interval + + forgejo-runner-service-type)) ;;;; Commentary: ;;; @@ -146,3 +164,178 @@ (default-value (laminar-configuration)) (description "Run the Laminar continuous integration service."))) + + +;;; +;;; Forgejo runner. +;;; + +(define-record-type* <forgejo-runner-configuration> + forgejo-runner-configuration + make-forgejo-runner-configuration + forgejo-runner-configuration? + (package forgejo-runner-configuration-package + (default forgejo-runner)) + (data-directory forgejo-runner-configuration-data-directory + (default "/var/lib/forgejo-runner")) + (run-directory forgejo-runner-configuration-run-directory + (default "/var/run/forgejo-runner")) + + ;; Configuration options for the YAML config file: + ;; <https://forgejo.org/docs/latest/admin/runner-installation/#configuration>. + (name forgejo-runner-configuration-name + (default #~(gethostname))) + (labels forgejo-runner-configuration-labels + (default '("guix"))) + (capacity forgejo-runner-configuration-job-capacity + (default 1)) + (timeout forgejo-runner-configuration-timeout + (default (* 3 3600))) + (fetch-timeout forgejo-runner-configuration-fetch-timeout + (default 5)) + (fetch-interval forgejo-runner-configuration-fetch-interval + (default 2)) + (report-interval forgejo-runner-configuration-report-interval + (default 1))) + +(define (create-forgejo-runner-account config) + (list (user-account + (name "forgejo-runner") + (group "forgejo-runner") + (system? #t) + (comment "Forgejo Runner user") + (home-directory + (forgejo-runner-configuration-data-directory config))) + (user-group + (name "forgejo-runner") + (system? #t)))) + +(define (forgejo-runner-activation config) + (match-record config <forgejo-runner-configuration> + (data-directory run-directory) + #~(let* ((user (getpwnam "forgejo-runner"))) + (mkdir-p #$run-directory) + (chown #$run-directory (passwd:uid user) (passwd:gid user))))) + +;; Very naive YAML writer that does just enough for our needs. +(define* (write-yaml port exp depth) + (match exp + ((? string? str) + (write str port)) + ((? number? n) + (display n port)) + (('seconds (? number? n)) + (format port "~as" n)) + (#(values ...) + (display "[ " port) + (let ((strings + (map (lambda (value) + (call-with-output-string + (lambda (port) + (write-yaml port value depth)))) + values))) + (display (string-join strings ", ") port)) + (display " ]" port)) + (() (values)) + ((((? symbol? k) . v) . rest) + (do ((i 0 (1+ i))) + ((= i depth)) + (display " " port)) + (display k port) + (display ": " port) + (match v + (((k* . v*) . _) ; subtree + (newline port) + (write-yaml port v (1+ depth))) + (_ (write-yaml port v depth))) + (newline port) + (write-yaml port rest depth)))) + +(define (yaml-file name exp) + (plain-file name + (call-with-output-string + (lambda (port) + (write-yaml port exp 0))))) + +(define (forgejo-runner-shepherd-service config) + (match-record config <forgejo-runner-configuration> + (package data-directory run-directory name + capacity timeout fetch-timeout fetch-interval report-interval + labels) + (define runner (file-append package "/bin/forgejo-runner")) + (define runner-file (string-append data-directory "/runner")) + (define config + (yaml-file + "forgejo-runner-config.yml" + `((runner . ((file . ,runner-file) + (capacity . ,capacity) + (timeout . (seconds ,timeout)) + (fetch_timeout . (seconds ,fetch-timeout)) + (fetch_interval . (seconds ,fetch-interval)) + (report_interval . (seconds ,report-interval)) + (labels . ,(list->vector labels)))) + (cache . ((dir . ,(string-append run-directory "/cache")))) + (host . ((workdir_parent + . ,(string-append run-directory "/act"))))))) + + (list (shepherd-service + (provision '(forgejo-runner)) + (requirement '(user-processes networking)) + (start #~(make-forkexec-constructor + (list #$runner "daemon" "--config" #$config) + #:user "forgejo-runner" + #:group "forgejo-runner" + #:directory #$run-directory + #:environment-variables + ;; Provide access to a fresh Guix obtained via 'guix + ;; pull'. + (cons* (string-append "PATH=" + #$data-directory "/.config/guix/current/bin" + ":/run/current-system/profile/bin") + (string-append "HOME=" #$data-directory) + "GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt" + (default-environment-variables)))) + (stop #~(make-kill-destructor)) + (actions + (list + (shepherd-configuration-action config) + (shepherd-action + (procedure + #~(lambda (running instance token) + (define status + (spawn-command (list #$runner "register" + "--no-interactive" + "--config" #$config + "--name" #$name + "--instance" instance + "--token" token) + #:user "forgejo-runner" + #:group "forgejo-runner")) + + (if (zero? status) + (format #t "Successfully registered runner \ +'~a' for '~a'.~%" + #$name instance) + (format #t "'~a register' failed with status ~a.~%" + #$runner status)) + (zero? status))) + (name 'register) + (documentation "Register this runner with a Forgejo server. +This action takes two arguments: the Forgejo server URL and an access +token.")))) + (documentation "Forgejo task runner"))))) + +(define forgejo-runner-service-type + (service-type + (name 'forgejo-runner) + (extensions + (list (service-extension activation-service-type + forgejo-runner-activation) + (service-extension account-service-type + create-forgejo-runner-account) + (service-extension shepherd-root-service-type + forgejo-runner-shepherd-service))) + (default-value (forgejo-runner-configuration)) + (description + "Run @command{forgejo-runner}, a daemon to run tasks for the Forgejo +source code collaboration service."))) |