summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--guix/forge/tissue.scm296
1 files changed, 296 insertions, 0 deletions
diff --git a/guix/forge/tissue.scm b/guix/forge/tissue.scm
new file mode 100644
index 0000000..4ffdf9f
--- /dev/null
+++ b/guix/forge/tissue.scm
@@ -0,0 +1,296 @@
+;;; guix-forge --- Guix software forge meta-service
+;;; Copyright © 2022 Arun Isaac <arunisaac@systemreboot.net>
+;;;
+;;; This file is part of guix-forge.
+;;;
+;;; guix-forge is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation, either version 3 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; guix-forge is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with guix-forge.  If not, see
+;;; <https://www.gnu.org/licenses/>.
+
+(define-module (forge tissue)
+  #:use-module (srfi srfi-1)
+  #:use-module (forge socket)
+  #:use-module ((gnu packages admin) #:select (shadow))
+  #:use-module ((gnu packages autotools) #:select (autoconf automake))
+  #:use-module ((gnu packages gettext) #:select (gnu-gettext))
+  #:use-module ((gnu packages guile) #:select (guile-3.0 guile-git))
+  #:use-module ((gnu packages guile-xyz) #:select (guile-filesystem guile-xapian))
+  #:use-module ((gnu packages skribilo) #:select (skribilo) #:prefix guix:)
+  #:use-module (gnu services)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system shadow)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix gexp)
+  #:use-module (guix modules)
+  #:use-module (guix git-download)
+  #:use-module ((guix licenses) #:prefix license:)
+  #:use-module (guix packages)
+  #:use-module (guix records)
+  #:export (tissue-service-type
+            <tissue-configuration>
+            tissue-configuration
+            tissue-configuration?
+            tissue-configuration-package
+            tissue-configuration-socket
+            tissue-configuration-state-directory
+            tissue-configuration-hosts
+            <tissue-host>
+            tissue-host
+            tissue-host?
+            tissue-host-name
+            tissue-host-user
+            tissue-host-upstream-repository))
+
+;; tissue requires an unreleased version of skribilo for its gemtext
+;; reader.
+(define-public skribilo-latest
+  (let ((commit "621eb1945aec8f26f5aee4bdf896f2434e145182")
+        (revision "1"))
+    (package
+      (inherit guix:skribilo)
+      (name "skribilo")
+      (version (git-version "0.9.5" revision commit))
+      (source (origin
+                (method git-fetch)
+                (uri (git-reference
+                      (url "https://git.systemreboot.net/skribilo")
+                      (commit commit)))
+                (file-name (git-file-name name version))
+                (sha256
+                 (base32
+                  "16rdcvszl9x183y32hjdwns0lkrvkmwd2fsshymspb12k4cxj6i4"))))
+      (native-inputs
+       (modify-inputs (package-native-inputs guix:skribilo)
+         (prepend autoconf)
+         (prepend automake)
+         (prepend gnu-gettext))))))
+
+;; TODO: Contribute tissue package upstream to Guix after its first
+;; release.
+(define-public tissue
+  (let ((commit "743f2eb0b2f107c8089bbd925dbd5052ff9fa9f9")
+        (revision "0"))
+    (package
+      (name "tissue")
+      (version (git-version "0.1.0" revision commit))
+      (source (origin
+                (method git-fetch)
+                (uri (git-reference
+                      (url "https://git.systemreboot.net/tissue")
+                      (commit commit)))
+                (file-name (git-file-name name version))
+                (sha256
+                 (base32
+                  "1fcxwclhpy0p17m6qingrzmaq0py1ys92v4p2ygyn69hmq9i048f"))))
+      (build-system gnu-build-system)
+      (arguments
+       (list #:make-flags #~(list (string-append "prefix=" #$output))
+             #:modules `(((guix build guile-build-system)
+                          #:select (target-guile-effective-version))
+                         ,@%gnu-build-system-modules)
+             #:phases
+             (with-imported-modules '((guix build guile-build-system))
+               #~(modify-phases %standard-phases
+                   (replace 'patch-source-shebangs
+                     (lambda* (#:key inputs #:allow-other-keys)
+                       (substitute* "bin/tissue"
+                         (("^exec guile")
+                          (string-append "exec " (search-input-file inputs "/bin/guile"))))))
+                   (delete 'configure)
+                   (add-after 'install 'wrap
+                     (lambda* (#:key inputs outputs #:allow-other-keys)
+                       (let ((out (assoc-ref outputs "out"))
+                             (effective-version (target-guile-effective-version)))
+                         (wrap-program (string-append out "/bin/tissue")
+                           `("GUILE_LOAD_PATH" prefix
+                             (,(string-append out "/share/guile/site/" effective-version)
+                              ,(getenv "GUILE_LOAD_PATH")))
+                           `("GUILE_LOAD_COMPILED_PATH" prefix
+                             (,(string-append out "/lib/guile/" effective-version "/site-ccache")
+                              ,(getenv "GUILE_LOAD_COMPILED_PATH")))))))))))
+      (inputs (list guile-3.0 guile-filesystem guile-git guile-xapian))
+      (propagated-inputs
+       (list skribilo-latest))
+      (home-page "https://tissue.systemreboot.net")
+      (synopsis "Text based project information management system")
+      (description
+       "tissue is an issue tracker and project information management system
+built on plain text files and git.  It features a static site
+generator to build a project website and a powerful search interface
+to search through project issues and documentation.  The search
+interface is built on the Xapian search engine library, and is
+available both as a command-line program and as a web server.")
+      (license license:gpl3+))))
+
+(define-record-type* <tissue-configuration>
+  tissue-configuration make-tissue-configuration
+  tissue-configuration?
+  (package tissue-configuration-package
+           (default tissue))
+  (socket tissue-configuration-socket
+          (default (forge-unix-socket
+                    (path "/var/run/tissue/socket"))))
+  (state-directory tissue-configuration-state-directory
+                   (default "/var/lib/tissue"))
+  (hosts tissue-configuration-hosts
+         (default '())))
+
+(define-record-type* <tissue-host>
+  tissue-host make-tissue-host
+  tissue-host?
+  (name tissue-host-name)
+  (user tissue-host-user
+        (default "tissue"))
+  (upstream-repository tissue-host-upstream-repository))
+
+(define %tissue-accounts
+  (list (user-account
+         (name "tissue")
+         (group "tissue")
+         (system? #t)
+         (comment "tissue user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))
+        (user-group
+         (name "tissue")
+         (system? #t))))
+
+(define (tissue-conf-gexp config)
+  (match-record config <tissue-configuration>
+    (socket state-directory hosts)
+    #~(begin
+        (use-modules (ice-9 pretty-print))
+
+        (call-with-output-file #$output
+          (lambda (port)
+            (pretty-print
+             '((listen . #$(cond
+                            ((forge-ip-socket? socket)
+                             (match-record socket <forge-ip-socket>
+                               (ip port)
+                               (string-append (if (ipv4-address? ip)
+                                                  ip
+                                                  (string-append "[" ip "]"))
+                                              ":"
+                                              (number->string port))))
+                            ((forge-unix-socket? socket)
+                             (string-append "unix:" (forge-unix-socket-path socket)))
+                            (else (raise (condition
+                                          (make-message-condition
+                                           "Socket must be a <forge-ip-socket> or <forge-unix-socket> record")
+                                          (make-irritants-condition socket))))))
+               (state-directory . #$state-directory)
+               (hosts . #$(map (lambda (host)
+                                 (match-record host <tissue-host>
+                                   (name upstream-repository)
+                                   `(,name (upstream-repository . ,upstream-repository))))
+                               hosts)))
+             port))))))
+
+;; We cannot just pass the configuration file on the command-line
+;; because we need future `tissue pull' invocations to find it. These
+;; `tissue pull' invocations are beyond the scope of this service, and
+;; will need to find the configuration at a standard location.
+(define (tissue-etc-files config)
+  `(("tissue.conf" ,(computed-file "tissue.conf"
+                                   (tissue-conf-gexp config)))))
+
+(define (tissue-activation config)
+  (match-record config <tissue-configuration>
+    (socket state-directory hosts)
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (ice-9 match))
+
+          (let ((user (getpw "tissue")))
+            ;; Create socket directory.
+            (when #$(forge-unix-socket? socket)
+              (let ((socket-directory #$(dirname (forge-unix-socket-path socket))))
+                (mkdir-p socket-directory)
+                (chown socket-directory (passwd:uid user) (passwd:gid user))))
+            ;; Create state directory.
+            (mkdir-p #$state-directory)
+            (chown #$state-directory (passwd:uid user) (passwd:gid user)))
+          ;; Create host directories if they don't exist, and set
+          ;; permissions.
+          (for-each (match-lambda
+                      ((hostname username)
+                       (let ((host-directory (string-append #$state-directory "/" hostname))
+                             (user (getpw username)))
+                         (mkdir-p host-directory)
+                         (for-each (lambda (file)
+                                     (chown file (passwd:uid user) (passwd:gid user)))
+                                   (find-files host-directory #:directories? #t)))))
+                    '#$(map (lambda (host)
+                              (match-record host <tissue-host>
+                                (name user)
+                                (list name user)))
+                            hosts))))))
+
+(define (tissue-shepherd-service config)
+  (match-record config <tissue-configuration>
+    (socket state-directory)
+    (shepherd-service
+     (documentation "Run tissue web server.")
+     (provision '(tissue))
+     (requirement '(networking))
+     (modules '((gnu build shepherd)
+                (gnu system file-systems)))
+     (start
+      (with-imported-modules (source-module-closure '((gnu build shepherd)
+                                                      (gnu system file-systems)))
+        #~(make-forkexec-constructor/container
+           (list #$(file-append (tissue-configuration-package config)
+                                "/bin/tissue")
+                 "run-web"
+                 (string-append "--config=" #$(computed-file "tissue.conf"
+                                                             (tissue-conf-gexp config))))
+           #:user "tissue"
+           #:group "tissue"
+           #:mappings (append (list (file-system-mapping
+                                     (source #$state-directory)
+                                     (target source))
+                                    (file-system-mapping
+                                     (source "/var/log/tissue.log")
+                                     (target source)
+                                     (writable? #t)))
+                              (if #$(forge-unix-socket? socket)
+                                  (list (file-system-mapping
+                                         (source #$(dirname (forge-unix-socket-path socket)))
+                                         (target source)
+                                         (writable? #t)))
+                                  (list)))
+           #:log-file "/var/log/tissue.log")))
+     (stop #~(make-kill-destructor)))))
+
+(define tissue-service-type
+  (service-type
+   (name 'tissue)
+   (description "Run tissue web server.")
+   (extensions
+    (list (service-extension account-service-type
+                             (const %tissue-accounts))
+          (service-extension etc-service-type
+                             tissue-etc-files)
+          (service-extension activation-service-type
+                             tissue-activation)
+          (service-extension shepherd-root-service-type
+                             (compose list tissue-shepherd-service))))
+   (compose concatenate)
+   (extend (lambda (config host-extensions)
+             (tissue-configuration
+              (inherit config)
+              (hosts (append (tissue-configuration-hosts config)
+                             host-extensions)))))
+   (default-value (tissue-configuration))))