home-server/services/observer-effect.nix

87 lines
2.4 KiB
Nix
Raw Normal View History

2026-06-15 21:22:41 -07:00
{ 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;
};
};
}