This is an automated email from the git hooks/post-receive script.

guix_mirror_bot pushed a commit to branch master
in repository guix.

The following commit(s) were added to refs/heads/master by this push:
     new 0c506e6f52 gnu: services: Add gunicorn-service-type.
0c506e6f52 is described below

commit 0c506e6f526e51d0dd9c69e4141711bc8ab7659c
Author: Fabio Natali <[email protected]>
AuthorDate: Sun Feb 8 11:16:38 2026 +0000

    gnu: services: Add gunicorn-service-type.
    
    * gnu/services/web.scm
    (<gunicorn-configuration>, <gunicorn-app>): New records.
    (unix-socket?, unix-socket-path, gunicorn-activation,
    gunicorn-shepherd-services): New procedures.
    (gunicorn-service-type): New variable.
    * doc/guix.texi (Web Services): Document the new service.
    
    Co-authored-by: Arun Isaac <[email protected]>
    Change-Id: I3aa970422e6a5d31158b798b1061e6928ad2160b
    Signed-off-by: jgart <[email protected]>
---
 doc/guix.texi        | 108 +++++++++++++++++++++++++++
 gnu/services/web.scm | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 308 insertions(+), 3 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index de1dc149ea..70bee4ad0c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -35165,6 +35165,114 @@ flattened into one line.
 @end table
 @end deftp
 
