1
0
mirror of https://git.savannah.gnu.org/git/guix.git synced 2026-04-06 13:10:33 +02:00

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 <arunisaac@systemreboot.net>
Change-Id: I3aa970422e6a5d31158b798b1061e6928ad2160b
Signed-off-by: jgart <jgart@dismail.de>
This commit is contained in:
Fabio Natali
2026-02-08 11:16:38 +00:00
committed by jgart
parent 3a0ec29019
commit 0c506e6f52
2 changed files with 311 additions and 6 deletions

View File

@@ -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

View File

@@ -11,7 +11,7 @@
;;; Copyright © 2019, 2020 Florian Pelz <pelzflorian@pelzflorian.de>
;;; Copyright © 2020, 2022 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
;;; Copyright © 2020, 2025 Arun Isaac <arunisaac@systemreboot.net>
;;; Copyright © 2020, 2025, 2026 Arun Isaac <arunisaac@systemreboot.net>
;;; Copyright © 2020 Oleg Pykhalov <go.wigust@gmail.com>
;;; Copyright © 2020, 2021 Alexandru-Sergiu Marton <brown121407@posteo.ro>
;;; Copyright © 2022 Simen Endsjø <simendsjo@gmail.com>
@@ -20,6 +20,7 @@
;;; Copyright © 2024 Leo Nikkilä <hello@lnikki.la>
;;; Copyright © 2025 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2025 Rodion Goritskov <rodion@goritskov.com>
;;; Copyright © 2026 Fabio Natali <me@fabionatali.com>
;;;
;;; 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 least-authority)
#:use-module (guix packages)
#:use-module (guix records)
#:use-module (guix modules)
#:use-module (guix utils)
#: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 search-paths)
#:use-module (guix utils)
#: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?