From 70443c83ce082733d8db9356b786a1bd56c3423e Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 1 Feb 2026 17:57:33 +0300 Subject: [PATCH] nix: harden Systemd service in NixOS module Signed-off-by: NotAShelf Change-Id: I086cdbfcd0a0d1d87173a2cf97f5b5416a6a6964 --- nix/modules/nixos.nix | 142 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 26 deletions(-) diff --git a/nix/modules/nixos.nix b/nix/modules/nixos.nix index b9502c7..4368d9c 100644 --- a/nix/modules/nixos.nix +++ b/nix/modules/nixos.nix @@ -25,59 +25,149 @@ in { ''; }; - user = mkOption { - type = str; - default = "troutbot"; - }; - - group = mkOption { - type = str; - default = "troutbot"; - }; - - port = mkOption { - type = port; - default = 3000; - }; - environmentFile = mkOption { type = nullOr str; default = null; + description = '' + Environment file for specifying additional settings such as secrets. + ''; }; configPath = mkOption { type = nullOr str; default = null; + description = '' + File path containing the Troutbot Typescript config. + ''; + }; + + settings = { + port = mkOption { + type = port; + default = 3000; + }; + + user = mkOption { + type = str; + default = "troutbot"; + description = "User to run Troutbot under"; + }; + + group = mkOption { + type = str; + default = "troutbot"; + description = "Group to run Troutbot under"; + }; }; }; config = mkIf cfg.enable { - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; + users = { + groups.${cfg.settings.group} = {}; + users.${cfg.settings.user} = { + isSystemUser = true; + inherit (cfg.settings) group; + }; }; - users.groups.${cfg.group} = {}; - systemd.services.troutbot = { - description = "Troutbot"; - after = ["network.target"]; + description = "Troutbot - GitHub PR/Issue Analysis Bot"; + documentation = ["https://github.com/notashelf/troutbot"]; + after = ["network-online.target"]; + wants = ["network-online.target"]; wantedBy = ["multi-user.target"]; + + environment = { + NODE_ENV = "production"; + CONFIG_PATH = cfg.configPath; + PORT = toString cfg.port; + }; + serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; ExecStart = "${lib.getExe cfg.package}"; + + # Restart policy with rate limiting Restart = "on-failure"; - EnvironmentFile = cfg.environmentFile; - NODE_ENV = "production"; - CONFIG_PATH = cfg.configPath; - PORT = toString cfg.port; + RestartSec = "5s"; + StartLimitInterval = "60s"; + StartLimitBurst = 3; + + # Timeouts + TimeoutStartSec = "30s"; + TimeoutStopSec = "30s"; + + # Working directory and state + WorkingDirectory = "/var/lib/troutbot"; + StateDirectory = "troutbot"; + StateDirectoryMode = "0750"; + + # Environment file for secrets + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; + + # Security hardening + # Filesystem restrictions ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; + PrivateDevices = true; + + # Process restrictions NoNewPrivileges = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + ProtectClock = true; + + # Memory and execution restrictions + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + + # IPC cleanup + RemoveIPC = true; + + # Capabilities + CapabilityBoundingSet = [""]; # no capabilities needed + AmbientCapabilities = []; # no ambient capabilities + + # System call filtering + SystemCallFilter = [ + "@system-service" + "~@privileged" + "~@resources" + ]; + SystemCallErrorNumber = "EPERM"; + + # Address families, only IPv4/IPv6 needed + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + "AF_NETLINK" # for DNS resolution + ]; + + UMask = "0027"; + DeviceAllow = []; }; }; + + serviceConfig = { + Type = "simple"; + User = cfg.settings.user; + Group = cfg.settings.group; + ExecStart = "${lib.getExe cfg.package}"; + Restart = "on-failure"; + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; + WorkingDirectory = "/var/lib/troutbot"; + StateDirectory = "troutbot"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + }; }; }