+@subsubheading gunicorn
+@cindex gunicorn
+
+@defvar gunicorn-service-type
+Service type for the @uref{https://gunicorn.org/,gunicorn} Python Web
+Server Gateway Interface (WSGI) HTTP server.  The value for this
+service type is a @code{<gunicorn-configuration>} record.  A simple
+example follows where gunicorn is used in combination with a web
+server, Nginx, configured as a reverse proxy.
+
+@lisp
+(define %socket "unix:/var/run/gunicorn/werkzeug/socket")
+(define %body (format #f "proxy_pass http://~a;"; %socket))
+
+(operating-system
+  ;; ...
+  (services
+    ;; ...
+    (service gunicorn-service-type
+             (gunicorn-configuration
+              (apps
+               (list (gunicorn-app
+                      (name "werkzeug")
+                      (package python-werkzeug)
+                      (wsgi-app-module "werkzeug.testapp:test_app")
+                      (sockets `(,%socket))
+                      (user "user")
+                      (group "users"))))))
+    (service nginx-service-type
+             (nginx-configuration
+              (server-blocks
+               (list (nginx-server-configuration
+                      (server-name '("localhost"))
+                      (listen '("127.0.0.1:80"))
+                      (locations
+                       (list (nginx-location-configuration
+                              (uri "/")
+                              (body `(,%body))))))))))))
+@end lisp
+@end defvar
+
+In practice, it is likely one might want to use
+@code{gunicorn-service-type} by extending it from within a Python
+service that requires gunicorn.
+
+@deftp {Data Type} gunicorn-configuration
+This data type represents the configuration for gunicorn.
+
+@table @asis
+@item @code{gunicorn} (default: @code{gunicorn})
+The gunicorn package to use.
+
+@item @code{apps} (default: @code{'()})
+This is a list of gunicorn apps.
+
+@end table
+@end deftp
+
+@deftp {Data Type} gunicorn-app
+This data type represents the configuration for a gunicorn
+application.
+
+@table @asis
+@item @code{name} (type: string)
+The name of the gunicorn application.
+
+@item @code{package} (type: symbol)
+The Python package associated to the gunicorn app.
+
+@item @code{wsgi-app-module} (type: string)
+The Python module that should be invoked by gunicorn.
+
+@item @code{user} (type: string)
+Launch the app as this user (it must be an existing user).
+
+@item @code{group} (type: string)
+Launch the app as this group (it must be an existing group).
+
+@item @code{sockets} (default: 
@code{'("unix:/var/run/gunicorn/APP-NAME/socket")}) (type: list-of-strings)
+A list of sockets (as path strings) which gunicorn will be listening
+on.  This list must contain at least one socket.
+
+@item @code{workers} (default: @code{1}) (type: integer)
+The number of workers for the gunicorn app.
+
+@item @code{extra-cli-arguments} (default: @code{'()}) (type: list-of-strings)
+A list of extra arguments to be passed to gunicorn.
+
+@item @code{environment-variables} (default: @code{'()}) (type: list)
+An association list of environment variables that will be visible to
+the gunicorn app, as per this example:
+
+@lisp
+(gunicorn-app (name "example")
+              ...
+              (environment-variables '(("foo" . "bar"))))
+@end lisp
+
+@item @code{timeout} (default: @code{30}) (type: integer)
+Workers silent for more than this many seconds are killed and
+restarted.
+
+@item @code{mappings} (default: @code{'()}) (type: list)
+Volumes that will be visible to the gunicorn app, as a list of
+source-target pairs.
+@end table
+@end deftp
+
 @subsubheading Varnish Cache
 @cindex Varnish
 Varnish is a fast cache server that sits in between web applications
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index 3354b3295b..7df1c66b9f 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -11,7 +11,7 @@
 ;;; Copyright © 2019, 2020 Florian Pelz <[email protected]>
 ;;; Copyright © 2020, 2022 Ricardo Wurmus <[email protected]>
 ;;; Copyright © 2020 Tobias Geerinckx-Rice <[email protected]>
-;;; Copyright © 2020, 2025 Arun Isaac <[email protected]>
+;;; Copyright © 2020, 2025, 2026 Arun Isaac <[email protected]>
 ;;; Copyright © 2020 Oleg Pykhalov <[email protected]>
 ;;; Copyright © 2020, 2021 Alexandru-Sergiu Marton <[email protected]>
 ;;; Copyright © 2022 Simen Endsjø <[email protected]>
@@ -20,6 +20,7 @@
 ;;; Copyright © 2024 Leo Nikkilä <[email protected]>
 ;;; Copyright © 2025 Maxim Cournoyer <[email protected]>
 ;;; Copyright © 2025 Rodion Goritskov <[email protected]>
+;;; Copyright © 2026 Fabio Natali <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -65,12 +66,14 @@
   #:autoload   (guix i18n) (G_)
   #:autoload   (gnu build linux-container) (%namespaces)
   #:use-module (guix diagnostics)
+  #:use-module (guix gexp)
   #:use-module (guix least-authority)
+  #:use-module (guix modules)
   #:use-module (guix packages)
+  #:use-module (guix profiles)
   #:use-module (guix records)
-  #:use-module (guix modules)
+  #:use-module (guix search-paths)
   #:use-module (guix utils)
-  #:use-module (guix gexp)
   #:use-module ((guix store) #:select (text-file))
   #:use-module ((guix utils) #:select (version-major))
   #:use-module ((guix packages) #:select (package-version))
@@ -81,6 +84,7 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
   #:use-module (ice-9 regex)
+  #:use-module (web uri)
   #:export (httpd-configuration
             httpd-configuration?
             httpd-configuration-package
@@ -170,6 +174,25 @@
             nginx-service
             nginx-service-type
 
+            gunicorn-app
+            gunicorn-app-environment-variables
+            gunicorn-app-extra-cli-arguments
+            gunicorn-app-group
+            gunicorn-app-mappings
+            gunicorn-app-name
+            gunicorn-app-package
+            gunicorn-app-sockets
+            gunicorn-app-timeout
+            gunicorn-app-user
+            gunicorn-app-workers
+            gunicorn-app-wsgi-app-module
+            gunicorn-app?
+            gunicorn-configuration
+            gunicorn-configuration-apps
+            gunicorn-configuration-package
+            gunicorn-configuration?
+            gunicorn-service-type
+
             fcgiwrap-configuration
             fcgiwrap-configuration?
             fcgiwrap-service-type
@@ -1002,6 +1025,180 @@ renewed TLS certificates, or @code{include}d files.")
                 (default-value (nginx-configuration))
                 (description "Run the nginx Web server.")))
 
+(define-record-type* <gunicorn-configuration>
+  gunicorn-configuration make-gunicorn-configuration
+  gunicorn-configuration?
+  (package gunicorn-configuration-package
+           (default gunicorn))
+  (apps gunicorn-configuration-apps
+        (default '())))
+
+(define (sanitize-gunicorn-app-sockets value)
+  (unless (and (list? value)
+               (not (null? value)))
+    (leave (G_ "gunicorn: '~a' is invalid; must be a non-empty list~%") value))
+  value)
+
+(define-record-type* <gunicorn-app>
+  gunicorn-app make-gunicorn-app
+  gunicorn-app?
+  this-gunicorn-app
+  (name gunicorn-app-name)
+  (package gunicorn-app-package)
+  (wsgi-app-module gunicorn-app-wsgi-app-module)
+  (user gunicorn-app-user)
+  (group gunicorn-app-group)
+  (sockets gunicorn-app-sockets
+           (default (list (string-append "unix:/var/run/gunicorn/"
+                                         (gunicorn-app-name this-gunicorn-app)
+                                         "/socket")))
+           (sanitize sanitize-gunicorn-app-sockets)
+           (thunked))
+  (workers gunicorn-app-workers
+           (default 1))
+  (extra-cli-arguments gunicorn-app-extra-cli-arguments
+                       (default '()))
+  (environment-variables gunicorn-app-environment-variables
+                         (default '()))
+  (timeout gunicorn-app-timeout
+           (default 30))
+  (mappings gunicorn-app-mappings
+            (default '())))
+
+(define (unix-socket? string)
+  "Whether STRING indicates a Unix socket."
+  (eq? 'unix (uri-scheme (string->uri string))))
+
+(define (socket-dir string)
+  "Return the socket directory."
+  (dirname (uri-path (string->uri string))))
+
+(define (gunicorn-activation config)
+  (with-imported-modules '((guix build utils))
+    #~(begin
+        (use-modules (guix build utils)
+                     (ice-9 match))
+
+        ;; Create socket directories and set ownership.
+        (for-each (match-lambda
+                    ((user group socket-directories ...)
+                     (for-each (lambda (socket-directory)
+                                 (mkdir-p socket-directory)
+                                 (chown socket-directory
+                                        (passwd:uid (getpw user))
+                                        (group:gid (getgrnam group))))
+                               socket-directories)))
+                  '#$(map (lambda (app)
+                            (cons* (gunicorn-app-user app)
+                                   (gunicorn-app-group app)
+                                   (filter-map (lambda (socket)
+                                                 (and
+                                                  (unix-socket? socket)
+                                                  (socket-dir socket)))
+                                               (gunicorn-app-sockets app))))
+                          (gunicorn-configuration-apps config))))))
+
+(define (gunicorn-shepherd-services config)
+  (map (lambda (app)
+         (let ((name (string-append "gunicorn-" (gunicorn-app-name app)))
+               (user (gunicorn-app-user app))
+               (group (gunicorn-app-group app)))
+           (shepherd-service
+            (documentation (string-append "Run gunicorn for app "
+                                          (gunicorn-app-name app)
+                                          "."))
+            (provision (list (string->symbol name)))
+            (requirement '(networking))
+            (modules '((guix search-paths)
+                       (ice-9 match)))
+            (start
+             (let* ((app-manifest (packages->manifest
+                                   ;; Using python-minimal in the
+                                   ;; manifest creates collisions with
+                                   ;; the python in the app package.
+                                   (list python
+                                         (gunicorn-app-package app))))
+                    (app-profile (profile
+                                  (content app-manifest)
+                                  (allow-collisions? #t))))
+               (with-imported-modules
+                   (source-module-closure '((guix search-paths)))
+                 #~(make-forkexec-constructor
+                    (cons*
+                     #$(least-authority-wrapper
+                        (file-append (gunicorn-configuration-package config)
+                                     "/bin/gunicorn")
+                        #:name (string-append name "-pola-wrapper")
+                        #:mappings
+                        (cons (file-system-mapping
+                               ;; Mapping the app package
+                               (source app-profile)
+                               (target source))
+                              (append
+                               ;; Mappings for Unix socket directories
+                               (filter-map
+                                (lambda (socket)
+                                  (and (unix-socket? socket)
+                                       (file-system-mapping
+                                        (source (socket-dir socket))
+                                        (target source)
+                                        (writable? #t))))
+                                (gunicorn-app-sockets app))
+                               ;; Additional mappings
+                               (gunicorn-app-mappings app)))
+                        #:preserved-environment-variables
+                        (map search-path-specification-variable
+                             (manifest-search-paths app-manifest))
+                        #:namespaces (delq 'net %namespaces))
+                     "--workers" #$(number->string (gunicorn-app-workers app))
+                     "--timeout" #$(number->string (gunicorn-app-timeout app))
+                     (list
+                      #$@(append
+                          (append-map (lambda (socket)
+                                        (list "--bind" socket))
+                                      (gunicorn-app-sockets app))
+                          (append-map
+                           (lambda (pair)
+                             (list "--env"
+                                   #~(string-append
+                                      #$(car pair) "=" #$(cdr pair))))
+                           (gunicorn-app-environment-variables app))
+                          (gunicorn-app-extra-cli-arguments app)
+                          (list (gunicorn-app-wsgi-app-module app)))))
+                    #:user #$user
+                    #:group #$group
+                    #:environment-variables
+                    (map (match-lambda
+                           ((spec . value)
+                            (string-append
+                             (search-path-specification-variable spec)
+                             "="
+                             value)))
+                         (evaluate-search-paths
+                          (map sexp->search-path-specification
+                               '#$(map search-path-specification->sexp
+                                       (manifest-search-paths app-manifest)))
+                          (list #$app-profile)))
+                    #:log-file #$(string-append "/var/log/" name ".log")))))
+            (stop #~(make-kill-destructor)))))
+       (gunicorn-configuration-apps config)))
+
+(define gunicorn-service-type
+  (service-type
+   (name 'gunicorn)
+   (description "Run gunicorn.")
+   (extensions (list (service-extension activation-service-type
+                                        gunicorn-activation)
+                     (service-extension shepherd-root-service-type
+                                        gunicorn-shepherd-services)))
+   (compose concatenate)
+   (extend (lambda (config apps)
+             (gunicorn-configuration
+              (inherit config)
+              (apps (append (gunicorn-configuration-apps config)
+                            apps)))))
+   (default-value (gunicorn-configuration))))
+
 (define-record-type* <fcgiwrap-configuration> fcgiwrap-configuration
   make-fcgiwrap-configuration
   fcgiwrap-configuration?

Reply via email to