From 36f7bbb00d43ccbfaa50ae2d2efedfbb3761cc91 Mon Sep 17 00:00:00 2001 From: Timo Wilken Date: Wed, 29 Nov 2023 00:40:38 +0100 Subject: Migrate Nextcloud from Apache to nginx --- tw/services/nextcloud.scm | 191 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 58 deletions(-) (limited to 'tw/services/nextcloud.scm') 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. - - AllowOverride None - - - - Options +FollowSymlinks - AllowOverride All - - Dav off - - SetEnv HOME /var/www/nextcloud - SetEnv HTTP_HOME /var/www/nextcloud - - -# Redirect to local php-fpm if mod_php is not available - - - # Enable http authorization headers - - SetEnvIfNoCase ^Authorization$ \"(.+)\" HTTP_AUTHORIZATION=$1 - - - - SetHandler \"proxy:unix:/var/run/php-fpm.sock|fcgi://localhost/\" - - - # Deny access to raw PHP sources and files without filename (e.g. '.php') - - Require all denied - - - -")))) + (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))))) -- cgit v1.2.3