diff --git a/doc/guix.texi b/doc/guix.texi index af91f02d1f1..9aadad4c2ea 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -144,6 +144,7 @@ Copyright @copyright{} 2024 Evgeny Pisemsky@* Copyright @copyright{} 2025 jgart@* Copyright @copyright{} 2025 Artur Wroblewski@* Copyright @copyright{} 2025 Edouard Klein@* +Copyright @copyright{} 2025 Rodion Goritskov@* Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -35700,6 +35701,80 @@ The file which should store the logging output of Agate. @end table @end deftp +@subsubheading Miniflux + +@cindex miniflux +The @uref{https://miniflux.app/, Miniflux} is a minimalist RSS feed reader +with a web interface. + +Depending on the configuration, an initial administrator user can be pre-created +on startup. To enable this, @code{create-admin?} should be set to @code{#t}, and +both @code{admin-username-file} and @code{admin-password-file} should point to +files containing the username and password, respectively. However, it is +recommended to manually change the password to a secure one via the web +UI after the initial service startup. + +@defvar miniflux-service-type +This is the type of the Miniflux service. Its value +must be a @code{miniflux-configuration} record as in this example: + +@lisp +(service miniflux-service-type + (miniflux-configuration + (listen-address "0.0.0.0:8080") + (base-url "http://my-news-source.test") + (create-administrator-account? #t) + (administrator-account-name "/var/miniflux/initial-admin-username") + (administrator-account-password "/var/miniflux/initial-admin-password"))) +@end lisp + +The details of the @code{miniflux-configuration} record type are given below. + +@end defvar + +@deftp {Data Type} miniflux-configuration +Available @code{miniflux-configuration} fields are: + +@table @asis +@item @code{listen-address} (default: @code{"127.0.0.1:8080"}) (type: string) +Address to listen on. +Use absolute path like @code{"/var/run/miniflux/miniflux.sock"} for a Unix socket. + +@item @code{base-url} (default: @code{"http://127.0.0.1/"}) (type: string) +Base URL to generate HTML links and base path for cookies. + +@item @code{create-administrator-account?} (default: @code{#f}) (type: boolean) +Create an initial administrator account. + +@item @code{administrator-account-name} (type: maybe-string-or-file-path) +Initial administrator account name as a string or an absolute path +to a file with an account name inside. + +@item @code{administrator-account-password} (type: maybe-string-or-file-path) +Initial administrator account password as a string or an absolute path +to a file with a password inside. + +@item @code{run-migrations?} (default: @code{#t}) (type: boolean) +Run database migrations during application startup. + +@item @code{database-url} (default: @code{"host=/var/run/postgresql"}) (type: string) +PostgreSQL connection string. + +@item @code{user} (default: @code{"miniflux"}) (type: string) +User name for Postgresql and system account. + +@item @code{group} (default: @code{"miniflux"}) (type: string) +Group for the system account. + +@item @code{log-file} (default: @code{"/var/log/miniflux.log"}) (type: string) +Path to the log file. + +@item @code{extra-settings} (type: maybe-list) +Extra configuration parameters as a list of strings. + +@end table +@end deftp + @node High Availability Services @subsection High Availability Services diff --git a/etc/teams.scm b/etc/teams.scm index 69057e16efa..21a19cc24ca 100755 --- a/etc/teams.scm +++ b/etc/teams.scm @@ -1058,8 +1058,8 @@ the \"texlive\" importer." (define-member (person "Ludovic Courtès" "ludo@gnu.org" "civodul") - core home bootstrap core-packages installer - documentation mentors) + core core-packages hpc installer + mentors) (define-member (person "Andreas Enge" "andreas@enge.fr" diff --git a/gnu/home/services/sway.scm b/gnu/home/services/sway.scm index bf001de1e5a..835053de1af 100644 --- a/gnu/home/services/sway.scm +++ b/gnu/home/services/sway.scm @@ -168,7 +168,7 @@ (lambda (e) (or (member e '("no-warn" "whole-window" "border" "exclude-titlebar" - "release" "locked" "inhibited" "no-repeat")) + "release" "locked" "to-code" "inhibited" "no-repeat")) (string-prefix? "input-device=" e))) lst)) @@ -177,7 +177,7 @@ (lambda (e) (or (member e '("no-warn" "whole-window" "border" "exclude-titlebar" - "release" "locked" "to-code" "inhibited" "no-repeat")) + "release" "locked" "inhibited" "no-repeat")) (string-prefix? "input-device=" e))) lst)) diff --git a/gnu/packages/admin.scm b/gnu/packages/admin.scm index 3c7bebc189e..53483d22165 100644 --- a/gnu/packages/admin.scm +++ b/gnu/packages/admin.scm @@ -601,8 +601,13 @@ interface and is based on GNU Guile.") "/bin/gzip") (string-append "--with-zstd=" #$(this-package-input "zstd") "/bin/zstd"))))) - (inputs (modify-inputs (package-inputs shepherd-0.10) - (append gzip zstd))))) + (native-inputs + (modify-inputs (package-native-inputs shepherd-0.10) + (replace "guile-fibers" guile-fibers))) ;use latest guile-fibers available + (inputs + (modify-inputs (package-inputs shepherd-0.10) + (replace "guile-fibers" guile-fibers) ;use latest guile-fibers available + (append gzip zstd))))) (define-public shepherd shepherd-0.10) diff --git a/gnu/packages/docker.scm b/gnu/packages/docker.scm index f2aa2ae9340..e6f4e9c38a8 100644 --- a/gnu/packages/docker.scm +++ b/gnu/packages/docker.scm @@ -211,7 +211,7 @@ client.") python-docker-5 python-dockerpty python-docopt - python-dotenv + python-dotenv-0.13.0 python-jsonschema-3 python-pyyaml python-requests diff --git a/gnu/packages/gcal.scm b/gnu/packages/gcal.scm index 32d3849faaa..c9ac90055bd 100644 --- a/gnu/packages/gcal.scm +++ b/gnu/packages/gcal.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013, 2018 Ludovic Courtès +;;; Copyright © 2025 Andy Tai ;;; ;;; This file is part of GNU Guix. ;;; @@ -20,19 +21,22 @@ #:use-module (guix packages) #:use-module (guix download) #:use-module (guix build-system gnu) - #:use-module (guix licenses)) + #:use-module (guix licenses) + #:use-module (gnu packages check) + #:use-module (gnu packages pkg-config)) + (define-public gcal (package (name "gcal") - (version "4.1") + (version "4.2.0") (source (origin (method url-fetch) - (uri (string-append "mirror://gnu/gcal/gcal-" + (uri (string-append "https://www.alteholz.dev/gnu/gcal-" version ".tar.xz")) (sha256 (base32 - "1av11zkfirbixn05hyq4xvilin0ncddfjqzc4zd9pviyp506rdci")) + "1p3q6his31bxs24nsgpfavw3nlhalqf0zak4f3b530p725s2vgfq")) (modules '((guix build utils))) (snippet '(begin @@ -50,6 +54,8 @@ "/* BSD stdio derived implementations"))) #t)))) (build-system gnu-build-system) + (native-inputs (list check pkg-config)) + (arguments `(#:configure-flags '("LDFLAGS=-lm"))) (home-page "https://www.gnu.org/software/gcal/") (synopsis "Calculating and printing a wide variety of calendars") (description diff --git a/gnu/packages/guile-xyz.scm b/gnu/packages/guile-xyz.scm index b5b6a3b887b..ad45984a064 100644 --- a/gnu/packages/guile-xyz.scm +++ b/gnu/packages/guile-xyz.scm @@ -1189,7 +1189,20 @@ is not available for Guile 2.0.") (base32 "15ynxr3pfjscd6mz641zagv6i84jh9y65i5dnbb3j3q72j6bbvnb")) (patches '()))) - (arguments '()))) + (arguments + (if (target-aarch64?) + (list #:phases + #~(modify-phases %standard-phases + (add-before 'check 'disable-jit + (lambda _ + ;; XXX: Due to a JIT bug in Guile 3.0.9 on AArch64, + ;; some tests would hang: + ;; . + ;; Disable JIT for now; re-enable it when Guile 3.0.10+ + ;; is used. (Note: The Shepherd disables JIT on + ;; AArch64 so it can safely use Fibers.) + (setenv "GUILE_JIT_THRESHOLD" "-1"))))) + '())))) (define-public guile-fibers guile-fibers-1.4) diff --git a/gnu/packages/guile.scm b/gnu/packages/guile.scm index 773e3423f23..87a1649970b 100644 --- a/gnu/packages/guile.scm +++ b/gnu/packages/guile.scm @@ -502,7 +502,7 @@ without requiring the source code to be rewritten.") ;; The main goal here is to allow for '--with-branch'. (method git-fetch) (uri (git-reference - (url "https://git.savannah.gnu.org/git/guile.git") + (url "https://codeberg.org/guile/guile.git") (commit commit))) (file-name (git-file-name name version)) (sha256 diff --git a/gnu/packages/mail.scm b/gnu/packages/mail.scm index fba7b9e08d0..faeaeb70e2e 100644 --- a/gnu/packages/mail.scm +++ b/gnu/packages/mail.scm @@ -4353,16 +4353,16 @@ It is a replacement for the @command{urlview} program.") (define-public mumi (package (name "mumi") - (version "0.13.0") + (version "0.14.0") (source (origin (method git-fetch) (uri (git-reference - (url "https://git.savannah.gnu.org/git/guix/mumi.git/") + (url "https://codeberg.org/guix/mumi.git") (commit version))) (file-name (git-file-name name version)) (sha256 (base32 - "04mcd1xkdpvxlvpf4k4mvnwi06sdy8vy1di6gxxsr9msgdb366ir")))) + "1v5gjzh8idz926518c0bv0qsmyggr6lvqn5vksf5j0qdh6r6dar7")))) (build-system gnu-build-system) (arguments (list diff --git a/gnu/packages/ocaml.scm b/gnu/packages/ocaml.scm index a8410be78b7..8e9fe03a41e 100644 --- a/gnu/packages/ocaml.scm +++ b/gnu/packages/ocaml.scm @@ -1470,14 +1470,15 @@ Knuth’s LR(1) parser construction technique.") (wrap-program (string-append #$output "/bin/" "binsec") `("OCAMLPATH" ":" prefix ,ocamlpath)))))))) (inputs (list bash-minimal)) - (native-inputs (list gmp ocaml-qcheck ocaml-ounit2)) + (native-inputs (list gmp ocaml-qcheck ocaml-ounit2 z3)) (propagated-inputs (list dune-site ocaml-base ocaml-menhir ocaml-graph ocaml-zarith ocaml-grain-dypgen - ocaml-toml)) + ocaml-toml + ocaml-z3)) (synopsis "Binary-level analysis platform") (description "BINSEC is a binary analysis platform which implements analysis diff --git a/gnu/packages/package-management.scm b/gnu/packages/package-management.scm index 4fa251d6294..9b0c4f1857d 100644 --- a/gnu/packages/package-management.scm +++ b/gnu/packages/package-management.scm @@ -1931,7 +1931,7 @@ This package just includes the agent component."))) (define-public guix-jupyter (package (name "guix-jupyter") - (version "0.3.0") + (version "0.3.1") (home-page "https://codeberg.org/guix-science/guix-jupyter") (source (origin (method git-fetch) @@ -1939,7 +1939,7 @@ This package just includes the agent component."))) (commit (string-append "v" version)))) (sha256 (base32 - "0cvjxv60la2bqmwb7m2bfpvjy8hx1hmjk2qy9wfzaffcabgr0x44")) + "1yvrmaj4qcb9vn2nfjz1q0cil830hvmxpp8cgi76aylbnv36aask")) (file-name (string-append "guix-jupyter-" version "-checkout")))) (build-system gnu-build-system) (arguments diff --git a/gnu/packages/prolog.scm b/gnu/packages/prolog.scm index c6d5f42aa5b..7cdcc3c576a 100644 --- a/gnu/packages/prolog.scm +++ b/gnu/packages/prolog.scm @@ -185,7 +185,7 @@ it.") (define-public trealla (package (name "trealla") - (version "2.83.8") + (version "2.83.9") (source (origin (method git-fetch) @@ -194,7 +194,7 @@ it.") (url "https://github.com/trealla-prolog/trealla") (commit (string-append "v" version)))) (sha256 - (base32 "1bpfzrwsgbmjl1maiaw5b8ixkgh548gw1lkiznsjgkjm7dxr4ns4")) + (base32 "01gxml7g6qf185pa51v8vrsv1m42b3dz5rcnyqf7ic041s6p9bwl")) (file-name (git-file-name name version)))) (build-system gnu-build-system) (native-inputs diff --git a/gnu/packages/python-web.scm b/gnu/packages/python-web.scm index 3fe87747bfc..ee9b4aadaab 100644 --- a/gnu/packages/python-web.scm +++ b/gnu/packages/python-web.scm @@ -5294,7 +5294,10 @@ WebSocket usage in Python programs.") (method url-fetch) (uri (pypi-uri "websocket-client" version)) (sha256 - (base32 "0p0cz2mdissq7iw1n7jrmsfir0jfmgs1dvnpnrx477ffx9hbsxnk")))))) + (base32 "0p0cz2mdissq7iw1n7jrmsfir0jfmgs1dvnpnrx477ffx9hbsxnk")))) + (native-inputs + (modify-inputs (package-native-inputs python-websocket-client) + (append python-six))))) (define-public python-purl (package diff --git a/gnu/packages/python-xyz.scm b/gnu/packages/python-xyz.scm index e8e373652f9..f1fb89a2951 100644 --- a/gnu/packages/python-xyz.scm +++ b/gnu/packages/python-xyz.scm @@ -4809,6 +4809,31 @@ Unicode-to-LaTeX conversion.") @code{subprocess} feature.") (license license:expat))) +;; Old version just for python-dotenv-0.13.0 for docker-compose; remove once +;; that is updated. +(define-public python-sh-1 + (package + (inherit python-sh) + (version "1.14.2") + (source + (origin + (method url-fetch) + (uri (pypi-uri "sh" version)) + (sha256 + (base32 + "03gyss1rhj4in7pgysg4q0hxp3230whinlpy1532ljs99lrx0ywx")))) + ;(build-system python-build-system) + (arguments + '(#:phases + (modify-phases %standard-phases + (replace 'check + (lambda _ + ;; XXX: A Python 2 test fails when HOME=/homeless-shelter. + (setenv "HOME" "/tmp") + (invoke "python" "sh.py" "test")))))) + (native-inputs + (list python-setuptools)))) + (define-public python-cftime (package (name "python-cftime") @@ -20156,18 +20181,22 @@ and dataclasses.") (define-public python-argparse-manpage (package (name "python-argparse-manpage") - (version "4.5") + (version "4.7") (source (origin (method url-fetch) - (uri (pypi-uri "argparse-manpage" version)) + (uri (pypi-uri "argparse_manpage" version)) (sha256 - (base32 - "1nq4sq1zk1xzdsqq61hd27jhj978ys136aba1zjg02x1g0c0cg11")))) + (base32 "0clb20scp408gxac675v731vnj89pk9d5fb7pcy7bj1a45mvgshx")))) (build-system pyproject-build-system) + (arguments + (list + #:test-flags + ;; Tests require PIP. + #~(list "--ignore=tests/test_examples.py"))) (native-inputs - (list python-pip python-pytest python-setuptools python-tomli - python-wheel)) + (list python-pytest + python-setuptools)) (home-page "https://github.com/praiskup/argparse-manpage") (synopsis "Build manual page from Python's ArgumentParser object") (description @@ -36879,6 +36908,35 @@ systems in Python.") key-value pairs from a @code{.env} file and set them as environment variables.") (license license:bsd-3))) +;; Old version just for docker-compose; remove once that is updated. +(define-public python-dotenv-0.13.0 + (package (inherit python-dotenv) + (name "python-dotenv") + (version "0.13.0") + (source + (origin + (method url-fetch) + (uri (pypi-uri "python-dotenv" version)) + (sha256 + (base32 + "0x5dagmfn31phrbxlwacw3s4w5vibv8fxqc62nqcdvdhjsy0k69v")))) + (arguments + `(#:phases + (modify-phases %standard-phases + (replace 'check + (lambda* (#:key tests? inputs outputs #:allow-other-keys) + (when tests? + (add-installed-pythonpath inputs outputs) + (setenv "PATH" (string-append (getenv "PATH") ":" + (assoc-ref outputs "out") "/bin")) + ;; Skip the ipython tests. + (delete-file "tests/test_ipython.py") + (invoke "python" "-m" "pytest"))))))) + (native-inputs + (modify-inputs (package-native-inputs python-dotenv) + (append python-mock) + (replace "python-sh" python-sh-1))))) + (define-public date2name (let ((commit "6c8f37277e8ec82aa50f90b8921422be30c4e798") (revision "1")) diff --git a/gnu/services/web.scm b/gnu/services/web.scm index 3989607646e..ae33a25394d 100644 --- a/gnu/services/web.scm +++ b/gnu/services/web.scm @@ -19,6 +19,7 @@ ;;; Copyright © 2023 Miguel Ángel Moreno ;;; Copyright © 2024 Leo Nikkilä ;;; Copyright © 2025 Maxim Cournoyer +;;; Copyright © 2025 Rodion Goritskov ;;; ;;; This file is part of GNU Guix. ;;; @@ -40,8 +41,10 @@ #:use-module (gnu services shepherd) #:use-module (gnu services admin) #:use-module (gnu services configuration) + #:use-module (gnu services databases) #:use-module (gnu services getmail) #:use-module (gnu services mail) + #:use-module (gnu system file-systems) #:use-module (gnu system pam) #:use-module (gnu system shadow) #:use-module (gnu packages admin) @@ -59,7 +62,9 @@ #:use-module (gnu packages mail) #:use-module (gnu packages rust-apps) #:autoload (guix i18n) (G_) + #:autoload (gnu build linux-container) (%namespaces) #:use-module (guix diagnostics) + #:use-module (guix least-authority) #:use-module (guix packages) #:use-module (guix records) #:use-module (guix modules) @@ -74,6 +79,7 @@ #:use-module (srfi srfi-34) #:use-module (ice-9 match) #:use-module (ice-9 format) + #:use-module (ice-9 regex) #:export (httpd-configuration httpd-configuration? httpd-configuration-package @@ -328,7 +334,23 @@ agate-configuration-group agate-configuration-log-file - agate-service-type)) + agate-service-type + + miniflux-configuration + miniflux-configuration? + miniflux-configuration-listen-address + miniflux-configuration-base-url + miniflux-configuration-create-administrator-account? + miniflux-configuration-administrator-account-name + miniflux-configuration-administrator-account-password + miniflux-configuration-run-migrations? + miniflux-configuration-database-url + miniflux-configuration-user + miniflux-configuration-group + miniflux-configuration-log-file + miniflux-configuration-extra-settings + + miniflux-service-type)) ;;; Commentary: ;;; @@ -2279,3 +2301,173 @@ root=/srv/gemini (default-value (agate-configuration)) (description "Run Agate, a simple Gemini protocol server written in Rust."))) + +(define (serialize-string field-name val) + (format #f "~a=~a\n" field-name val)) + +(define (string-or-file-path? val) + (string? val)) +(define (serialize-string-or-file-path field-name val) + (serialize-string (if (absolute-file-name? val) + (format #f "~a_FILE" field-name) field-name) val)) +(define-maybe string-or-file-path) + +(define (serialize-list field-name val) + (string-append (string-join val "\n") "\n")) +(define-maybe list) + +(define (serialize-boolean field-name val) + (if val (serialize-string field-name "1") (serialize-string field-name "0"))) + +(define-configuration/no-serialization miniflux-configuration + (listen-address + (string "127.0.0.1:8080") + "Address to listen on. +Use absolute path like @code{\"/var/run/miniflux/miniflux.sock\"} for a Unix socket.") + (base-url + (string "http://127.0.0.1/") + "Base URL to generate HTML links and base path for cookies.") + (create-administrator-account? + (boolean #f) + "Create an initial administrator account.") + (administrator-account-name + maybe-string-or-file-path + "Initial administrator account name as a string or an absolute path to a file with a account name inside.") + (administrator-account-password + maybe-string-or-file-path + "Initial administrator account password as a string or an absolute path to a file with a password inside.") + (run-migrations? + (boolean #t) + "Run database migrations during application startup.") + (database-url + (string "host=/var/run/postgresql") + "PostgreSQL connection string.") + (user + (string "miniflux") + "User name for Postgresql and system account.") + (group + (string "miniflux") + "Group for the system account.") + (log-file + (string "/var/log/miniflux.log") + "Path to the log file.") + (extra-settings + maybe-list + "Extra configuration parameters as a list of strings.")) + +(define (miniflux-serialize-configuration config) + (match-record config + (listen-address base-url create-administrator-account? + administrator-account-name administrator-account-password + run-migrations? database-url extra-settings) + (string-append (serialize-string "LISTEN_ADDR" listen-address) + (serialize-string "BASE_URL" base-url) + (serialize-boolean "CREATE_ADMIN" create-administrator-account?) + (serialize-maybe-string-or-file-path "ADMIN_USERNAME" administrator-account-name) + (serialize-maybe-string-or-file-path "ADMIN_PASSWORD" administrator-account-password) + (serialize-boolean "RUN_MIGRATIONS" run-migrations?) + (serialize-string "DATABASE_URL" database-url) + (serialize-maybe-list #f extra-settings)))) + +(define (miniflux-configuration-file config) + (mixed-text-file "miniflux.conf" (miniflux-serialize-configuration config))) + +(define (pair->file-system-mapping pair previous) + (if (pair? pair) + (let ((path (car pair)) + (writable (cdr pair))) + (if (or (and (string? path) + (absolute-file-name? path)) + (computed-file? path)) + (append previous (list (file-system-mapping + (source path) + (target source) + (writable? writable)))) + previous)) + previous)) + +(define (miniflux-shepherd-service config) + (match-record config + (user group log-file database-url listen-address + administrator-account-name administrator-account-password) + (let ((config-file (miniflux-configuration-file config))) + (list (shepherd-service + (documentation "Run Miniflux server") + (provision '(miniflux)) + (requirement '(postgres networking)) + (start #~(make-forkexec-constructor + (list #$(least-authority-wrapper + (file-append miniflux "/bin/miniflux") + #:name "miniflux" + #:user user + #:group group + #:preserved-environment-variables + (append %default-preserved-environment-variables + '("SSL_CERT_FILE")) + #:mappings + (fold pair->file-system-mapping + '() + `((,log-file . #t) + (,config-file . #f) + ("/etc/ssl/certs/ca-certificates.crt" . #f) + (,administrator-account-name . #f) + (,administrator-account-password . #f) + (,(dirname listen-address) . #t) + ,(let* ((db-socket-match (string-match ".*host=(/[^ ]*).*" database-url)) + (db-socket (if db-socket-match (match:substring db-socket-match 1) #f))) + (if db-socket + `(,db-socket . #t))))) + #:namespaces + (fold delq %namespaces '(net user))) + "-config-file" + #$config-file) + #:log-file #$log-file)) + (stop #~(make-kill-destructor))))))) + +(define (miniflux-accounts config) + (match-record config + (user group) + `(,(user-group + (name group) + (system? #t)) + ,(user-account + (name user) + (group group) + (system? #t) + (comment "miniflux server user") + (home-directory "/var/empty") + (shell (file-append shadow "/sbin/nologin")))))) + +(define (miniflux-postgresql-role config) + (list (postgresql-role + (name (miniflux-configuration-user config)) + (create-database? #t)))) + +(define (miniflux-log-files config) + (list (miniflux-configuration-log-file config))) + +(define (miniflux-activation-service-type config) + (match-record config + (user listen-address) + #~(begin + (use-modules (gnu build activation)) + (let ((user (getpwnam #$user))) + (if (absolute-file-name? #$listen-address) + (mkdir-p/perms (dirname #$listen-address) user #o755)))))) + +(define miniflux-service-type + (service-type + (name 'miniflux) + (default-value (miniflux-configuration)) + (extensions + (list (service-extension account-service-type + miniflux-accounts) + (service-extension postgresql-role-service-type + miniflux-postgresql-role) + (service-extension shepherd-root-service-type + miniflux-shepherd-service) + (service-extension log-rotation-service-type + miniflux-log-files) + (service-extension activation-service-type + miniflux-activation-service-type))) + (description "Run Miniflux, minimalist feed reader"))) diff --git a/gnu/tests/web.scm b/gnu/tests/web.scm index 431996ede48..419b5f0b5bf 100644 --- a/gnu/tests/web.scm +++ b/gnu/tests/web.scm @@ -5,6 +5,7 @@ ;;; Copyright © 2018 Pierre-Antoine Rouby ;;; Copyright © 2018 Marius Bakke ;;; Copyright © 2024 Maxim Cournoyer +;;; Copyright © 2025 Rodion Goritskov ;;; ;;; This file is part of GNU Guix. ;;; @@ -37,6 +38,7 @@ #:use-module (gnu packages base) #:use-module (gnu packages databases) #:use-module (gnu packages guile-xyz) + #:use-module (gnu packages gnupg) #:use-module (gnu packages patchutils) #:use-module (gnu packages python) #:use-module (gnu packages tls) @@ -56,7 +58,10 @@ %test-hpcguix-web %test-anonip %test-patchwork - %test-agate)) + %test-agate + %test-miniflux-admin-string + %test-miniflux-admin-file + %test-miniflux-socket)) (define %index.html-contents ;; Contents of the /index.html file. @@ -848,3 +853,190 @@ HTTP-PORT." (name "agate") (description "Connect to a running Agate service.") (value (run-agate-test name %agate-os %index.gmi-contents)))) + + +;;; +;;; Miniflux +;;; + +(define %miniflux-create-admin-credentials + #~(begin + (mkdir "/var/miniflux") + (call-with-output-file "/var/miniflux/admin-username" + (lambda (port) + (display "test" port))) + (call-with-output-file "/var/miniflux/admin-password" + (lambda (port) + (display "testpassword" port))))) + +(define miniflux-base-system + (lambda (miniflux-config) + (simple-operating-system + (simple-service 'create-admin-credentials + activation-service-type + %miniflux-create-admin-credentials) + (service dhcpcd-service-type) + (service postgresql-service-type + (postgresql-configuration + (postgresql postgresql-13))) + (service miniflux-service-type + miniflux-config)))) + +(define %miniflux-with-admin-as-string + (miniflux-base-system + (miniflux-configuration + (listen-address "0.0.0.0:8080") + (create-administrator-account? #t) + (administrator-account-name "test") + (administrator-account-password "testpassword")))) + +(define %miniflux-with-admin-as-file + (miniflux-base-system + (miniflux-configuration + (listen-address "0.0.0.0:8080") + (create-administrator-account? #t) + (administrator-account-name "/var/miniflux/admin-username") + (administrator-account-password "/var/miniflux/admin-password")))) + +(define %miniflux-with-socket + (miniflux-base-system + (miniflux-configuration + (listen-address "/var/run/miniflux/miniflux.sock")))) + +(define* (run-miniflux-test name test-os) + (define os + (marionette-operating-system + test-os + #:imported-modules '((gnu services herd) + (guix combinators)))) + + (define forwarded-port 8080) + + (define vm + (virtual-machine + (operating-system os) + (memory-size 512) + (port-forwardings `((8080 . ,forwarded-port))))) + + (define test + (with-extensions (list guile-gcrypt) + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (srfi srfi-64) + (srfi srfi-11) + (gnu build marionette) + (web client) + (web uri) + (web response) + (ice-9 match) + (ice-9 iconv) + (gcrypt base64)) + + (define marionette + (make-marionette (list #$vm))) + + (test-runner-current (system-test-runner #$output)) + (test-begin #$name) + + (test-assert "Check Miniflux service is running" + (begin + (#$retry-on-error + (lambda () + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service '#$(string->symbol "miniflux")) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + (#f #f) + ((running) #t))))) + marionette)) + #:delay 1 + #:times 10))) + + (test-assert "Miniflux TCP port ready, IPv4" + (wait-for-tcp-port #$forwarded-port marionette)) + + (test-assert "Miniflux login page is opened" + (begin + (wait-for-tcp-port #$forwarded-port marionette) + (#$retry-on-error + (lambda () + (let-values (((_ text) + (http-get + #$(format #f "http://localhost:~A/" forwarded-port) + #:decode-body? #t))) + (string-contains text "Sign In - Miniflux"))) + #:times 10 + #:delay 2))) + + (define authorization-header + (let ((encoded (base64-encode (string->bytevector "test:testpassword" "utf-8")))) + `(authorization . (basic . ,encoded)))) + + (test-equal "Miniflux initial admin API call is successful" + 200 + (begin + (wait-for-tcp-port #$forwarded-port marionette) + (#$retry-on-error + (lambda () + (let-values (((response _) + (http-get #$(format #f "http://localhost:~A/v1/me" forwarded-port) + #:headers (list authorization-header) + #:decode-body? #t))) + + (response-code response))) + #:times 10 + #:delay 2))) + + (test-end))))) + (gexp->derivation (string-append name "-test") test)) + +(define* (run-miniflux-socket-test name test-os) + (define os + (marionette-operating-system + test-os + #:imported-modules '((gnu services herd) + (guix combinators)))) + + (define vm + (virtual-machine + (operating-system os) + (memory-size 512))) + + (define test + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (srfi srfi-64) + (gnu build marionette)) + + (define marionette + (make-marionette (list #$vm))) + + (test-runner-current (system-test-runner #$output)) + (test-begin #$name) + + (test-assert "Check socket file is created" + (wait-for-unix-socket "/var/run/miniflux/miniflux.sock" marionette)) + + (test-end)))) + (gexp->derivation (string-append name "-test") test)) + +(define %test-miniflux-admin-string + (system-test + (name "miniflux-admin-string") + (description "Run Miniflux with initial admin credentials as string.") + (value (run-miniflux-test name %miniflux-with-admin-as-string)))) + +(define %test-miniflux-admin-file + (system-test + (name "miniflux-admin-file") + (description "Run Miniflux with initial admin credentials as file.") + (value (run-miniflux-test name %miniflux-with-admin-as-file)))) + +(define %test-miniflux-socket + (system-test + (name "miniflux-socket") + (description "Run Miniflux on unix socket.") + (value (run-miniflux-socket-test name %miniflux-with-socket))))