A Claude Code plugin for managing a fleet of Linux servers over SSH. Keeps a per-server inventory and an "active server" pointer, deploys a dedicated SSH key, and provides guided setup, connectivity diagnostics and system status across hosts.
Modeled on synology-manager-plus;
the NAS stays there (DSM-specific), fleet-manager is for generic Linux hosts.
Your real inventory lives outside the plugin, in
${XDG_CONFIG_HOME:-~/.config}/fleet-manager (servers/*.md, active-server,
inventory.md). It is therefore never committed — server topology cannot land in
git — and it survives claude plugin update, which replaces the whole
version-pinned plugin directory. Set FM_CONTEXT_DIR to relocate it.
Installs from before 0.3.3 kept this state inside the plugin directory, where each update stranded it. The library copies such an inventory forward automatically on first use and leaves the originals in place.
| Command | Description |
|---|---|
/first-run |
Onboard your first server (guided intake) |
/add-server |
Add another server (re-add protected) |
/list-servers |
Show the inventory; mark the active server |
/use <name> |
Set the active server |
/setup-ssh [server] |
Generate + deploy the plugin keypair (idempotent) |
/diag [server] |
Connectivity + health check |
/status [server] |
Disk / memory / uptime / load |
/compose-list [server] |
List Compose projects |
/docker-list [server] [--all] |
List containers |
/compose-logs [server] <project> |
Compose logs |
/compose-up [server] <project|--file path> |
Start a stack (scope-gated) |
/compose-down [server] <project> [--remove] |
Stop a stack (critical → --confirm) |
/compose-update [server] <project> |
Pull + recreate |
/logs [server] [unit] |
System journal (journalctl/syslog) |
/health-summary |
Fleet-wide health (one row per server) |
/copy <src> <dst> |
Copy local↔server (rsync/scp) |
/sync <src> <dst> [--apply] |
Mirror local↔server (dry-run default) |
The active server is targeted when no argument is given; pass a name to override.
Every command prints → Ziel: <name> (user@host:port) before acting.
/health-summary does a sequential one-row-per-server sweep (UP/DOWN/SKIP + verdict),
one SSH round-trip each. /copy and /sync move files between the local machine and a
server using the <server>:<path> syntax (the name before : is an inventory server,
resolved through the plugin key). Both require the server's file_transfer scope; rsync
is used (--protect-args), with an scp fallback for /copy. /sync is dry-run by
default (--apply to execute); --delete additionally needs the typed
--confirm-delete=<server> token, and a protected-path destination needs --confirm=<server>.
Paths must be space-free at the command boundary.
The compose suite drives docker compose on each host via a per-server docker_cmd
discovered at /diag/intake (e.g. docker, sudo -n docker) and allowlist-validated.
Compose commands require the server's docker_compose scope; /logs requires
system_monitoring. Destructive actions against a project listed in the profile's
critical_compose_projects require a human-typed --confirm=<project> token (or
FM_CONFIRM_CRITICAL=yes) — a fail-safe against accidental runs (it makes intent
visible in the command line; it is not an agent-proof gate). A never-started stack is
brought up with /compose-up --file /abs/path/docker-compose.yml.
- SSH access to each server; the plugin uses its own key
~/.ssh/fleet-manager_ed25519. - Bash >= 4, OpenSSH client locally.
The plugin does not disable host-key checking — it relies on the OpenSSH
default, which is the secure choice. Because every connection runs with
BatchMode=yes (no interactive prompts), the first connection to a host whose
key is not yet in your ~/.ssh/known_hosts will fail rather than silently
trust-on-first-use. Establish the host key once, out of band, before /diag:
ssh-keyscan -p <port> <host> >> ~/.ssh/known_hosts # review the fingerprint first
# or simply connect once interactively and accept the key:
ssh -p <port> <user>@<host>This is intentional: it prevents a silent man-in-the-middle on an unknown host.
/setup-ssh surfaces this as a likely cause when key auth fails.
- Unit tests:
for t in tests/unit/*.sh; do bash "$t"; done - Static checks:
for s in tests/static/*.sh; do bash "$s"; done - Integration (needs Docker + rsync):
bash tests/integration/run-all.sh
MIT — CallMeTechie