aboutsummaryrefslogtreecommitdiff
path: root/tw
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
parentd5d9951c7675f29931be3ce2a6c79bb6498914d6 (diff)
Migrate Nextcloud from Apache to nginx
Diffstat (limited to 'tw')
-rw-r--r--tw/services.scm6
-rw-r--r--tw/services/matrix.scm25
-rw-r--r--tw/services/nextcloud.scm191
-rw-r--r--tw/services/web.scm63
-rw-r--r--tw/system/lud.scm36
5 files changed, 202 insertions, 119 deletions
diff --git a/tw/services.scm b/tw/services.scm
deleted file mode 100644
index 2ef11298..00000000
--- a/tw/services.scm
+++ /dev/null
@@ -1,6 +0,0 @@
-(define-module (tw services)
- #:use-module (guix gexp))
-
-(define-public %httpd-cert-deploy-hook
- (program-file "httpd-cert-deploy-hook"
- #~(kill (call-with-input-file "/var/run/httpd" read) SIGHUP)))
diff --git a/tw/services/matrix.scm b/tw/services/matrix.scm
index db21f172..6b184f49 100644
--- a/tw/services/matrix.scm
+++ b/tw/services/matrix.scm
@@ -1,27 +1,14 @@
(define-module (tw services matrix)
#:use-module (gnu services)
- #:use-module (gnu services certbot)
- #:use-module (gnu services web)
- #:use-module (tw services))
+ #:use-module (tw services web))
(define-public %matrix-services
- (list (simple-service 'synapse-certificates certbot-service-type
- (list (certificate-configuration
+ (list (simple-service 'synapse-reverse-proxy https-reverse-proxy-service-type
+ ;; Synapse can't access certbot certs, but nginx can, so proxy HTTPS
+ ;; access through. Also, it's good to have Synapse available on :443.
+ (list (https-reverse-proxy-configuration
(domains '("matrix.twilken.net"))
- (deploy-hook %httpd-cert-deploy-hook))))
-
- (simple-service 'synapse-https-proxy httpd-service-type
- ;; Synapse can't access certbot certs, but Apache/httpd
- ;; can, so proxy HTTPS access through. It's good to have
- ;; Synapse available on port 443 anyway.
- (list (httpd-virtualhost "*:443" (list "\
-# Redirect to Synapse, to avoid having to specify its port number in Matrix clients.
-ServerName matrix.twilken.net
-SSLEngine on
-SSLCertificateFile \"/etc/letsencrypt/live/matrix.twilken.net/fullchain.pem\"
-SSLCertificateKeyFile \"/etc/letsencrypt/live/matrix.twilken.net/privkey.pem\"
-ProxyPass \"/\" \"https://127.0.0.1:48448/\"
-"))))
+ (destination-port 48448))))
;; TODO: Postgres for Synapse
;; (service postgresql-service-type
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)))))
diff --git a/tw/services/web.scm b/tw/services/web.scm
new file mode 100644
index 00000000..12851a72
--- /dev/null
+++ b/tw/services/web.scm
@@ -0,0 +1,63 @@
+(define-module (tw services web)
+ #:use-module (gnu services)
+ #:use-module (gnu services configuration)
+ #:use-module (gnu services certbot)
+ #:use-module (gnu services web)
+ #:use-module (guix gexp)
+ #:use-module (guix records)
+ #:use-module ((srfi srfi-1) #:select (concatenate))
+ #:export (https-reverse-proxy-service-type
+ https-reverse-proxy-configuration))
+
+(define-configuration/no-serialization https-reverse-proxy-configuration
+ (domains list-of-strings "List of domain names that nginx should proxy
+requests for.")
+ (destination-port integer "The port number of the service that should be
+proxied to.")
+ (destination-ip (string "127.0.0.1") "The IP address of the server that
+should be proxied to. Usually, this should be localhost.")
+ (destination-protocol (string "http") "The protocol that the proxied service
+speaks. Set to @code{\"https\"} if you want to proxy HTTPS-to-HTTPS.")
+ (nginx-pid-file (string "/var/run/nginx/pid") "The file containing nginx's
+process ID. This may differ from the default if nginx's @code{run-directory}
+differs from its default."))
+
+(define (reverse-proxy-certificate config)
+ (match-record config <https-reverse-proxy-configuration> (domains nginx-pid-file)
+ (certificate-configuration
+ (domains domains)
+ (deploy-hook
+ (program-file "nginx-cert-deploy-hook"
+ #~(kill (call-with-input-file #$nginx-pid-file read) SIGHUP))))))
+
+(define (reverse-proxy-nginx-server config)
+ (match-record config <https-reverse-proxy-configuration>
+ (domains destination-port destination-ip)
+ (nginx-server-configuration
+ (listen '("443 ssl http2"))
+ (server-name domains)
+ (ssl-certificate (string-append "/etc/letsencrypt/live/" (car domains) "/fullchain.pem"))
+ (ssl-certificate-key (string-append "/etc/letsencrypt/live/" (car domains) "/privkey.pem"))
+ (server-tokens? #f)
+ (locations
+ (list (nginx-location-configuration
+ (uri "/")
+ (body `(("proxy_pass http://" ,destination-ip ":"
+ ,(number->string destination-port))))))))))
+
+(define (reverse-proxy-certificates configs)
+ (map reverse-proxy-certificate configs))
+
+(define (reverse-proxy-nginx-servers configs)
+ (map reverse-proxy-nginx-server configs))
+
+(define https-reverse-proxy-service-type
+ (service-type
+ (name 'reverse-proxy)
+ (extensions (list (service-extension nginx-service-type reverse-proxy-nginx-servers)
+ (service-extension certbot-service-type reverse-proxy-certificates)))
+ (default-value '())
+ (compose concatenate)
+ (extend append)
+ (description "Configure nginx as a reverse proxy proxying external HTTPS
+requests to another host or a local port over plain HTTP.")))
diff --git a/tw/system/lud.scm b/tw/system/lud.scm
index 82a3e43f..9a07fb0d 100644
--- a/tw/system/lud.scm
+++ b/tw/system/lud.scm
@@ -26,21 +26,6 @@
(define data-partition ; /dev/sdc1
(uuid "4715ae0e-5cef-48f2-a59e-025321153888" 'btrfs))
-(define httpd-intermediate-ssl-config "\
-# SSL configuration.
-# https://ssl-config.mozilla.org/#server=apache&version=2.4.53&config=intermediate&openssl=1.1.1n&ocsp=false&guideline=5.6
-SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
-SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
-SSLHonorCipherOrder Off
-SSLSessionTickets Off
-SSLUseStapling On
-SSLStaplingCache \"shmcb:logs/ssl_stapling(32768)\"
-SSLSessionCache \"shmcb:logs/ssl_scache(65535)\"
-# 20 minutes -- default is 5 minutes, which is not long, and the cache
-# size is limited anyway above.
-SSLSessionCacheTimeout 1200
-")
-
(define-public %lud-system
(operating-system
(host-name "lud.twilken.net")
@@ -145,27 +130,6 @@ SSLSessionCacheTimeout 1200
(certbot-configuration
(email "letsencrypt@twilken.net")))
- (service httpd-service-type
- (httpd-configuration
- (config
- (httpd-config-file
- (listen '("443")) ; leave port 80 free for certbot/nginx
- (modules
- (cons* (httpd-module (name "ssl_module") (file "modules/mod_ssl.so"))
- (httpd-module (name "proxy_module") (file "modules/mod_proxy.so"))
- (httpd-module (name "rewrite_module") (file "modules/mod_rewrite.so"))
- (httpd-module (name "alias_module") (file "modules/mod_alias.so"))
- (httpd-module (name "socache_shmcb_module") ; for SSLStaplingCache
- (file "modules/mod_socache_shmcb.so"))
- (httpd-module (name "proxy_fcgi_module") ; for PHP/FastCGI
- (file "modules/mod_proxy_fcgi.so"))
- %default-httpd-modules))
- ;; Preserve default value for `extra-config'.
- (extra-config
- (list "TypesConfig etc/httpd/mime.types\n"
- "ServerAdmin webmaster@twilken.net\n"
- httpd-intermediate-ssl-config))))))
-
;; For Nextcloud (and Streama)
(service mysql-service-type
(mysql-configuration