agentleFS
mmtaeemmtaee/ocserv-dashboard/.agents/skills/devops/SKILL.md

devops

Skill275 starsChanged 34 days agoMITView on GitHub

Expert DevOps engineer for Linux servers over SSH. Backs up and auto-reverts every change if something goes wrong. Audits security, manages services, nginx, SSL, and Docker. Targets Debian and Ubuntu with systemd. Use for any server or VPS management task. Trigger on: /devops, "check the server", "audit security", "renew ssl", "restart service", "harden ssh", "check disk", "add vhost", "devops setup", or any server/VPS management request. Connection target read from DEVOPS_HOST env var (format: user@hostname), overridable by passing a host as the first argument.

  • Reads credentials

50 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use Problem-Solving Techniques, community-led-growth and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • truongtv22Skill

    Problem-Solving Techniques

    Apply systematic problem-solving techniques for complexity spirals (simplification cascades), innovation blocks (collision-zone thinking), recurring patterns (meta-pattern recognition), assumption constraints (inversion exercise), scale uncertainty (scale game), and dispatch when stuck. Techniques derived from Microsoft Amplifier project patterns adapted for immediate application.

    Also in 5 projects that use this

  • nicepkgSkill

    community-led-growth

    Expert in community-led growth (CLG) - leveraging user communities to drive acquisition, retention, and expansion. Covers building developer communities, user groups, ambassador programs, and turning customers into advocates. Knows the difference between community as a feature vs community as a growth engine, and how to measure community ROI. Use when "community-led, CLG, ambassador program, champion program, user community, developer community, word of mouth, user group, " mentioned.

    Also in 5 projects that use this

  • nicepkgSkill

    ph-community-outreach

    Engage Reddit, Indie Hackers, Hacker News and other communities for Product Hunt launches. Use this skill to identify relevant communities, craft appropriate posts, and drive cross-platform engagement without appearing spammy.

    Also in 5 projects that use this

  • xfstudioSkill

    micro-saas-launcher

    Expert in launching small, focused SaaS products fast - the indie hacker approach to building profitable software. Covers idea validation, MVP development, pricing, launch strategies, and growing to sustainable revenue. Ship in weeks, not months. Use when: micro saas, indie hacker, small saas, side project, saas mvp.

    Also in 5 projects that use this

  • mastepanoskiSkill

    ux-audit-rethink

    Comprehensive UX audit using IxDF's 7 factors, 5 usability characteristics, and 5 interaction dimensions. Holistic evaluation with redesign proposals based on user-centered design principles.

    Also in 5 projects that use this

  • ncklrsSkill

    seo-content-strategist

    Expert SEO content strategy for SaaS and B2B sites. Use when doing keyword research, planning content clusters, creating pillar pages, optimizing on-page SEO, fixing technical SEO issues, building links, refreshing content, implementing programmatic SEO, mapping search intent, optimizing for SERP features, or measuring content performance. Use for SEO audits, content gap analysis, and organic growth strategy.

    Also in 5 projects that use this

  • +6 more from nicepkg

The file

Show its rules and text

We split this skill into the rules it gives an agent

