diff options
Diffstat (limited to 'tw/services')
-rwxr-xr-x | tw/services/files/nextcloud-backup | 68 | ||||
-rw-r--r-- | tw/services/matrix.scm | 40 | ||||
-rw-r--r-- | tw/services/nextcloud.scm | 125 | ||||
-rw-r--r-- | tw/services/wireguard.scm | 107 |
4 files changed, 340 insertions, 0 deletions
diff --git a/tw/services/files/nextcloud-backup b/tw/services/files/nextcloud-backup new file mode 100755 index 00000000..4c533758 --- /dev/null +++ b/tw/services/files/nextcloud-backup @@ -0,0 +1,68 @@ +#!/bin/sh -e +# Nextcloud backup script, to run nightly. +# Documentation on backups: +# https://docs.nextcloud.com/server/latest/admin_manual/maintenance/backup.html +# https://docs.nextcloud.com/server/latest/admin_manual/maintenance/restore.html +# https://git.mdns.eu/nextcloud/passwords/-/wikis/Administrators/Backups + +. /etc/default/nextcloud-backup +: "${DATABASE_PASSWORD:?You must pass the MySQL database password as DATABASE_PASSWORD}" + +php_ini=$1 +backup_dir=/var/backups/nextcloud/$(date -u '+%Y-%m-%d') +nextcloud_dir=/var/www/nextcloud +nextcloud_data_partition=/var/data # mountpoint of the partition containing Nextcloud data dir +nextcloud_data_path=nextcloud # relative to $nextcloud_data_partition +snapshot=$nextcloud_data_partition/tmp-nextcloud-backup + +nc_maintenance () { + # Enable (--on) or disable (--off) Nextcloud's maintenance mode. + sudo -nu httpd php ${php_ini:+-c "$php_ini"} "$nextcloud_dir/occ" maintenance:mode "$@" +} + +# If there is a previous backup, compare against it later (so we don't have to +# transfer every file). +last_backup_dir=$(ls -1d "$(dirname "$backup_dir")"/????-??-?? | LC_ALL=C sort | tail -1) +[ -d "$last_backup_dir" ] || unset last_backup_dir + +# Don't overwrite existing backups. mkdir will fail if $backup_dir exists. +mkdir -m 750 "$backup_dir" + +# Always turn off maintenance mode and clean up the temporary snapshot on exit, +# whether or not the backup succeeded. +cleanup () { + nc_maintenance --off || : + if [ -d "$snapshot" ]; then + btrfs subvolume delete -c "$snapshot" || : + fi +} +trap cleanup EXIT HUP INT TERM # can't trap KILL + +# Turn Nextcloud off temporarily so the data doesn't change during the backup. +nc_maintenance --on + +# Backup the database. This can only be done offline. +mysqldump --single-transaction --default-character-set=utf8mb4 \ + -u nextcloud -p"$DATABASE_PASSWORD" nextcloud > "$backup_dir/nextcloud.sql" + +# These shouldn't be copied while Nextcloud is online. +rsync -AUXHavx ${last_backup_dir+--link-dest="$last_backup_dir"} \ + "$nextcloud_dir/config" "$nextcloud_dir/themes" "$backup_dir" + +# Make sure everything is synced to disk so it's in our snapshot. +btrfs filesystem sync "$nextcloud_data_partition/$nextcloud_data_path" +btrfs subvolume snapshot -r "$nextcloud_data_partition" "$snapshot" + +# At this point, the data directory is in the snapshot, so Nextcloud can be +# turned on again. +nc_maintenance --off + +# --link-dest is brittle (it only hardlinks to the old file if no metadata has +# changed). Reflinks would be better, but rsync doesn't seem to support them. +# We don't need files under preview/, as those are thumbnails from the Previews +# "app" and can be regenerated using `php -f occ preview:pre-generate`. +rsync -AUXHavx --exclude='appdata_*/preview' --exclude='appdata_*/passwords/*Cache' \ + ${last_backup_dir+--link-dest="$last_backup_dir/data"} \ + "$snapshot/$nextcloud_data_path/" "$backup_dir/data" +# Make sure everything is written out to the backup disk before we exit. +btrfs filesystem sync "$backup_dir" diff --git a/tw/services/matrix.scm b/tw/services/matrix.scm new file mode 100644 index 00000000..db21f172 --- /dev/null +++ b/tw/services/matrix.scm @@ -0,0 +1,40 @@ +(define-module (tw services matrix) + #:use-module (gnu services) + #:use-module (gnu services certbot) + #:use-module (gnu services web) + #:use-module (tw services)) + +(define-public %matrix-services + (list (simple-service 'synapse-certificates certbot-service-type + (list (certificate-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/\" +")))) + + ;; TODO: Postgres for Synapse + ;; (service postgresql-service-type + ;; (postgresql-configuration + ;; (postgresql postgresql-15) + ;; (data-directory "/var/lib/postgresql/data"))) + + ;; (service postgresql-role-service-type + ;; (postgresql-role-configuration + ;; (roles (list (postgresql-role + ;; (name "synapse") ; TODO + ;; (create-database? #t)))))) + + ;; TODO: Matrix/Synapse + ;; TODO: Matrix bridges + )) diff --git a/tw/services/nextcloud.scm b/tw/services/nextcloud.scm new file mode 100644 index 00000000..ca68cf77 --- /dev/null +++ b/tw/services/nextcloud.scm @@ -0,0 +1,125 @@ +(define-module (tw services nextcloud) + #:use-module (gnu) + #:use-module (gnu packages php) + #:use-module (gnu services certbot) + #:use-module (gnu services mcron) + #:use-module (gnu services web) + #:use-module (guix gexp) + #:use-module (tw services)) + +(define-public %nextcloud-php.ini + (computed-file "nextcloud-php.ini" + #~(begin + (use-modules (ice-9 popen) (ice-9 rdelim)) + (let* ((php-config #$(file-append php "/bin/php-config")) + (pipe (open-pipe* OPEN_READ php-config "--extension-dir")) + (php-extdir (read-line pipe))) + (unless (zero? (status:exit-val (close-pipe pipe))) + (error "Failed to get PHP extension dir")) + (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 + (lambda () (display (string-append "\ +memory_limit=512M +extension_dir=/run/current-system/profile/lib/php/extensions/" (basename php-extdir) " +; Caching extensions for Nextcloud +extension=apcu +apc.enable_cli=1 +zend_extension=opcache +; https://www.php.net/manual/en/opcache.configuration.php +opcache.enable=1 +opcache.interned_strings_buffer=32 +opcache.max_accelerated_files=10000 +opcache.memory_consumption=128 +opcache.save_comments=1 +; It will take up to revalidate_freq seconds for changes to config.php to be applied. +opcache.revalidate_freq=120 +")))))))) + +(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> +")))) + + (service 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)))) + + ;; Nextcloud cron + (simple-service 'nextcloud-cron mcron-service-type + (list #~(job "*/5 * * * *" + (lambda () + (chdir "/var/www/nextcloud") + ;; `setgid' first while we're still root + (setgid (group:gid (getgr "httpd"))) + (setuid (passwd:uid (getpw "httpd"))) + (execl #$(file-append php "/bin/php") "php" + "-c" #$%nextcloud-php.ini "cron.php")) + (string-append + #$(file-append php "/bin/php") + " -c " #$%nextcloud-php.ini + " /var/www/nextcloud/cron.php")) + + ;; Nextcloud backups + ;; Requires: sudo, php, btrfs, mysqldump, rsync + (let ((backup-script (local-file "files/nextcloud-backup" #:recursive? #t))) + #~(job "0 6 * * *" + (lambda () + ;; Pass through the php.ini file that allows us to + ;; use Nextcloud's occ script. + (execl #$backup-script "nextcloud-backup" #$%nextcloud-php.ini)) + (string-append #$backup-script " " #$%nextcloud-php.ini))))))) diff --git a/tw/services/wireguard.scm b/tw/services/wireguard.scm new file mode 100644 index 00000000..3d35cd2e --- /dev/null +++ b/tw/services/wireguard.scm @@ -0,0 +1,107 @@ +(define-module (tw services wireguard) + #:use-module (ice-9 regex) + #:use-module ((srfi srfi-1) #:select (append-map every)) + #:use-module ((srfi srfi-26) #:select (cut)) + #:use-module (gnu services) + #:use-module (gnu services base) + #:use-module (gnu services configuration) + #:use-module (gnu services vpn) + #:export (%wireguard-peers + tw-wireguard-configuration + tw-wireguard-service-type)) + +(define %wireguard-peers + `(("lap.twilken.net" . + ,(wireguard-peer + (name "lap.wg") + (public-key "lap/DvCb8xXLUCqcaPEx8kCRcoeV4ScTMVZW5hvvNzA=") + (preshared-key "/etc/wireguard/lap.psk") + (allowed-ips '("10.0.0.1/32" "fc00::1/128")))) + ("lud.twilken.net" . + ,(wireguard-peer + (name "lud.wg") + (endpoint "lud.twilken.net:58921") + (public-key "lud/9sbXVdOYXxOkRgAB+b/17QxbwllfJY/pbA3/MkE=") + (preshared-key "/etc/wireguard/lud.psk") + (allowed-ips '("10.0.0.2/32" "fc00::2/128")))) + ("vin.twilken.net" . + ,(wireguard-peer + (name "vin.wg") + (endpoint "vin.twilken.net:58921") + (public-key "vin/Im+sOszZFE01UF1+QlyxLP1PsPXJgTz4KmgvL3Y=") + (preshared-key "/etc/wireguard/vin.psk") + (allowed-ips '("10.0.0.3/32" "fc00::3/128")))) + ("fp4.twilken.net" . + ,(wireguard-peer + (name "fp4.wg") + (public-key "fp4/aLAVBADTy+UGmNh011w1CFOOwq70Df6EWlZRkAs=") + (preshared-key "/etc/wireguard/fp4.psk") + (allowed-ips '("10.0.0.4/32" "fc00::4/128")))) + ("pi3.twilken.net" . + ,(wireguard-peer + (name "pi3.wg") + (endpoint "pi3.twilken.net:58922") + (public-key "pi3/ThUH4qDTuyvNQIiiyy2dbziF/xLRTwO0+vcUoVY=") + (preshared-key "/etc/wireguard/pi3.psk") + (allowed-ips '("10.0.0.5/32" "fc00::5/128")))))) + +(define (wireguard-peers-list? object) + (and (list? object) + (every (compose string? car) object) + (every (compose wireguard-peer? cdr) object))) + +(define-configuration/no-serialization tw-wireguard-configuration + (this-host + (string) + "The host name of the machine being configured.") + (peers + (wireguard-peers-list %wireguard-peers) + "An alist of WireGuard peers to install.")) + +(define (tw-wireguard-service config) + "Create a full WireGuard config from the personal network CONFIG." + (let ((own-peer (assoc-ref (tw-wireguard-configuration-peers config) + (tw-wireguard-configuration-this-host config)))) + (wireguard-configuration + (addresses + (map (lambda (cidr) + (let ((ipv4 (string-match "/32$" cidr)) + (ipv6 (string-match "/128$" cidr))) + (cond + (ipv4 (regexp-substitute #f ipv4 'pre "/24")) + (ipv6 (regexp-substitute #f ipv6 'pre "/64")) + (#t cidr)))) + (wireguard-peer-allowed-ips own-peer))) + (port + (let ((endpoint (wireguard-peer-endpoint own-peer))) + (if endpoint + (string->number (cadr (string-split endpoint #\:))) + 58921))) + (private-key "/etc/wireguard/private.key") + (peers (delq own-peer (map cdr (tw-wireguard-configuration-peers config))))))) + +(define (peer->ips peer) + "Extract IP addresses assigned to the given `wireguard-peer' PEER." + (map (compose car (cut string-split <> #\/)) + (wireguard-peer-allowed-ips peer))) + +(define (tw-wireguard-hosts config) + "Generate a hosts file entries from the personal WireGuard network CONFIG." + (append-map (lambda (peer) + (map (cut host <> (wireguard-peer-name peer)) + (peer->ips peer))) + (map cdr (tw-wireguard-configuration-peers config)))) + +(define tw-wireguard-service-type + (service-type + (name 'tw-wireguard) + (description "Set up my personal WireGuard network.") + (extensions + (cons* (service-extension hosts-service-type tw-wireguard-hosts) + ;; FIXME: `wireguard-service-type' cannot be extended, so copy its + ;; service-extensions directly. + (map (lambda (ext) + (service-extension (service-extension-target ext) + (compose (service-extension-compute ext) + tw-wireguard-service))) + (service-type-extensions wireguard-service-type)))))) |