summaryrefslogtreecommitdiff
path: root/tw/services/nextcloud.scm
diff options
context:
space:
mode:
authorTimo Wilken2023-11-29 00:40:38 +0100
committerTimo Wilken2023-11-29 00:45:52 +0100
commit36f7bbb00d43ccbfaa50ae2d2efedfbb3761cc91 (patch)
tree5cc7d595027b73157288616d30477fabfe3e715c /tw/services/nextcloud.scm
parentd5d9951c7675f29931be3ce2a6c79bb6498914d6 (diff)
Migrate Nextcloud from Apache to nginx
Diffstat (limited to 'tw/services/nextcloud.scm')
-rw-r--r--tw/services/nextcloud.scm191
1 files changed, 133 insertions, 58 deletions
diff --git a/tw/services/nextcloud.scm b/tw/services/nextcloud.scm
index 7275cabf..eed2f4fd 100644
--- a/tw/services/nextcloud.scm
+++ b/tw/services/nextcloud.scm
@@ -1,15 +1,18 @@
(define-module (tw services nextcloud)
+ #:use-module (ice-9 match)
#:use-module (gnu)
#:use-module (gnu packages backup)
#:use-module (gnu packages certs)
#:use-module (gnu packages databases)
#:use-module (gnu packages linux)
#:use-module (gnu packages php)
+ #:use-module (gnu packages web)
#:use-module (gnu services certbot)
#:use-module (gnu services mcron)
#:use-module (gnu services web)
#:use-module (guix gexp)
- #:use-module (tw services)
+ #:use-module ((guix packages) #:select (package-version))
+ #:use-module ((guix utils) #:select (version-major))
#:use-module (tw services restic))
(define-public %nextcloud-php.ini
@@ -24,7 +27,8 @@
(with-output-to-file #$output
;; Guix's PHP comes with the following extensions built-in,
;; so no extension= line necessary:
- ;; pdo_mysql, bcmath, bz2, exif, gd, iconv, intl
+ ;; pdo_mysql, pdo_pgsql, pgsql, bcmath, bz2, exif, gd, iconv, intl
+ ;; The default pgsql.* settings are fine.
(lambda () (display (string-append "\
memory_limit=512M
extension_dir=/run/current-system/profile/lib/php/extensions/" (basename php-extdir) "
@@ -43,6 +47,8 @@ opcache.save_comments=1
opcache.revalidate_freq=120
"))))))))
+(define nextcloud-domain "cloud.wilkenfamily.de")
+
(define nextcloud-backup-repository "/var/backups/nextcloud")
(define nextcloud-backup-password-file "/etc/restic/lud-nextcloud")
@@ -68,8 +74,8 @@ opcache.revalidate_freq=120
(let ((child-pid (primitive-fork)))
(if (zero? child-pid)
(begin ; this is the child
- (setgid (group:gid (getgr "httpd"))) ; while still root
- (setuid (passwd:uid (getpw "httpd")))
+ (setgid (group:gid (getgr "php-fpm"))) ; while still root
+ (setuid (passwd:uid (getpw "php-fpm")))
(execl #$(file-append php "/bin/php")
"php" "-c" #$%nextcloud-php.ini
(string-append nextcloud-dir "/occ")
@@ -148,66 +154,135 @@ opcache.revalidate_freq=120
(cleanup))))
(define-public %nextcloud-services
- (list (simple-service 'nextcloud-https-server httpd-service-type
- ;; The certbot service redirects everything on port 80 to
- ;; port 443 by default, modulo its own /.well-known paths.
- (list (httpd-virtualhost "*:443" (list "\
-# For Nextcloud.
-ServerName cloud.wilkenfamily.de
-DocumentRoot /var/www/nextcloud
-SSLEngine on
-SSLCertificateFile \"/etc/letsencrypt/live/cloud.wilkenfamily.de/fullchain.pem\"
-SSLCertificateKeyFile \"/etc/letsencrypt/live/cloud.wilkenfamily.de/privkey.pem\"
-Header always set Strict-Transport-Security \"max-age=15552000\"
-
-# Don't check for .htaccess files above DocumentRoot.
-<Directory \"/\">
- AllowOverride None
-</Directory>
-
-<Directory /var/www/nextcloud>
- Options +FollowSymlinks
- AllowOverride All
- <IfModule mod_dav.c>
- Dav off
- </IfModule>
- SetEnv HOME /var/www/nextcloud
- SetEnv HTTP_HOME /var/www/nextcloud
-</Directory>
-
-# Redirect to local php-fpm if mod_php is not available
-<IfModule !mod_php7.c>
- <IfModule proxy_fcgi_module>
- # Enable http authorization headers
- <IfModule setenvif_module>
- SetEnvIfNoCase ^Authorization$ \"(.+)\" HTTP_AUTHORIZATION=$1
- </IfModule>
- <FilesMatch \".+\\.ph(ar|p|tml)$\">
- <If \"-f %{REQUEST_FILENAME}\">
- SetHandler \"proxy:unix:/var/run/php-fpm.sock|fcgi://localhost/\"
- </If>
- </FilesMatch>
- # Deny access to raw PHP sources and files without filename (e.g. '.php')
- <FilesMatch \"^\\.ph(ar|p|ps|tml)$|.*\\.phps$\">
- Require all denied
- </FilesMatch>
- </IfModule>
-</IfModule>
-"))))
+ (list (simple-service 'nextcloud-https-server nginx-service-type
+ ;; https://docs.nextcloud.com/server/latest/admin_manual/installation/nginx.html
+ (list (nginx-server-configuration
+ ;; The certbot service redirects everything on port 80 to
+ ;; port 443 by default, modulo its own /.well-known paths.
+ (listen '("443 ssl http2"))
+ (server-name (list nextcloud-domain))
+ (root "/var/www/nextcloud")
+ (index '("index.php" "index.html" "/index.php$request_uri"))
+ (try-files '("$uri" "$uri/" "/index.php$request_uri"))
+ (ssl-certificate (string-append "/etc/letsencrypt/live/" nextcloud-domain "/fullchain.pem"))
+ (ssl-certificate-key (string-append "/etc/letsencrypt/live/" nextcloud-domain "/privkey.pem"))
+ (server-tokens? #f)
+ (raw-content
+ `(;; Set max upload size and increase upload timeout
+ "client_max_body_size 512M;"
+ "client_body_timeout 300s;"
+ "fastcgi_buffers 64 4K;"
+
+ ;; Enable gzip but do not remove ETag headers
+ "gzip on;"
+ "gzip_vary on;"
+ "gzip_comp_level 4;"
+ "gzip_min_length 256;"
+ "gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;"
+ ("gzip_types application/atom+xml text/javascript application/javascript "
+ "application/json application/ld+json application/manifest+json "
+ "application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject "
+ "application/wasm application/x-font-ttf application/x-web-app-manifest+json "
+ "application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml "
+ "image/x-icon text/cache-manifest text/css text/plain text/vcard text/vtt "
+ "text/vnd.rim.location.xloc text/x-component text/x-cross-domain-policy;")
+
+ ;; HTTP response headers borrowed from Nextcloud `.htaccess`
+ ,@(map (match-lambda
+ ((hdr . value)
+ `("add_header " ,hdr " \"" ,value "\" always;")))
+ '(("Referrer-Policy" . "no-referrer")
+ ("X-Content-Type-Options" . "nosniff")
+ ("X-Frame-Options" . "SAMEORIGIN")
+ ("X-Permitted-Cross-Domain-Policies" . "none")
+ ("X-Robots-Tag" . "noindex, nofollow")
+ ("X-XSS-Protection" . "1; mode=block")))
+
+ ;; Remove X-Powered-By, which is an information leak
+ "fastcgi_hide_header X-Powered-By;"
+
+ ;; Add .mjs as a file extension for JavaScript
+ ("include " ,nginx "/share/nginx/conf/mime.types;")
+ "types { text/javascript js mjs; }"))
+
+ (locations
+ (list
+ (nginx-location-configuration ; Handle Microsoft DAV clients
+ (uri "= /")
+ (body '("if ( $http_user_agent ~ ^DavClnt ) { return 302 /remote.php/webdav/$is_args$args; }")))
+ (nginx-location-configuration
+ (uri "= /robots.txt")
+ (body '("allow all;" "log_not_found off;" "access_log off;")))
+
+ ;; The rules in these blocks are an adaptation of the rules
+ ;; in `.htaccess` that concern `/.well-known`.
+ (nginx-location-configuration
+ (uri "= /.well-known/carddav")
+ (body '("return 301 /remote.php/dav/;")))
+ (nginx-location-configuration
+ (uri "= /.well-known/caldav")
+ (body '("return 301 /remote.php/dav/;")))
+ (nginx-location-configuration
+ (uri "= /.well-known/acme-challenge")
+ (body '("try_files $uri $uri/ =404;")))
+ (nginx-location-configuration
+ (uri "= /.well-known/pki-validation")
+ (body '("try_files $uri $uri/ =404;")))
+ ;; Let Nextcloud's API for `/.well-known` URIs handle all other
+ ;; requests by passing them to the front-end controller.
+ (nginx-location-configuration
+ (uri "^~ /.well-known")
+ (body '("return 301 /index.php$request_uri;")))
+
+ ;; Rules borrowed from `.htaccess` to hide certain paths from clients
+ (nginx-location-configuration
+ (uri "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)")
+ (body '("return 404;")))
+ (nginx-location-configuration
+ ;; Exclude .well-known to avoid overriding rules above (regexes take precedence).
+ (uri "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)")
+ (body '("return 404;")))
+
+ ;; Ensure this block, which passes PHP files to the PHP process, is above the blocks
+ ;; which handle static assets (as seen below). If this block is not declared first,
+ ;; then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
+ ;; to the URI, resulting in a HTTP 500 error response.
+ (nginx-location-configuration
+ (uri "~ \\.php(?:$|/)")
+ (body
+ (let ((phpver (version-major (package-version php))))
+ `(("rewrite ^/(?!index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|" ; Legacy support
+ "updater\\/.+|ocs-provider\\/.+|.+\\/richdocumentscode\\/proxy) /index.php$request_uri;")
+ "fastcgi_request_buffering off;"
+ "fastcgi_split_path_info ^(.+?\\.php)(/.*)$;"
+ "try_files $fastcgi_script_name =404;"
+ ("include " ,nginx "/share/nginx/conf/fastcgi.conf;")
+ "fastcgi_param HTTP_PROXY \"\";" ; Mitigate https://httpoxy.org/
+ "fastcgi_param modHeadersAvailable true;" ; Avoid sending the security headers twice
+ "fastcgi_param front_controller_active true;" ; Enable pretty urls
+ ("fastcgi_pass unix:/var/run/php" ,phpver "-fpm.sock;"))))) ; Match php-fpm-configuration
+
+ (nginx-location-configuration
+ (uri "~ \\.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$")
+ (body '("try_files $uri /index.php$request_uri;"
+ "add_header Cache-Control \"public, max-age=15778463, immutable\";")))
+ (nginx-location-configuration
+ (uri "~ \\.woff2?$")
+ (body '("try_files $uri /index.php$request_uri;" "expires 7d;")))
+ (nginx-location-configuration
+ (uri "/remote")
+ (body '("return 301 /remote.php$request_uri;"))))))))
(service (@ (tw services php-fpm) php-fpm-service-type)
(php-fpm-configuration
- (user "httpd")
- (group "httpd")
- (socket "/var/run/php-fpm.sock")
- (socket-user "httpd")
- (socket-group "httpd")
(php-ini-file %nextcloud-php.ini)))
(simple-service 'nextcloud-certificates certbot-service-type
(list (certificate-configuration
- (domains '("cloud.wilkenfamily.de"))
- (deploy-hook %httpd-cert-deploy-hook))))
+ (domains (list nextcloud-domain))
+ (deploy-hook
+ (program-file "nginx-cert-deploy-hook"
+ #~(kill (call-with-input-file "/var/run/nginx/pid" read) SIGHUP))))))
(simple-service 'nextcloud-backup-cleanup restic-cleanup-service-type
(list (restic-scheduled-cleanup
@@ -229,7 +304,7 @@ Header always set Strict-Transport-Security \"max-age=15552000\"
(setenv "SSL_CERT_DIR" #$(file-append nss-certs "/etc/ssl/certs"))
(execl #$(file-append php "/bin/php") "php"
"-c" #$%nextcloud-php.ini "/var/www/nextcloud/cron.php")))
- #:user "httpd")
+ #:user "php-fpm")
;; TODO: try `with-mail-out' from `(mcron redirect)'?
#~(job "0 6 * * *" #$nextcloud-backup-program)))))