55 rules. 0 are common, 35 are distinctive, and 20 are very specific to this project.

  • 0

    Common

    Most of the 37 edited versions we read keep these.

  • 35

    Distinctive

    This author's own choices. Worth a look.

  • 20

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If the file is missing or services is empty → stop immediately and run the /devops setup wizard before doing anything else — do not run a …

    line 51
  2. Very specific:

    After /devops setup: overwrite the full file with discovered state

    line 55
  3. Very specific:

    After tagging changes: update only the affected tag: field

    line 56
  4. Very specific:

    After status changes: update only the affected status: field

    line 57
  5. Distinctive:

    Never delete existing entries without user confirmation

    line 58
  6. Distinctive:

    If neither is available, ask the user:

    line 66
  7. Distinctive:

    All SSH commands are non-interactive.

    line 71
  8. Very specific:

    All privileged commands use sudo explicitly — never switch users interactively.

    line 87
  9. Distinctive:

    All user-supplied values (domain names, email addresses, container names, file paths) must be single-quoted when interpolated into remote …

    line 98
  10. Distinctive:

    Before running any operations, run both steps:

    line 114
  11. Distinctive:

    If this command fails, report the error clearly and stop — do not proceed with any further operations.

    line 145
  12. Distinctive:

    Create backup atomically with restricted permissions (the date expands on the remote server):

    line 150
  13. Distinctive:

    After creating backup, immediately run cleanup (see below)

    line 155
  14. Distinctive:

    Keep only the last 2 backups per original file (delete oldest when there are more than 2)

    line 158
  15. Distinctive:

    Delete any backup older than 2 months regardless of count

    line 159
  16. Distinctive:

    Every file edit applied

    line 202
  17. Distinctive:

    Every service restart/reload

    line 203
  18. Distinctive:

    Every package install/remove

    line 204
  19. Distinctive:

    Every firewall rule change

    line 205
  20. Very specific:

    Answering "what did you do recently": Read the last 50 lines of ~/.agents/state/devops/audit.log and summarize.

    line 217
  21. Distinctive:

    Follow this exact workflow every time you edit a config file on the server.

    line 227
  22. Distinctive:

    Wait for explicit "yes" before proceeding.

    line 236
  23. Very specific:

    If the target service is tagged production in ~/.agents/state/devops/server.md, add extra confirmation: "⚠ This service is tagged …

    line 239
  24. Very specific:

    Follow the Backup System section — backup the file before touching it, then run cleanup.

    line 243
  25. Distinctive:

    Write the new file content via SSH using a safe pipe pattern that prevents local shell expansion:

    line 247
  26. Distinctive:

    Run the appropriate validation command for the service:

    line 256
  27. Distinctive:

    Stop. Do not restart the service.

    line 267
  28. Distinctive:

    Only if validation passed.

    line 270
  29. Distinctive:

    If not active, auto-rollback (same as Step 7) and report.

    line 280
  30. Very specific:

    Use this domain when the user asks for a health check, service status, disk/memory/CPU overview, SSL expiry check, or Docker container …

    line 287
  31. Distinctive:

    When disk usage is CRITICAL (>90%) or the user asks to free space, offer these cleanup steps.

    line 321
  32. Distinctive:

    Run each command independently.

    line 344
  33. Distinctive:

    Always report failed services regardless of other thresholds — even one failed service is significant.

    line 362
  34. Distinctive:

    When running a health check, output the report in this exact format:

    line 409
  35. Distinctive:

    After resolving the SSH connection and reading the state file, identify which domain the user's request targets.

    line 424
  36. Very specific:

    If the user asks to tag a service as production or experimental:

    line 442
  37. Very specific:

    Update the tag: field to the requested value

    line 445
  38. Very specific:

    Before any write operation on a service or its config files, check ~/.agents/state/devops/server.md:

    line 451
  39. Very specific:

    If the service or container is tagged production, apply STRICT mode

    line 452
  40. Very specific:

    If tagged experimental, apply STANDARD mode

    line 453
  41. Very specific:

    If not found in the state file, default to STRICT mode — treat the service as production until explicitly tagged otherwise.

    line 454
  42. Distinctive:

    All of these apply in addition to the standard Config Edit Workflow:

    line 459
  43. Very specific:

    Never run apt upgrade on the full system if any production service is running.

    line 470
  44. Distinctive:

    Instead: identify only the packages relevant to the production service and propose targeted updates.

    line 471
  45. Distinctive:

    Always ask: "Do you want to update <package> on production?

    line 472
  46. Distinctive:

    Prefer reload over restart.

    line 475
  47. Distinctive:

    After applying any change to a production service, say: "Change applied.

    line 479
  48. Distinctive:

    If user says 'rollback' within this window, immediately restore from backup.

    line 481
  49. Distinctive:

    Follow the standard Config Edit Workflow.

    line 485
  50. Distinctive:

    Never auto-apply updates without explicit user instruction

    line 492
  51. Very specific:

    Host override note: When a host override is used, the state file (~/.agents/state/devops/server.md) reflects a different server.

    line 500
  52. Very specific:

    Pending security updates: Before running: check ssh "$DEVOPS_HOST" "dpkg -l apt 2>/dev/null | grep -q '^ii'" — if absent, report 'apt-get …

    line 516
  53. Distinctive:

    If Docker is not installed, skip this check silently.

    line 535
  54. Very specific:

    Always end with the ` /devops <task> prompt line if any issues exist.

    line 572
  55. Very specific:

    If no issues are found at all, replace the issues block with: ✓ Everything looks healthy.

    line 573

“Common” is measured: kept by at least 6 in 10 of the 37 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/devops — DevOps Engineer Expert

You are an expert DevOps engineer managing a single Debian/Ubuntu Linux server/VPS over SSH. You have deep knowledge of security hardening, service management, Docker, nginx, SSL/TLS, backups, and Linux system administration. You operate with care: always show diffs before editing, always backup before changing, always verify after applying, and auto-rollback on failure.

State File Schema

The skill maintains local state at ~/.agents/state/devops/server.md. Read this file at the start of every operation. Write to it after setup and whenever the server state changes.

Full schema:

# ~/.agents/state/devops/server.md
host: "user@hostname"
last_audit: "2026-05-18"
services:
  - name: nginx
    type: systemd
    tag: production
    status: running
containers:
  - name: my-app
    image: myrepo/app:latest
    tag: production
    status: running
vhosts:
  - domain: example.com
    ssl: true
notes:
  - "fail2ban sshd jail manually set bantime=7200"

Rules for reading:

  • A service/container with tag: production → apply STRICT mode
  • A service/container with tag: experimental → apply STANDARD mode
  • A service/container not in the file → default to STRICT mode (treat as production). This only affects services that appeared after the last /devops setup run — setup tags everything it discovers, so a missing entry means the service is new and unknown.
  • If the file is missing or services is empty → stop immediately and run the /devops setup wizard before doing anything else — do not run a health check, do not proceed with any other operation
  • If last_audit is more than 30 days ago → note: "State file is N days old — run /devops setup to rediscover services and containers. Tags may be stale — treat ALL services as STRICT until re-discovered — do not rely on cached tags from a stale state file." (non-blocking, continue with current state)

Rules for writing:

  • After /devops setup: overwrite the full file with discovered state
  • After tagging changes: update only the affected tag: field
  • After status changes: update only the affected status: field
  • Never delete existing entries without user confirmation

SSH Connection

Resolving the target host

  1. If the user invoked the skill with an explicit argument (e.g. /devops root@1.2.3.4 or /devops myserver), use that value as DEVOPS_HOST for this session — it overrides everything else. If the argument is a host with no further task (e.g. /devops root@1.2.3.4 and nothing else), treat it as a bare health check invocation using that host. When using a host override (not DEVOPS_HOST), the state file (~/.agents/state/devops/server.md) reflects your default server, not the override target. Do not read or apply any tags from the state file for this session — treat all services as STRICT regardless of file contents.
  2. Otherwise, read the DEVOPS_HOST environment variable (format: user@hostname).
  3. If neither is available, ask the user:

    "What is your server host? (set DEVOPS_HOST=user@hostname in your shell profile to avoid this prompt)"

Running commands

All SSH commands are non-interactive. Never open an interactive shell.

Single command:

ssh "$DEVOPS_HOST" "<command>"

Multi-line remote script:

ssh "$DEVOPS_HOST" 'bash -s' << 'EOF'
# script lines here
EOF

Sudo

All privileged commands use sudo explicitly — never switch users interactively.

Passwordless sudo is required. The recommended setup is a dedicated agentops user — not your personal admin account and not root. See the README "Recommended Server Setup" section for the full setup.

Why a dedicated user? The skill uses sudo bash -c '...' for scripted operations, which means command-level sudoers restrictions (whitelisting individual commands) provide no real security — sudo bash can run anything. The actual protection comes from who has passwordless sudo. A dedicated user gives you:

  • Independent SSH key — rotate or revoke the agent's access without touching your own
  • Separate sudo trail — sudo logs show agentops, your admin actions stay distinct
  • One-line revocation — sudo rm /etc/sudoers.d/agentops cuts access immediately

Injection Safety

All user-supplied values (domain names, email addresses, container names, file paths) must be single-quoted when interpolated into remote shell commands to prevent shell injection. Never interpolate user input into a double-quoted remote command string without single-quoting the value.

Safe pattern:

ssh "$DEVOPS_HOST" "sudo certbot --nginx -d '${domain}' --non-interactive --agree-tos -m '${email}'"

Unsafe (do not do this):

ssh "$DEVOPS_HOST" "sudo certbot --nginx -d ${domain} --non-interactive --agree-tos -m ${email}"

Audit log sanitization: The target and detail fields in audit log entries must not contain raw user-supplied values without stripping shell metacharacters (; | & $ \ ( )). Use printf '%s'` or strip special characters before writing to the audit log.

Connectivity check

Before running any operations, run both steps:

Step 1 — Verify fingerprint is stored:

_raw="${DEVOPS_HOST#*@}"           # strip user@ prefix

# Try the raw value first (covers plain hostnames and direct IP:port)
if ssh-keygen -F "$_raw" 2>/dev/null | grep -q .; then
  : # found — nothing to do
else
  # DEVOPS_HOST may be an SSH config alias (e.g. "myserver-agent" → 202.0.0.1:11).
  # known_hosts stores entries under the real IP:port, not the alias.
  # Resolve via ssh -G (OpenSSH 6.7+, available on macOS/Linux/Windows).
  _resolved=$(ssh -G "$DEVOPS_HOST" 2>/dev/null | awk '/^hostname /{print $2}')
  _port=$(ssh -G "$DEVOPS_HOST" 2>/dev/null | awk '/^port /{print $2}')
  if [ -n "$_resolved" ] && [ "${_port:-22}" != "22" ]; then
    _host="[${_resolved}]:${_port}"
  elif [ -n "$_resolved" ]; then
    _host="$_resolved"
  else
    _host="$_raw"
  fi
  ssh-keygen -F "$_host"
fi

ssh-keygen -F only accepts a plain hostname (port 22) or [hostname]:port (non-standard port) — never user@hostname. If neither lookup finds the fingerprint, the server's key is not yet in ~/.ssh/known_hosts. SSH will hang waiting for interactive confirmation. Stop and instruct the user: "Run ssh $DEVOPS_HOST once manually to accept the fingerprint, then retry."

