(define-module (tw system lud)
#:use-module (gnu)
#:use-module (gnu bootloader grub)
#:use-module (gnu system locale)
#:use-module (gnu system nss)
#:use-module (guix gexp)
#:use-module (tw packages php)
#:use-module (tw system))
(use-package-modules admin bash certs databases linux man php python rsync
shells tls tor video)
(use-service-modules certbot databases file-sharing mcron monitoring
networking pm ssh syncthing vpn web)
(define efi-system-partition ; /dev/sda1
(uuid "51F3-FB71" 'fat32))
(define guixsd-root-partition ; /dev/sda2
(uuid "c63af3e6-3c2b-43d2-b1e6-944f09a10e0f" 'btrfs))
(define backups-partition ; /dev/sdb1
(uuid "c6ac4033-cce6-4365-abcc-483b79c4ca36" 'btrfs))
(define data-partition ; /dev/sdc1
(uuid "4715ae0e-5cef-48f2-a59e-025321153888" 'btrfs))
(define httpd-cert-deploy-hook
(program-file "httpd-cert-deploy-hook"
#~(kill (call-with-input-file "/var/run/httpd" read) SIGHUP)))
(define 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 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 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
"))))
(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)))))))
(define 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
))
(define-public %lud-system
(operating-system
(host-name "lud.twilken.net")
(timezone "Europe/Berlin")
(locale "en_GB.utf8")
(locale-definitions
(list (locale-definition (name "en_GB.utf8") (source "en_GB"))
(locale-definition (name "de_DE.utf8") (source "de_DE"))
(locale-definition (name "fr_FR.utf8") (source "fr_FR"))
(locale-definition (name "pt_BR.utf8") (source "pt_BR"))
(locale-definition (name "en_US.utf8") (source "en_US"))))
(hosts-file %wireguard-etc-hosts)
;; Allow resolution of '.local' host names with mDNS.
(name-service-switch %mdns-host-lookup-nss)
;; Choose UK English console keyboard layout.
(keyboard-layout %british-keyboard)
;; Packages installed system-wide. Users can also install packages
;; under their own account: use 'guix search KEYWORD' to search
;; for packages and 'guix install PACKAGE' to install a package.
(packages
(cons*
ffmpeg tor ; for video downloader
mariadb ; for Nextcloud backup script
;; For Nextcloud. PHP modules must be installed in system
;; profile, as that's referred to in Nextcloud's php.ini.
php php-apcu openssl ; curl is in `%base-system-packages'
%base-system-packages))
;; Below is the list of system services. To search for available
;; services, run 'guix system search KEYWORD' in a terminal.
(services
(append
(list (simple-service 'extra-access openssh-service-type
`(("ira" ; for Duplicity backups
,(local-file "files/kitchen-pc.pub")
,(local-file "files/wilken-laptop.pub"))
("timo" ; for Password Store app on phone
,(local-file "files/timo-phone-password-store.pub"))))
(service tor-service-type)
(service thermald-service-type
(thermald-configuration
(adaptive? #t)))
;; Transmission (torrents)
(service transmission-daemon-service-type
(transmission-daemon-configuration
(download-dir "/var/data/bt")
(incomplete-dir "/var/data/bt/incomplete")
(incomplete-dir-enabled? #t)
(speed-limit-up-enabled? #t)
(speed-limit-up 512) ; KiB/s
(encryption 'require-encrypted-connections)
;; Don't try to configure port forwarding automatically.
(port-forwarding-enabled? #f)
;; Make RPC interface only accessible via WireGuard.
(rpc-bind-address "10.0.0.2")
(rpc-whitelist-enabled? #t)
(rpc-whitelist '("127.0.0.1" "::1"
"10.0.0.*" "fc00::*"))
(rpc-host-whitelist-enabled? #t)
(rpc-host-whitelist '("lud.wg"))))
;; TODO: Streama
;; Syncthing
(service syncthing-service-type
(syncthing-configuration
(user "syncthing")
(group "syncthing")))
;; certbot for Synapse + Apache/Nextcloud
;; This also installs a nginx server on port 80, redirecting to port 443.
(service certbot-service-type
(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
(extra-content "\
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
# https://wiki.archlinux.org/title/Nextcloud
skip_networking
transaction_isolation = READ-COMMITTED
# https://docs.nextcloud.com/server/stable/admin_manual/installation/server_tuning.html#using-mariadb-mysql-instead-of-sqlite
innodb_buffer_pool_size = 1G
innodb_io_capacity = 4000
")))
;; TODO: JSON exporter (Nextcloud)
;; TODO: Syncthing exporter
;; TODO: Transmission exporter
)
nextcloud-services
matrix-services
(server-base-services host-name)))
;; The list of user accounts ('root' is implicit).
(users
(cons* (user-account ; TODO: merge with "timo"?
(name "timo-phone")
(comment "Backups of Timo's phone")
(group "users")
(home-directory "/var/backups/timo-phone")
(shell (file-append bash-minimal "/bin/sh")))
(user-account
(name "ira")
(comment "Ira Wilken")
(group "users")
(home-directory "/home/ira"))
(user-account
(system? #t)
(name "syncthing")
(comment "Syncthing service")
(group "syncthing")
(home-directory "/var/data/syncthing"))
%server-base-user-accounts))
(groups
(cons* (user-group ; This is NOT implict from the "syncthing" user.
(system? #t)
(name "syncthing"))
%base-groups))
;; Use the UEFI variant of GRUB with the EFI System Partition mounted
;; on /boot/efi.
(bootloader
(bootloader-configuration
(bootloader grub-efi-bootloader)
(targets '("/boot/efi"))
(keyboard-layout keyboard-layout)))
;; The list of file systems that get "mounted". The unique
;; file system identifiers there ("UUIDs") can be obtained
;; by running 'blkid' in a terminal.
(file-systems
(cons* (file-system
(mount-point "/")
(device guixsd-root-partition)
(flags '(no-atime))
(options (alist->file-system-options
'("ssd" ("compress" . "zstd"))))
(type "btrfs"))
(file-system
(mount-point "/boot/efi")
(device efi-system-partition)
(flags '(no-atime))
(type "vfat"))
(file-system
(mount-point "/var/backups")
(create-mount-point? #t)
(device backups-partition)
(flags '(no-atime))
(type "btrfs"))
(file-system
(mount-point "/var/data")
(create-mount-point? #t)
(device data-partition)
(flags '(no-atime))
(type "btrfs"))
%base-file-systems))))
%lud-system