--- /dev/null
+user nginx nginx;
+pid /var/run/nginx/pid;
+error_log /var/log/nginx/error.log error;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include @MIME_TYPES@;
+ default_type application/octet-stream;
+
+ sendfile on;
+ tcp_nopush on;
+ keepalive_timeout 65;
+ server_tokens off;
+
+ gzip on;
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 6;
+ gzip_types text/plain text/css text/xml
+ application/json application/javascript
+ application/xml+rss image/svg+xml;
+
+ client_body_temp_path /var/run/nginx/client_body_temp;
+ proxy_temp_path /var/run/nginx/proxy_temp;
+ fastcgi_temp_path /var/run/nginx/fastcgi_temp;
+ uwsgi_temp_path /var/run/nginx/uwsgi_temp;
+ scgi_temp_path /var/run/nginx/scgi_temp;
+
+ access_log /var/log/nginx/access.log combined;
+
+ include @SITES_DIR@/*.conf;
+}
--- /dev/null
+;;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+;;; License: GPL-3.0 or later.
+;;;
+;;; nginx.scm — Nginx server configuration, with site configs
+;;; collected from all site package inputs under
+;;; etc/nginx/sites-available/.
+
+(define-module (conf vps nginx)
+ #:use-module (conf vps website)
+ #:use-module (gnu packages tls)
+ #:use-module (gnu packages web)
+ #:use-module (gnu services)
+ #:use-module (gnu services web)
+ #:use-module (guix packages)
+ #:use-module (guix build-system trivial)
+ #:use-module (guix gexp)
+ #:use-module ((guix licenses)
+ #:prefix license:)
+ #:use-module (ice-9 ftw)
+ #:use-module (srfi srfi-1)
+ #:export (nginx-config %nginx-service))
+
+(define nginx-config-file
+ (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH")
+ "/conf/vps/nginx.conf"))
+ "nginx.conf"))
+
+(define nginx-config
+ (package
+ (name "nginx-config")
+ (version "20260725")
+ (source
+ nginx-config-file)
+ (build-system trivial-build-system)
+ (arguments
+ (list
+ #:modules '((guix build utils)
+ (ice-9 ftw)
+ (srfi srfi-1))
+ #:builder
+ #~(begin
+ (use-modules (guix build utils)
+ (ice-9 ftw)
+ (srfi srfi-1))
+ (let* ((out #$output)
+ (etc (string-append out "/etc/nginx"))
+ (sites (string-append etc "/sites-available")))
+ (mkdir-p sites)
+ ;; 1. Copy and patch the main nginx.conf.
+ (copy-file #$source
+ (string-append etc "/nginx.conf"))
+ (substitute* (string-append etc "/nginx.conf")
+ (("@MIME_TYPES@")
+ #$(file-append nginx "/share/nginx/conf/mime.types"))
+ (("@SITES_DIR@")
+ sites))
+ ;; 1.5. Generate a self-signed certificate so that
+ ;; nginx -t passes even without real SSL certs.
+ (let* ((openssl-bin #$(file-append openssl "/bin/openssl"))
+ (certs (string-append out "/etc/nginx/certs")))
+ (mkdir-p certs)
+ (invoke openssl-bin
+ "req"
+ "-x509"
+ "-newkey"
+ "rsa:2048"
+ "-keyout"
+ (string-append certs "/privkey.pem")
+ "-out"
+ (string-append certs "/fullchain.pem")
+ "-days"
+ "365"
+ "-nodes"
+ "-subj"
+ "/CN=example.org"))
+ ;; 2. Symlink site configs from all inputs.
+ (define (site-configs dir)
+ "Return the list of .conf files in DIR."
+ (filter (lambda (f)
+ (string-suffix? ".conf" f))
+ (or (scandir dir)
+ '())))
+ (define (collect input)
+ "Symlink site configs from INPUT into sites/."
+ (let ((dir (string-append (cdr input)
+ "/etc/nginx/sites-available")))
+ (when (directory-exists? dir)
+ (unless (member (car input)
+ '("source" "nginx"))
+ (for-each (lambda (f)
+ (symlink (string-append dir "/" f)
+ (string-append sites "/" f)))
+ (site-configs dir))))))
+ (for-each collect %build-inputs)
+ ;; 3. Substitute the SSL cert directory in
+ ;; site configs so nginx -t passes.
+ (let ((certs (string-append out "/etc/nginx/certs")))
+ (for-each (lambda (f)
+ (substitute* (string-append sites "/" f)
+ (("\\$\\{public_ssl_cert_dir\\}")
+ certs)))
+ (site-configs sites)))))))
+ (inputs (list nginx openssl website))
+ (home-page "https://git.ekhem.eu.org")
+ (synopsis "Nginx configuration for the personal VPS")
+ (description "Nginx configuration bundling @file{nginx.conf} and
+site-specific configurations auto-collected from all
+site package inputs under
+@file{etc/nginx/sites-available/}.")
+ (license license:gpl3+)))
+
+(define %nginx-config
+ (let ((conf (file-append nginx-config "/etc/nginx/nginx.conf")))
+ (nginx-configuration (file conf))))
+
+(define %nginx-service
+ (service nginx-service-type %nginx-config))
--- /dev/null
+;;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+;;; License: GPL-3.0 or later.
+;;;
+;;; nginx test cases.
+
+(define-module (tests nginx)
+ #:use-module (conf vps website)
+ #:use-module (gnu packages web)
+ #:use-module (guix gexp)
+ #:export (nginx-test-cases))
+
+(define %nginx-binary
+ (file-append nginx "/sbin/nginx"))
+
+(define (nginx-test-cases marionette)
+ "Return a gexp with nginx test assertions."
+ #~(begin
+ (test-assert "nginx: service running"
+ (marionette-eval
+ '(begin (use-modules (gnu services herd))
+ (start-service 'nginx))
+ #$marionette))
+
+ (test-assert "nginx: port 80 TCP"
+ (wait-for-tcp-port 80 #$marionette))
+
+ (test-assert "nginx: config syntax is valid"
+ (marionette-eval
+ '(zero? (system* #$%nginx-binary
+ "-c" "/etc/nginx/nginx.conf"
+ "-t"))
+ #$marionette))))