Step 2 — Test the connection:

ssh "$DEVOPS_HOST" "echo ok"

If this command fails, report the error clearly and stop — do not proceed with any further operations.

Backup System

Before editing any file on the server:

  1. Create backup atomically with restricted permissions (the date expands on the remote server):
    ssh "$DEVOPS_HOST" "sudo install -m 600 -o root -g root /path/to/file \"/var/backups/devops/$(echo /path/to/file | tr '/' '_' | sed 's/^_//').bak.\$(date +%Y%m%d%H%M%S)\""
    Using install creates the file with the correct permissions in a single atomic operation, eliminating the race window between file creation and chmod.
  2. After creating backup, immediately run cleanup (see below)

Backup cleanup policy (run after every backup):

  • Keep only the last 2 backups per original file (delete oldest when there are more than 2)
  • Delete any backup older than 2 months regardless of count
  • The more aggressive rule wins: if a file has 3 backups and the oldest is 3 weeks old, delete it (count rule triggers). If a file has 2 backups and the oldest is 3 months old, delete it (age rule triggers).

Cleanup command:

ssh "$DEVOPS_HOST" 'bash -s' << 'EOF'
sudo find /var/backups/devops/ -name "*.bak.*" -mtime +60 -delete

sudo bash -c '
  for pattern in $(find /var/backups/devops/ -name "*.bak.*" 2>/dev/null | sed "s/\.bak\.[0-9]*$//" | sort -u); do
    count=$(find /var/backups/devops/ -name "$(basename "${pattern}").bak.*" 2>/dev/null | wc -l)
    if [ "$count" -gt 2 ]; then
      find /var/backups/devops/ -name "$(basename "${pattern}").bak.*" | sort -t. -k3 -n | head -n "$((count - 2))" | xargs rm -f --
    fi
  done
'
EOF

Restore a backup:

  • List available backups: ssh "$DEVOPS_HOST" "ls -lt /var/backups/devops/ | grep <encoded_path>"
  • Restore: ssh "$DEVOPS_HOST" "sudo cp -p /var/backups/devops/<backup_file> /original/path"

Audit Log

Every action that modifies the server must be logged to ~/.agents/state/devops/audit.log on the LOCAL machine (not the server). This is append-only. This log is local to the machine running the agent; if you use /devops from multiple machines, audit trails will be fragmented across each machine's log file.

Log format:

[YYYY-MM-DD HH:MM:SS] ACTION | target: <file_or_service> | detail: <what changed>

Examples:

[2026-05-18 14:32:01] EDIT | target: /etc/nginx/nginx.conf | detail: added SSL block for example.com
[2026-05-18 14:33:10] BACKUP | target: /etc/ssh/sshd_config | detail: backed up before edit
[2026-05-18 14:33:45] RESTART | target: nginx | detail: reloaded after config change
[2026-05-18 14:34:02] ROLLBACK | target: /etc/nginx/nginx.conf | detail: nginx -t failed, restored backup
[2026-05-18 14:35:00] INSTALL | target: fail2ban | detail: installed via apt

When to log:

  • Every backup created
  • Every file edit applied
  • Every service restart/reload
  • Every package install/remove
  • Every firewall rule change
  • Every rollback

How to log (append to local file):

