From f8e77c44b1e63bb713a47464cc8db1de0aa7a092 Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 3 Jan 2026 09:35:40 -0800 Subject: [PATCH] Fix code review items from security scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - egress-watchdog: Use process substitution to avoid subshell gotcha - killswitch: Rename USER to TARGET_USER (avoid shadowing builtin) - Add documentation comments for UID range and grep -P dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .beads/issues.jsonl | 9 +++++---- configuration.nix | 4 +++- scripts/egress-watchdog | 6 ++++-- scripts/killswitch | 18 +++++++++--------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 61a746c..45735fa 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -20,15 +20,16 @@ {"id":"ops-jrz1-5ki","title":"Set up programmatic QA test user for bridge testing","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-05T20:17:04.312571398-08:00","updated_at":"2025-12-05T20:17:04.312571398-08:00"} {"id":"ops-jrz1-5oe","title":"Create NixOS module for code-server containers","description":"Module to manage per-user Podman containers, nginx routing, secrets. Use virtualisation.oci-containers. Generate systemd units.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T17:16:54.656121092-08:00","updated_at":"2025-12-28T00:05:44.743524099-05:00","closed_at":"2025-12-28T00:05:44.743524099-05:00","close_reason":"Parent epic cancelled - browser-based dev approach abandoned","dependencies":[{"issue_id":"ops-jrz1-5oe","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:17:36.386278268-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"ops-jrz1-5oe","depends_on_id":"ops-jrz1-d58","type":"blocks","created_at":"2025-12-05T17:17:38.694752468-08:00","created_by":"daemon","metadata":"{}"}]} {"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-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":"open","priority":4,"issue_type":"task","created_at":"2026-01-03T08:17:35.893969961-08:00","created_by":"dan","updated_at":"2026-01-03T08:26:28.782329252-08:00"} +{"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"} {"id":"ops-jrz1-9gd","title":"Upgrade VPS RAM for dev environments","description":"Current: 2GB. Need 4-8GB for multiple code-server containers. Coordinate with Vultr, plan maintenance window.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-05T17:16:54.267689439-08:00","updated_at":"2025-12-28T00:08:06.748175273-05:00","closed_at":"2025-12-28T00:08:06.748175273-05:00","close_reason":"Browser-based dev environment cancelled","dependencies":[{"issue_id":"ops-jrz1-9gd","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:17:36.331146543-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"ops-jrz1-9pe","title":"Research: System packages for learner accounts","description":"How do dev users get access to toolchains (Go, Node, Rust, etc.)?\n\n## Findings\n\n**Users CAN self-install packages:**\n```bash\nnix profile install nixpkgs#go\nnix profile install nixpkgs#nodejs\nnix profile install nixpkgs#rustc\n```\n\nPackages go to `~/.nix-profile/bin`, already in PATH. Works today.\n\n**Devshells work too:**\n```bash\n# In project with flake.nix\nnix develop\n```\n\n## Options\n\n| Option | Pros | Cons |\n|--------|------|------|\n| **Self-service only** | Minimal config, user learns nix | Cold start friction |\n| **Global defaults** | Zero friction for common tools | Bloats system, version conflicts |\n| **Starter script** | One command setup, customizable | Another thing to maintain |\n| **direnv + devshells** | Per-project envs, reproducible | Needs direnv installed globally |\n\n## Current State\n- `nix profile install` works for users ✅\n- `nix develop` works ✅\n- direnv NOT installed globally\n- Only python3, uv in system packages\n\n## Recommendation\n1. Add `direnv` to global packages (enables per-project devshells)\n2. Document `nix profile install` for quick one-offs\n3. Provide example flake.nix templates for Go, Node, Rust projects\n4. Keep system packages minimal (python3, uv, direnv, git, vim)\n\n## Test Results\n```\n$ nix profile install nixpkgs#go\n$ go version\ngo version go1.22.8 linux/amd64\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T12:27:32.894163417-08:00","created_by":"dan","updated_at":"2026-01-02T12:32:32.502649201-08:00","closed_at":"2026-01-02T12:32:32.502649201-08:00","close_reason":"Users can self-install via nix profile. Added direnv globally for devshells."} {"id":"ops-jrz1-9x8","title":"Claude CLI update mechanism","description":"Claude Code CLI is manually installed to /usr/local/bin/claude.\n\n## Current state\n- Installed via: curl -fsSL https://claude.ai/install.sh | bash\n- Copied to /usr/local/bin/claude\n- No automatic updates\n\n## Options\n1. Periodic manual update (run install script again)\n2. Systemd timer to check for updates\n3. Package via nix (would need custom derivation)\n\n## Acceptance criteria\nDocument the update process at minimum.","status":"open","priority":3,"issue_type":"task","created_at":"2026-01-02T16:46:03.908575951-08:00","created_by":"dan","updated_at":"2026-01-02T16:46:03.908575951-08:00"} -{"id":"ops-jrz1-a3w","title":"egress-watchdog: Document GNU grep -P dependency","description":"grep -oP requires GNU grep with PCRE. NixOS always has it, but add comment for portability awareness. scripts/egress-watchdog:25","status":"open","priority":4,"issue_type":"task","created_at":"2026-01-03T08:17:35.642729617-08:00","created_by":"dan","updated_at":"2026-01-03T08:26:28.546281769-08:00"} +{"id":"ops-jrz1-a3w","title":"egress-watchdog: Document GNU grep -P dependency","description":"grep -oP requires GNU grep with PCRE. NixOS always has it, but add comment for portability awareness. scripts/egress-watchdog:25","status":"closed","priority":4,"issue_type":"task","created_at":"2026-01-03T08:17:35.642729617-08:00","created_by":"dan","updated_at":"2026-01-03T09:32:23.646375858-08:00","closed_at":"2026-01-03T09:32:23.646375858-08:00","close_reason":"Added comment noting grep -oP requires GNU grep with PCRE"} {"id":"ops-jrz1-av0","title":"Configure wildcard DNS and ACME cert","description":"Set up *.code.clarun.xyz DNS record and wildcard SSL cert via ACME. Depends on subdomain routing decision (kg0).","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-05T17:16:54.387356964-08:00","updated_at":"2025-12-05T17:16:54.387356964-08:00","dependencies":[{"issue_id":"ops-jrz1-av0","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:17:36.34918436-08:00","created_by":"daemon","metadata":"{}"},{"issue_id":"ops-jrz1-av0","depends_on_id":"ops-jrz1-kg0","type":"blocks","created_at":"2025-12-05T17:17:38.676800677-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"ops-jrz1-ayl","title":"Rename sna-instagram-bot to something memorable","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-08T16:54:31.223265094-08:00","updated_at":"2025-12-08T16:54:31.223265094-08:00"} {"id":"ops-jrz1-b09","title":"Research: Forgejo backup strategy","description":"Consider backup strategy for the Forgejo instance.\n\n## Questions\n- What data needs backing up? (repos, issues, users, config)\n- Where to back up to? (off-site, object storage, etc.)\n- Frequency?\n- Restore procedure?\n\n## Context\nForgejo runs on ops-jrz1 at :3000, stores git repos and metadata.","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-02T19:01:55.854723242-08:00","created_by":"dan","updated_at":"2026-01-02T19:01:55.854723242-08:00"} @@ -56,11 +57,11 @@ {"id":"ops-jrz1-iok","title":"Instagram bot missing base-config.yaml","description":"Plugin was missing base-config.yaml required by maubot Config class. Fixed in commit 4b9481d.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-06T13:02:10.103730128-08:00","updated_at":"2025-12-06T13:02:15.055396318-08:00","closed_at":"2025-12-06T13:02:15.055396318-08:00"} {"id":"ops-jrz1-jit","title":"Logging and monitoring for dev environments","description":"No observability plan. Need: container CPU/mem metrics, nginx logs, disk usage monitoring, alert on repeated 401s or resource exhaustion.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-05T15:32:41.318448038-08:00","updated_at":"2025-12-28T00:08:06.750234292-05:00","closed_at":"2025-12-28T00:08:06.750234292-05:00","close_reason":"Browser-based dev environment cancelled","dependencies":[{"issue_id":"ops-jrz1-jit","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:05:47.343610481-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"ops-jrz1-jvt","title":"Add beads (bd) to system packages via flake input","description":"Add beads CLI as a system-wide package using the flake input pattern.\n\n## Implementation\n```nix\n# flake.nix inputs\nbeads = {\n url = \"github:steveyegge/beads\";\n inputs.nixpkgs.follows = \"nixpkgs-unstable\";\n};\n\n# configuration.nix or module\nenvironment.systemPackages = [\n inputs.beads.packages.${system}.default\n];\n```\n\n## Why this approach\nPer orch consensus:\n- Flake inputs + systemPackages is NixOS best practice\n- Reproducible, version-pinned via flake.lock\n- All users get same version\n- Config stays per-repo (.beads/)\n\n## Acceptance criteria\n- `bd --version` works for all users\n- No manual install needed\n- Pinned in flake.lock","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:36:04.106750429-08:00","created_by":"dan","updated_at":"2026-01-02T17:25:59.114217153-08:00","closed_at":"2026-01-02T17:25:59.114217153-08:00","close_reason":"Closed"} -{"id":"ops-jrz1-k1z","title":"killswitch: Rename USER variable to avoid shadowing","description":"USER=\"$1\" shadows shell builtin $USER. Rename to TARGET_USER for clarity. scripts/killswitch:15","status":"open","priority":3,"issue_type":"task","created_at":"2026-01-03T08:17:35.258067292-08:00","created_by":"dan","updated_at":"2026-01-03T08:17:50.295351625-08:00"} +{"id":"ops-jrz1-k1z","title":"killswitch: Rename USER variable to avoid shadowing","description":"USER=\"$1\" shadows shell builtin $USER. Rename to TARGET_USER for clarity. scripts/killswitch:15","status":"closed","priority":3,"issue_type":"task","created_at":"2026-01-03T08:17:35.258067292-08:00","created_by":"dan","updated_at":"2026-01-03T09:31:54.318499842-08:00","closed_at":"2026-01-03T09:31:54.318499842-08:00","close_reason":"Renamed USER to TARGET_USER to avoid shadowing shell builtin"} {"id":"ops-jrz1-k2a","title":"Security posture and threat analysis for dev server","description":"Perform security analysis for the shared dev server environment.\n\n## Scope\n- Multi-user SSH access\n- Agentic coders (Claude, etc.) running with user privileges\n- Shared Slack tokens\n- Network egress\n- Nix store access\n\n## Questions to answer\n- What's the threat model? (curious devs vs malicious actors)\n- What can a compromised user account access?\n- What can an agentic coder do that a human couldn't?\n- Are there secrets at risk?\n- What isolation is needed?\n\n## Deliverables\n- Threat model document\n- Risk assessment\n- Recommendations for mitigations","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-02T16:34:32.681248615-08:00","created_by":"dan","updated_at":"2026-01-02T19:14:53.774623716-08:00","closed_at":"2026-01-02T19:14:53.774623716-08:00","close_reason":"Closed"} {"id":"ops-jrz1-kg0","title":"Switch to subdomain routing (dan.code.clarun.xyz)","description":"Path-based routing (/code/dan/) is fragile. Extensions assume root path, cookies scope incorrectly, PWA breaks. Switch to wildcard subdomains for cleaner isolation.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-05T15:32:19.283887085-08:00","updated_at":"2025-12-05T17:23:11.983564455-08:00","closed_at":"2025-12-05T17:23:11.983564455-08:00","dependencies":[{"issue_id":"ops-jrz1-kg0","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:05:47.043217984-08:00","created_by":"daemon","metadata":"{}"}]} {"id":"ops-jrz1-kia","title":"Container reset mechanism (keep workspace)","description":"If user breaks their environment, need simple way to wipe container and restore default image while preserving /workspace. Script or admin command.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-05T15:32:31.045592689-08:00","updated_at":"2025-12-28T00:05:44.757842852-05:00","closed_at":"2025-12-28T00:05:44.757842852-05:00","close_reason":"Parent epic cancelled - browser-based dev approach abandoned","dependencies":[{"issue_id":"ops-jrz1-kia","depends_on_id":"ops-jrz1-3so","type":"parent-child","created_at":"2025-12-05T17:05:47.275530016-08:00","created_by":"daemon","metadata":"{}"}]} -{"id":"ops-jrz1-lae","title":"egress-watchdog: Fix subshell gotcha in while-read pipeline","description":"while-read in pipeline runs in subshell - variables don't persist outside loop. Use process substitution: while read ...; done \u003c \u003c(echo \"$hits\" | grep ...). scripts/egress-watchdog:25","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-03T08:17:35.401495377-08:00","created_by":"dan","updated_at":"2026-01-03T08:26:28.256622008-08:00"} +{"id":"ops-jrz1-lae","title":"egress-watchdog: Fix subshell gotcha in while-read pipeline","description":"while-read in pipeline runs in subshell - variables don't persist outside loop. Use process substitution: while read ...; done \u003c \u003c(echo \"$hits\" | grep ...). scripts/egress-watchdog:25","status":"closed","priority":2,"issue_type":"task","created_at":"2026-01-03T08:17:35.401495377-08:00","created_by":"dan","updated_at":"2026-01-03T09:30:50.535018144-08:00","closed_at":"2026-01-03T09:30:50.535018144-08:00","close_reason":"Fixed: using process substitution instead of pipeline subshell"} {"id":"ops-jrz1-meh","title":"cpu-watchdog: Add flock for atomic strike counter updates","description":"Read-modify-write of strike counter not atomic. Systemd timer serializes runs so low risk now, but add flock if parallelism added later. scripts/cpu-watchdog:29-31","status":"open","priority":3,"issue_type":"task","created_at":"2026-01-03T08:17:35.759126212-08:00","created_by":"dan","updated_at":"2026-01-03T08:26:28.66076716-08:00"} {"id":"ops-jrz1-mh2","title":"Research: Forgejo integration for shared projects","description":"How does beads/bd integrate with our Forgejo git server (git.clarun.xyz)?\n\n## Questions\n- Can bd sync to Forgejo repos?\n- How do dev users on the server collaborate on shared projects?\n- Is there a git workflow that makes sense (forks? shared repo? branches?)\n- Does bd need any special config for Forgejo vs GitHub?\n\n## Context\n- Forgejo running at git.clarun.xyz\n- Dev users have SSH access to server\n- May want shared project tracking via beads","status":"open","priority":2,"issue_type":"task","created_at":"2026-01-02T16:24:01.771168961-08:00","created_by":"dan","updated_at":"2026-01-02T16:24:01.771168961-08:00"} {"id":"ops-jrz1-ndl","title":"Browser-based dev environment (code-server)","description":"Explore setting up browser-based development:\n\nOptions:\n- code-server / openvscode-server - VS Code in browser\n- ttyd / wetty - terminal in browser \n- PWA install to home screen for native app feel\n\nCould combine with Tailscale for secure access without exposing ports.\n\nRef: ops-dev thin client brainstorm session","notes":"Design doc created: specs/004-browser-dev-environment/design.md - covers architecture, tech choices, resource planning, security model, rollout phases","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-04T15:08:02.406274744-08:00","updated_at":"2025-12-05T17:05:52.872944892-08:00","closed_at":"2025-12-05T17:05:52.872944892-08:00"} diff --git a/configuration.nix b/configuration.nix index 16c6ca5..0b7e274 100644 --- a/configuration.nix +++ b/configuration.nix @@ -115,7 +115,9 @@ in enable = true; allowedTCPPorts = [ 22 80 443 ]; # SSH, HTTP, HTTPS - # Egress controls for regular users (uid 1000+) + # Egress controls for regular users + # UID range: 1000 (first regular user) to 65534 (nobody - excluded from controls) + # This covers all learner 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 \ diff --git a/scripts/egress-watchdog b/scripts/egress-watchdog index dcc2d1d..bc2a26b 100755 --- a/scripts/egress-watchdog +++ b/scripts/egress-watchdog @@ -22,7 +22,9 @@ if [[ -z "$hits" ]]; then exit 0 fi -echo "$hits" | grep -oP 'UID=\K[0-9]+' | sort | uniq -c | while read -r count uid; do +# Process substitution avoids subshell - variables persist outside loop +# Note: grep -oP requires GNU grep with PCRE (provided by runtimeInputs in Nix) +while read -r count uid; do # Skip if count or uid is empty [[ -z "$count" || -z "$uid" ]] && continue @@ -46,6 +48,6 @@ echo "$hits" | grep -oP 'UID=\K[0-9]+' | sort | uniq -c | while read -r count ui # Reset counter if below threshold rm -f "$COUNTDIR/$user" fi -done +done < <(echo "$hits" | grep -oP 'UID=\K[0-9]+' | sort | uniq -c) exit 0 diff --git a/scripts/killswitch b/scripts/killswitch index 30af6df..7165776 100755 --- a/scripts/killswitch +++ b/scripts/killswitch @@ -12,27 +12,27 @@ if [ $# -lt 1 ]; then exit 1 fi -USER="$1" +TARGET_USER="$1" REASON="${2:-manual kill}" -if ! id "$USER" &>/dev/null; then - echo "User not found: $USER" >&2 +if ! id "$TARGET_USER" &>/dev/null; then + echo "User not found: $TARGET_USER" >&2 exit 1 fi # Don't allow killing root or system users -UID_NUM=$(id -u "$USER") +UID_NUM=$(id -u "$TARGET_USER") if [ "$UID_NUM" -lt 1000 ]; then - echo "Refusing to kill system user: $USER (uid $UID_NUM)" >&2 + echo "Refusing to kill system user: $TARGET_USER (uid $UID_NUM)" >&2 exit 1 fi -logger -t killswitch "Killing all processes for $USER: $REASON" +logger -t killswitch "Killing all processes for $TARGET_USER: $REASON" # Kill all processes -pkill -u "$USER" 2>/dev/null || true +pkill -u "$TARGET_USER" 2>/dev/null || true # Terminate login session -loginctl terminate-user "$USER" 2>/dev/null || true +loginctl terminate-user "$TARGET_USER" 2>/dev/null || true -echo "Killed $USER: $REASON" +echo "Killed $TARGET_USER: $REASON"