Patchwork [WIP,1/2] services: Add php-fpm service.

login
register
mail settings
Submitter Hartmut Goebel
Date Dec. 8, 2016, 5:52 p.m.
Message ID <1481219530-786-2-git-send-email-h.goebel@crazy-compilers.com>
Download mbox | patch
Permalink /patch/18296/
State New
Headers show

Comments

Hartmut Goebel - Dec. 8, 2016, 5:52 p.m.
* gnu/services/php.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
---
 gnu/local.mk         |   1 +
 gnu/services/php.scm | 568 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 569 insertions(+)
 create mode 100644 gnu/services/php.scm

Patch

diff --git a/gnu/local.mk b/gnu/local.mk
index eec0e01..a42f6cd 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -413,6 +413,7 @@  GNU_SYSTEM_MODULES =				\
   %D%/services/mcron.scm			\
   %D%/services/networking.scm			\
   %D%/services/nfs.scm			\
+  %D%/services/php.scm				\
   %D%/services/shepherd.scm			\
   %D%/services/herd.scm				\
   %D%/services/sddm.scm				\
diff --git a/gnu/services/php.scm b/gnu/services/php.scm
new file mode 100644
index 0000000..e45b54f
--- /dev/null
+++ b/gnu/services/php.scm
@@ -0,0 +1,568 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2016 Hartmut Goebel <h.goebel@crazy-compilers.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+;;;
+;;; Some of the help text was taken from the default php-fpm.conf files.
+
+(define-module (gnu services php)
+  #:use-module (gnu services)
+  #:use-module (gnu services base)
+  #:use-module (gnu services configuration)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system shadow)
+  #:use-module (gnu packages php)
+  #:use-module (guix records)
+  #:use-module (guix packages)
+  #:use-module (guix gexp)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex) ;; for uglify-field-name
+  #:export (php-fpm-service
+            php-ini
+            php-fpm-configuration
+            php-fpm-pool))
+
+(define (uglify-field-name field-name)
+  (let ((str (symbol->string field-name)))
+    (if (string-suffix? "?" str)
+        (substring str 0 (1- (string-length str)))
+        (regexp-substitute/global #f "-"  str 'pre "_" 'post))))
+(define (serialize-field field-name val)
+  (format #t "~a = ~a\n" (uglify-field-name field-name) val))
+
+(define (serialize-string field-name val)
+  (serialize-field field-name val))
+
+(define (serialize-space-separated-string-list field-name val)
+  (serialize-field field-name (string-join val " ")))
+
+(define (serialize-boolean field-name val)
+  (serialize-string field-name (if val "on" "off")))
+
+(define (non-negative-integer? val)
+  (and (exact-integer? val) (not (negative? val))))
+(define (serialize-non-negative-integer field-name val)
+  (serialize-field field-name val))
+
+(define (optional-string? val)
+  (or (and (boolean? val) (not val)) (string? val)))
+(define (serialize-optional-string field-name val)
+  (when (not (boolean? val))
+        (serialize-field field-name val)))
+
+(define (section? val) #t)
+(define (serialize-section field-name val)
+  (format #t "\n[~a]\n" val))
+
+(define (production? val)
+  (boolean? val))
+(define (serialize-production field-name val)
+  (serialize-boolean 'display-errors (not val))
+  (serialize-boolean 'display-startup-errors (not val))
+  (serialize-boolean 'track-errors (not val))
+  (serialize-string  'error-reporting
+                     (if val "E_ALL & ~E_DEPRECATED & ~E_STRICT" 
+                             "E_ALL")))
+
+(define (pools-list? val)
+  (and (list? val) (and-map php-fpm-pool? val)))
+(define (serialize-pools-list field-name val)
+  (for-each (lambda (val) (serialize-php-fpm-pool field-name val)) val))
+
+(define (serialize-php-fpm-pool field-name val)
+  (format #t "\n\n; Start a new pool.")
+  (serialize-configuration val php-fpm-pool-fields))
+
+;;; ---- php.ini ---
+
+(define-configuration php-ini
+  (php
+   (package php)
+   "The php package to use.")
+
+  (PHP (section "PHP") "The global options section")
+
+  (production
+   (production #t)
+   "This variable determines if the @file{php.ini} configuration
+will be configured towards 'production' systems (don't display
+errors), or 'development' systems (display all errors).")
+
+ (max-execution-time
+  (non-negative-integer 30)
+  "Specify default maximum execution time, in seconds.")
+
+ (max-input-time
+  (non-negative-integer 60)
+  "Specify default maximum input time, in seconds.")
+
+ (memory-limit 
+  (string "128M")
+  "Specify maximum memory limit for PHP processes, in megabytes.")
+
+ (post-max-size 
+  (string "8M")
+  "Specify maximum size of the POST data, in megabytes.")
+
+ (file-uploads 
+  (boolean #t)
+  "Enable or disable file uploading in PHP applications.")
+
+ (upload-max-filesize 
+  (string "8M") ;; TODO default to what has been passed to post-max-size
+  "Specify maximum size of uploaded files, in megabytes.")
+
+ (max-file-uploads 
+  (non-negative-integer 20)
+  "Specify maximum number of files uploaded at once.")
+
+ (default-charset 
+   (string "UTF-8")
+   "Specify default charset used in PHP environment.")
+
+ (allow-url-fopen 
+  (boolean #f)
+  "Enable or disable access to remote URLs in PHP applications.")
+
+ ;; CGI
+ (CGI (section "CGI") "The CGI section")
+
+ (cgi.fix-pathinfo
+  (boolean #f)
+  "Enable or disable 'cgi.fix_pathinfo' option in PHP.  This is highly
+dependent on the used webserver (nginx should have the option disabled,
+apache2 needs it to be enabled).")
+
+ ;; Date
+ (Date (section "Date") "The Date section")
+
+ (date.timezone 
+  (string "Etc/UTC")  ;; TODO: Take from system timezone
+  "Configure the PHP timezone."))
+
+;;; ---- php-fpm.conf ----
+
+(define-configuration php-fpm-configuration
+  ;; All relative paths in this configuration file are relative to PHP's
+  ;; install prefix.
+  (global (section "global") "The global options section")
+
+  ;;(pid "/var/run/php/php-fpm.pid") configured on command line
+
+  (error-log
+   (string "/var/log/php-fpm.log")
+   "Path to the 'error.log' file which is used by PHP-FPM to log
+error messages.  If it's set to 'syslog', error logs are sent to the
+local log daemon.")
+
+  (syslog.ident
+   (string "php-fpm")
+   "When 'syslog' logging is enabled, specify the program identification
+string used by PHP-FPM.  This should be one word string, without spaces.")
+
+  (syslog.facility
+   (string "daemon")
+   "When 'syslog' logging is enabled, specify the 'syslog' facility to use.")
+
+  (log-level
+   (string "notice")
+   "When 'syslog' logging is enabled, specify the log level used by PHP-FPM.")
+
+  (emergency-restart-threshold
+   (non-negative-integer 0)
+   "Specify number of PHP-FPM child processes that exit with errors
+during a given interval (see below) that will trigger an automatic
+restart of the master PHP-FPM process.")
+
+  (emergency-restart-interval
+   (non-negative-integer 0)
+   "Specify the interval which is used to determine
+emergency-restart-threshold.")
+
+  (process-control-timeout
+   (non-negative-integer 0)
+   "Specify maximum wait time the master PHP-FPM process waits for a reaction
+on the signals sent to the child processes.")
+
+  (process.max 
+   (non-negative-integer 128)
+   "Maximum number of PHP-FPM child processes.")
+
+  ;; not yet implemented
+  ;; ("process.priority"  "-19")
+  ;; ("daemonize  "yes"
+  ;; ("rlimit_files"  "1024")
+  ;; (rlimit_core  0")
+  ;; ("events.mechanism"  "epoll")
+  ;; ("systemd_interval"  "10")
+
+  (pools
+   (pools-list (error "At least one pool is required."))
+   "Pools defined by this service."))
+
+;;; ---- pool-configuration ----
+
+(define-configuration php-fpm-pool
+  (name
+   (section (error "pool name is required"))
+   "Name for this pool.")
+
+  ;; TODO: prefix (think about whether we want it or not)
+
+  (user
+   (string "$pool")
+   ;; TODO: Rethink: The username of the user that owns the site files.
+   "The user to run the processes.")
+  (group
+   (string "$pool") ;; TODO: check what happens if empty
+   "Unix group of processes.  If the group is not set, the default user's group
+will be used.")
+
+  (listen
+   (string "/var/run/php-fpm-$pool.sock")
+    "The address on which to accept FastCGI requests.
+Valid syntaxes are:
+  'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4
+                            address on a specific port;
+  '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6
+                            address on a specific port;
+  'port'                 - to listen on a TCP socket to all addresses
+                           (IPv6 and IPv4-mapped) on a specific port;
+  '/path/to/unix/socket' - to listen on a unix socket.")
+
+  (listen.owner
+   (string "nginx")
+   "The system user that will be the owner of the PHP-FPM socket.  This should
+be the username of the webserver account, so that it can use the socket to
+communicate with the PHP-FPM process.  This account needs to exist before the
+PHP-FPM process is started (the 'nginx' account is created by default on
+GuixSD systems when using the nginx-service).")
+
+  (listen.group
+   (string "nginx")
+   "The system group that will be the primary group of the PHP-FPM socket.
+This should be the group that the webserver belongs to, so that it can use the
+socket to communicate with the PHP-FPM process.  This group needs to exist
+before the PHP-FPM process is started (the 'nginx' group is created by default
+on GuixSD systems when using the nginx-service).")
+
+  (listen.mode
+   (string "0660")
+   "The default permissions applied to the PHP-FPM pool sockets.")
+
+  ;; TODO:
+  ;; When POSIX Access Control Lists are supported you can set them using
+  ;; these options, value is a comma separated list of user/group names.
+  ;; When set, listen.owner and listen.group are ignored
+  ;(listen.acl-users  '())
+  ;(listen.acl-groups '())
+
+  ;; TODO: listen.allowed-clients = 127.0.0.1
+  ;; TODO: listen.backlog = -1
+  ;; TODO: process.priority = -19
+
+  (pm ;; TODO: nicer name: process-manager
+   (string "ondemand")
+   "Select the default way the PHP-FPM master process will manage pool child
+processes.  Possible values: 'static', 'dynamic' or 'ondemand'.")
+
+  (pm.max-children  ;; TODO: nicer name
+   (non-negative-integer 75) ;; TODO: processor-vcpus
+   "Maximum number of child processes to be created when pm is set to
+'static' and the maximum number of child processes when pm is set to
+'dynamic' or 'ondemand'.")
+
+  (pm.start-servers  ;; TODO: nicer name
+   (non-negative-integer 10) ;; TODO: processor-vcpus
+   "The number of pool child processes created at startup, used by the
+'dynamic' management mode.")
+
+  (pm.min-spare-servers   ;; TODO: nicer name
+   (non-negative-integer 5)
+   "Number of minimum idle spare servers that should be kept around,
+used by the 'dynamic' management mode.")
+
+  (pm.max-spare-servers    ;; TODO: nicer name
+   (non-negative-integer 20) ;; TODO: default to pm.max-children
+   "Number of maximum idle spare servers that should be kept around,
+used by the 'dynamic' management mode.")
+
+  (pm.process-idle-timeout ;; TODO: nicer name
+   (string "10s")
+   "Timeout in seconds for the PHP-FPM pool child processes, used by
+the 'ondemand' management mode.")
+
+  (pm.max-requests ;; TODO: nicer name
+   (non-negative-integer 500)
+   "Maximum number of requests after which PHP-FPM pool child
+processes will be respawned.")
+
+  (pm.status-path   ;; TODO: nicer name
+   ;; FIXME: should be unset by default for security reasons
+   (optional-string "/php_status")
+   "Enable or disable pool status page in all PHP-FPM pools.  If this value is
+not set (#f), no URI will be recognized as a status page.  The value must
+start with a leading slash (/).  You might need to configure the webserver to
+allow access to this page as well.  The value can be anything, but it may not
+be a good idea to use the .php extension or it may conflict with a real PHP
+file.")
+
+  (ping.path   ;; TODO: nicer name
+   ;; FIXME: should be unset by default for security reasons
+   (optional-string "/php_ping")
+   "The ping URI to call the monitoring page of FPM.  If this value is not set,
+no URI will be recognized as a ping page.  This could be used to test
+from outside that FPM is alive and responding, or to - create a graph
+of FPM availability (rrd or such); - remove a server from a group if
+it is not responding (load balancing); - trigger alerts for the
+operating team (24/7).
+
+The value must start with a leading slash (/).  The value can be
+anything, but it may not be a good idea to use the .php extension or
+it may conflict with a real PHP file.")
+
+  (ping.response  ;; TODO: nicer name
+   (string "pong")
+   "A string that defines the expected response of the 'ping' request.")
+
+  (access.log
+   (optional-string "/var/log/php-fpm/$pool_access.log")
+    "The access log file for this PHP-FPM pool .  Set to #f to disable
+logging.")
+
+  (access.format ;; TODO: should be privacy enhanced
+   (string "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%")
+   "The access log format.")
+
+  (slowlog
+   (string "/var/log/php-fpm/$pool_slow.log")
+   "The log file for slow requests.  Note: slowlog is mandatory if
+request_slowlog_timeout is set.")
+
+  (request-slowlog-timeout
+   (string "0")
+   "The timeout for serving a single request after which a PHP backtrace will
+be dumped to the 'slowlog' file.  A value of '0s' means 'off'.  Available
+units: s(econds)(default), m(inutes), h(ours), or d(ays)")
+
+  (request-terminate-timeout
+   (string "0")
+   "The timeout for serving a single request after which the worker
+process will be killed.  This option should be used when the
+'max-execution-time' ini option does not stop script execution for
+some reason.  A value of '0' means 'off'.  Available units:
+s(econds)(default), m(inutes), h(ours), or d(ays)")
+
+  (rlimit-files
+   (non-negative-integer 1024) ; TODO: Check; what if the default is set to 0?
+  "Maximum number of opened file descriptors.")
+  
+  (rlimit-core
+   (non-negative-integer 0) ; TODO: Check; what if the default is set to 0?
+   ;; TODO: Possible Values: 'unlimited' or an integer greater or equal to 0
+   "Specify maximum size of the 'core' files.")
+
+  (chroot
+   (optional-string #f)
+   "Chroot to this directory at the start.  This value must be defined as an
+absolute path.  When this value is not set, chroot is not used.
+Note: you can prefix with '$prefix' to chroot to the pool prefix or one
+of its subdirectories.  If the pool prefix is not set, the global prefix
+will be used instead.
+Note: chrooting is a great security feature and should be used whenever
+      possible.  However, all PHP paths will be relative to the chroot
+      (error_log, sessions.save_path, ...).")
+
+  (chdir
+   (string "/")
+   "Chdir to this directory at the start.")
+
+  (catch-workers-output
+   (boolean #f)
+   "Redirect worker stdout and stderr into main error log.  If not set, stdout
+and stderr will be redirected to /dev/null according to FastCGI specs.  Note:
+on highloaded environement, this can cause some delay in the page process
+time (several ms).")
+
+   (clear-env
+    (boolean #t)
+    "Clear environment in FPM workers. Prevents arbitrary environment
+variables from reaching FPM worker processes by clearing the environment in
+workers before env vars specified in this pool configuration are added.
+Setting to #f will make all environment variables available to PHP code
+via getenv(), $_ENV and $_SERVER.")
+
+  (security.limit-extensions
+   (space-separated-string-list '(".php"))
+   "Limits the extensions of the main script FPM will allow to parse.
+This can prevent configuration mistakes on the web server side.  You
+should only limit FPM to .php extensions to prevent malicious users to
+use other extensions to exectute php code.  Note: set an empty list to
+allow all extensions.")
+
+  ;; TODO: environment:
+  ;; These values are taken from the nginx example configuration:
+  ;; ; Pass environment variables
+  ;; env[HOSTNAME] = $HOSTNAME
+  ;; env[PATH] = /usr/local/bin:/usr/bin:/bin
+  ;; env[TMP] = /tmp
+  ;; env[TMPDIR] = /tmp
+  ;; env[TEMP] = /tmp
+
+  ;; host-specific php ini settings here (from the nginx example configuration)
+  ;; php_admin_value[open_basedir] = /var/www/DOMAINNAME/htdocs:/tmp
+
+  ;; TODO: php_flag[ key ] =  value
+  ;; TODO: php_value[ key ] =  value
+  ;; TODO: php_admin_flag[ key ] =  value
+  ;; TODO: php_admin_value[ key ] =  value
+)
+
+;;; --- obaque configs ---
+
+(define-configuration opaque-php-ini
+  (php
+   (package php)
+   "The php package to use.")
+
+  (string
+   (string (configuration-missing-field 'opaque-php-ini 'string))
+   "The contents of the @code{php.ini} to use."))
+
+(define-configuration opaque-php-fpm-configuration
+  (string
+   (string (configuration-missing-field 'opaque-fpm 'string))
+   "The contents of the @code{php-fpm.conf} to use."))
+
+(define-configuration opaque-php-fpm-pool
+  (name
+   (section (configuration-missing-field 'opaque-php-fpm-pool 'name))
+   "The FPM pool name.")
+  (string
+   (string (configuration-missing-field 'opaque-php-fpm-pool 'string))
+   "The contents of the @code{pool} to use."))
+
+;;; --- service definition ---
+
+(define %php-fpm-activation
+  ;; Activation gexp.
+  #~(begin
+      (use-modules (guix build utils))
+      (define (mkdir-p/perms directory owner perms)
+        (mkdir-p directory)
+        (chown directory (passwd:uid owner) (passwd:gid owner))
+        (chmod directory perms))
+      (let ((root (getpwnam "root")))
+        (mkdir-p/perms "/etc/php/fpm" root #o755)
+        (mkdir-p/perms "/var/log/php-fpm" root #o700)
+        )
+      ;; TODO: Check configuration file syntax.
+      ;; How can both the php-ini-file and the fpm-config-file be passed here?
+      ;; (system* (string-append #$php-pkg "/sbin/php-fpm")
+      ;;          "--test"
+      ;;          "--php-ini" #$php-ini-file
+      ;;          "--fpm-config" #$fpm-config-file)
+      ))
+
+(define (php-fpm-shepherd-service configs)
+  "Return a list of <shepherd-service> for PHP-INI and FPM-CONFIG."
+  (let* ((php-ini (car configs))
+         (fpm-config (car (cdr configs)))
+         (fpm-config-str
+          (cond
+           ((opaque-php-fpm-configuration? fpm-config)
+            (opaque-php-fpm-configuration-string fpm-config))
+           (else
+            (with-output-to-string
+              (lambda ()
+                (serialize-configuration fpm-config
+                                         php-fpm-configuration-fields))))))
+         (php-ini-str
+          (cond
+           ((opaque-php-ini? php-ini)
+            (opaque-php-ini-string php-ini))
+           (else
+            (with-output-to-string
+              (lambda ()
+                (serialize-configuration php-ini
+                                         php-ini-fields))))))
+         (fpm-config-file (plain-file "php-fpm.conf" fpm-config-str))
+         (php-ini-file (plain-file "php.ini" php-ini-str))
+         (php-pkg (if (opaque-php-ini? php-ini)
+                      (opaque-php-ini-php php-ini)
+                      (php-ini-php php-ini))))
+    (list (shepherd-service
+           (documentation "Run the PHP-FPM server.")
+           (provision '(php-fpm))
+           (requirement '(networking))
+           (start #~(make-forkexec-constructor
+                     (list (string-append #$php-pkg "/sbin/php-fpm")
+                           "--php-ini" #$php-ini-file
+ 			   "--pid" "/var/run/php-fpm.pid"
+                           "--fpm-config" #$fpm-config-file)
+                     ;; Wait for the PID file.
+                     #:pid-file "/var/run/php-fpm.pid"))
+           (stop #~(make-kill-destructor))))))
+
+
+(define php-fpm-service-type
+  (service-type (name 'php-fpm)
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          php-fpm-shepherd-service)
+                       ;; TODO: Collect the user ids from the pools
+                       ;;       How can this be done?
+                       ;;(service-extension account-service-type
+                       ;;                   (const %php-accounts))
+                       (service-extension activation-service-type
+                                          (const %php-fpm-activation))))))
+
+(define* (php-fpm-service #:key (config (php-fpm-configuration))
+                          (php-ini (php-ini)))
+  (validate-configuration php-ini
+                          (if (opaque-php-ini? php-ini)
+                              opaque-php-ini-fields
+                              php-ini-fields))
+  (validate-configuration config
+                          (if (opaque-php-fpm-configuration? config)
+                              opaque-php-fpm-configuration-fields
+                              php-fpm-configuration-fields))
+  (service php-fpm-service-type (list php-ini config)))
+
+
+;; --testt --fpm-config /tmp/fpm.conf
+
+;; /tmp/fpm.conf
+;; ;;[global]
+;; pid = /run/php-fpm.pid
+;; error_log = /tmp/php-fpm.log
+;;
+;; [www]
+;; include = /tmp/pool-defaults.conf
+;; ;pm.status_path = False
+;; user = hartmut
+;; pm.max_children = 7
+;; catch_workers_output = On
+;;
+;; ----
+;; /tmp/pool-defaults.conf
+;;
+;; pm = dynamic
+;; pm.max_children = 10
+;; pm.min_spare_servers = 1
+;; pm.max_spare_servers = 5
+;;
+;; listen = /tmp/fpm/$pool.sock