87 lines
2.4 KiB
Nix
87 lines
2.4 KiB
Nix
|
|
{ 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;
|
||
|
|
};
|
||
|
|
};
|
||
|
|
}
|