summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/guix.texi319
-rw-r--r--gnu/local.mk2
-rw-r--r--gnu/packages/forgejo.scm6
-rw-r--r--gnu/services/forgejo.scm913
-rw-r--r--gnu/tests/forgejo.scm416
5 files changed, 2 insertions, 1654 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 4d28f7c4aab..f5efd34024e 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -429,7 +429,6 @@ Services
429* Desktop Services:: D-Bus and desktop services. 429* Desktop Services:: D-Bus and desktop services.
430* Sound Services:: ALSA and Pulseaudio services. 430* Sound Services:: ALSA and Pulseaudio services.
431* File Search Services:: Tools to search for files. 431* File Search Services:: Tools to search for files.
432* Forge Services:: Hosting your own software forge.
433* Database Services:: SQL databases, key-value stores, etc. 432* Database Services:: SQL databases, key-value stores, etc.
434* Mail Services:: IMAP, POP3, SMTP, and all that. 433* Mail Services:: IMAP, POP3, SMTP, and all that.
435* Messaging Services:: Messaging services. 434* Messaging Services:: Messaging services.
@@ -20359,7 +20358,6 @@ declaration.
20359* Desktop Services:: D-Bus and desktop services. 20358* Desktop Services:: D-Bus and desktop services.
20360* Sound Services:: ALSA and Pulseaudio services. 20359* Sound Services:: ALSA and Pulseaudio services.
20361* File Search Services:: Tools to search for files. 20360* File Search Services:: Tools to search for files.
20362* Forge Services:: Hosting your own software forge.
20363* Database Services:: SQL databases, key-value stores, etc. 20361* Database Services:: SQL databases, key-value stores, etc.
20364* Mail Services:: IMAP, POP3, SMTP, and all that. 20362* Mail Services:: IMAP, POP3, SMTP, and all that.
20365* Messaging Services:: Messaging services. 20363* Messaging Services:: Messaging services.
@@ -28760,323 +28758,6 @@ G-exp denoting the channels to use when updating the database
28760@end table 28758@end table
28761@end deftp 28759@end deftp
28762 28760
28763@node Forge Services
28764@subsection Forge Services
28765
28766Forges are applications designed to ease collaboration on software
28767development. Their usual features include an issue tracker, code
28768versioning and a system to request and conduct change reviews.
28769
28770@cindex software forge, self-hosting
28771@cindex forgejo, software forge
28772@subsubheading Forgejo
28773
28774This is the service for running Forgejo (@url{https://forgejo.org/}).
28775It is provided via the @code{(gnu services forgejo)} module.
28776
28777@defvar forgejo-service-type
28778The service type for the Forgejo source forge. It takes a
28779@code{forgejo-configuration} object as a value, documented below. In
28780its simplest form, a Forgejo service can be defined as just:
28781
28782@lisp
28783(service forgejo-service-type)
28784@end lisp
28785
28786Here's a slightly more involved service configuration, intended as a
28787secure, private software forge that is able to send notification emails.
28788
28789@lisp
28790(service forgejo-service-type
28791 (forgejo-configuration
28792 (offline-mode? #t)
28793 (require-signin-to-view? #t)
28794 (http-address "10.1.1.1") ;exposed via wireguard
28795 (ssh-address "10.1.1.1")
28796 (mailer? #t)
28797 (mail-from "some-email@@example.com")
28798 (smtp-address "smtp.example.com")
28799 (smtp-user "some-email@@example.com")
28800 (smtp-password-file "/etc/forgejo/smtp_secret")
28801 (mail-notification? #t)))
28802@end lisp
28803@end defvar
28804
28805Below is the documentation for the @code{forgejo-configuration} record:
28806
28807@c %start of fragment
28808
28809@deftp {Data Type} forgejo-configuration
28810Available @code{forgejo-configuration} fields are:
28811
28812@table @asis
28813@item @code{forgejo} (default: @code{forgejo}) (type: file-like)
28814The forgejo package.
28815
28816@item @code{work-directory} (default: @code{"/var/lib/forgejo"}) (type: string)
28817The directory where Forgejo keeps its state, including its database when
28818using @code{"sqlite3"} as the @code{database-type}.
28819
28820@item @code{shepherd-provision} (default: @code{(forgejo)}) (type: list-of-symbols)
28821The name(s) of the service.
28822
28823@item @code{configuration-wizard?} (default: @code{#f}) (type: boolean)
28824Whether to use the initial configuration page (wizard) to setup Forgejo.
28825Setting this to @code{#t} means that none of the declarative
28826configuration values will be honored. The Forgejo-generated
28827configuration file will be kept under @file{custom/conf/app.ini},
28828relative to the value of the @code{work-directory}.
28829
28830@item @code{user} (default: @code{"forgejo"}) (type: string)
28831The name of the user under which Forgejo will be executed.
28832
28833@item @code{group} (default: @code{"forgejo"}) (type: string)
28834The name of the group under which Forgejo will be executed.
28835
28836@item @code{custom-configuration-file} (type: maybe-file-like)
28837File-like object to provide a complete Forgejo configuration file as an
28838escape hatch, overriding every other fields of this configuration.
28839
28840@item @code{application-name} (default: @code{"Forgejo"}) (type: string)
28841The application name that shows in every page title.
28842
28843@item @code{application-slogan} (default: @code{""}) (type: string)
28844Slogan to show near the application name in every page title.
28845
28846@item @code{run-mode} (default: @code{"prod"}) (type: run-mode)
28847The run mode to use; either @code{"dev"} or @code{"prod"}.
28848
28849@item @code{domain} (default: @code{"localhost"}) (type: string)
28850The domain name or host address of the server.
28851
28852@item @code{offline-mode?} (default: @code{#f}) (type: boolean)
28853Set to @code{#t} to run Forgejo in offline mode, meaning it won't reach
28854out to any external services such as Gravatar, CDNs, etc. and serve
28855everything locally instead.
28856
28857@item @code{port} (default: @code{3000}) (type: unprivileged-port)
28858The port Forgejo will listen on. The port should be an unprivileged
28859port; to serve Forgejo on a privileged port like @code{80}, configure a
28860reverse proxy. It is ignored when using a Unix-domain socket.
28861
28862@item @code{protocol} (default: @code{"http"}) (type: protocol)
28863Listen protocol. Either @code{http}, @code{https}, @code{fcgi},
28864@code{"http+unix"} or @code{"fcgi+unix"}. @samp{+unix} means @emph{via}
28865a Unix-domain socket, in which case @code{http-address} must be the file
28866name of the socket to use.
28867
28868@item @code{root-url} (type: maybe-string)
28869Overwrite the automatically generated public URL, which implicitly
28870defaults to @samp{@var{protocol}://@var{domain}:@var{port}}, which is
28871necessary using a reverse proxy.
28872
28873@item @code{http-address} (default: @code{"0.0.0.0"}) (type: string)
28874The address to listen to. It can be an absolute file name when using a
28875Unix-domain socket, for example @file{/run/forgejo.sock}, or a file name
28876relative to the @code{work-directory}, for example @file{forgejo.sock}.
28877
28878@item @code{ssh-address} (default: @code{"0.0.0.0"}) (type: string)
28879The IP address to listen on for the SSH server.
28880
28881@item @code{lfs?} (default: @code{#t}) (type: boolean)
28882Whether to enable Git LFS support.
28883
28884@item @code{lfs-secret-file} (default: @code{"/etc/forgejo/lfs_jwt_secret"}) (type: string)
28885The file name of the LFS JWT (JSON Web Token) secret, necessary for
28886proper Git LFS operation, which can be generated with @samp{forgejo
28887generate secret JWT_SECRET}. Automatically generated the first time
28888Forgejo runs if it does not yet exist.
28889
28890@item @code{mail-notification?} (default: @code{#f}) (type: boolean)
28891Whether to enable email notifications.
28892
28893@item @code{acme?} (default: @code{#f}) (type: boolean)
28894Whether to enable @acronym{ACME,Automatic Certificate Management
28895Environment}, to automatically provision TLS certificates, which can be
28896useful if you use the @code{https} or @code{https+unix} protocols.
28897
28898@item @code{tls-cert-file} (default: @code{"https/cert.pem"}) (type: string)
28899File name of a TLS certificate file. Ignored if @code{acme?} is
28900enabled. A relative file name is located under
28901@file{@var{work-directory}/custom}.
28902
28903@item @code{tls-key-file} (default: @code{"https/key.pem"}) (type: string)
28904File name of a TLS key. Ignored if @code{acme?} is enabled. A relative
28905file name is located under @file{@var{work-directory}/custom}.
28906
28907@item @code{ssh-port} (default: @code{22}) (type: port-or-false)
28908The SSH port Forgejo will use; if you already run an OpenSSH as the SSH
28909server, specify the port used by the SSH daemon. Otherwise, you need to
28910use an unprivileged port (greater or equal to 1024) which Forgejo will
28911use with its built-in SSH server. Set to @code{#f} to disable SSH
28912support.
28913
28914@item @code{actions?} (default: @code{#t}) (type: boolean)
28915Whether to enable actions capabilities.
28916
28917@item @code{default-actions-url} (default: @code{"https://code.forgejo.org"}) (type: string)
28918The default address to fetch action plugins from.
28919
28920@item @code{internal-token-file} (default: @code{"/etc/forgejo/internal_token"}) (type: string)
28921The file name of the internal token used to validate communication
28922within Forgejo, which can be generated with @samp{forgejo generate
28923secret INTERNAL_TOKEN}
28924
28925@item @code{secret-key-file} (default: @code{"/etc/forgejo/secret_key"}) (type: string)
28926The file name containing the global secret key used for encrypting data
28927like 2FA secrets; it is thus very important to back it up somewhere safe
28928to avoid losing access to encrypted data.
28929
28930@item @code{oauth2-jwt-secret-file} (default: @code{"/etc/forgejo/oauth2_jwt_secret"}) (type: string)
28931The file name of the OAuth2 JWT secret, which Forgejo expects to exist.
28932
28933@item @code{mail-notification-on-new-user-signin?} (default: @code{#f}) (type: boolean)
28934Whether to notify administrators by email when a new user signs in for
28935the first time.
28936
28937@item @code{mailer?} (default: @code{#f}) (type: boolean)
28938Whether to enable the mail server integration, used for sending email
28939notifications for example.
28940
28941@item @code{mail-from} (default: @code{""}) (type: string)
28942The FROM email address used for sent emails, per the RFC 5322
28943specification. This can be for example just an email address, or
28944something like @samp{"Name" <email@@example.com>}. This field
28945@emph{must} be specified if @code{mailer?} is set to @code{#t}.
28946
28947@item @code{mail-subject-prefix} (default: @code{""}) (type: string)
28948The prefix displayed before the subject in emails.
28949
28950@item @code{mail-protocol} (default: @code{""}) (type: mail-protocol)
28951The mail server protocol. One of @code{"smtp"}, @code{"smtps"},
28952@code{"smtp+starttls"}, @code{"smtp+unix"}, @code{"sendmail"},
28953@code{"dummy"} or @code{""}. The empty string defaults means the
28954protocol is inferred from the value of the @code{smtp-port} value,
28955meaning its effective implicit value is @code{"smtps"}. @code{"dummy"}
28956causes emails to be sent to the log, useful for testing. When
28957@code{"sendmail"} is used, it is expected to be fully and correctly
28958externally configured, with the other @samp{smtp-} prefixed options of
28959this configuration ignored.
28960
28961@item @code{smtp-address} (default: @code{""}) (type: string)
28962The mail server address, for example @code{"smtp.example.com"} or an
28963absolute file name, if using @code{"smtp+unix"} for the
28964@code{mail-protocol}.
28965
28966@item @code{smtp-port} (default: @code{465}) (type: port)
28967The mail server port. This field affects the implicit value of the
28968@code{mail-protocol}.
28969
28970@item @code{smtp-user} (default: @code{""}) (type: string)
28971The SMTP server user name, if required
28972
28973@item @code{smtp-password-file} (default: @code{""}) (type: string)
28974An absolute file name containing the password of the SMTP server.
28975
28976@item @code{mail-plain-text?} (default: @code{#f}) (type: boolean)
28977Send emails only in plain text, without HTML alternative.
28978
28979@item @code{sendmail-command} (default: @code{"sendmail"}) (type: string)
28980The file name of the @command{sendmail} command to use, which can be
28981either an absolute file name or a command name to be looked from
28982@env{"PATH"}.
28983
28984@item @code{sendmail-options} (default: @code{()}) (type: list-of-strings)
28985Optional options to pass to the @command{sendmail} command. If your
28986@code{sendmail} program, like that of Postfix, interprets options, you
28987should provide @code{"--"} as the first option.
28988
28989@item @code{sendmail-timeout} (default: @code{"5m"}) (type: string)
28990The timeout for Sendmail, provided as a
28991@uref{https://pkg.go.dev/time#ParseDuration,Go @code{time.Duration}
28992string}, for example @code{"300ms"}, @code{"1.5h"} or @code{"2h45m"}.
28993
28994@item @code{sendmail-convert-crlf?} (default: @code{#t}) (type: boolean)
28995Whether to convert @samp{\r\n} to @samp{\n} for Sendmail.
28996
28997@item @code{log-level} (default: @code{"info"}) (type: log-level)
28998The log level to use. Either @code{"trace"}, @code{"debug"},
28999@code{"info"}, @code{"warn"}, @code{"error"} or @code{"none"}.
29000
29001@item @code{openid-signin?} (default: @code{#t}) (type: boolean)
29002Whether to allow signing in using OpenID.
29003
29004@item @code{disable-registration?} (default: @code{#f}) (type: boolean)
29005Disallow registration, only allowing administrators to create accounts.
29006
29007@item @code{confirm-mail-on-registration?} (default: @code{#f}) (type: boolean)
29008Whether to require email confirmation when new users register.
29009
29010@item @code{require-signin-to-view?} (default: @code{#f}) (type: boolean)
29011Whether users must sign in before they can view the explore pages.
29012
29013@item @code{default-keep-email-private?} (default: @code{#f}) (type: boolean)
29014Whether by default a user email is displayed on their profile.
29015
29016@item @code{user-push-to-create?} (default: @code{#f}) (type: boolean)
29017Allow users to push local repositories to Forgejo and have them
29018automatically created for a user.
29019
29020@item @code{organization-push-to-create?} (default: @code{#f}) (type: boolean)
29021Allow users to push local repositories to Forgejo and have them
29022automatically created for an organization.
29023
29024@item @code{default-push-to-create-private?} (default: @code{#t}) (type: boolean)
29025Whether to make newly created repositories private by default when they
29026were created via push.
29027
29028@item @code{default-merge-style} (default: @code{"merge"}) (type: merge-style)
29029The default merge style to use. Either @code{"merge"}, @code{"rebase"},
29030@code{"rebase-merge"}, @code{"squash"} or @code{"fast-forward-only"}.
29031
29032@item @code{default-trust-model} (default: @code{"committer"}) (type: trust-model)
29033The default trust model for repositories. Either @code{"collaborator"},
29034@code{"committer"} or @code{"collaboratorcommitter"}
29035
29036@item @code{metrics?} (default: @code{#f}) (type: boolean)
29037Whether to enable the metrics endpoint.
29038
29039@item @code{metrics-token} (default: @code{""}) (type: string)
29040A secret to use, if authorization to the metrics endpoint is desired.
29041
29042@item @code{database-type} (default: @code{"sqlite3"}) (type: database-type)
29043The database type to use. Either @code{"sqlite3"}, @code{"mysql"} or
29044@code{"postgres"}. The sqlite3 type is the simplest to use, but a
29045full-fledged database like PostgreSQL may scale better for multiple
29046users. If you change this, make sure to adjust the other
29047@samp{database-*} values accordingly. Also note that currently, the
29048PostgreSQL and MySQL databases must be manually created: refer to the
29049official Forgejo documentation for
29050@uref{https://forgejo.org/docs/latest/admin/installation/database-preparation,database
29051preparation}.
29052
29053@item @code{database-host} (default: @code{"/var/run/postgresql"}) (type: string)
29054This is the @samp{@var{host}:@var{port}} tuple for remote database
29055MySQL/PostgreSQL hosts, or a local Unix-domain socket file. If using
29056MySQL, this should be adjusted to @file{/run/mysqld/mysqld.sock}
29057
29058@item @code{database-name} (default: @code{"forgejo"}) (type: string)
29059The MySQL/PostgreSQL database name to use.
29060
29061@item @code{database-user} (default: @code{"forgejo"}) (type: string)
29062The MySQL/PostgreSQL database user to use.
29063
29064@item @code{database-password-file} (type: maybe-string)
29065A file name containing the password to access the MySQL/PostgreSQL
29066database, for example @file{"/etc/forgejo/db_passwd"}.
29067
29068@item @code{database-tls?} (default: @code{#f}) (type: ssl-mode)
29069Whether the connection to a MySQL/PostgreSQL database should use TLS.
29070Set to @code{#t} to enable TLS with full verification or to
29071@code{'skip-verify} to enable TLS without verification.
29072
29073@end table
29074
29075@end deftp
29076
29077
29078@c %end of fragment
29079
29080 28761
29081@node Database Services 28762@node Database Services
29082@subsection Database Services 28763@subsection Database Services
diff --git a/gnu/local.mk b/gnu/local.mk
index 3c83861908b..e53fe8b5a0f 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -760,7 +760,6 @@ GNU_SYSTEM_MODULES = \
760 %D%/services/authentication.scm \ 760 %D%/services/authentication.scm \
761 %D%/services/file-sharing.scm \ 761 %D%/services/file-sharing.scm \
762 %D%/services/firmware.scm \ 762 %D%/services/firmware.scm \
763 %D%/services/forgejo.scm \
764 %D%/services/games.scm \ 763 %D%/services/games.scm \
765 %D%/services/ganeti.scm \ 764 %D%/services/ganeti.scm \
766 %D%/services/getmail.scm \ 765 %D%/services/getmail.scm \
@@ -875,7 +874,6 @@ GNU_SYSTEM_MODULES = \
875 %D%/tests/emacs.scm \ 874 %D%/tests/emacs.scm \
876 %D%/tests/file-sharing.scm \ 875 %D%/tests/file-sharing.scm \
877 %D%/tests/foreign.scm \ 876 %D%/tests/foreign.scm \
878 %D%/tests/forgejo.scm \
879 %D%/tests/ganeti.scm \ 877 %D%/tests/ganeti.scm \
880 %D%/tests/gdm.scm \ 878 %D%/tests/gdm.scm \
881 %D%/tests/guix.scm \ 879 %D%/tests/guix.scm \
diff --git a/gnu/packages/forgejo.scm b/gnu/packages/forgejo.scm
index 2bd1694b7ee..65127f1778d 100644
--- a/gnu/packages/forgejo.scm
+++ b/gnu/packages/forgejo.scm
@@ -31,12 +31,10 @@
31 #:use-module (gnu packages sqlite) 31 #:use-module (gnu packages sqlite)
32 #:use-module (gnu packages ssh)) 32 #:use-module (gnu packages ssh))
33 33
34;;; Do not forget running:
35;;; 'make check-system TESTS='forgejo forgejo-mysql forgejo-postgres' when updating.
36(define-public forgejo 34(define-public forgejo
37 (package 35 (package
38 (name "forgejo") 36 (name "forgejo")
39 (version "15.0.2") 37 (version "15.0.0")
40 (source 38 (source
41 (origin 39 (origin
42 (method url-fetch) 40 (method url-fetch)
@@ -50,7 +48,7 @@
50 "/forgejo-src-" version ".tar.gz")) 48 "/forgejo-src-" version ".tar.gz"))
51 (sha256 49 (sha256
52 (base32 50 (base32
53 "16d142hfcz2mf3l0b4nc7dkgn1cf4hvg6vf0gdjjcx6ya7vpsan5")))) 51 "1b25zhlr81b577swfzxlhc7hiaxnznfygxbiv125vabjmahdmffy"))))
54 (build-system gnu-build-system) 52 (build-system gnu-build-system)
55 (outputs (list "out" "data")) 53 (outputs (list "out" "data"))
56 (arguments 54 (arguments
diff --git a/gnu/services/forgejo.scm b/gnu/services/forgejo.scm
deleted file mode 100644
index 86db26775d2..00000000000
--- a/gnu/services/forgejo.scm
+++ /dev/null
@@ -1,913 +0,0 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Maxim Cournoyer <maxim@guixotic.coop>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu services forgejo)
20 #:use-module (guix gexp)
21 #:use-module (guix least-authority)
22 #:use-module (guix records)
23 #:use-module (gnu build linux-container)
24 #:use-module (gnu packages forgejo)
25 #:use-module (gnu services)
26 #:use-module (gnu services shepherd)
27 #:use-module (gnu services configuration)
28 #:use-module (gnu system file-systems)
29 #:use-module (gnu system shadow)
30 #:use-module (ice-9 match)
31 #:use-module (srfi srfi-1)
32 #:export (forgejo-service-type
33 forgejo-configuration
34 forgejo-configuration-forgejo
35 forgejo-configuration-application-name
36 forgejo-configuration-application-slogan
37 forgejo-configuration-run-mode
38 forgejo-configuration-configuration-wizard?
39 forgejo-configuration-domain
40 forgejo-configuration-port
41 forgejo-configuration-protocol
42 forgejo-configuration-root-url
43 forgejo-configuration-http-address
44 forgejo-configuration-acme?
45 forgejo-configuration-tls-cert-file
46 forgejo-configuration-tls-key-file
47 forgejo-configuration-ssh-address
48 forgejo-configuration-ssh-port
49 forgejo-configuration-actions?
50 forgejo-configuration-default-actions-url
51 forgejo-configuration-disable-registration?
52 forgejo-configuration-confirm-mail-on-registration?
53 forgejo-configuration-internal-token-file
54 forgejo-configuration-secret-key-file
55 forgejo-configuration-lfs?
56 forgejo-configuration-lfs-secret-file
57 forgejo-configuration-log-level
58 forgejo-configuration-oauth2-secret-file
59 forgejo-configuration-mail-notification?
60 forgejo-configuration-mail-notification-on-new-user-signin?
61 forgejo-configuration-mailer?
62 forgejo-configuration-mail-subject-prefix
63 forgejo-configuration-mail-protocol
64 forgejo-configuration-smtp-address
65 forgejo-configuration-smtp-port
66 forgejo-configuration-smtp-user
67 forgejo-configuration-smtp-password-file
68 forgejo-configuration-mail-plain-text?
69 forgejo-configuration-mail-from
70 forgejo-configuration-sendmail-command
71 forgejo-configuration-sendmail-options
72 forgejo-configuration-sendmail-timeout
73 forgejo-configuration-sendmail-convert-crlf?
74 forgejo-configuration-openid-signin?
75 forgejo-configuration-metrics?
76 forgejo-configuration-metrics-token
77 forgejo-configuration-database-type
78 forgejo-configuration-database-host
79 forgejo-configuration-database-name
80 forgejo-configuration-database-user
81 forgejo-configuration-database-password-file
82 forgejo-configuration-database-tls?
83 forgejo-configuration-user-push-to-create?
84 forgejo-configuration-organization-push-to-create?
85 forgejo-configuration-default-push-to-create-private?
86 forgejo-configuration-require-signin-to-view?
87 forgejo-configuration-default-merge-style
88 forgejo-configuration-default-trust-model
89 forgejo-configuration-default-keep-email-private?
90 forgejo-configuration-offline-mode?
91 forgejo-configuration-user
92 forgejo-configuration-group
93 forgejo-configuration-custom-configuration-file
94 forgejo-configuration-shepherd-provision
95 forgejo-configuration-work-directory))
96
97;;; Copied from (gnu services base)
98(define (ipv6-address? str)
99 "Return true if STR denotes an IPv6 address."
100 (false-if-exception (->bool (inet-pton AF_INET6 str))))
101
102(define (database-type? x)
103 (member x '("sqlite3" "mysql" "postgres")))
104
105(define (port? x)
106 (and (number? x)
107 (and (>= x 1) (<= x 65535))))
108
109(define (privileged-port? x)
110 (and (number? x)
111 (and (>= x 1) (<= x 1023))))
112
113(define (unprivileged-port? x)
114 (and (number? x)
115 (and (>= x 1024) (<= x 65535))))
116
117(define (port-or-false? x)
118 (or (not x)
119 (port? x)))
120
121(define (log-level? x)
122 (member x '("trace" "debug" "info" "warn" "error" "none")))
123
124(define (merge-style? x)
125 (member x '("merge" "rebase" "rebase-merge" "squash" "fast-forward-only")))
126
127(define (protocol? x)
128 (member x '("http" "https" "fcgi" "http+unix" "fcgi+unix")))
129
130(define (run-mode? x)
131 (member x '("dev" "prod")))
132
133(define (trust-model? x)
134 (member x '("collaborator" "committer" "collaboratorcommitter")))
135
136(define (ssl-mode? x)
137 (member x '(#t #f 'skip-verify)))
138
139(define (serialize-database-tls type value)
140 (match type
141 ("sqlite3" "disable") ;does not apply
142 ("mysql" (match value
143 (#t "true")
144 (#f "false")
145 ('skip-verify "skip-verify")))
146 ("postgres" (match value
147 (#t "verify-full")
148 (#f "disable")
149 ('skip-verify "require")))))
150
151(define-maybe file-like)
152
153(define-maybe/no-serialization string)
154
155(define (mail-protocol? x)
156 (member x '("" "dummy" "sendmail"
157 "smtp" "smtps" "smtp+starttls" "smtp+unix")))
158
159(define-configuration/no-serialization forgejo-configuration
160 (forgejo
161 (file-like forgejo)
162 "The forgejo package.")
163
164 (work-directory
165 (string "/var/lib/forgejo")
166 "The directory where Forgejo keeps its state, including its database when
167using @code{\"sqlite3\"} as the @code{database-type}.")
168
169 (shepherd-provision
170 (list-of-symbols '(forgejo))
171 "The name(s) of the service.")
172
173 (configuration-wizard?
174 (boolean #f)
175 "Whether to use the initial configuration page (wizard) to setup Forgejo.
176Setting this to @code{#t} means that none of the declarative configuration
177values will be honored. The Forgejo-generated configuration file will be kept
178under @file{custom/conf/app.ini}, relative to the value of the
179@code{work-directory}.")
180
181 (user
182 (string "forgejo")
183 "The name of the user under which Forgejo will be executed.")
184
185 (group
186 (string "forgejo")
187 "The name of the group under which Forgejo will be executed.")
188
189 (custom-configuration-file
190 maybe-file-like
191 "File-like object to provide a complete Forgejo configuration file as an
192escape hatch, overriding every other fields of this configuration.")
193
194 ;; Global settings (no section).
195 (application-name
196 (string "Forgejo")
197 "The application name that shows in every page title.")
198
199 (application-slogan
200 (string "")
201 "Slogan to show near the application name in every page title.")
202
203 (run-mode
204 (run-mode "prod")
205 "The run mode to use; either @code{\"dev\"} or @code{\"prod\"}.")
206
207 ;; [server] settings.
208 (domain
209 (string "localhost")
210 "The domain name or host address of the server.")
211
212 (offline-mode?
213 (boolean #f)
214 "Set to @code{#t} to run Forgejo in offline mode, meaning it won't reach
215out to any external services such as Gravatar, CDNs, etc. and serve everything
216locally instead.")
217
218 (port
219 (unprivileged-port 3000)
220 "The port Forgejo will listen on. The port should be an unprivileged port;
221to serve Forgejo on a privileged port like @code{80}, configure a reverse
222proxy. It is ignored when using a Unix-domain socket.")
223
224 (protocol
225 (protocol "http")
226 "Listen protocol. Either @code{http}, @code{https}, @code{fcgi},
227@code{\"http+unix\"} or @code{\"fcgi+unix\"}. @samp{+unix} means @emph{via} a
228Unix-domain socket, in which case @code{http-address} must be the file name of
229the socket to use.")
230
231 (root-url
232 maybe-string
233 "Overwrite the automatically generated public URL,
234which implicitly defaults to @samp{@var{protocol}://@var{domain}:@var{port}},
235which is necessary using a reverse proxy.")
236
237 (http-address
238 (string "0.0.0.0")
239 "The address to listen to. It can be an absolute file name when using a
240Unix-domain socket, for example @file{/run/forgejo.sock}, or a file name
241relative to the @code{work-directory}, for example @file{forgejo.sock}.")
242
243 (ssh-address
244 (string "0.0.0.0")
245 "The IP address to listen on for the SSH server.")
246
247 (lfs?
248 (boolean #t)
249 "Whether to enable Git LFS support.")
250
251 (lfs-secret-file
252 (string "/etc/forgejo/lfs_jwt_secret")
253 "The file name of the LFS JWT (JSON Web Token) secret, necessary for
254proper Git LFS operation, which can be generated with @samp{forgejo generate
255secret JWT_SECRET}. Automatically generated the first time Forgejo runs if it
256does not yet exist.")
257
258 (mail-notification?
259 (boolean #f)
260 "Whether to enable email notifications.")
261
262 (acme?
263 (boolean #f)
264 "Whether to enable @acronym{ACME, Automatic Certificate Management
265Environment}, to automatically provision TLS certificates, which can be useful
266if you use the @code{https} or @code{https+unix} protocols.")
267
268 (tls-cert-file
269 (string "https/cert.pem")
270 "File name of a TLS certificate file. Ignored if @code{acme?} is enabled.
271A relative file name is located under @file{@var{work-directory}/custom}.")
272
273 (tls-key-file
274 (string "https/key.pem")
275 "File name of a TLS key. Ignored if @code{acme?} is enabled. A relative
276file name is located under @file{@var{work-directory}/custom}.")
277
278 (ssh-port
279 (port-or-false 22)
280 "The SSH port Forgejo will use; if you already run an OpenSSH as the SSH
281server, specify the port used by the SSH daemon. Otherwise, you need to use
282an unprivileged port (greater or equal to 1024) which Forgejo will use with
283its built-in SSH server. Set to @code{#f} to disable SSH support.")
284
285 ;; [actions] settings.
286 (actions?
287 (boolean #t)
288 "Whether to enable actions capabilities.")
289
290 (default-actions-url
291 (string "https://code.forgejo.org")
292 "The default address to fetch action plugins from.")
293
294 ;; [security] settings.
295 (internal-token-file
296 (string "/etc/forgejo/internal_token")
297 "The file name of the internal token used to validate communication within
298Forgejo, which can be generated with @samp{forgejo generate
299secret INTERNAL_TOKEN}")
300
301 (secret-key-file
302 (string "/etc/forgejo/secret_key")
303 "The file name containing the global secret key used for encrypting data
304like 2FA secrets; it is thus very important to back it up somewhere safe to
305avoid losing access to encrypted data.")
306
307 ;; [oauth2] settings.
308 (oauth2-jwt-secret-file
309 (string "/etc/forgejo/oauth2_jwt_secret")
310 "The file name of the OAuth2 JWT secret, which Forgejo expects to exist.")
311
312 ;; [admin] settings.
313 (mail-notification-on-new-user-signin?
314 (boolean #f)
315 "Whether to notify administrators by email when a new user signs in for the
316first time.")
317
318 ;; [mailer] settings.
319 (mailer?
320 (boolean #f)
321 "Whether to enable the mail server integration, used for sending email
322notifications for example.")
323
324 (mail-from
325 (string "")
326 "The FROM email address used for sent emails, per the RFC 5322
327specification. This can be for example just an email address, or something
328like @samp{\"Name\" <email@@example.com>}. This field @emph{must} be specified
329if @code{mailer?} is set to @code{#t}.")
330
331 (mail-subject-prefix
332 (string "")
333 "The prefix displayed before the subject in emails.")
334
335 (mail-protocol
336 (mail-protocol "")
337 "The mail server protocol. One of @code{\"smtp\"}, @code{\"smtps\"},
338@code{\"smtp+starttls\"}, @code{\"smtp+unix\"}, @code{\"sendmail\"},
339@code{\"dummy\"} or @code{\"\"}. The empty string defaults means the protocol
340is inferred from the value of the @code{smtp-port} value, meaning its
341effective implicit value is @code{\"smtps\"}. @code{\"dummy\"} causes emails
342to be sent to the log, useful for testing. When @code{\"sendmail\"} is used,
343it is expected to be fully and correctly externally configured, with the other
344@samp{smtp-} prefixed options of this configuration ignored.")
345
346 (smtp-address
347 (string "")
348 "The mail server address, for example @code{\"smtp.example.com\"} or an
349absolute file name, if using @code{\"smtp+unix\"} for the
350@code{mail-protocol}.")
351
352 (smtp-port
353 (port 465) ;implies smtps protocol
354 "The mail server port. This field affects the implicit value of the
355@code{mail-protocol}.")
356
357 (smtp-user
358 (string "")
359 "The SMTP server user name, if required ")
360
361 (smtp-password-file
362 (string "")
363 "An absolute file name containing the password of the SMTP server.")
364
365 (mail-plain-text?
366 (boolean #f)
367 "Send emails only in plain text, without HTML alternative.")
368
369 (sendmail-command
370 (string "sendmail")
371 "The file name of the @command{sendmail} command to use, which can be
372either an absolute file name or a command name to be looked from
373@env{\"PATH\"}.")
374
375 (sendmail-options
376 (list-of-strings '())
377 "Optional options to pass to the @command{sendmail} command. If your
378@code{sendmail} program, like that of Postfix, interprets options, you should
379provide @code{\"--\"} as the first option.")
380
381 (sendmail-timeout
382 (string "5m")
383 "The timeout for Sendmail, provided as a
384@url{https://pkg.go.dev/time#ParseDuration, Go @code{time.Duration} string},
385for example @code{\"300ms\"}, @code{\"1.5h\"} or @code{\"2h45m\"}.")
386
387 (sendmail-convert-crlf?
388 (boolean #t)
389 "Whether to convert @samp{\\r\\n} to @samp{\\n} for Sendmail.")
390
391 ;; [log] settings.
392 (log-level
393 (log-level "info")
394 "The log level to use. Either @code{\"trace\"}, @code{\"debug\"},
395@code{\"info\"}, @code{\"warn\"}, @code{\"error\"} or @code{\"none\"}.")
396
397 ;; [openid] settings.
398 (openid-signin?
399 (boolean #t)
400 "Whether to allow signing in using OpenID.")
401
402 ;; [service] settings.
403 (disable-registration?
404 (boolean #f)
405 "Disallow registration, only allowing administrators to create accounts.")
406
407 (confirm-mail-on-registration?
408 (boolean #f)
409 "Whether to require email confirmation when new users register.")
410
411 (require-signin-to-view?
412 (boolean #f)
413 "Whether users must sign in before they can view the explore pages.")
414
415 (default-keep-email-private?
416 (boolean #f)
417 "Whether by default a user email is displayed on their profile.")
418
419 (user-push-to-create?
420 (boolean #f)
421 "Allow users to push local repositories to Forgejo and have them
422automatically created for a user.")
423
424 (organization-push-to-create?
425 (boolean #f)
426 "Allow users to push local repositories to Forgejo and have them
427automatically created for an organization.")
428
429 (default-push-to-create-private?
430 (boolean #t)
431 "Whether to make newly created repositories private by default when they
432were created via push.")
433
434 ;; [repository.pull-request] settings.
435 (default-merge-style
436 (merge-style "merge")
437 "The default merge style to use. Either @code{\"merge\"},
438@code{\"rebase\"}, @code{\"rebase-merge\"}, @code{\"squash\"} or
439@code{\"fast-forward-only\"}.")
440
441 ;; [repository.signing] settings.
442 (default-trust-model
443 (trust-model "committer")
444 "The default trust model for repositories. Either
445@code{\"collaborator\"}, @code{\"committer\"} or
446@code{\"collaboratorcommitter\"}")
447
448 ;; [metrics] settings.
449 (metrics?
450 (boolean #f)
451 "Whether to enable the metrics endpoint.")
452
453 (metrics-token
454 (string "")
455 "A secret to use, if authorization to the metrics endpoint is desired.")
456
457 ;; [database] settings.
458 (database-type
459 (database-type "sqlite3")
460 "The database type to use. Either @code{\"sqlite3\"}, @code{\"mysql\"} or
461@code{\"postgres\"}. The sqlite3 type is the simplest to use, but a
462full-fledged database like PostgreSQL may scale better for multiple users. If
463you change this, make sure to adjust the other @samp{database-*} values
464accordingly. Also note that currently, the PostgreSQL and MySQL databases
465must be manually created: refer to the official Forgejo documentation for
466@url{https://forgejo.org/docs/latest/admin/installation/database-preparation,
467database preparation}.")
468
469 (database-host
470 (string "/var/run/postgresql")
471 "This is the @samp{@var{host}:@var{port}} tuple for remote database
472MySQL/PostgreSQL hosts, or a local Unix-domain socket file. If using MySQL,
473this should be adjusted to @file{/run/mysqld/mysqld.sock}")
474
475 (database-name
476 ;; Keep the default database name in sync with the database user name, so
477 ;; that it can be easily provisioned via a the
478 ;; postgresql-role-service-type, which assumes both match.
479 (string "forgejo")
480 "The MySQL/PostgreSQL database name to use.")
481
482 (database-user
483 (string "forgejo")
484 "The MySQL/PostgreSQL database user to use.")
485
486 (database-password-file
487 maybe-string
488 "A file name containing the password to access the MySQL/PostgreSQL
489database, for example @file{\"/etc/forgejo/db_passwd\"}.")
490
491 (database-tls?
492 (ssl-mode #f)
493 "Whether the connection to a MySQL/PostgreSQL database should use TLS. Set
494to @code{#t} to enable TLS with full verification or to @code{'skip-verify} to
495enable TLS without verification."))
496
497;;; TODO: Allow enabling/configuring email integration
498;;; See custom/conf/app.example.ini for a documentation of the fields.
499(define forgejo-configuration->file
500 (match-record-lambda <forgejo-configuration>
501 ( application-name application-slogan work-directory
502 run-mode domain protocol http-address port root-url
503 acme? tls-cert-file tls-key-file
504 ssh-address ssh-port
505 default-keep-email-private?
506 default-merge-style default-trust-model
507 actions? default-actions-url
508 disable-registration? confirm-mail-on-registration?
509 require-signin-to-view? mail-notification?
510 internal-token-file secret-key-file
511 lfs? lfs-secret-file oauth2-jwt-secret-file
512 mail-notification-on-new-user-signin?
513 mailer? mail-subject-prefix mail-protocol
514 smtp-address smtp-port smtp-user smtp-password-file
515 mail-plain-text? mail-from
516 sendmail-command sendmail-options sendmail-timeout
517 sendmail-convert-crlf?
518 log-level offline-mode? openid-signin?
519 metrics? metrics-token
520 user-push-to-create? organization-push-to-create?
521 default-push-to-create-private?
522 user group
523 custom-configuration-file
524 database-type database-host database-name database-user
525 database-password-file database-tls?)
526 (if (maybe-value-set? custom-configuration-file)
527 custom-configuration-file
528 (mixed-text-file "forgejo.ini" "
529APP_NAME = " application-name "
530APP_SLOGAN = " application-slogan "
531RUN_USER = " user "
532WORK_PATH = " work-directory "
533RUN_MODE = " run-mode "
534
535[database]
536DB_TYPE = " database-type "
537HOST = " database-host "
538NAME = " database-name "
539USER = " database-user "\n"
540(if (maybe-value-set? database-password-file)
541 (string-append "PASSWD_URI = file://" database-password-file "\n")
542 "")
543"SSL_MODE = " (serialize-database-tls database-type database-tls?) "
544
545[repository]
546ROOT = " work-directory "/data/forgejo-repositories
547DEFAULT_PUSH_CREATE_PRIVATE = "
548(if default-push-to-create-private? "true" "false") "
549ENABLE_PUSH_CREATE_USER = " (if user-push-to-create? "true" "false") "
550ENABLE_PUSH_CREATE_ORG = " (if organization-push-to-create? "true" "false") "
551
552[server]
553SSH_DOMAIN = " domain "
554SSH_LISTEN_HOST = " ssh-address "
555PROTOCOL = " protocol "
556DOMAIN = " domain "
557HTTP_ADDR = " http-address "
558HTTP_PORT = " (number->string port) "\n"
559(if (maybe-value-set? root-url)
560 (string-append "ROOT_URL = " root-url "\n")
561 "")
562"ENABLE_ACME = " (if acme? "true" "false") "
563CERT_FILE = " tls-cert-file "
564KEY_FILE = " tls-key-file "
565APP_DATA_PATH = " work-directory "/data
566DISABLE_SSH = " (if (not ssh-port) "true" "false") "
567SSH_PORT = " (if (not ssh-port) "" (number->string ssh-port)) "
568LFS_START_SERVER = " (if lfs? "true" "false") "
569LFS_JWT_SECRET_URI = file://" lfs-secret-file "
570OFFLINE_MODE = " (if offline-mode? "true" "false") "
571
572[lfs]
573PATH = " work-directory "/data/lfs
574
575[service]
576REGISTER_EMAIL_CONFIRM = false
577ENABLE_NOTIFY_MAIL = " (if mail-notification? "true" "false") "
578DISABLE_REGISTRATION = " (if disable-registration? "true" "false") "
579REGISTER_EMAIL_CONFIRM = " (if confirm-mail-on-registration? "true" "false") "
580ENABLE_CAPTCHA = false
581REQUIRE_SIGNIN_VIEW = " (if require-signin-to-view? "true" "false") "
582DEFAULT_KEEP_EMAIL_PRIVATE = " (if default-keep-email-private? "true" "false") "
583
584[openid]
585ENABLE_OPENID_SIGNIN = " (if openid-signin? "true" "false") "
586
587[cron.update_checker]
588ENABLED = false
589
590[log]
591LEVEL = " log-level "
592ROOT_PATH = " work-directory "/log
593
594[repository.pull-request]
595DEFAULT_MERGE_STYLE = " default-merge-style "
596
597[repository.signing]
598DEFAULT_TRUST_MODEL = " default-trust-model "
599
600[security]
601INSTALL_LOCK = true
602SECRET_KEY = file://" secret-key-file "
603INTERNAL_TOKEN_URI = file://" internal-token-file "
604
605[oauth2]
606JWT_SECRET_URI = file://" oauth2-jwt-secret-file "
607
608[admin]
609SEND_NOTIFICATION_EMAIL_ON_NEW_USER = "
610(if mail-notification-on-new-user-signin? "true" "false") "
611
612[mailer]
613ENABLED = " (if mailer? "true" "false") "
614SUBJECT_PREFIX = " mail-subject-prefix "
615PROTOCOL = " mail-protocol "
616SMTP_ADDR = " smtp-address "
617SMTP_PORT = " (number->string smtp-port) "
618USER = " smtp-user "
619PASSWD_URI = " (if (string-null? smtp-password-file)
620 ""
621 (string-append "file://" smtp-password-file)) "
622SEND_AS_PLAIN_TEXT = " (if mail-plain-text? "true" "false") "
623FROM = " mail-from "
624SENDMAIL_PATH = " sendmail-command "
625SENDMAIL_ARGS = " (string-join sendmail-options " ") "
626SENDMAIL_TIMEOUT = " sendmail-timeout "
627SENDMAIL_CONVERT_CRLF = " (if sendmail-convert-crlf? "true" "false") "
628
629[metrics]
630ENABLED = " (if metrics? "true" "false") "
631TOKEN = " metrics-token "
632
633[actions]
634ENABLED = " (if actions? "true" "false") "
635DEFAULT_ACTIONS_URL = " default-actions-url "
636"))))
637
638(define (forgejo-shepherd-service config)
639 (match-record config <forgejo-configuration>
640 ( forgejo work-directory user group port
641 http-address protocol ssh-port
642 database-type database-host
643 configuration-wizard?
644 internal-token-file lfs-secret-file
645 smtp-password-file
646 tls-cert-file tls-key-file
647 oauth2-jwt-secret-file secret-key-file
648 shepherd-provision)
649 (let* ((mappings (append
650 %network-file-mappings
651 (list %store-mapping ;XXX: coarse-grained
652 (file-system-mapping
653 (source "/etc/ssl/certs")
654 (target source))
655 (file-system-mapping
656 (source work-directory)
657 (target source)
658 (writable? #t))
659 (file-system-mapping
660 (source internal-token-file)
661 (target source))
662 (file-system-mapping
663 (source lfs-secret-file)
664 (target source))
665 (file-system-mapping
666 (source oauth2-jwt-secret-file)
667 (target source))
668 (file-system-mapping
669 (source secret-key-file)
670 (target source)))
671 (if (absolute-file-name? smtp-password-file)
672 (list (file-system-mapping
673 (source smtp-password-file)
674 (target source)))
675 '())
676 (if (absolute-file-name? tls-cert-file)
677 (list (file-system-mapping
678 (source tls-cert-file)
679 (target source)))
680 '())
681 (if (absolute-file-name? tls-key-file)
682 (list (file-system-mapping
683 (source tls-key-file)
684 (target source)))
685 '())
686 (if (and (not (string=? "sqlite3" database-type))
687 (absolute-file-name? database-host)) ;socket
688 (list (file-system-mapping
689 (source database-host)
690 (target source)
691 (writable? #t)))
692 '())))
693 (forgejo (file-append forgejo "/bin/forgejo"))
694 (forgejo-wrapper (least-authority-wrapper
695 forgejo
696 #:name "forgejo-pola-wrapper"
697 #:user user #:group group
698 #:namespaces
699 ;; The wrapper needs access to the forgejo user
700 ;; to be able to mount
701 ;; /etc/forgejo/internal_token for example.
702 (fold delq %namespaces '(net user))
703 #:mappings mappings
704 #:preserved-environment-variables
705 (cons* "FORGEJO_WORK_DIR"
706 "SSL_CERT_DIR"
707 %default-preserved-environment-variables)))
708 (config (forgejo-configuration->file config))
709 (init-provision (list (symbol-append (first shepherd-provision)
710 '-init))))
711 (list (shepherd-service
712 (documentation "Run Forgejo pre-start actions.")
713 (requirement '(networking user-processes))
714 (provision init-provision)
715 (one-shot? #t)
716 (modules (cons '(srfi srfi-19) %default-modules))
717 (start
718 #~(lambda _
719 (chdir #$work-directory)
720
721 (define app.ini "custom/conf/app.ini")
722
723 (define (symlink? x)
724 (eq? (stat:type (stat x)) 'symlink))
725
726 (define (delete-file/backup x)
727 (let* ((timestamp (date->string
728 (current-date) "~4"))
729 (bak (string-append
730 x "-" timestamp ".bak")))
731 (copy-file x bak)
732 (delete-file x)))
733
734 (define (fork+exec-forgejo . options)
735 ;; We can't change the environment in a one-shot
736 ;; service/Shepherd (is this a bug?), so use this wrapper
737 ;; of fork+exec-command that passes the needed
738 ;; environment variables and raises on unclean exit.
739 (let ((pid
740 (apply fork+exec-command
741 (cons #$forgejo-wrapper options)
742 (list #:environment-variables
743 (list #$(string-append
744 "FORGEJO_WORK_DIR="
745 work-directory)
746 #$(string-append
747 "HOME=" work-directory))))))
748 (unless (zero? (cdr (waitpid pid)))
749 (error "failed to run forgejo command"
750 #$forgejo-wrapper options))))
751
752 (unless #$configuration-wizard?
753 ;; When users transition from configuration-wizard? #t to
754 ;; #f, the stale wizard configuration is backed up.
755 (when (file-exists? app.ini)
756 (if (symlink? app.ini)
757 (delete-file app.ini)
758 (delete-file/backup app.ini)))
759 (symlink #$config app.ini))
760
761 (when (file-exists? app.ini)
762 ;; Initialize Forgejo's database.
763 (fork+exec-forgejo "migrate")
764 ;; Refresh binary file names captured in git hooks.
765 (fork+exec-forgejo "admin" "regenerate" "hooks")
766 ;; Update command option in 'authorized_keys' file.
767 (fork+exec-forgejo "admin" "regenerate" "keys")
768 ;; Sanity check configuration/environment.
769 (fork+exec-forgejo "doctor" "check" "--all")))))
770 (shepherd-service
771 (documentation "Run the Forgejo Git forge")
772 (requirement (append init-provision
773 '(networking
774 user-processes)
775 (if (privileged-port? ssh-port)
776 '(ssh-daemon)
777 '())
778 (if (string=? "postgres" database-type)
779 '(postgres)
780 '())
781 (if (string=? "mysql" database-type)
782 '(mysql)
783 '())))
784 (provision shepherd-provision)
785 (actions
786 (list (shepherd-configuration-action
787 (if configuration-wizard?
788 (string-append work-directory "/custom/conf/app.ini")
789 config))))
790 (start
791 #~(make-systemd-constructor
792 (list #$forgejo-wrapper "web"
793 #$@(if configuration-wizard?
794 '()
795 #~("--config" #$config)))
796 (cond
797 (#$(string-suffix? "+unix" protocol)
798 (list (endpoint (make-socket-address AF_UNIX
799 #$http-address)
800 #:socket-owner #$user
801 #:socket-group #$group)))
802 (#$(string=? "0.0.0.0" http-address)
803 (list (endpoint (make-socket-address AF_INET
804 INADDR_ANY #$port))
805 (endpoint (make-socket-address AF_INET6
806 INADDR_ANY #$port))))
807 (#$(ipv6-address? http-address)
808 (list (endpoint (make-socket-address
809 AF_INET6
810 (inet-pton AF_INET6 #$http-address)
811 #$port))))
812 (else ;assumed ipv4
813 (list (endpoint (make-socket-address
814 AF_INET
815 (inet-pton AF_INET #$http-address)
816 #$port)))))
817 #:log-file "/var/log/forgejo.log"
818 #:environment-variables
819 (list (string-append "HOME=" #$work-directory)
820 "SSL_CERT_DIR=/etc/ssl/certs")))
821 (stop #~(make-systemd-destructor)))))))
822
823(define %forgejo-accounts
824 (match-record-lambda <forgejo-configuration>
825 (user group work-directory)
826 (list (user-group (name user)
827 (system? #t))
828 (user-account
829 (name user)
830 (group group)
831 (system? #t)
832 (comment "Forgejo User")
833 (home-directory work-directory)))))
834
835(define %forgejo-activation
836 (match-record-lambda <forgejo-configuration>
837 ( forgejo user configuration-wizard? work-directory
838 protocol http-address
839 secret-key-file internal-token-file lfs-secret-file
840 oauth2-jwt-secret-file)
841 #~(begin
842 (use-modules (gnu build activation)
843 (guix build utils)
844 (ice-9 match))
845
846 (define secret-files `(("SECRET_KEY" . ,#$secret-key-file)
847 ("INTERNAL_TOKEN" . ,#$internal-token-file)
848 ("LFS_JWT_SECRET" . ,#$lfs-secret-file)
849 ("JWT_SECRET" . ,#$oauth2-jwt-secret-file)))
850
851 (define forgejo #$(file-append forgejo "/bin/forgejo"))
852
853 (define (generate-secret type file)
854 (with-exception-handler
855 (lambda (e)
856 (false-if-exception (delete-file file))
857 (raise-exception e))
858 (lambda ()
859 (call-with-output-file file
860 (lambda (port)
861 (let ((pid (spawn forgejo
862 (list forgejo "generate" "secret" type)
863 #:output port)))
864 (match (waitpid pid)
865 ((_ . status)
866 (unless (zero? status)
867 (error "failed to generate forgejo secret" type))))))))))
868
869 (let* ((owner (getpw #$user))
870 (uid (passwd:uid owner))
871 (gid (passwd:gid owner)))
872 ;; Top-level directories of secrets; needed even when not using the
873 ;; wizard.
874 (for-each (match-lambda
875 ((_ . file)
876 (mkdir-p/perms (dirname file) owner #o750)))
877 secret-files)
878 (when (and (string-suffix? "-unix" #$protocol)
879 (absolute-file-name? http-address))
880 (mkdir-p/perms (dirname http-address) owner #o750))
881 ;; To please 'forgejo doctor check --all' out of the box.
882 (mkdir-p/perms #$work-directory owner #o750)
883 (with-directory-excursion #$work-directory
884 (mkdir-p/perms ".ssh" owner #o700)
885 (mkdir-p/perms "custom" owner #o750)
886 (mkdir-p/perms "custom/conf" owner #o750)
887 (mkdir-p/perms "data" owner #o750)
888 (mkdir-p/perms "data/forgejo-repositories" owner #o750)
889 (mkdir-p/perms "log" owner #o750))
890 (unless #$configuration-wizard?
891 ;; Bootstrap secret files.
892 (for-each (match-lambda
893 ((type . file)
894 (unless (file-exists? file)
895 (generate-secret type file))
896 (chown file uid gid)
897 (chmod file #o400)))
898 secret-files))))))
899
900(define forgejo-service-type
901 (service-type
902 (name 'forgejo)
903 (extensions
904 (list (service-extension shepherd-root-service-type
905 forgejo-shepherd-service)
906 (service-extension account-service-type
907 %forgejo-accounts)
908 (service-extension activation-service-type
909 %forgejo-activation)
910 (service-extension profile-service-type
911 (compose list forgejo-configuration-forgejo))))
912 (default-value (forgejo-configuration))
913 (description "Run Forgejo.")))
diff --git a/gnu/tests/forgejo.scm b/gnu/tests/forgejo.scm
deleted file mode 100644
index d13114228f7..00000000000
--- a/gnu/tests/forgejo.scm
+++ /dev/null
@@ -1,416 +0,0 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Maxim Cournoyer <maxim@guixotic.coop>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu tests forgejo)
20 #:use-module (gnu packages databases)
21 #:use-module (gnu packages guile)
22 #:use-module (gnu packages linux)
23 #:use-module (gnu packages sqlite)
24 #:use-module (gnu packages version-control)
25 #:use-module (gnu tests)
26 #:use-module (gnu system)
27 #:use-module (gnu system vm)
28 #:use-module (gnu services)
29 #:use-module (gnu services base)
30 #:use-module (gnu services databases)
31 #:use-module (gnu services forgejo)
32 #:use-module (gnu services networking)
33 #:use-module (gnu services ssh)
34 #:use-module (guix gexp)
35 #:use-module (ice-9 match)
36 #:use-module (srfi srfi-1)
37 #:export (%test-forgejo
38 %test-forgejo-mysql
39 %test-forgejo-postgres))
40
41;;; Sample SSH key generated with 'ssh-keygen -t ed25519'.
42(define dummy-ssh-private-key "\
43-----BEGIN OPENSSH PRIVATE KEY-----
44b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
45QyNTUxOQAAACAsDitIHHy6wlmXz7cJO0UQQbrszdpLvBSiTZk1l08z0AAAAJBl4y2eZeMt
46ngAAAAtzc2gtZWQyNTUxOQAAACAsDitIHHy6wlmXz7cJO0UQQbrszdpLvBSiTZk1l08z0A
47AAAEDxsrZIsEgI6cehDgFNsP/FQ3aefvb7fUtp/RF6sZf3GiwOK0gcfLrCWZfPtwk7RRBB
48uuzN2ku8FKJNmTWXTzPQAAAAC21heGltQHRlcnJhAQI=
49-----END OPENSSH PRIVATE KEY-----
50")
51
52(define dummy-ssh-public-key "\
53ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICwOK0gcfLrCWZfPtwk7RRBBuuzN2ku8FK\
54JNmTWXTzPQ dummy")
55
56;;; For a manual test/inspection:
57#;(
58
59$(./pre-inst-env guix system vm \
60-e '(@@ (gnu tests forgejo) %forgejo-os)' --no-graphic) \
61-nic user,model=virtio-net-pci,hostfwd=tcp::3000-:3000 -m 1024
62
63)
64
65(define %forgejo-os
66 (let ((base %simple-os))
67 (operating-system
68 (inherit %simple-os)
69 (packages (cons* git-minimal git-lfs
70 ;;sqlite strace ;for debug
71 %base-packages))
72 (services
73 (cons* (service dhcpcd-service-type)
74 (service openssh-service-type)
75 (service forgejo-service-type
76 (forgejo-configuration
77 (application-slogan "In Code We Trust")
78 (run-mode "dev")
79 (log-level "debug")
80 (user-push-to-create? #t)
81 (default-push-to-create-private? #f)
82 (mailer? #t)
83 (mail-from "Forgejo Test <test@example.com>")
84 (mail-protocol "dummy")
85 (mail-notification? #t)
86 (mail-notification-on-new-user-signin? #t)))
87 %base-services)))))
88
89(define %forgejo-os/postgres
90 (operating-system
91 (inherit %forgejo-os)
92 (services
93 (cons*
94 (service postgresql-service-type
95 (postgresql-configuration
96 (postgresql postgresql)))
97 (service postgresql-role-service-type
98 (postgresql-role-configuration
99 (roles (list (postgresql-role
100 (name "forgejo")
101 (create-database? #t))))))
102 (modify-services (operating-system-user-services %forgejo-os)
103 (forgejo-service-type
104 config =>
105 (forgejo-configuration
106 (inherit config)
107 (database-type "postgres"))))))))
108
109(define %forgejo-os/mysql
110 (operating-system
111 (inherit %forgejo-os)
112 (services
113 (cons
114 (service mysql-service-type)
115 (modify-services (operating-system-user-services %forgejo-os)
116 (forgejo-service-type
117 config =>
118 (forgejo-configuration
119 (inherit config)
120 (database-type "mysql")
121 (database-host "/run/mysqld/mysqld.sock"))))))))
122
123(define (os->forgejo-configuration os)
124 (let ((forgejo-service
125 (find (lambda (x)
126 (eq? forgejo-service-type (service-kind x)))
127 (operating-system-user-services os))))
128 (service-value forgejo-service)))
129
130(define* (make-forgejo-test name #:key (os %forgejo-os))
131 "Return a test of an OS running the Forgejo service."
132
133 (define forgejo-config
134 (os->forgejo-configuration os))
135
136 (define database-type
137 (forgejo-configuration-database-type forgejo-config))
138
139 (define database-name
140 (forgejo-configuration-database-name forgejo-config))
141
142 (define database-user
143 (forgejo-configuration-database-user forgejo-config))
144
145 (define vm
146 (virtual-machine
147 (operating-system (marionette-operating-system
148 os
149 #:imported-modules '((gnu services herd))
150 #:requirements (match database-type
151 ("mysql" '(mysql))
152 ("postgres" '(postgres))
153 ("sqlite3" '()))))
154 (memory-size 1024)
155 (port-forwardings '((3333 . 3000))))) ;3333 on host
156
157 (define test
158 (with-extensions (list guile-json-4)
159 (with-imported-modules '((gnu build dbus-service)
160 (gnu build marionette)
161 (guix base64))
162 #~(begin
163 (use-modules (gnu build dbus-service)
164 (gnu build marionette)
165 (guix base64)
166 (json)
167 (rnrs base)
168 (rnrs bytevectors)
169 (srfi srfi-64)
170 (srfi srfi-71)
171 (web client)
172 (web response)
173 (web uri))
174
175 (define marionette
176 (make-marionette (list #$vm)))
177
178 (test-runner-current (system-test-runner #$output))
179 (test-begin "forgejo")
180
181 (unless (string=? "mysql" #$database-type)
182 (test-skip 2))
183 (test-assert "wait for mysql socket"
184 ;; This can be removed after Shepherd gains systemd notify
185 ;; support (mysql uses it to notify when it's ready).
186 (wait-for-unix-socket
187 #$(forgejo-configuration-database-host forgejo-config)
188 marionette))
189 (test-assert "mysql setup"
190 (marionette-eval
191 '(begin
192 (use-modules (guix build utils))
193 (invoke "mysql" "-e" (string-append "\
194CREATE USER '" #$database-user "'@'localhost';
195CREATE DATABASE " #$database-name ";
196GRANT ALL PRIVILEGES ON " #$database-name ".*"
197"TO '" #$database-user "'@'localhost';
198FLUSH PRIVILEGES;")))
199 marionette))
200
201 (test-assert "forgejo can be restarted"
202 (marionette-eval
203 '(begin
204 (use-modules (gnu services herd))
205 (restart-service 'forgejo))
206 marionette))
207
208 (test-assert "forgejo runs"
209 (marionette-eval
210 '(begin
211 (use-modules (gnu services herd))
212 (start-service 'forgejo))
213 marionette))
214
215 (test-assert "shepherd listens on tcp port 3000"
216 (wait-for-tcp-port 3000 marionette))
217
218 (test-assert "slogan can be retrieved from main page"
219 ;; This socket-activates Forgejo, so we must wait a bit while it
220 ;; becomes ready.
221 (with-retries 20 1
222 (let (((values response body)
223 (http-get "http://localhost:3333")))
224 (assert (= 200 (response-code response)))
225 (string-contains body "In Code We Trust"))))
226
227 (test-assert "create test forgejo user"
228 (marionette-eval
229 '(begin
230 (use-modules (guix build utils))
231 (invoke "su" "-l" "forgejo" "-c"
232 "FORGEJO_WORK_DIR=/var/lib/forgejo forgejo \
233admin user create --username dummy --password dummy \
234--email dummy@localhost --must-change-password=false"))
235 marionette))
236
237 (test-assert "upload ssh key for dummy forgejo user"
238 ;; We use the Forgejo API to get a token, then use it to set the
239 ;; SSH public key of the user, which is needed for
240 ;; non-interactively pushing to a repository.
241 (let (((values response body)
242 (http-post "http://localhost:3333/api/v1/user/keys"
243 #:headers
244 `((Accept . "application/json")
245 (Authorization
246 . ,(string-append
247 "Basic " (base64-encode
248 (string->utf8 "dummy:dummy"))))
249 (Content-Type . "application/json"))
250 #:body (string->utf8
251 (scm->json-string
252 '(("key" . #$dummy-ssh-public-key)
253 ("read_only" . #t)
254 ("title" . "Dummy's SSH key")))))))
255 (= 201 (response-code response))))
256
257 (test-assert "configure git client for dummy user"
258 (marionette-eval
259 '(begin
260 (setenv "HOME" "/root")
261 (chdir "/root")
262
263 ;; Populate private SSH key.
264 (mkdir ".ssh")
265 (call-with-output-file ".ssh/id_ed25519"
266 (lambda (port)
267 (display #$dummy-ssh-private-key port)))
268 (chmod ".ssh/id_ed25519" #o700)
269
270 ;; To avoid interactive prompts.
271 (call-with-output-file ".ssh/config"
272 (lambda (port)
273 (display "StrictHostKeyChecking no\n" port)))
274
275 ;; Init git.
276 (invoke "git" "config" "--global" "user.name" "dummy")
277 (invoke "git" "config" "--global" "user.email"
278 "dummy@localhost"))
279 marionette))
280
281 (test-assert "git create dummy local repo"
282 (marionette-eval
283 '(begin
284 (mkdir "dummy-repo")
285 (chdir "dummy-repo")
286 (invoke "git" "init")
287 (invoke "git" "remote" "add" "origin"
288 "forgejo@localhost:dummy/hello") ;SSH remote
289 (call-with-output-file "hello.txt"
290 (lambda (port)
291 (display "is anybody here?\n" port)))
292 (invoke "git" "add" "hello.txt")
293 (invoke "git" "commit" "-m" "init repo"))
294 marionette))
295
296 (test-assert "git push repo to forgejo via ssh"
297 (marionette-eval
298 '(begin
299 (invoke "git" "push" "origin" "master"))
300 marionette))
301
302 (test-assert "git clone repo from forgejo via http"
303 (marionette-eval
304 '(begin
305 (use-modules (ice-9 textual-ports))
306 ;; FIXME: Switch back 127.0.0.1 to localhost after
307 ;; <https://codeberg.org/shepherd/shepherd/issues/122> is
308 ;; resolved.
309 (invoke "git" "clone" "http://127.0.0.1:3000/dummy/hello"
310 "/tmp/hello-copy")
311 (call-with-input-file "/tmp/hello-copy/hello.txt"
312 (lambda (port)
313 (string-contains (get-string-all port)
314 "is anybody here?"))))
315 marionette))
316
317 (test-assert "setup git lfs"
318 (marionette-eval
319 '(begin
320 (invoke "git" "lfs" "install")
321 (invoke "git" "lfs" "track" "*.blob")
322 (invoke "git" "add" ".gitattributes"))
323 marionette))
324
325 (test-assert "commit blob file with git lfs"
326 (marionette-eval
327 '(begin
328 (use-modules (ice-9 binary-ports)
329 (rnrs base)
330 ((scheme base) #:select (read-bytevector
331 write-bytevector)))
332
333 (call-with-output-file "one-mebibyte.blob"
334 (lambda (out)
335 (write-bytevector
336 (call-with-input-file "/dev/random"
337 (lambda (in)
338 (read-bytevector (expt 2 20) in))
339 #:binary #t)
340 out))
341 #:binary #t)
342
343 (invoke "git" "add" "one-mebibyte.blob")
344 (invoke "git" "commit" "-m" "add binary blob")
345 (zero?
346 (system "git lfs ls-files|grep -Fq one-mebibyte.blob")))
347 marionette))
348
349 (test-assert "push lfs tracked files to forgejo"
350 (marionette-eval
351 '(begin
352 (invoke "git" "push" "origin" "master"))
353 marionette))
354
355 (test-assert "cloned repo contains lfs tracked files"
356 (marionette-eval
357 '(begin
358 ;; FIXME: Switch back 127.0.0.1 to localhost after
359 ;; <https://codeberg.org/shepherd/shepherd/issues/122> is
360 ;; resolved.
361 (invoke "git" "clone" "http://127.0.0.1:3000/dummy/hello"
362 "/tmp/hello-with-blob")
363 (= (expt 2 20)
364 (stat:size
365 (stat "/tmp/hello-with-blob/one-mebibyte.blob"))))
366 marionette))
367
368 (test-assert "email notification works"
369 (begin
370 (let (((values response body)
371 (http-post
372 "http://localhost:3333/user/forgot_password"
373 #:headers `((Content-Type
374 . "application/x-www-form-urlencoded"))
375 #:body (string->utf8
376 (string-append
377 "email=" (uri-encode "dummy@localhost"))))))
378 (assert (= 200 (response-code response))))
379
380 (with-retries 10 1
381 (marionette-eval
382 '(begin
383 (use-modules (ice-9 textual-ports))
384 (string-contains (call-with-input-file
385 "/var/log/forgejo.log"
386 get-string-all)
387 "Subject: Recover your account"))
388 marionette))))
389
390 (test-end)))))
391
392 (gexp->derivation (string-append "forgejo-test-" name) test))
393
394(define %test-forgejo
395 (system-test
396 (name "forgejo")
397 (description "Basic tests for the Forgejo service.")
398 (value (make-forgejo-test "sqlite"))))
399
400(define %test-forgejo-mysql
401 (system-test
402 (name "forgejo-mysql")
403 (description "Basic tests for the Forgejo service, using a MySQL
404database.")
405 (value (make-forgejo-test "mysql" #:os %forgejo-os/mysql))))
406
407(define %test-forgejo-postgres
408 (system-test
409 (name "forgejo-postgres")
410 (description "Basic tests for the Forgejo service, using a PostgreSQL
411database.")
412 (value (make-forgejo-test "postgres" #:os %forgejo-os/postgres))))
413
414;; Local Variables:
415;; eval: (put 'with-retries 'scheme-indent-function 2)
416;; End: