{ config, pkgs, beads, opencode, ... }: let # ========================================================================== # Watchdog Scripts - Referenced directly by systemd, NOT in PATH # ========================================================================== killswitch = pkgs.writeShellApplication { name = "killswitch"; runtimeInputs = with pkgs; [ procps systemd util-linux coreutils ]; text = builtins.readFile ./scripts/killswitch; }; cpu-watchdog = pkgs.writeShellApplication { name = "cpu-watchdog"; runtimeInputs = with pkgs; [ procps gawk coreutils util-linux killswitch ]; text = builtins.replaceStrings [ "/usr/local/bin/killswitch" ] [ "killswitch" ] (builtins.readFile ./scripts/cpu-watchdog); }; egress-watchdog = pkgs.writeShellApplication { name = "egress-watchdog"; # Note: -e removed intentionally - grep returns 1 when no matches bashOptions = [ "nounset" "pipefail" ]; runtimeInputs = with pkgs; [ systemd gnugrep coreutils util-linux killswitch gawk ]; text = builtins.replaceStrings [ "/usr/local/bin/killswitch" ] [ "killswitch" ] (builtins.readFile ./scripts/egress-watchdog); }; # ========================================================================== # Admin Scripts - Added to systemPackages for interactive use # ========================================================================== dev-add = pkgs.writeShellApplication { name = "dev-add"; runtimeInputs = with pkgs; [ shadow coreutils iproute2 gnugrep gawk ]; text = builtins.readFile ./scripts/dev-add.sh; }; dev-remove = pkgs.writeShellApplication { name = "dev-remove"; runtimeInputs = with pkgs; [ shadow coreutils gnutar procps findutils ]; text = builtins.readFile ./scripts/dev-remove.sh; }; in { # Main NixOS configuration for ops-jrz1 server # Imports host-specific configuration from hosts/ops-jrz1.nix # Boot loader configuration (Legacy BIOS for Vultr VPS) boot.loader.grub.enable = true; boot.loader.grub.device = "/dev/vda"; # Install to MBR # Network configuration networking.useDHCP = false; networking.interfaces.ens3.useDHCP = true; # Time zone time.timeZone = "UTC"; # Internationalization i18n.defaultLocale = "en_US.UTF-8"; # System packages environment.systemPackages = with pkgs; [ vim git htop curl tmux # Dev environment python3 uv direnv # AI coding tools (via flake inputs) beads # Issue tracker (bd CLI) opencode # AI coding agent (opencode CLI) # For npm-based AI tools (gemini-cli, codex): users run npm install nodejs_22 # Admin scripts (declarative deployment) dev-add dev-remove ]; # Add ~/.local/bin and /usr/local/bin to PATH for manually installed tools environment.localBinInPath = true; environment.shellInit = '' export PATH="/usr/local/bin:$PATH" ''; # Enable Nix flakes nix.settings.experimental-features = [ "nix-command" "flakes" ]; # SSH configuration services.openssh = { enable = true; settings = { PermitRootLogin = "prohibit-password"; PasswordAuthentication = false; }; }; # SSH authorized keys for root users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOqHsgAuD/8LL6HN3fo7X1ywryQG393pyQ19a154bO+h delpad-2025" ]; # Firewall (will be configured for Matrix services) networking.firewall = { enable = true; allowedTCPPorts = [ 22 80 443 ]; # SSH, HTTP, HTTPS # Egress controls for regular users # UID range: 1000 (first regular user) to 65534 (nobody - excluded from controls) # This covers all dev accounts while excluding system services extraCommands = '' # Log all new outbound connections from regular users iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j LOG --log-prefix "EGRESS: " --log-level info # Rate limit new outbound connections (30/min sustained, burst 60) iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -m limit --limit 30/min --limit-burst 60 -j ACCEPT iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j REJECT ''; # Clean up on stop extraStopCommands = '' iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j LOG --log-prefix "EGRESS: " --log-level info 2>/dev/null || true iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -m limit --limit 30/min --limit-burst 60 -j ACCEPT 2>/dev/null || true iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j LOG --log-prefix "EGRESS-LIMIT: " --log-level warning 2>/dev/null || true iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \ -j REJECT 2>/dev/null || true ''; }; # ACME for Let's Encrypt certificates security.acme = { acceptTerms = true; defaults.email = "dlei@duck.com"; }; # Allow deprecated olm library for Matrix bridges # Note: olm is deprecated with known CVEs but required by mautrix bridges # This is necessary for Matrix bridge functionality until alternatives are available nixpkgs.config.permittedInsecurePackages = [ "olm-3.2.16" ]; # Resource limits for user slices (prevent one user from crashing server) systemd.slices."user".sliceConfig = { MemoryMax = "80%"; # Users collectively can't exceed 80% RAM TasksMax = 500; # Max 500 total processes across all users CPUWeight = 100; # Fair sharing when contended, bursts allowed }; # Per-user limits - create drop-in on activation # (user-.slice is the template for user-XXXX.slice) system.activationScripts.userSliceLimits = '' mkdir -p /run/systemd/system/user-.slice.d cat > /run/systemd/system/user-.slice.d/50-limits.conf << 'EOF' [Slice] MemoryMax=50% TasksMax=200 CPUQuota=200% EOF ''; # CPU watchdog - detects sustained abuse, kills offending user systemd.services.cpu-watchdog = { description = "CPU abuse watchdog"; serviceConfig = { Type = "oneshot"; ExecStart = "${cpu-watchdog}/bin/cpu-watchdog"; }; }; systemd.timers.cpu-watchdog = { description = "Run CPU watchdog every minute"; wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = "1min"; OnUnitActiveSec = "1min"; }; }; # Egress watchdog - detects users hitting rate limits, kills after 3 strikes systemd.services.egress-watchdog = { description = "Egress abuse watchdog"; serviceConfig = { Type = "oneshot"; ExecStart = "${egress-watchdog}/bin/egress-watchdog"; }; }; systemd.timers.egress-watchdog = { description = "Run egress watchdog every minute"; wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = "1min"; OnUnitActiveSec = "1min"; }; }; # This value determines the NixOS release compatibility system.stateVersion = "24.05"; }