summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2025-07-01 17:36:01 +0200
committerLudovic Courtès <ludo@gnu.org>2025-07-04 18:41:43 +0200
commit6b42df3ad65e4aadb4e848eed8f36eea1974391a (patch)
tree7ff0fe65f2120916f40933e529bc632ed1371cb3
parent821e517ea4cfb1a5b0e7ef16e756cfce3e6fa92d (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.texi107
-rw-r--r--gnu/services/ci.scm195
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.")))