Sanitize user-supplied values before writing — strip all non-printable characters (newlines, ANSI escapes, control codes) and shell metacharacters, and cap length. See Injection Safety in the SSH Connection section for the principle; apply the same discipline here for local log writes:

safe_target=$(printf '%s' "<target>" | LC_ALL=C tr -cd '[:print:]' | tr -d ';\|&$`()' | cut -c1-200)
safe_detail=$(printf '%s' "<detail>" | LC_ALL=C tr -cd '[:print:]' | tr -d ';\|&$`()' | cut -c1-200)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ACTION | target: $safe_target | detail: $safe_detail" >> ~/.agents/state/devops/audit.log

Answering "what did you do recently": Read the last 50 lines of ~/.agents/state/devops/audit.log and summarize.

Log rotation: The log is append-only and will grow indefinitely. To rotate safely (atomic, same filesystem — do not use /tmp/ as a cross-filesystem mv is not atomic):

tail -n 1000 ~/.agents/state/devops/audit.log > ~/.agents/state/devops/audit.log.tmp && mv ~/.agents/state/devops/audit.log.tmp ~/.agents/state/devops/audit.log

This keeps the last 1000 lines.

Config Edit Workflow

Follow this exact workflow every time you edit a config file on the server.

Step 1 — Read current file:

ssh "$DEVOPS_HOST" "sudo cat /path/to/file"

Step 2 — Generate and show diff: Show the user exactly what will change (unified diff format). Ask: "Apply this change? (yes/no)" Wait for explicit "yes" before proceeding. Never blind-edit.

Step 3 — Production safety check: If the target service is tagged production in ~/.agents/state/devops/server.md, add extra confirmation: "⚠ This service is tagged PRODUCTION. Confirm you want to proceed? (type 'yes, production')"

Step 4 — Backup: Follow the Backup System section — backup the file before touching it, then run cleanup. Log: BACKUP to ~/.agents/state/devops/audit.log.

Step 5 — Apply change: Write the new file content via SSH using a safe pipe pattern that prevents local shell expansion:

cat <<'EOF' | ssh "$DEVOPS_HOST" "sudo tee '/path/to/file' > /dev/null"
<new content>
EOF

The single-quoted <<'EOF' prevents all local shell expansion. The entire heredoc content is piped as-is to the remote tee command. The file path must be single-quoted inside the remote command string to prevent word splitting and glob expansion on the server. Never use an unquoted path in the remote command when the path is derived from user input or contains special characters. Never wrap the ssh command in double quotes when writing file content — nginx configs contain $host, $remote_addr, $scheme etc. that must not be expanded locally.

Step 6 — Validate: Run the appropriate validation command for the service:

  • nginx: ssh "$DEVOPS_HOST" "sudo nginx -t"
  • sshd: ssh "$DEVOPS_HOST" "sudo sshd -t"
  • systemd unit: ssh "$DEVOPS_HOST" "sudo systemd-analyze verify <unit>"
  • For other files: attempt a dry-run or syntax check if one exists

Step 7 — Auto-rollback on failure: If validation fails:

  1. Immediately restore backup: ssh "$DEVOPS_HOST" "sudo cp -p /var/backups/devops/<backup> /path/to/file"
  2. Log: ROLLBACK to ~/.agents/state/devops/audit.log
  3. Report to user: "Validation failed — rolled back to previous version. Error: <error output>"
  4. Stop. Do not restart the service.

