Kieran's opinionated (and probably slightly dumb) nix config

feat: add battleship

dunkirk.sh 6be37ef7 9923b828

verified
+176
+22
flake.lock
··· 37 "type": "github" 38 } 39 }, 40 "catppuccin": { 41 "inputs": { 42 "nixpkgs": [ ··· 1055 "root": { 1056 "inputs": { 1057 "agenix": "agenix", 1058 "catppuccin": "catppuccin", 1059 "catppuccin-vsc": "catppuccin-vsc", 1060 "cedarlogic": "cedarlogic",
··· 37 "type": "github" 38 } 39 }, 40 + "battleship-arena": { 41 + "inputs": { 42 + "nixpkgs": [ 43 + "nixpkgs" 44 + ] 45 + }, 46 + "locked": { 47 + "lastModified": 1764790763, 48 + "narHash": "sha256-zeitWozGRMpBXpv0w2JchhvijPceJ1ZBE18Y3j19je8=", 49 + "owner": "taciturnaxolotl", 50 + "repo": "battleship-arena", 51 + "rev": "ea9ed8d601369a4fb29b92ea6f408f1610ac4280", 52 + "type": "github" 53 + }, 54 + "original": { 55 + "owner": "taciturnaxolotl", 56 + "repo": "battleship-arena", 57 + "rev": "ea9ed8d601369a4fb29b92ea6f408f1610ac4280", 58 + "type": "github" 59 + } 60 + }, 61 "catppuccin": { 62 "inputs": { 63 "nixpkgs": [ ··· 1076 "root": { 1077 "inputs": { 1078 "agenix": "agenix", 1079 + "battleship-arena": "battleship-arena", 1080 "catppuccin": "catppuccin", 1081 "catppuccin-vsc": "catppuccin-vsc", 1082 "cedarlogic": "cedarlogic",
+5
flake.nix
··· 104 url = "git+https://tangled.org/tangled.org/core"; 105 inputs.nixpkgs.follows = "nixpkgs"; 106 }; 107 }; 108 109 outputs =
··· 104 url = "git+https://tangled.org/tangled.org/core"; 105 inputs.nixpkgs.follows = "nixpkgs"; 106 }; 107 + 108 + battleship-arena = { 109 + url = "github:taciturnaxolotl/battleship-arena/ea9ed8d601369a4fb29b92ea6f408f1610ac4280"; 110 + inputs.nixpkgs.follows = "nixpkgs"; 111 + }; 112 }; 113 114 outputs =
+26
machines/terebithia/default.nix
··· 123 file = ../../secrets/github-knot-sync.age; 124 owner = "git"; 125 }; 126 }; 127 128 environment.sessionVariables = { ··· 246 } 247 ''; 248 }; 249 extraConfig = '' 250 # Default response for unhandled domains 251 :80 { ··· 277 enable = true; 278 domain = "emojibot.dunkirk.sh"; 279 secretsFile = config.age.secrets.emojibot.path; 280 }; 281 282 services.tangled.knot = {
··· 123 file = ../../secrets/github-knot-sync.age; 124 owner = "git"; 125 }; 126 + battleship-arena = { 127 + file = ../../secrets/battleship-arena.age; 128 + owner = "battleship-arena"; 129 + }; 130 }; 131 132 environment.sessionVariables = { ··· 250 } 251 ''; 252 }; 253 + virtualHosts."battleship.dunkirk.sh" = { 254 + extraConfig = '' 255 + tls { 256 + dns cloudflare {env.CLOUDFLARE_API_TOKEN} 257 + } 258 + header { 259 + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 260 + } 261 + reverse_proxy localhost:8081 { 262 + header_up X-Forwarded-Proto {scheme} 263 + header_up X-Forwarded-For {remote} 264 + } 265 + ''; 266 + }; 267 extraConfig = '' 268 # Default response for unhandled domains 269 :80 { ··· 295 enable = true; 296 domain = "emojibot.dunkirk.sh"; 297 secretsFile = config.age.secrets.emojibot.path; 298 + }; 299 + 300 + atelier.services.battleship-arena = { 301 + enable = true; 302 + domain = "battleship.dunkirk.sh"; 303 + sshPort = 2222; 304 + package = inputs.battleship-arena.packages.aarch64-linux.default; 305 + secretsFile = config.age.secrets.battleship-arena.path; 306 }; 307 308 services.tangled.knot = {
+120
modules/nixos/services/battleship-arena.nix
···
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + with lib; 4 + 5 + let 6 + cfg = config.atelier.services.battleship-arena; 7 + in 8 + { 9 + options.atelier.services.battleship-arena = { 10 + enable = mkEnableOption "battleship-arena service"; 11 + 12 + domain = mkOption { 13 + type = types.str; 14 + default = "battleship.dunkirk.sh"; 15 + description = "Domain name for the web interface"; 16 + }; 17 + 18 + sshPort = mkOption { 19 + type = types.port; 20 + default = 2222; 21 + description = "SSH port for battleship arena"; 22 + }; 23 + 24 + webPort = mkOption { 25 + type = types.port; 26 + default = 8081; 27 + description = "Web interface port"; 28 + }; 29 + 30 + uploadDir = mkOption { 31 + type = types.str; 32 + default = "/var/lib/battleship-arena/submissions"; 33 + description = "Directory for uploaded submissions"; 34 + }; 35 + 36 + resultsDb = mkOption { 37 + type = types.str; 38 + default = "/var/lib/battleship-arena/results.db"; 39 + description = "Path to results database"; 40 + }; 41 + 42 + adminPasscode = mkOption { 43 + type = types.str; 44 + default = "battleship-admin-override"; 45 + description = "Admin passcode for batch uploads"; 46 + }; 47 + 48 + secretsFile = mkOption { 49 + type = types.nullOr types.path; 50 + default = null; 51 + description = "Path to agenix secrets file containing BATTLESHIP_ADMIN_PASSCODE"; 52 + }; 53 + 54 + package = mkOption { 55 + type = types.package; 56 + description = "The battleship-arena package to use"; 57 + }; 58 + }; 59 + 60 + config = mkIf cfg.enable { 61 + users.users.battleship-arena = { 62 + isSystemUser = true; 63 + group = "battleship-arena"; 64 + home = "/var/lib/battleship-arena"; 65 + createHome = true; 66 + }; 67 + 68 + users.groups.battleship-arena = {}; 69 + 70 + systemd.services.battleship-arena = { 71 + description = "Battleship Arena SSH/Web Service"; 72 + after = [ "network.target" ]; 73 + wantedBy = [ "multi-user.target" ]; 74 + 75 + environment = { 76 + BATTLESHIP_HOST = "0.0.0.0"; 77 + BATTLESHIP_SSH_PORT = toString cfg.sshPort; 78 + BATTLESHIP_WEB_PORT = toString cfg.webPort; 79 + BATTLESHIP_UPLOAD_DIR = cfg.uploadDir; 80 + BATTLESHIP_RESULTS_DB = cfg.resultsDb; 81 + BATTLESHIP_ADMIN_PASSCODE = cfg.adminPasscode; 82 + BATTLESHIP_EXTERNAL_URL = "https://${cfg.domain}"; 83 + }; 84 + 85 + serviceConfig = { 86 + Type = "simple"; 87 + User = "battleship-arena"; 88 + Group = "battleship-arena"; 89 + WorkingDirectory = "/var/lib/battleship-arena"; 90 + ExecStart = "${cfg.package}/bin/battleship-arena"; 91 + Restart = "always"; 92 + RestartSec = "10s"; 93 + 94 + # Load secrets if provided 95 + EnvironmentFile = mkIf (cfg.secretsFile != null) cfg.secretsFile; 96 + 97 + # Security hardening 98 + NoNewPrivileges = true; 99 + PrivateTmp = true; 100 + ProtectSystem = "strict"; 101 + ProtectHome = true; 102 + ReadWritePaths = [ "/var/lib/battleship-arena" ]; 103 + }; 104 + 105 + preStart = '' 106 + mkdir -p ${cfg.uploadDir} 107 + mkdir -p $(dirname ${cfg.resultsDb}) 108 + 109 + # Generate SSH host key if it doesn't exist 110 + if [ ! -f /var/lib/battleship-arena/.ssh/battleship_arena ]; then 111 + mkdir -p /var/lib/battleship-arena/.ssh 112 + ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /var/lib/battleship-arena/.ssh/battleship_arena -N "" 113 + chown -R battleship-arena:battleship-arena /var/lib/battleship-arena/.ssh 114 + fi 115 + ''; 116 + }; 117 + 118 + networking.firewall.allowedTCPPorts = [ cfg.sshPort ]; 119 + }; 120 + }
secrets/battleship-arena.age

This is a binary file and will not be displayed.

+3
secrets/secrets.nix
··· 38 "emojibot.age".publicKeys = [ 39 kierank 40 ]; 41 }
··· 38 "emojibot.age".publicKeys = [ 39 kierank 40 ]; 41 + "battleship-arena.age".publicKeys = [ 42 + kierank 43 + ]; 44 }