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
This commit is contained in:
Ludovic Courtès 2025-07-01 17:36:01 +02:00
parent 821e517ea4
commit 6b42df3ad6
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
2 changed files with 301 additions and 1 deletions

View file

@ -37870,6 +37870,113 @@ Base URL to use for links to laminar itself.
@end table @end table
@end deftp @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 @node Power Management Services
@subsection Power Management Services @subsection Power Management Services

View file

@ -1,6 +1,8 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018, 2019, 2020, 2021 Christopher Baines <mail@cbaines.net> ;;; Copyright © 2018, 2019, 2020, 2021 Christopher Baines <mail@cbaines.net>
;;; Copyright © 2021, 2022 Arun Isaac <arunisaac@systemreboot.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. ;;; This file is part of GNU Guix.
;;; ;;;
@ -20,6 +22,7 @@
(define-module (gnu services ci) (define-module (gnu services ci)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module (guix records) #:use-module (guix records)
#:autoload (guix modules) (source-module-closure)
#:use-module (gnu packages admin) #:use-module (gnu packages admin)
#:use-module (gnu packages ci) #:use-module (gnu packages ci)
#:use-module (gnu services) #:use-module (gnu services)
@ -39,7 +42,22 @@
laminar-configuration-archive-url laminar-configuration-archive-url
laminar-configuration-base-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: ;;;; Commentary:
;;; ;;;
@ -146,3 +164,178 @@
(default-value (laminar-configuration)) (default-value (laminar-configuration))
(description (description
"Run the Laminar continuous integration service."))) "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.")))