--- /dev/null
+;;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+;;; License: GPL-3.0 or later.
+;;;
+;;; website.scm — Static website package served via nginx with SSI.
+
+(define-module (conf vps website)
+ #:use-module (gnu packages web)
+ #:use-module (guix build-system trivial)
+ #:use-module (ice-9 string-fun)
+ #:use-module (guix gexp)
+ #:use-module (guix git-download)
+ #:use-module (guix packages)
+ #:use-module ((guix licenses)
+ #:prefix license:)
+ #:export (website %public-domain %nginx-site-config))
+
+(define %public-domain
+ "example.org")
+
+(define website
+ (package
+ (name "website")
+ (version "20240511")
+ (source
+ (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://git.ekhem.eu.org/website.git")
+ (commit "0a48cd756788223be5a2c1c3865b65fb9a8476c8")))
+ (sha256
+ (base32 "10038b680vq39ar0r0f2v2br7803r3531zyknycf6zbih6bjyvx5"))))
+ (build-system trivial-build-system)
+ (arguments
+ (list
+ #:modules '((guix build utils))
+ #:builder
+ #~(begin
+ (use-modules (guix build utils))
+ (let ((out (string-append #$output "/share/website")))
+ (mkdir-p out)
+ (copy-recursively #$source out)
+ (for-each (lambda (f)
+ (when (file-exists? (string-append out "/" f))
+ (delete-file (string-append out "/" f))))
+ '("views.sh" "revision.sh" "create.sql" "drop.sql"))))))
+ (home-page "https://git.ekhem.eu.org")
+ (synopsis "Static website files served via nginx with SSI")
+ (description "HTML and media files for a personal website,
+served by nginx with Server-Side Includes.")
+ (license license:gpl3+)))
+
+(define %nginx-site-config
+ (package
+ (name "nginx-site-config")
+ (version "0")
+ (source
+ #f)
+ (build-system trivial-build-system)
+ (inputs (list website nginx))
+ (arguments
+ (list
+ #:modules '((guix build utils)
+ (ice-9 rdelim)
+ (ice-9 string-fun))
+ #:builder
+ #~(begin
+ (use-modules (guix build utils)
+ (ice-9 rdelim)
+ (ice-9 string-fun))
+ (let* ((domain #$%public-domain)
+ (webroot (string-append #$website "/share/website"))
+ (conf-in (string-append webroot "/website.conf"))
+ (out-dir (string-append #$output "/share/nginx"))
+ (out-file (string-append out-dir "/site.conf"))
+ (mime-types (string-append #$nginx
+ "/share/nginx/conf/mime.types"))
+ (skip? #f)
+ (in-location? #f))
+ (mkdir-p out-dir)
+ (call-with-output-file out-file
+ (lambda (out)
+ (format out
+ "user nginx nginx;
+pid /var/run/nginx/pid;
+error_log /var/log/nginx/error.log error;
+events {}
+http {
+ 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 ~a;
+
+"
+ mime-types)
+ (call-with-input-file conf-in
+ (lambda (in)
+ (let lp
+ ((line (read-line in)))
+ (unless (eof-object? line)
+ (set! line
+ (string-replace-substring line "${dollar}" "$"))
+ (set! line
+ (string-replace-substring line
+ "${public_domain}"
+ domain))
+ (set! line
+ (string-replace-substring line "${prod_dir}/www"
+ webroot))
+ (set! line
+ (string-replace-substring line "${prod_dir}"
+ webroot))
+ (set! line
+ (string-replace-substring line
+ "${website_log_file}"
+ "/var/log/nginx/website-access.log"))
+ (cond
+ ((or (string-contains line "/views/")
+ (string-contains line "/revision/"))
+ (set! in-location? #t))
+ ((and in-location?
+ (string-contains line "}"))
+ (set! in-location? #f))
+ (in-location? #f)
+ ((string-contains line "return 301 https")
+ (set! skip? #t))
+ ((and skip?
+ (string-contains line "}"))
+ (set! skip? #f))
+ (skip? #f)
+ ((or (string-contains line "443")
+ (string-contains line "ssl_certificate")
+ (string-contains line "ssl_trusted")
+ (string-contains line "userid"))
+ #f)
+ ((string-contains line "[::]:80")
+ #f)
+ ((string-contains line "listen 80")
+ (display " listen 80;\n" out))
+ (else (display line out)
+ (newline out)))
+ (lp (read-line in))))))
+ (display "}\n" out)))))))
+ (home-page #f)
+ (synopsis "Nginx configuration for the website")
+ (description #f)
+ (license license:gpl3+)))
(define-module (tests common)
#:use-module (guix gexp)
- #:export (assert-service-running assert-binary-exists))
+ #:export (assert-service-running assert-binary-exists assert-http-200))
(define (assert-service-running label service-name marionette-gexp)
"Return a gexp that asserts the Shepherd service SERVICE-NAME is
(marionette-eval '(file-exists? (string-append #$pkg "/bin/"
#$binary-name))
#$marionette-gexp)))
+
+(define (assert-http-200 label uri marionette-gexp)
+ "Return a gexp that asserts an HTTP GET to URI returns 200."
+ (let ((body `(begin
+ (use-modules (ice-9 rdelim)
+ (srfi srfi-13))
+ (let* ((req (string-append "GET "
+ ,uri " HTTP/1.0\r\n\r\n"))
+ (s (socket PF_INET SOCK_STREAM 0))
+ (addr (make-socket-address AF_INET INADDR_LOOPBACK 80)))
+ (catch 'system-error
+ (lambda ()
+ (connect s addr)
+ (display req s)
+ (force-output s)
+ (let ((rsp (read-line s)))
+ (close-port s)
+ (string-contains rsp "200")))
+ (lambda (key . args)
+ (close-port s) #f))))))
+ #~(test-assert #$label
+ (marionette-eval '#$body
+ #$marionette-gexp))))