build-vps: build-vps-system build-vps-home
+# VPS test suite — validates configurations then boots a VM and
+# runs all test cases. Uses cache by default; tests are
+# deterministic for given inputs.
+test-vps: build-vps-system build-vps-home
+ @out=$$(GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+ -e '(@ (tests vps-base) %test-vps)' 2>/dev/null); \
+ if [ -n "$$out" ] && [ -e "$$out" ]; then \
+ echo "📋 Cached: $$out"; \
+ echo " Run 'make test-vps-fresh' to re-execute."; \
+ else \
+ GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+ -e '(@ (tests vps-base) %test-vps)'; \
+ fi
+
+# Force fresh tests — delete cached result and rebuild.
+test-vps-fresh: build-vps-system build-vps-home
+ @out=$$(GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+ -e '(@ (tests vps-base) %test-vps)' 2>/dev/null); \
+ [ -n "$$out" ] && [ -e "$$out" ] \
+ && guix gc --delete "$$out" 2>/dev/null; \
+ GUIX_PACKAGE_PATH=$(CURDIR) guix build -L $(CURDIR) \
+ -e '(@ (tests vps-base) %test-vps)'
+
tags:
find $(guix-store) -wholename "*guix-$(guix-version)*.scm" -or \
-wholename "*guix-module-union*.scm" -or \
--- /dev/null
+;;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
+;;; License: GPL-3.0 or later.
+;;;
+;;; VPS test harness — one VM for all test sections.
+;;; Inherits from the production operating system so any change to
+;;; vps-system.scm or vps-home.scm is automatically tested.
+
+(define-module (tests vps-base)
+ #:use-module (gnu home)
+ #:use-module (gnu tests)
+ #:use-module (gnu system)
+ #:use-module (gnu system vm)
+ #:use-module (gnu services)
+ #:use-module (gnu services base)
+ #:use-module (gnu services ssh)
+ #:use-module (gnu services networking)
+ #:use-module (gnu services shepherd)
+ #:use-module (gnu packages admin)
+ #:use-module (gnu packages base)
+ #:use-module (gnu packages bash)
+ #:use-module (gnu packages ssh)
+ #:use-module (vps-system)
+ #:use-module (vps-home)
+ #:use-module (guix gexp)
+ #:export (%test-vps %vps-test-os run-vps-test))
+
+;;;
+;;; Test SSH keypair — generated at build time.
+;;;
+
+(define %test-ssh-keypair
+ (computed-file "test-ssh-keypair"
+ (with-imported-modules '((guix build utils))
+ #~(begin
+ (use-modules (guix build utils))
+ (mkdir #$output)
+ (invoke #$(file-append openssh
+ "/bin/ssh-keygen")
+ "-t"
+ "ed25519"
+ "-f"
+ (string-append #$output
+ "/id_ed25519")
+ "-N"
+ ""
+ "-C"
+ "guix-test@vps")
+ (copy-file (string-append #$output
+ "/id_ed25519.pub")
+ (string-append #$output
+ "/authorized_key"))))
+ #:local-build? #f))
+
+;;;
+;;; Static networking — QEMU user-mode DHCP is unreliable during
+;;; early boot, causing dhcpcd to hang and block the marionette REPL.
+;;;
+
+(define %static-network-config
+ (let ((addr (network-address (device "eth0")
+ (value "10.0.2.15/24")))
+ (route (network-route (destination "default")
+ (gateway "10.0.2.2"))))
+ (static-networking (addresses (list addr))
+ (routes (list route)))))
+
+(define %static-networking
+ (service static-networking-service-type
+ (list %static-network-config)))
+
+;;;
+;;; Test operating system — inherits from production with only two
+;;; overrides: static networking and a throwaway SSH key for dak.
+;;;
+
+(define (test-os-services base-os)
+ "Return the service list for the test VM. Removes dhcpcd (QEMU
+DHCP is unreliable), prepends static networking, and adds a throwaway
+ed25519 key to the production SSH config."
+ (define (add-test-key config)
+ (let ((key (file-append %test-ssh-keypair "/authorized_key")))
+ (openssh-configuration (inherit config)
+ (authorized-keys `(("dak" ,key))))))
+ (cons %static-networking
+ (modify-services (operating-system-user-services base-os)
+ (delete dhcpcd-service-type)
+ (openssh-service-type config =>
+ (add-test-key config)))))
+
+(define %vps-test-os
+ (marionette-operating-system (operating-system
+ (inherit vps-operating-system)
+ (packages (append (home-environment-packages
+ vps-home-environment)
+ (operating-system-packages
+ vps-operating-system)))
+ (services
+ (test-os-services vps-operating-system)))
+ #:imported-modules '((gnu services herd)
+ (guix combinators))))
+
+;;;
+;;; SSH session helper — key-only auth as user dak via the
+;;; forwarded port (host:2222 -> guest:72).
+;;;
+
+(define (make-ssh-connect marionette-gexp)
+ "Return a gexp that connects over SSH using the system ssh
+command, runs a shell command, and verifies the witness file."
+ #~(lambda ()
+ (let* ((ssh #$(file-append openssh "/bin/ssh"))
+ (key (string-append #$%test-ssh-keypair "/id_ed25519"))
+ (exit (system* ssh
+ "-i"
+ key
+ "-o"
+ "StrictHostKeyChecking=no"
+ "-o"
+ "UserKnownHostsFile=/dev/null"
+ "-o"
+ "BatchMode=yes"
+ "-o"
+ "ConnectTimeout=10"
+ "-p"
+ "2222"
+ "dak@localhost"
+ "echo hello > /tmp/witness")))
+ (and (zero? exit)
+ (wait-for-file "/tmp/witness"
+ #$marionette-gexp)))))
+
+;;;
+;;; Test harness.
+;;;
+
+(define (run-vps-test test-cases)
+ "Return a derivation that boots a VM with %vps-test-os and runs
+every function in TEST-CASES. Each element is a function
+(marionette-gexp) -> gexp that returns test assertions."
+ (define os
+ %vps-test-os)
+ (define vm
+ (virtual-machine (operating-system
+ os)
+ (port-forwardings '((2222 . 72)))))
+
+ (define test
+ (with-imported-modules '((gnu build marionette))
+ #~(begin
+ (use-modules (gnu build marionette)
+ (srfi srfi-64)
+ (ice-9 match))
+
+ (define marionette
+ (make-marionette (list #$vm)))
+
+ (define runner
+ (system-test-runner #$output))
+
+ (test-runner-current runner)
+
+ (test-begin "vps")
+
+ #$@(map (lambda (tc)
+ (tc #~marionette)) test-cases)
+
+ (test-end "vps"))))
+ (gexp->derivation "vps-test" test))
+
+;;;
+;;; System test record — test cases are added incrementally in
+;;; subsequent commits.
+;;;
+
+(define %test-vps
+ (system-test (name "vps")
+ (description "VPS test suite.")
+ (value (run-vps-test '()))))