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:
108
doc/guix.texi
108
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
|
||||
|
||||
@@ -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?
|
||||
|
||||
Reference in New Issue
Block a user