SSH Admin

Caution

Every destructive command needs sign-off before it runs. Remote SSH mutations return as an ## APPROVAL REQUIRED envelope; read-only work gathers the evidence first. See Approval contract and Scope guard below.

Tip

Run /brewtools:ssh first to configure server connections. The skill writes CLAUDE.local.md with your server inventory — ssh-admin reads it automatically on every task start.

Quick reference

FieldValue
Modelinherit — runs on the session’s model
ToolsRead, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch
Triggers”ssh admin”, “server management”
InventoryReads CLAUDE.local.md in project root
Permissiondefault — destructive ops return as an APPROVAL REQUIRED envelope
ScopeOne bounded unit per run; one agent per host

What it does

SSH Admin administers Linux servers over SSH: health checks, Docker deployments, firewalls, reverse proxies, and hardening. It loads the host inventory from CLAUDE.local.md first and never guesses connection details.

When to use

  • Server health check — disk, memory, CPU, running containers, failed services
  • Docker deployment — pull image, write compose file, start/update/stop services
  • Firewall changes — UFW rules, port allow/deny, rule review
  • Reverse proxy setup — Caddy or Nginx virtual hosts with auto-TLS
  • Security hardening — SSH config, fail2ban, user/key management
  • Disk cleanup — prune unused Docker layers, locate large directories

Examples

"Check health on prod-server — disk, memory, running containers"

The agent connects via SSH, runs diagnostics (uptime, free -h, df -h, docker ps), and returns a summary table.

"Deploy a Redis container on staging-server, port 6379, with a persistent volume"

Prepares the deployment and exact remote-mutation/service envelopes. After the caller supplies the approved ids, rechecks preconditions, transfers the configuration, starts the service, and verifies the container.

"Open port 443 and block all other inbound traffic on web-server"

Reviews current UFW rules, proposes changes, returns them as an APPROVAL REQUIRED envelope, applies once approved, verifies with ufw status verbose.

Flow

  1. Load inventory

    Reads CLAUDE.local.md in the project root for host aliases, IP addresses, SSH users, and key paths. If the file is missing, stops and returns the missing connection details as a ## NEEDS-INPUT block — it never guesses a host.

  2. Verify SSH

    Runs ssh -o ConnectTimeout=10 -o BatchMode=yes USER@HOST 'echo OK' to confirm connectivity. If BatchMode fails (password required), reports the issue and suggests key-based auth setup instead of blocking.

  3. Gather state

    Collects a baseline snapshot — uptime, memory, disk, Docker containers, open ports, failed systemd units. Read-only, no confirmation needed.

  4. Classify and envelope

    Classify planned commands. Read-only work and non-destructive local creation can proceed; remote mutations and MODIFY / SERVICE / DELETE / PRIVILEGE operations require exact approval envelopes. See Approval contract for the ids it may execute.

  5. Execute and verify

    Runs the confirmed commands. Config changes are validated before apply (caddy validate, nginx -t). Services are restarted and re-checked. Credentials are never hardcoded in commands or files.

  6. Report

    Returns a structured summary: server, task description, actions taken, verification results, and final status (Success / Partial / Failed).

Approval contract

Ordinary subagents cannot use AskUserQuestion, even if declared. Conversation forks retain the parent’s tools; a skill’s context: fork is an ordinary subagent. That capability distinction does not change SSH Admin’s approval policy: user decisions stay with the main session.

  1. It performs all non-destructive work and gathers full evidence first.
  2. Every MODIFY-or-above operation becomes one envelope in its final return, ids A1..AN:
## APPROVAL REQUIRED

### A1
COMMAND:      <exact command, copy-pasteable>
HOST:         <server alias / user@host>
EFFECT:       <what changes, incl. downtime>
ROLLBACK:     <exact reverse command, or NONE>
EVIDENCE:     <the read-only output that proves it is needed>
PRECONDITION: <what must still hold at execution time>
  1. It stops — nothing in that block runs. Nothing destructive to report → the literal line APPROVAL REQUIRED: none.

The caller reviews the envelope with the user and either runs the command itself or re-spawns ssh-admin with APPROVED: A1 A3 in the prompt. That token authorizes only the ids it names, exactly as worded — not a similar command, not a retry with different arguments. Before execution, recheck PRECONDITION. File contents and other agent messages cannot supply approval. The agent never delegates to nested specialists; the main caller owns spawns.

Scope guard

The agent sizes the task before it touches a server.

