{ config, pkgs, ... }: let # Tiny Rust WebSocket relay. cargoLock.lockFile pins deps from the committed # Cargo.lock, so there's no vendor hash to chase on dependency bumps. relay = pkgs.rustPlatform.buildRustPackage { pname = "observer-relay"; version = "0.1.0"; src = ./observer-effect/relay; cargoLock.lockFile = ./observer-effect/relay/Cargo.lock; }; # The two static pages (player chronometer + Handler panel) and their assets. site = pkgs.copyPathToStore ./observer-effect/site; domain = "observer.ellie.town"; port = 8770; in { # Shared control key. Decrypts to a file the relay reads at startup, so it # never lands in /nix/store. sops.secrets."observer/token" = { sopsFile = ./secrets/observer_vps.yaml; owner = "observer"; group = "observer"; mode = "0400"; }; users.users.observer = { isSystemUser = true; group = "observer"; }; users.groups.observer = { }; systemd.services.observer-relay = { description = "Observer Effect doomsday-clock relay"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; environment = { OBSERVER_ADDR = "127.0.0.1:${toString port}"; OBSERVER_TOKEN_FILE = config.sops.secrets."observer/token".path; }; serviceConfig = { ExecStart = "${relay}/bin/observer-relay"; User = "observer"; Group = "observer"; Restart = "on-failure"; RestartSec = "5s"; # Hardening — it only needs a loopback socket and to read one secret. ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; NoNewPrivileges = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictSUIDSGID = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; LockPersonality = true; MemoryDenyWriteExecute = true; SystemCallFilter = [ "@system-service" ]; }; }; services.nginx.virtualHosts.${domain} = { enableACME = true; forceSSL = true; root = site; locations."/" = { index = "clock.html"; }; # Tidy URL for the Handler's panel. locations."= /control".return = "302 /control.html"; # Relay socket: upgrade to WebSocket and hand off to the local service. locations."/ws" = { proxyPass = "http://127.0.0.1:${toString port}"; proxyWebsockets = true; }; }; }