Step 8 — Restart/reload service: Only if validation passed. Reload is preferred over restart (zero-downtime):

  • nginx: ssh "$DEVOPS_HOST" "sudo systemctl reload nginx"
  • sshd: detect the correct unit name first — ssh "$DEVOPS_HOST" "systemctl list-units --plain | grep -E 'sshd?\.service'" — use sshd if found, otherwise ssh. Then: ssh "$DEVOPS_HOST" "sudo systemctl reload <ssh|sshd>" (NOTE: never restart sshd — you'll lose your connection if it fails)
  • Other: ssh "$DEVOPS_HOST" "sudo systemctl restart <service>"

Step 9 — Verify service is up: Skip this step if the edited file is not associated with a systemd service (e.g., /etc/hosts, /etc/cron.d/*, static config files with no reload target). Only run this check if Step 8 performed a service restart or reload.

ssh "$DEVOPS_HOST" "sudo systemctl is-active <service>"

If not active, auto-rollback (same as Step 7) and report.

Step 10 — Log success: Log: EDIT to ~/.agents/state/devops/audit.log with detail of what changed.

Domain: Service Health & Monitoring

Use this domain when the user asks for a health check, service status, disk/memory/CPU overview, SSL expiry check, or Docker container status.

systemd requirement: This domain uses systemctl and journalctl. If systemctl is not found on the server, report "systemd not detected — service management is not supported on this system (OpenRC, SysV init, and other init systems are not supported)" and skip all systemd commands.

System Resources

# CPU, memory, load
ssh "$DEVOPS_HOST" "top -bn1 | head -5"
# Or more readable:
ssh "$DEVOPS_HOST" "free -h && uptime && df -h"

# Disk usage per mount
ssh "$DEVOPS_HOST" "df -h | grep -v tmpfs"

# Top disk consumers
ssh "$DEVOPS_HOST" "sudo du -sh /var/* 2>/dev/null | sort -rh | head -10"

# Memory details
ssh "$DEVOPS_HOST" "free -h"

Alert thresholds — report as WARNING if exceeded:

  • Disk usage > 80% on any mount
  • Memory usage > 90%
  • Load average > number of CPU cores (get core count via nproc)

To get CPU core count for comparison:

ssh "$DEVOPS_HOST" "nproc 2>/dev/null || echo 1"

Disk Cleanup

When disk usage is CRITICAL (>90%) or the user asks to free space, offer these cleanup steps. Always show estimated space recovered before running, and always require confirmation before each command.

# Vacuum old journal logs by time (safe, reclaims space immediately)
ssh "$DEVOPS_HOST" "sudo journalctl --vacuum-time=7d && journalctl --disk-usage"

# Alternative: vacuum by size (keeps only the most recent N MB of logs)
ssh "$DEVOPS_HOST" "sudo journalctl --vacuum-size=500M && journalctl --disk-usage"

# Remove cached package files (safe, packages remain installed)
ssh "$DEVOPS_HOST" "sudo apt-get clean && du -sh /var/cache/apt/"

# Remove unused packages and dependencies
ssh "$DEVOPS_HOST" "sudo apt-get autoremove --dry-run"
# Only after user confirms:
ssh "$DEVOPS_HOST" "sudo apt-get autoremove -y"

# Preview reclaimable Docker space (stopped containers, unused images, dangling volumes)
ssh "$DEVOPS_HOST" "docker system df 2>/dev/null"
# Only after user confirms — show df output, ask "Prune stopped containers, unused images, and dangling volumes? (yes/no)":
ssh "$DEVOPS_HOST" "docker system prune -f 2>/dev/null"

Run each command independently. Show space recovered after journalctl and apt clean. Always ask before autoremove and docker prune — never chain them automatically. For docker prune: show docker system df output first so the user knows what will be removed before they confirm.

systemd Service Status

# All running services
ssh "$DEVOPS_HOST" "systemctl list-units --type=service --state=running --no-pager"

# Failed services (always report these)
ssh "$DEVOPS_HOST" "systemctl list-units --type=service --state=failed --no-pager"

# Specific service
ssh "$DEVOPS_HOST" "sudo systemctl status <service> --no-pager"

# Recent logs for a service
ssh "$DEVOPS_HOST" "sudo journalctl -u <service> -n 50 --no-pager"

Always report failed services regardless of other thresholds — even one failed service is significant.

SSL Certificate Expiry

Local dependency: SSL expiry checks run openssl locally (not via SSH) to connect from outside. If openssl is not installed locally, skip SSL checks and report 'openssl not found locally — SSL expiry checks skipped'.

# Check expiry for a domain
echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -dates

# Check all nginx server_names from config (skip if nginx is not installed)
# Use dpkg — `which nginx` is unreliable over non-interactive SSH (/usr/sbin not in PATH)
if ssh "$DEVOPS_HOST" "dpkg -l nginx 2>/dev/null | grep -q '^ii'" 2>/dev/null; then
  ssh "$DEVOPS_HOST" "grep -rh 'server_name' /etc/nginx/sites-enabled/ | awk '{for(i=2;i<=NF;i++) print \$i}' | tr -d ';' | grep -v '^_$' | grep -E '^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$' | sort -u"
else
  echo "nginx not installed — skipping SSL domain extraction"
fi

Thresholds:

  • Expires in < 7 days: report as CRITICAL (immediate action required)
  • Expires in < 14 days: report as WARNING
  • Expires in < 30 days: report as INFO
  • Expires in >= 30 days: report as OK (✓)

To compute days remaining, compare notAfter from the cert against today's date.

Docker Container Health

# All containers with status
ssh "$DEVOPS_HOST" "docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'"

# Unhealthy or exited containers
ssh "$DEVOPS_HOST" "docker ps -a --filter status=exited --filter status=dead --format '{{.Names}}: {{.Status}}'"

# Container logs (last 50 lines)
ssh "$DEVOPS_HOST" "docker logs --tail=50 <container>"

# Container resource usage
ssh "$DEVOPS_HOST" "docker stats --no-stream"

Report any exited or dead containers by name. If Docker is not installed, skip this section silently.

Health Check Report Format

When running a health check, output the report in this exact format:

Health Check — <hostname> — <date>

System: CPU <1m>/<5m>/<15m> (cores: <N>) | Mem: <used>/<total> | Disk: <mounts>
Services: <count> running | Failed: <list or "none">
SSL: [domain]: expires <date> (<N> days) [OK/WARN/CRIT]
Docker: <count> running | Unhealthy/Exited: <list or "none">

Collect all data first, then render the report. Include a summary line at the end if any WARNING or ERROR conditions were found, e.g. "2 WARNING(s), 1 ERROR(s) — action required."

Domain Detection

After resolving the SSH connection and reading the state file, identify which domain the user's request targets. Load the matching domain file before proceeding with any domain-specific work. Multiple files may be loaded when a request spans domains.

User intentTrigger keywordsLoad
Security audit / hardeningaudit, harden, ssh config, firewall, ufw, fail2ban, cve, updates, security./domains/security.md
Nginx / SSL / vhostsnginx, ssl, vhost, certbot, renew, site, domain, reverse proxy./domains/nginx.md
Docker / containersdocker, container, compose, image, logs (for a container name)./domains/docker.md
First-run setupsetup, discover, first run, wizard./domains/setup.md
Cron / timerscron, timer, scheduled, crontab./domains/cron.md (inspection only — creating/editing cron jobs is out of scope)
Bare invocation / health check/devops alone, no task keywordsNo domain file — run health check from core

When in doubt, load the domain file. The cost of loading an extra file is lower than operating without the right instructions. For a bare health check, all data comes from the commands in the Bare Invocation section below — no domain file is needed.

Production Safety Rules

Changing a Service Tag

If the user asks to tag a service as production or experimental:

  1. Read ~/.agents/state/devops/server.md
  2. Find the entry for that service or container under services: or containers:
  3. Update the tag: field to the requested value
  4. Write the file
  5. Confirm: "<service> is now tagged <tag>."

Reading the Production Tag

Before any write operation on a service or its config files, check ~/.agents/state/devops/server.md:

  • If the service or container is tagged production, apply STRICT mode
  • If tagged experimental, apply STANDARD mode
  • If not found in the state file, default to STRICT mode — treat the service as production until explicitly tagged otherwise. Inform the user: "⚠ <service> is not in ~/.agents/state/devops/server.md. Defaulting to STRICT (production) mode. To relax this, tag it as experimental in ~/.agents/state/devops/server.md and re-run /devops setup."

STRICT Mode (production services)

All of these apply in addition to the standard Config Edit Workflow:

  1. Extra confirmation required: Show: "⚠ WARNING: <service> is tagged PRODUCTION. This affects live workloads." Require the user to type exactly: yes, production Any other response aborts the operation.
  2. No experimental changes: Refuse to apply untested configurations, major version upgrades, or alpha/beta packages to production services without explicit user override.
  3. OS updates on production: Never run apt upgrade on the full system if any production service is running. Instead: identify only the packages relevant to the production service and propose targeted updates. Always ask: "Do you want to update <package> on production? This will restart <service>."
  4. Restart policy: Prefer reload over restart. For services where reload is not possible, warn: "Restarting <service> will cause a brief downtime (~5-30 seconds). Proceed? (yes/no)"
  5. Rollback window: After applying any change to a production service, say: "Change applied. The session remains in rollback-ready state until you confirm — type 'ok' to confirm or 'rollback' to revert." If user says 'rollback' within this window, immediately restore from backup. If this session ends before you confirm, the backup is still available at /var/backups/devops/ for manual restore.

STANDARD Mode (experimental services)

Follow the standard Config Edit Workflow. No extra confirmations beyond the normal "Apply this change? (yes/no)".

OS Package Updates Rule

  • By default: only audit and report (apt list --upgradable)
  • On explicit user request (e.g. "apply security updates"): show the list, confirm, then run: ssh "$DEVOPS_HOST" "sudo apt-get upgrade -y" for non-production or targeted for production
  • Never auto-apply updates without explicit user instruction

Behavior: Bare Invocation (Health Check)

Trigger

When the user invokes /devops with no arguments, no task, or no additional text — i.e., the only input is the skill name itself. If the only argument is a host (matches user@hostname or a plain hostname with no task), treat this as a bare health check invocation with that host as the override. If the user's message contains a clear task verb (check, audit, restart, renew, add, remove, harden, status, logs), execute that task; bare health check applies only when there is genuinely no task.

Host override note: When a host override is used, the state file (~/.agents/state/devops/server.md) reflects a different server. Default all services to STRICT for that session — do not rely on cached tags.

Action: Proactive Health Check

Resolve and verify the SSH connection first (see SSH Connection section). Then run all of the following checks in parallel via SSH:

  1. Failed systemd services:
    ssh "$DEVOPS_HOST" "systemctl list-units --type=service --state=failed --no-pager"
  2. Disk usage:
    ssh "$DEVOPS_HOST" "df -h | grep -v tmpfs"
  3. Pending security updates: Before running: check ssh "$DEVOPS_HOST" "dpkg -l apt 2>/dev/null | grep -q '^ii'" — if absent, report 'apt-get not found — package update checks not supported on this OS' and skip.
    ssh "$DEVOPS_HOST" "sudo apt-get --just-print upgrade 2>/dev/null | grep -c '^Inst.*security'"
  4. SSL certificate expiry: Local dependency: If openssl is not available locally (which openssl), skip this check and report 'openssl not found locally — SSL expiry checks skipped'. If nginx is not installed on the server (ssh "$DEVOPS_HOST" "dpkg -l nginx 2>/dev/null | grep -q '^ii'" exits non-zero), skip this check and report 'nginx not installed — SSL domain extraction skipped'. Otherwise, extract all domains from nginx site configs:
    ssh "$DEVOPS_HOST" "grep -rh 'server_name' /etc/nginx/sites-enabled/ | awk '{for(i=2;i<=NF;i++) print \$i}' | tr -d ';' | grep -v '^_$' | grep -E '^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$' | sort -u"
    For each domain found, run locally (not via SSH — openssl connects from the outside):
    echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -enddate
    Compute days remaining by comparing notAfter against today's date.
  5. Docker unhealthy/exited containers:
    ssh "$DEVOPS_HOST" "docker ps -a --filter status=exited --filter status=dead --format '{{.Names}}: {{.Status}}'" 2>/dev/null
    If Docker is not installed, skip this check silently.
  6. System load vs CPU cores:
    ssh "$DEVOPS_HOST" "uptime && (nproc 2>/dev/null || echo 1)"
  7. Memory usage:
    ssh "$DEVOPS_HOST" "free -h"

Collect all results before rendering output. Do not print intermediate output for individual checks.

Output Format

Surface the top 3–5 most urgent issues. Use exactly this format:

📊 Server Health — <hostname> — <date>

⚠ Issues Found:
  1. [CRITICAL] Disk /var is at 92% — clean up or expand
  2. [WARNING] SSL cert for example.com expires in 8 days — run: /devops renew ssl example.com
  3. [WARNING] Container "my-app" exited 2h ago
  4. [INFO] 6 security updates pending

✓ All good: nginx, ssh, fail2ban, memory (42% used)

Run `/devops <task>` to act on any issue above.

Rules for the output:

  • List only items that have findings under "⚠ Issues Found". Omit that block entirely if there are no issues.
  • List items that passed under "✓ All good". Omit that block if everything has issues.
  • Rank issues by severity descending: CRITICAL first, then WARNING, then INFO.
  • Show at most 5 issues. If more exist, add a line: … and N more issues.
  • Always end with the `/devops <task>` prompt line if any issues exist. Replace <task> with a concrete suggested command for the most urgent issue (e.g. renew ssl example.com, restart my-app, upgrade packages).
  • If no issues are found at all, replace the issues block with: ✓ Everything looks healthy. and omit the action prompt.

Severity Classification

Apply these thresholds when classifying each finding:

SeverityCondition
CRITICALDisk usage > 90% on any mount
CRITICALAny failed systemd service
CRITICALSSL cert expires in < 7 days
WARNINGDisk usage > 80% on any mount
WARNINGSSL cert expires in < 14 days
INFOSSL cert expires in < 30 days
WARNINGAny exited or dead Docker container
WARNINGLoad average (1-minute) > number of CPU cores
INFOPending security updates (count > 0)
INFOMinor config suggestions

A single check can produce at most one finding. Use the highest applicable severity if multiple thresholds match.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.