From 26be2b15480e904d1e42a06b56d9b601c5c7c4c8 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 3 Jan 2026 10:02:37 -0800 Subject: [PATCH] Add per-user resource limits via activation script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates drop-in at /run/systemd/system/user-.slice.d/50-limits.conf to enforce per-user limits (vs slice-wide): - MemoryMax=50% (~1GB per user) - TasksMax=200 per user - CPUQuota=200% (max 2 cores sustained) Prevents one user from starving others. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .beads/issues.jsonl | 2 +- configuration.nix | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 45735fa..a8fbd55 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -22,7 +22,7 @@ {"id":"ops-jrz1-6of","title":"AI cost/rate limiting per user","description":"One user could drain API credits with runaway script. Need rate limiting per user, either via proxy middleware or opencode config. Track usage.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T15:32:30.772304538-08:00","updated_at":"2025-12-05T17:42:42.773613559-08:00","closed_at":"2025-12-05T17:42:42.773613559-08:00","dependencies":[{"issue_id":"ops-jrz1-6of","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:05:47.206816868-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"ops-jrz1-6of","depends_on_id":"ops-jrz1-wj2","type":"blocks","created_at":"2025-12-05T17:17:38.658742196-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"ops-jrz1-6t9","title":"Evaluate llm CLI: per-repo vs system-wide install","description":"Simon Willison's llm CLI tool. Options: (1) System-wide via nixpkgs, (2) Per-user via uv/pip, (3) Per-project .envrc. Consider: multiple users, plugin ecosystem, update frequency.","status":"open","priority":3,"issue_type":"task","created_at":"2026-01-03T09:35:13.705897177-08:00","created_by":"dan","updated_at":"2026-01-03T09:35:13.705897177-08:00"} {"id":"ops-jrz1-7j4","title":"Git credential strategy for non-programmers","description":"Non-programmers can't manage SSH keys. Pre-configure git-credential-store or provide simple PAT workflow with docs. Store in persistent home with 600 perms.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T15:32:19.673999683-08:00","updated_at":"2025-12-05T17:38:54.788694408-08:00","closed_at":"2025-12-05T17:38:54.788694408-08:00","dependencies":[{"issue_id":"ops-jrz1-7j4","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:05:47.139749437-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"ops-jrz1-86g","title":"Add per-user resource limits (not just slice-wide)","description":"Currently user.slice has TasksMax=500, MemoryMax=80%, but individual user-XXXX.slice has infinity. One user can starve others. Add per-user limits via systemd drop-ins or user-XXXX.slice config.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-03T08:40:25.937465595-08:00","created_by":"dan","updated_at":"2026-01-03T08:40:25.937465595-08:00"} +{"id":"ops-jrz1-86g","title":"Add per-user resource limits (not just slice-wide)","description":"Currently user.slice has TasksMax=500, MemoryMax=80%, but individual user-XXXX.slice has infinity. One user can starve others. Add per-user limits via systemd drop-ins or user-XXXX.slice config.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T08:40:25.937465595-08:00","created_by":"dan","updated_at":"2026-01-03T10:00:36.908904643-08:00","closed_at":"2026-01-03T10:00:36.908904643-08:00","close_reason":"Added per-user limits via activation script drop-in: MemoryMax=50%, TasksMax=200, CPUQuota=200%"} {"id":"ops-jrz1-88o","title":"Implement backup strategy for VPS","description":"No backups configured. Critical data: Matrix DB (622M), PostgreSQL (161M), Forgejo (2.5M), maubot (320K). No recovery path if disk fails. Need automated backups with off-site storage.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-04T22:55:25.546850172-08:00","updated_at":"2025-12-05T00:56:27.720623612-08:00","closed_at":"2025-12-05T00:56:27.720623612-08:00"} {"id":"ops-jrz1-8m7","title":"Add cgroups limits for user slices","description":"Add soft resource limits to prevent one user/agent from crashing server.\n\n## Config\n```nix\nsystemd.slices.\"user\".sliceConfig = {\n MemoryMax = \"80%\";\n TasksMax = 500;\n CPUWeight = 100; # Fair sharing, no hard quota\n};\n```\n\n## Behavior\n- Memory: Users collectively can't exceed 80% RAM\n- Tasks: Max 500 processes per user (prevents fork bombs)\n- CPU: Fair sharing when contended, bursts allowed\n\n## Testing\n- Verify with `systemctl show user-1001.slice`\n- Test fork bomb doesn't crash server","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T20:16:22.600133044-08:00","created_by":"dan","updated_at":"2026-01-02T21:02:35.455928291-08:00","closed_at":"2026-01-02T21:02:35.455928291-08:00","close_reason":"Closed"} {"id":"ops-jrz1-8mc","title":"configuration.nix: Document UID range 1000:65534 rationale","description":"UID range 1000:65534 excludes root but includes nobody (65534). Add comment explaining rationale. configuration.nix:70","status":"closed","priority":4,"issue_type":"task","created_at":"2026-01-03T08:17:35.893969961-08:00","created_by":"dan","updated_at":"2026-01-03T09:32:23.604873295-08:00","closed_at":"2026-01-03T09:32:23.604873295-08:00","close_reason":"Added comment explaining UID range 1000:65534"} diff --git a/configuration.nix b/configuration.nix index 0b7e274..04246d3 100644 --- a/configuration.nix +++ b/configuration.nix @@ -161,10 +161,22 @@ in # 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 processes per user (prevents fork bombs) + 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";