259 lines
8 KiB
Nix
259 lines
8 KiB
Nix
{ pkgs, pkgs-unstable, opencode, beads, ... }:
|
|
|
|
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.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.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;
|
|
};
|
|
|
|
egress-status = pkgs.writeShellApplication {
|
|
name = "egress-status";
|
|
runtimeInputs = with pkgs; [ systemd gnugrep gnused coreutils ];
|
|
text = builtins.readFile ./scripts/egress-status;
|
|
};
|
|
|
|
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;
|
|
interfaces.ens3.useDHCP = true;
|
|
|
|
# Firewall
|
|
firewall = {
|
|
enable = true;
|
|
allowedTCPPorts = [ 22 80 443 ]; # SSH, HTTP, HTTPS
|
|
allowedUDPPortRanges = [ { from = 60000; to = 60010; } ]; # mosh
|
|
|
|
# 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 = ''
|
|
# Rate limit new outbound connections (150/min sustained, burst 300)
|
|
# High enough for npm install, low enough to prevent abuse
|
|
# DROP instead of REJECT so apps back off naturally
|
|
iptables -A OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \
|
|
-m limit --limit 150/min --limit-burst 300 -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 DROP
|
|
'';
|
|
|
|
# Clean up on stop
|
|
extraStopCommands = ''
|
|
iptables -D OUTPUT -m state --state NEW -m owner --uid-owner 1000:65534 \
|
|
-m limit --limit 150/min --limit-burst 300 -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 DROP 2>/dev/null || true
|
|
'';
|
|
};
|
|
};
|
|
|
|
# Time zone
|
|
time.timeZone = "UTC";
|
|
|
|
# Internationalization
|
|
i18n.defaultLocale = "en_US.UTF-8";
|
|
|
|
# Environment configuration
|
|
environment = {
|
|
systemPackages = with pkgs; [
|
|
vim
|
|
git
|
|
htop
|
|
curl
|
|
tmux
|
|
# Dev environment
|
|
python3
|
|
uv
|
|
direnv
|
|
zig
|
|
# AI coding tools (via flake inputs)
|
|
opencode # AI coding agent (opencode CLI)
|
|
beads # Issue tracker for AI-supervised workflows (bd CLI)
|
|
# For JS-based AI tools (gemini-cli, claude-cli): users run bun/npm install
|
|
nodejs_22
|
|
bun
|
|
# Terminfo for modern terminals (ghostty, kitty, etc.)
|
|
pkgs-unstable.ghostty.terminfo
|
|
kitty.terminfo
|
|
# Classic Unix social tools
|
|
bsd-finger
|
|
ytalk
|
|
fortune
|
|
# Mobile shell - resilient SSH for spotty connections
|
|
mosh
|
|
# Admin scripts (declarative deployment)
|
|
dev-add
|
|
dev-remove
|
|
egress-status
|
|
];
|
|
|
|
# Add ~/.local/bin and /usr/local/bin to PATH for manually installed tools
|
|
localBinInPath = true;
|
|
shellInit = ''
|
|
export PATH="/usr/local/bin:$PATH"
|
|
|
|
# Set COLORTERM for truecolor terminals (SSH doesn't forward this)
|
|
case "$TERM" in
|
|
xterm-ghostty|xterm-kitty|alacritty|xterm-256color)
|
|
export COLORTERM=truecolor
|
|
;;
|
|
esac
|
|
'';
|
|
};
|
|
|
|
# Fortune on interactive shell login
|
|
programs.bash.interactiveShellInit = ''
|
|
echo
|
|
fortune
|
|
echo
|
|
'';
|
|
|
|
# Nix settings
|
|
nix.settings = {
|
|
experimental-features = [ "nix-command" "flakes" ];
|
|
# Allow dev users to use nix daemon (nix develop, nix build, etc.)
|
|
trusted-users = [ "root" "@devs" ];
|
|
};
|
|
|
|
# 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"
|
|
];
|
|
|
|
# Devs group for shared resources (Slack tokens, etc.)
|
|
users.groups.devs = {};
|
|
|
|
# ACME for Let's Encrypt certificates
|
|
security.acme = {
|
|
acceptTerms = true;
|
|
defaults.email = "dlei@duck.com";
|
|
};
|
|
|
|
# Allow deprecated olm library for Matrix bridges (maubot uses mautrix which needs olm)
|
|
# Note: Also permitted in flake.nix for pkgs-unstable (mautrix-slack)
|
|
nixpkgs.config.permittedInsecurePackages = [
|
|
"olm-3.2.16"
|
|
];
|
|
|
|
# Systemd configuration - resource limits and watchdogs
|
|
systemd = {
|
|
# Resource limits for user slices (prevent one user from crashing server)
|
|
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
|
|
};
|
|
|
|
services = {
|
|
# CPU watchdog - detects sustained abuse, kills offending user
|
|
cpu-watchdog = {
|
|
description = "CPU abuse watchdog";
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${cpu-watchdog}/bin/cpu-watchdog";
|
|
};
|
|
};
|
|
|
|
# Egress watchdog - detects users hitting rate limits, kills after 3 strikes
|
|
egress-watchdog = {
|
|
description = "Egress abuse watchdog";
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${egress-watchdog}/bin/egress-watchdog";
|
|
};
|
|
};
|
|
};
|
|
|
|
timers = {
|
|
cpu-watchdog = {
|
|
description = "Run CPU watchdog every minute";
|
|
wantedBy = [ "timers.target" ];
|
|
timerConfig = {
|
|
OnBootSec = "1min";
|
|
OnUnitActiveSec = "1min";
|
|
};
|
|
};
|
|
|
|
egress-watchdog = {
|
|
description = "Run egress watchdog every minute";
|
|
wantedBy = [ "timers.target" ];
|
|
timerConfig = {
|
|
OnBootSec = "1min";
|
|
OnUnitActiveSec = "1min";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# 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
|
|
'';
|
|
|
|
# This value determines the NixOS release compatibility
|
|
system.stateVersion = "24.05";
|
|
}
|