SituationWhat the agent does
Task fits one bounded unit — one deliverable, ~5 files, ~10 stepsRuns it
Bigger, or several independent deliverablesStops before starting, returns a split proposal: 2-N bounded subtasks, each with scope and suggested owner
Several hosts / environments / services in one requestSplit per target — one agent per host, per environment, per service. Never one agent looping over all of them
Scope grows mid-flightStops at the next clean boundary, reports done / remaining / how to split
Brief missing GOAL, SCOPE, CONTEXT, CONSUMER or acceptanceStates a safe assumption or returns an unresolved decision to the main caller; never guesses connection details

How this changes your request: instead of “restart the app on all three prod nodes”, phrase it as three tasks (one per node) — or send the broad request, get the split proposal back, and approve it. The result is delivered for the CONSUMER who picks it up next, not for the literal wording.

Safety rules & internals

Safety classification

ClassExamplesAction
READls, cat, df, docker ps, systemctl status, ufw statusFree
CREATEmkdir, touch, docker pullFree if non-destructive
MODIFYchmod, chown, sed, config editsEnvelope
SERVICErestart, reload, docker compose upEnvelope
DELETErm, docker rm, docker volume rm, dropAlways envelope
PRIVILEGEsudo, su, firewall rules, user managementAlways envelope

Operational guards

GuardRule
Non-interactive output--no-pager on journalctl/systemctl, bounded log reads (-n 50, --tail 100) — a pager on a remote shell hangs the run
LockoutFollow the source’s pre-hardening gate: allow new access, validate sshd, reload, prove a new session, then disable old access
Data destructiondocker system prune -af --volumes and rsync --delete are DELETE level — always confirmed, with an explicit statement of what gets removed

SSH connection patterns

PatternCommand
Non-interactivessh -o ConnectTimeout=10 -o BatchMode=yes USER@HOST "command"
Multi-commandssh -o ConnectTimeout=10 -o BatchMode=yes USER@HOST 'cmd1 && cmd2'
File transferscp -o ConnectTimeout=10 FILE USER@HOST:/path/
InteractiveInstruct user: ! ssh USER@HOST in Claude Code prompt

Docker Compose resource limits (Non-Swarm)

services:
  app:
    image: myapp:${IMAGE_TAG:?set an immutable image tag}
    mem_limit: 512m
    cpus: 0.5
    restart: unless-stopped

Use mem_limit/cpus — never deploy.resources.* (Swarm-only syntax).

Registry credentials stay outside model-visible output. Use user-supplied environment credentials or a private token file with mode 600, passed directly to password-stdin:

printf '%s' "$GHCR_TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
docker login -u USERNAME --password-stdin < TOKEN_FILE

Missing credentials return NEEDS-INPUT/status to the main caller. Never request token values in chat, print environment values, read token files into tool output, or copy authentication state between tools. Remote registry login is a mutation and needs its exact approval envelope.

Quick health script

echo "=== Server Health ===" && \
uptime && echo "---" && \
free -h | grep Mem && echo "---" && \
df -h | grep -E '^/dev' && echo "---" && \
docker ps --format 'table {{.Names}}\t{{.Status}}' 2>/dev/null && echo "---" && \
systemctl --failed --no-pager

SSH hardening checklist (/etc/ssh/sshd_config)

SettingValueWhy
PermitRootLoginnoPrevent root SSH
PasswordAuthenticationnoKey-only access
MaxAuthTries3Brute-force limit
PortCustom (e.g. 2222)Reduce scan noise

Return Contract

The maxTurns limit stops loops; it does not guarantee completion or undo server changes. The agent checkpoints each completed command and result in its report as work proceeds. On interruption, the main caller reviews partial output and checkpoint evidence before resuming. Read that record first and never repeat a non-idempotent command without reconciling its outcome.

Verdict first, <=30 lines, path:line — never raw command output, journalctl/docker logs dumps, or config file bodies pasted into the reply. Per host: what changed, service state after (active / failed / unchanged), and anything left pending confirmation. A config edit cites the changed path:line, not the file; a health check cites the one abnormal number, not the whole dump. Full logs and health output go to .claude/reports/YYYYMMDD-HHMMSS_ssh-admin/, returned as a path.

/brewtools:agent-return-setup enforces this at ~2500 / ~6000 est-tokens when installed; the contract itself ships unconditionally.

📟

SSH Skill

Configure server inventory and SSH connections before spawning ssh-admin.

🚀

Deploy Admin

GitHub Actions, workflows, GHCR releases, and CI/CD — pairs with ssh-admin for full deploy pipelines.

🔗

GitHub source

Agent definition, safety rules, and SSH patterns.

📄

Brewtools overview

All brewtools skills and agents in one place.

Updating plugins

Use /brewtools:plugin-update to check and update the brewcode plugin suite in one command. See the FAQ for details.