summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNguyễn Gia Phong <mcsinyx@disroot.org>2025-11-17 15:46:53 +0900
committerLudovic Courtès <ludo@gnu.org>2026-03-11 18:27:46 +0100
commit3433fb987bbc826a585a0d4d0a80e1f5369769a3 (patch)
tree404bf5c6a53dd084bc4ab26506455077e9a8eef3
parent6eb6971f4d721fc8d648323f0dbd52d0697b2a7b (diff)
services: Add fossil-service-type.
* gnu/services/version-control.scm (fossil-service-type, fossil-configuration): New public variables. * gnu/tests/version-control.scm (%test-fossil): Add system tests. * doc/guix.texi (Version Control Services): Add Fossil documentation. Change-Id: I84e09fe8c11e161ed7c4bdba42b0ae38ef4c2096 Signed-off-by: Ludovic Courtès <ludo@gnu.org>
-rw-r--r--doc/guix.texi150
-rw-r--r--gnu/services/version-control.scm257
-rw-r--r--gnu/tests/version-control.scm75
3 files changed, 480 insertions, 2 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 9924f4771f2..8841a483a8d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -149,6 +149,7 @@ Copyright @copyright{} 2025 Rodion Goritskov@*
149Copyright @copyright{} 2025 dan@* 149Copyright @copyright{} 2025 dan@*
150Copyright @copyright{} 2025 Noé Lopez@* 150Copyright @copyright{} 2025 Noé Lopez@*
151Copyright @copyright{} 2026 David Elsing@* 151Copyright @copyright{} 2026 David Elsing@*
152Copyright @copyright{} 2026 Nguyễn Gia Phong@*
152 153
153Permission is granted to copy, distribute and/or modify this document 154Permission is granted to copy, distribute and/or modify this document
154under the terms of the GNU Free Documentation License, Version 1.3 or 155under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -43773,6 +43774,155 @@ like to serve.
43773@end table 43774@end table
43774@end deftp 43775@end deftp
43775 43776
43777@anchor{fossil-service-type}
43778@subsubheading Fossil Service
43779
43780@cindex Fossil service
43781@cindex Fossil, forge
43782@uref{https://fossil-scm.org, Fossil} is a distributed
43783software configuration management system. In addition to version control
43784like Git, Fossil also supports bug tracking, wiki, forum, chat, etc.,
43785all accessible via its built-in web interface.
43786
43787Fossil is highly reliable thanks to its robust file format based on SQLite
43788with atomic transactions. Its server is CPU, memory and bandwidth efficient
43789enough run comfortably on a cheap VPS or single board computer,
43790and be accessed over suboptimal connections.
43791
43792The following example will configure Fossil to listen on a unix socket
43793behind a reverse proxy and serve repositories from a custom location.
43794
43795@lisp
43796(service fossil-service-type
43797 (fossil-configuration
43798 (repository "/srv/museum")
43799 (repo-list? #t)
43800 (base-url "https://museum.example")
43801 (socket-file "/var/run/fossil.sock")
43802 (compress? #f)))
43803@end lisp
43804
43805@deftp {Data Type} fossil-configuration
43806Available @code{fossil-configuration} fields are:
43807
43808@table @asis
43809@item @code{package} (default: @code{fossil}) (type: package)
43810The Fossil package to use.
43811
43812@item @code{user} (default: @code{"fossil"}) (type: string)
43813The user running the Fossil server.
43814
43815@item @code{group} (default: @code{"fossil"}) (type: string)
43816The user group running the Fossil server.
43817
43818@item @code{log-file} (default: @code{"/var/log/fossil.log"}) (type: string)
43819The path to the server's log.
43820
43821@item @code{repository} (default: @code{"/var/lib/fossil"}) (type: string)
43822The name of the Fossil repository to be served, or a directory
43823containing one or more repositories with names ending in @code{.fossil}.
43824In the latter case, a prefix of the URL pathname is used to search the
43825directory for an appropriate repository. Files not matching the pattern
43826@code{*.fossil*} will be served as static content. Invoke
43827@command{fossil server --help} for more information.
43828
43829@item @code{acme?} (default: @code{#f}) (type: boolean)
43830Deliver files from the @code{.well-known} subdirectory.
43831
43832@item @code{base-url} (type: maybe-string)
43833The URL used as the base (useful for reverse proxies)
43834
43835@item @code{chroot} (type: maybe-string)
43836The directory to use for chroot instead of @code{repository}.
43837
43838@item @code{ckout-alias} (type: maybe-string)
43839The @var{name} for @code{/doc/@var{name}/...} to be treated as
43840@code{/doc/ckout/...}.
43841
43842@item @code{compress?} (default: @code{#t}) (type: boolean)
43843Compress HTTP response.
43844
43845@item @code{create?} (default: @code{#f}) (type: boolean)
43846Create a new @code{repository} if it does not already exist.
43847
43848@item @code{error-log-file} (type: maybe-string)
43849The path for HTTP error log.
43850
43851@item @code{ext-root} (type: maybe-string)
43852The document root for the /ext extension mechanism.
43853
43854@item @code{files} (type: maybe-list-of-strings)
43855The glob patterns for static files.
43856
43857@item @code{from} (type: maybe-string)
43858The path to be used as the diff baseline for the /ckout page.
43859
43860@item @code{jail?} (default: @code{#t}) (type: boolean)
43861Whether to enter the chroot jail after dropping root privileges.
43862
43863@item @code{js-mode} (type: maybe-fossil-js-mode)
43864How JavaScript is delivered with pages, either @code{'inline} at the end
43865of the HTML file, as @code{'separate} HTTP requests, or one single HTTP
43866request for all JavaScript @code{'bundled} together. Depending on the
43867needs of any given page, @code{'inline} and @code{'bundled} modes might
43868result in a single amalgamated script or several, but both approaches
43869result in fewer HTTP requests than the @code{'separate} mode.
43870
43871@item @code{https?} (default: @code{#f}) (type: boolean)
43872Indicate that the requests are coming through a reverse proxy that has
43873already translated HTTPS into HTTP.
43874
43875@item @code{ip} (type: maybe-string)
43876The IP for the server to listen on.
43877
43878@item @code{local-authentication?} (default: @code{#f}) (type: boolean)
43879Enable automatic login for requests from localhost.
43880
43881@item @code{localhost?} (default: @code{#f}) (type: boolean)
43882Listen on @code{127.0.0.1} only.
43883
43884@item @code{main-menu} (type: maybe-string)
43885The file whose contents is to override the repository's @code{mainmenu}
43886setting.
43887
43888@item @code{max-latency} (type: maybe-number)
43889The maximum latency in seconds for a single HTTP request.
43890
43891@item @code{port} (default: @code{8080}) (type: port-number)
43892The port number for the server to listen on.
43893
43894@item @code{list-repositories?} (default: @code{#f}) (type: boolean)
43895If @code{repository} is dir, URL @code{/} lists repos.
43896
43897@item @code{redirect-to-https?} (default: @code{#t}) (type: boolean)
43898If set to @code{#f}, do not force redirects to HTTPS regardless of the
43899repository setting @code{redirect-to-https}.
43900
43901@item @code{scgi?} (default: @code{#f}) (type: boolean)
43902Accept SCGI rather than HTTP.
43903
43904@item @code{skin} (type: maybe-string)
43905The skin label to use, overriding repository settings.
43906
43907@item @code{socket-file} (type: maybe-string)
43908The unix-domain socket to use instead of TCP/IP.
43909
43910@item @code{socket-mode} (default: @code{0o640}) (type: mode-number)
43911The file permissions to set for the unix socket.
43912
43913@item @code{th-trace?} (default: @code{#f}) (type: boolean)
43914Trace TH1 execution (for debugging purposes).
43915
43916@item @code{tls-certificate} (type: maybe-string)
43917The certicate file (@file{fullchain.pem}) with which to enable TLS
43918(HTTPS) encryption.
43919
43920@item @code{tls-private-key} (type: maybe-string)
43921The file storing the TLS private key.
43922
43923@end table
43924
43925@end deftp
43776 43926
43777@node Game Services 43927@node Game Services
43778@subsection Game Services 43928@subsection Game Services
diff --git a/gnu/services/version-control.scm b/gnu/services/version-control.scm
index a7f40812a6c..dba38faa460 100644
--- a/gnu/services/version-control.scm
+++ b/gnu/services/version-control.scm
@@ -7,6 +7,7 @@
7;;; Copyright © 2021 Julien Lepiller <julien@lepiller.eu> 7;;; Copyright © 2021 Julien Lepiller <julien@lepiller.eu>
8;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz> 8;;; Copyright © 2025 Tomas Volf <~@wolfsden.cz>
9;;; Copyright © 2025 Evgeny Pisemsky <mail@pisemsky.site> 9;;; Copyright © 2025 Evgeny Pisemsky <mail@pisemsky.site>
10;;; Copyright © 2026 Nguyễn Gia Phong <cnx@loang.net>
10;;; 11;;;
11;;; This file is part of GNU Guix. 12;;; This file is part of GNU Guix.
12;;; 13;;;
@@ -26,12 +27,14 @@
26(define-module (gnu services version-control) 27(define-module (gnu services version-control)
27 #:use-module (gnu services) 28 #:use-module (gnu services)
28 #:use-module (gnu services base) 29 #:use-module (gnu services base)
30 #:use-module (gnu services configuration)
29 #:use-module (gnu services shepherd) 31 #:use-module (gnu services shepherd)
30 #:use-module (gnu services web) 32 #:use-module (gnu services web)
31 #:use-module (gnu system shadow) 33 #:use-module (gnu system shadow)
32 #:use-module (gnu packages version-control) 34 #:use-module (gnu packages version-control)
33 #:use-module (gnu packages admin) 35 #:use-module (gnu packages admin)
34 #:use-module (guix deprecation) 36 #:use-module (guix deprecation)
37 #:use-module (guix packages)
35 #:use-module (guix records) 38 #:use-module (guix records)
36 #:use-module (guix gexp) 39 #:use-module (guix gexp)
37 #:use-module (guix store) 40 #:use-module (guix store)
@@ -93,7 +96,44 @@
93 gitile-configuration-footer 96 gitile-configuration-footer
94 gitile-configuration-nginx 97 gitile-configuration-nginx
95 98
96 gitile-service-type)) 99 gitile-service-type
100
101 fossil-configuration
102 fossil-configuration-fields
103 fossil-configuration?
104 fossil-configuration-package
105 fossil-configuration-user
106 fossil-configuration-group
107 fossil-configuration-log-file
108 fossil-configuration-repository
109 fossil-configuration-acme?
110 fossil-configuration-base-url
111 fossil-configuration-chroot
112 fossil-configuration-ckout-alias
113 fossil-configuration-compress?
114 fossil-configuration-create?
115 fossil-configuration-error-log-file
116 fossil-configuration-ext-root
117 fossil-configuration-files
118 fossil-configuration-from
119 fossil-configuration-jail?
120 fossil-configuration-js-mode
121 fossil-configuration-https?
122 fossil-configuration-ip
123 fossil-configuration-local-authentication?
124 fossil-configuration-main-menu
125 fossil-configuration-max-latency
126 fossil-configuration-port
127 fossil-configuration-list-repositories?
128 fossil-configuration-redirect-to-https?
129 fossil-configuration-skin
130 fossil-configuration-socket-file
131 fossil-configuration-socket-mode
132 fossil-configuration-th-trace?
133 fossil-configuration-tls-certificate
134 fossil-configuration-tls-private-key
135
136 fossil-service-type))
97 137
98;;; Commentary: 138;;; Commentary:
99;;; 139;;;
@@ -603,3 +643,218 @@ on the web.")
603 gitile-shepherd-service) 643 gitile-shepherd-service)
604 (service-extension nginx-service-type 644 (service-extension nginx-service-type
605 gitile-nginx-server-block))))) 645 gitile-nginx-server-block)))))
646
647
648;;;
649;;; Fossil HTTP server.
650;;;
651
652(define (port-number? n)
653 (and (integer? n)
654 (> n 0)
655 (< n (expt 2 16))))
656
657(define (mode-number? n)
658 (and (integer? n)
659 (>= n 0)
660 (<= n #o777)))
661
662(define (fossil-js-mode? x)
663 (and (memq x '(inline separate bundled))
664 #t))
665
666(define-maybe/no-serialization number)
667(define-maybe/no-serialization string)
668(define-maybe/no-serialization list-of-strings)
669(define-maybe/no-serialization fossil-js-mode)
670
671(define-configuration/no-serialization fossil-configuration
672 (package (package fossil)
673 "The Fossil package to use.")
674 (user (string "fossil")
675 "The user running the Fossil server.")
676 (group (string "fossil")
677 "The user group running the Fossil server.")
678 (log-file (string "/var/log/fossil.log")
679 "The path to the server's log.")
680 (repository (string "/var/lib/fossil")
681 "The name of the Fossil repository to be served, or a directory
682containing one or more repositories with names ending in @code{.fossil}.
683
684In the latter case, a prefix of the URL pathname is used
685to search the directory for an appropriate repository.
686Files not matching the pattern @code{*.fossil*}
687will be served as static content. Invoke @command{fossil server --help}
688for more information.")
689 (acme? (boolean #f)
690 "Deliver files from the @code{.well-known} subdirectory.")
691 (base-url maybe-string
692 "The URL used as the base (useful for reverse proxies)")
693 (chroot maybe-string
694 "The directory to use for chroot instead of @code{repository}.")
695 (ckout-alias maybe-string
696 "The @var{name} for @code{/doc/@var{name}/...}
697to be treated as @code{/doc/ckout/...}.")
698 (compress? (boolean #t) "Compress HTTP response.")
699 (create? (boolean #f)
700 "Create a new @code{repository} if it does not already exist.")
701 (error-log-file maybe-string "The path for HTTP error log.")
702 (ext-root maybe-string "The document root for the /ext extension mechanism.")
703 (files maybe-list-of-strings "The glob patterns for static files.")
704 (from maybe-string
705 "The path to be used as the diff baseline for the /ckout page.")
706 (jail? (boolean #t)
707 "Whether to enter the chroot jail after dropping root privileges.")
708 (js-mode maybe-fossil-js-mode
709 "How JavaScript is delivered with pages, either @code{'inline}
710at the end of the HTML file, as @code{'separate} HTTP requests,
711or one single HTTP request for all JavaScript @code{'bundled} together.
712
713Depending on the needs of any given page, @code{'inline}
714and @code{'bundled} modes might result in a single amalgamated script
715or several, but both approaches result in fewer HTTP requests
716than the @code{'separate} mode.")
717 (https? (boolean #f)
718 "Indicate that the requests are coming through a reverse proxy
719that has already translated HTTPS into HTTP.")
720 (ip maybe-string "The IP for the server to listen on.")
721 (local-authentication? (boolean #f)
722 "Enable automatic login for requests from localhost.")
723 (localhost? (boolean #f) "Listen on @code{127.0.0.1} only.")
724 (main-menu maybe-string ;TODO: structure
725 "The file whose contents is to override
726the repository's @code{mainmenu} setting.")
727 (max-latency maybe-number
728 "The maximum latency in seconds for a single HTTP request.")
729 (port (port-number 8080) "The port number for the server to listen on.")
730 (list-repositories? (boolean #f)
731 "If @code{repository} is dir, URL @code{/} lists repos.")
732 (redirect-to-https? (boolean #t)
733 "If set to @code{#f}, do not force redirects to HTTPS
734regardless of the repository setting @code{redirect-to-https}.")
735 (scgi? (boolean #f) "Accept SCGI rather than HTTP.")
736 (skin maybe-string "The skin label to use, overriding repository settings.")
737 (socket-file maybe-string
738 "The unix-domain socket to use instead of TCP/IP.")
739 (socket-mode (mode-number #o640)
740 "The file permissions to set for the unix socket.")
741 (th-trace? (boolean #f)
742 "Trace TH1 execution (for debugging purposes).")
743 (tls-certificate maybe-string
744 "The certicate file (@file{fullchain.pem})
745with which to enable TLS (HTTPS) encryption.")
746 (tls-private-key maybe-string "The file storing the TLS private key."))
747
748(define (fossil-accounts config)
749 (match-record config <fossil-configuration> (user group)
750 (list (user-group (name group)
751 (system? #t))
752 (user-account (name user)
753 (group group)
754 (system? #t)
755 (comment "Fossil server user")
756 (home-directory "/var/empty")
757 (shell (file-append shadow "/sbin/nologin"))))))
758
759(define (fossil-activation config)
760 (match-record config <fossil-configuration> (user create? repository)
761 (with-imported-modules '((guix build utils))
762 #~(begin
763 (use-modules (guix build utils))
764 (let* ((pw (getpwnam #$user))
765 (uid (passwd:uid pw))
766 (gid (passwd:gid pw)))
767 (unless #$create? (chown #$repository uid gid)))))))
768
769(define (fossil-shepherd-service config)
770 (match-record config <fossil-configuration>
771 (package user group log-file repository acme? base-url
772 chroot ckout-alias compress? create? error-log-file ext-root
773 files from https? ip jail? js-mode list-repositories?
774 local-authentication? localhost? main-menu max-latency port
775 redirect-to-https? scgi? skin socket-file socket-mode
776 th-trace? tls-certificate tls-private-key)
777 (shepherd-service
778 (provision '(fossil))
779 (requirement '(user-processes networking))
780 (start #~(make-forkexec-constructor
781 (list #$(file-append package "/bin/fossil")
782 "server"
783 #$@(if acme? '("--acme") '())
784 #$@(if (maybe-value-set? base-url)
785 (list "--baseurl" base-url)
786 '())
787 #$@(if (maybe-value-set? chroot)
788 (list "--chroot" chroot)
789 '())
790 #$@(if (maybe-value-set? ckout-alias)
791 (list "--ckout-alias" ckout-alias)
792 '())
793 #$@(if compress? '() '("--nocompress"))
794 #$@(if create? '("--create") '())
795 #$@(if (maybe-value-set? error-log-file)
796 (list "--errorlog" error-log-file)
797 '())
798 #$@(if (maybe-value-set? ext-root)
799 (list "--extroot" ext-root)
800 '())
801 #$@(if (maybe-value-set? files)
802 (list "--files" (string-join files ","))
803 '())
804 #$@(if (maybe-value-set? from) (list "--from" from) '())
805 #$@(if https? '("--https") '())
806 #$@(if jail? '() '("--nojail"))
807 #$@(if (maybe-value-set? js-mode)
808 (list "--jsmode" (symbol->string js-mode))
809 '())
810 #$@(if local-authentication? '("--localauth") '())
811 #$@(if localhost? '("--localhost") '())
812 #$@(if (maybe-value-set? main-menu)
813 (list "--mainmenu" main-menu)
814 '())
815 #$@(if (maybe-value-set? max-latency)
816 (list "--max-latency"
817 (number->string max-latency))
818 '())
819 #$@(if redirect-to-https? '() '("--nossl"))
820 #$@(if scgi? '("--scgi") '())
821 #$@(if list-repositories? '("--repolist") '())
822 #$@(if (maybe-value-set? skin) (list "--skin" skin) '())
823 #$@(if (maybe-value-set? socket-file)
824 (list "--socket-name" socket-file
825 "--socket-mode" socket-mode
826 "--socket-owner"
827 (simple-format #f "~a:~a" user group))
828 (list "--port"
829 (if (maybe-value-set? ip)
830 (simple-format #f "~a:~a" ip port)
831 (number->string port))))
832 #$@(if th-trace? '("--th-trace") '())
833 #$@(if (maybe-value-set? tls-certificate)
834 (list "--cert" tls-certificate)
835 '())
836 #$@(if (maybe-value-set? tls-private-key)
837 (list "--pkey" tls-private-key)
838 '())
839 "--user" #$user
840 #$repository)
841 #:user #$user
842 #:group #$group
843 #:log-file #$log-file))
844 (stop #~(make-kill-destructor))
845 (documentation
846 "Run the HTTP server
847for the Fossil software configuration management system."))))
848
849(define fossil-service-type
850 (service-type
851 (name 'fossil)
852 (extensions
853 (list (service-extension account-service-type fossil-accounts)
854 (service-extension activation-service-type fossil-activation)
855 (service-extension shepherd-root-service-type
856 (compose list fossil-shepherd-service))))
857 (description
858 "Run the HTTP server for the Fossil software configuration management
859system. In addition to distributed version control, Fossil also supports
860bug tracking, wiki, forum, email alerts, chat, and technotes.")))
diff --git a/gnu/tests/version-control.scm b/gnu/tests/version-control.scm
index 8426555a18f..9df3aa9dbd4 100644
--- a/gnu/tests/version-control.scm
+++ b/gnu/tests/version-control.scm
@@ -3,6 +3,7 @@
3;;; Copyright © 2017-2018, 2020-2022 Ludovic Courtès <ludo@gnu.org> 3;;; Copyright © 2017-2018, 2020-2022 Ludovic Courtès <ludo@gnu.org>
4;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org> 4;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
5;;; Copyright © 2018 Christopher Baines <mail@cbaines.net> 5;;; Copyright © 2018 Christopher Baines <mail@cbaines.net>
6;;; Copyright © 2026 Nguyễn Gia Phong <cnx@loang.net>
6;;; 7;;;
7;;; This file is part of GNU Guix. 8;;; This file is part of GNU Guix.
8;;; 9;;;
@@ -39,7 +40,8 @@
39 #:export (%test-cgit 40 #:export (%test-cgit
40 %test-git-http 41 %test-git-http
41 %test-gitolite 42 %test-gitolite
42 %test-gitile)) 43 %test-gitile
44 %test-fossil))
43 45
44(define README-contents 46(define README-contents
45 "Hello! This is what goes inside the 'README' file.") 47 "Hello! This is what goes inside the 'README' file.")
@@ -519,3 +521,74 @@ HTTP-PORT."
519 (name "gitile") 521 (name "gitile")
520 (description "Connect to a running Gitile server.") 522 (description "Connect to a running Gitile server.")
521 (value (run-gitile-test)))) 523 (value (run-gitile-test))))
524
525
526;;;
527;;; Fossil server.
528;;;
529
530(define %test-fossil
531 (system-test
532 (name "fossil")
533 (description "Connect to a running Fossil server.")
534 (value
535 (gexp->derivation
536 (string-append name "-test")
537 (let* ((port 8080)
538 (base-url (simple-format #f "http://localhost:~a" port))
539 (index-url (string-append base-url "/index"))
540 (os (marionette-operating-system
541 (simple-operating-system
542 (service dhcpcd-service-type)
543 (service fossil-service-type
544 (fossil-configuration
545 (repository "/tmp/test.fossil")
546 (base-url base-url)
547 (create? #t)
548 (port port))))))
549 (vm (virtual-machine (operating-system os)
550 (port-forwardings (list (cons port port))))))
551 (with-imported-modules '((gnu build marionette)
552 (guix build utils))
553 #~(begin
554 (use-modules (gnu build marionette)
555 (guix build utils)
556 (srfi srfi-64)
557 (srfi srfi-71)
558 (web client)
559 (web response))
560 (define marionette (make-marionette (list #$vm)))
561 (test-runner-current (system-test-runner #$output))
562 (test-begin #$name)
563
564 (test-assert "server running"
565 (wait-for-tcp-port #$port marionette))
566
567 (test-assert "server log file"
568 (wait-for-file "/var/log/fossil.log" marionette))
569
570 (test-assert "cloning"
571 (begin
572 (setenv "HOME" #$output) ; fossil writes to $HOME
573 (invoke/quiet #$(file-append fossil "/bin/fossil") "clone"
574 "--admin-user" "alice"
575 "--httptrace"
576 "--verbose"
577 #$base-url
578 (string-append #$output "/test.fossil"))))
579
580 (test-assert "index redirect"
581 (let ((response text
582 (http-get #$base-url #:decode-body? #t)))
583 (and (= 302 (response-code response))
584 (string-contains text #$index-url))))
585
586 (test-equal "index page"
587 200 (response-code (http-get #$index-url)))
588
589 (test-equal "tarball download"
590 200 (response-code
591 (http-get (string-append #$base-url
592 "/tarball/test.tar.gz"))))
593
594 (test-end))))))))