Skip to content

push42/ssh-fleet-manager

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ssh-fleet-manager

CI Go TypeScript License: MIT

ssh-fleet-manager logo

ssh-fleet-manager banner

GitHub Stars GitHub Forks Open Issues Latest Release

Local-first SSH fleet manager and key lifecycle tool.
Manage SSH servers, identities, key rotation, authorized_keys, bulk operations,
live terminal access, and encrypted secrets — all self-hosted, no analytics.

Go backend · PostgreSQL · React dashboard · AES-256-GCM encrypted vault · Audit trail · WebSocket terminal


ssh-fleet-manager is a local-first SSH fleet and key lifecycle manager for operators who maintain many hosts and want a small, inspectable control plane. It combines a Go backend, PostgreSQL storage, and a React dashboard for server inventory, SSH identity management, key rotation workflows, Guix inventory, and live terminal access.

The project is designed for self-hosted use. There is no hosted control plane and no product analytics pipeline. Your database, encrypted key material, runtime logs, and audit records stay in the environment where you run the app.

Security note: ssh-fleet-manager is a trusted operator console. It can hold private SSH key material and can modify remote authorized_keys files. Do not expose it to an untrusted network without TLS, network-level access controls, and a deployment plan that treats the local login as only one layer.

Why ssh-fleet-manager?

If you manage more than a handful of SSH hosts, you probably have a spread of keys, a ~/.ssh/config that keeps growing, and no good way to see which key is on which host. ssh-fleet-manager gives you a single dashboard to:

  • See your whole fleet — every host, its pinned host-key fingerprint, attached identities, and connection status, in one searchable inventory.
  • Rotate keys safely — preview-first bulk deploy, renew, rotate, replace, and distribute workflows that preserve unknown or manual authorized_keys lines.
  • Keep keys encrypted — private key material is sealed with AES-256-GCM at rest, never stored in plaintext, and downloadable on demand.
  • Audit everything — every key install, rotation, remote scan, and secret reveal writes an audit row so you can trace who did what.
  • Resolve secrets locally — a loopback-only plaintext resolver turns the manager into a single source of truth for API keys, tokens, and DSNs that your local tooling can fetch without exposing values to the network.
  • Run a terminal in the browser — a WebSocket-backed xterm.js shell lets you drop into any server that has an attached identity, without leaving the app.

No SaaS, no telemetry, no hosted control plane. Your keys and your data stay on your infrastructure.

Contents

Project Status

ssh-fleet-manager is pre-1.0 software. The core workflows are usable, but operators should still treat it as early release infrastructure software:

  • Review preview plans before applying any remote key operation.
  • Keep backups of PostgreSQL and SSHM_DATA_DIR, especially master.key.
  • Test against non-critical hosts before managing production access.
  • Read the security notes before binding the app outside localhost or WSL.

What It Does

  • Maintains a searchable inventory of SSH servers, ports, usernames, tags, and pinned host-key fingerprints.
  • Generates or imports SSH identities, including encrypted OpenSSH private keys.
  • Stores private key material encrypted at rest with AES-256-GCM.
  • Scans local .ssh folders and ~/.ssh/config for import candidates.
  • Installs public keys on remote hosts through password auth or an existing attached identity.
  • Previews and applies bulk key deploy, renew, rotate, replace, and distribute workflows.
  • Scans remote authorized_keys files and distinguishes app-managed, adoptable, manual, and invalid entries.
  • Preserves unknown or manual remote keys by default.
  • Opens a browser terminal over WebSocket using xterm.js.
  • Captures GNU Guix profile snapshots with guix describe and guix package -I.
  • Tracks snippets, port forwards, SFTP file access, and audit events.
  • Manages non-SSH secrets (API keys, tokens, passwords, DSNs) in an encrypted store with a dashboard Secrets panel, and resolves them to local tooling over a loopback-only plaintext resolver.
  • Creates a default admin / ssh account, then forces credential rotation before dashboard/API access.

Safety Model

ssh-fleet-manager is conservative around SSH access because a bad key operation can lock an operator out of a host.

  • Bulk operations are preview-first. The app builds a plan before it mutates any remote authorized_keys file.
  • Unknown remote key lines are preserved unless the operator explicitly adopts them after importing the matching private key.
  • A key is app-managed only after it is generated or imported, named, fingerprinted, attached to a server, and active.
  • Passphrase hints are labels only. They are never treated as passphrases.
  • Replaced identities can be archived before retirement.
  • Remote apply results and local audit rows are recorded for traceability.

Screens and Workflows

ssh-fleet-manager product banner

The UI is an operator dashboard, not a marketing surface. The main areas are:

  • Home: fleet health, quick actions, recent audit activity, and vault state.
  • Fleet: server inventory, tags, SSH test, host-key pinning, key install, SFTP, port forwards, and live shell.
  • Keys: identity generation, import, passphrase storage, encrypted download, local .ssh sync, archive, and lifecycle operations.
  • Guix: package/profile inventory for hosts that use GNU Guix.
  • Snippets: reusable commands for manual or terminal-assisted workflows.
  • Secrets: encrypted store for API keys, tokens, passwords, and DSNs — masked list with kind badges and tags, add/edit form, reveal-with-confirm (audited), and rotation/last-used tracking. Values never appear in list views.
  • Audit: mutating events and key lifecycle history.
  • Docs: in-app operational guidance for key and fleet workflows.

Requirements

  • Go 1.23 or newer.
  • Node.js and npm for building the web UI. CI currently uses Node.js 24.
  • PostgreSQL reachable from the backend.
  • OpenSSH-compatible remote servers.
  • On Windows, Ubuntu-26.04 WSL is the recommended runtime path.

Quick Start

Clone the repository:

git clone https://github.com/push42/ssh-fleet-manager.git
cd ssh-fleet-manager

Create a local PostgreSQL role and database:

make pg-create

Or create them manually:

sudo -u postgres psql -c "CREATE ROLE sshm LOGIN PASSWORD 'sshm';"
sudo -u postgres psql -c "CREATE DATABASE sshm OWNER sshm;"

Build the web UI:

cd web
npm ci
npm run build
cd ..

Run the backend from the repository root:

SSHM_LISTEN=127.0.0.1:42428 go run ./cmd/ssh-fleet-manager

Open:

http://127.0.0.1:42428

Database migrations run automatically on backend startup.

On first launch, the app creates a single local admin account:

username: admin
password: ssh

After signing in with those defaults, you must change the password before the dashboard unlocks. The replacement password is stored as a bcrypt hash. You can change the local username or password later from Settings.

Windows and WSL

On Windows, use the bundled WSL launcher from PowerShell:

.\Start-ssh-fleet-manager-wsl.ps1 -RebuildWeb

Stop it with:

.\Stop-ssh-fleet-manager-wsl.ps1

The launcher starts the backend inside Ubuntu-26.04, checks PostgreSQL, builds the web UI when requested, waits for /api/health, computes the current WSL IP, and prints the URL to open. See docs/WSL_RUNBOOK.md.

Linux or macOS Launcher

For a local background process:

bash scripts/start_local_app.sh --rebuild-web

Check status or stop it:

bash scripts/start_local_app.sh --status
bash scripts/stop_local_app.sh

Build Artifact

Build a local release binary and web assets:

make build

Run from the repository root so the backend can serve web/dist, or set SSHM_STATIC_DIR to the directory that contains the built frontend:

SSHM_STATIC_DIR=/opt/ssh-fleet-manager/web/dist ./bin/ssh-fleet-manager

Configuration

The backend reads configuration from environment variables.

Variable Default Description
SSHM_LISTEN 0.0.0.0:42428 HTTP listen address. Use 127.0.0.1:42428 for local-only runs.
SSHM_DATA_DIR ./data Runtime directory for the encryption master key and local state.
SSHM_DATABASE_URL unset Full PostgreSQL URL. Overrides the split SSHM_PG* variables.
SSHM_PGHOST 127.0.0.1 PostgreSQL host when SSHM_DATABASE_URL is not set.
SSHM_PGPORT 5432 PostgreSQL port.
SSHM_PGUSER sshm PostgreSQL user.
SSHM_PGPASSWORD sshm PostgreSQL password.
SSHM_PGDATABASE sshm PostgreSQL database name.
SSHM_STATIC_DIR unset Directory for built web assets. The backend also checks web/dist.
SSHM_MASTER_KEY unset Optional master secret. If unset, the app creates SSHM_DATA_DIR/master.key.
SSHM_READONLY_TOKEN unset Static read-only token for metadata-only MCP/API access (/api/servers, /api/identities, /api/secrets). Never returns values or private keys.
SSHM_RESOLVE_TOKEN unset Distinct token for the loopback-only plaintext resolver. When set, a second listener starts on SSHM_RESOLVE_LISTEN serving GET /resolve/{name}. Keep this secret in your external secret store.
SSHM_RESOLVE_LISTEN 127.0.0.1:42429 Bind address for the plaintext resolver. Keep it off the public network; the reverse proxy never proxies it. When the backend runs in WSL and the bridge runs on Windows, bind to the WSL interface or add a netsh portproxy for 42429 (see docs/ARCHITECTURE.md).
SSHM_DEV 0 Enables development metadata in health responses.
SSHM_LOCAL_SSH_DIR unset Default destination for explicit local .ssh syncs.
SSHM_LOCAL_KEY_PREFIX unset Default basename prefix for locally synced identities.

Back up SSHM_DATA_DIR/master.key. Without the same master key, encrypted private key material stored in PostgreSQL cannot be decrypted.

Development

Run backend tests:

go test ./...

Run frontend checks:

cd web
npm ci
npm run typecheck
npm run build

Run the Vite dev server for frontend-only work:

cd web
npm run dev -- --host 127.0.0.1

The production-style app is still served by the Go backend on port 42428. Use Vite only while actively editing React code.

Documentation

  • Architecture: component layout, key lifecycle, storage, and remote mutation flow.
  • Contributing: setup, PR expectations, tests, and UI rules.
  • WSL Runbook: Windows/WSL startup, status, port proxy, and troubleshooting.
  • Visual Reference: screenshot gallery of every panel and workflow.
  • Agent Guide: repository rules for coding agents and automated assistants.
  • Claude Guide: concise guidance for Claude-based coding sessions.
  • Security Policy: reporting and operating guidance.

Roadmap

  • Reverse-proxy hardening recipes for remote operator access.
  • Release packaging that can serve the compiled frontend without relying on a working tree.
  • Scheduled key rotation.
  • More complete backup and restore tooling.
  • Expanded audit filtering and export.
  • Hardening around multi-operator deployments.

See [issues][issues-url] for current work and proposed features.

Contributing

Issues and pull requests are welcome. For large changes, please open an issue first so design, safety, and test expectations are clear.

Before opening a pull request:

go test ./...
cd web && npm run typecheck && npm run build

For detailed expectations, see docs/CONTRIBUTING.md.

Security

Do not open a public issue with private keys, passwords, logs containing secrets, hostnames that must remain private, or exploit details. See SECURITY.md for the reporting process.

Public-exposure posture

When the dashboard is reached over the public internet (for example via a reverse proxy behind Cloudflare), three controls keep secret values safe:

  1. Metadata-only public surface. The main listener returns secret names, kinds, tags, and dates — never values. Reveal is a POST that requires a logged-in session, is audited, and is served with Cache-Control: no-store.
  2. Loopback-only resolver. Plaintext values are served only by a second listener bound to 127.0.0.1:42429, gated by a distinct SSHM_RESOLVE_TOKEN. The /resolve/* route is never registered on the main listener, so it is physically absent on whatever port the proxy exposes.
  3. Rate limiting and headers. Login and resolve are per-IP rate-limited with lockout; both listeners apply a strict CSP, X-Frame-Options: DENY, nosniff, no-referrer, and HSTS.

The read-only and resolve tokens are bootstrap secrets kept in an external secret store (not the secrets table), so the manager never depends on itself to boot. See docs/ARCHITECTURE.md for the full listener and token model.

License

ssh-fleet-manager is distributed under the MIT License. See LICENSE.


Keywords

SSH fleet manager, SSH key management, SSH key rotation, authorized_keys management, SSH server inventory, key lifecycle, encrypted SSH key vault, bulk SSH key operations, SSH terminal, self-hosted SSH manager, infrastructure automation, sysadmin tool, DevOps tool, Go SSH manager, PostgreSQL SSH manager, React SSH dashboard, audit trail, AES-256-GCM encryption, homelab SSH manager, local-first secret manager, loopback secret resolver, host-key pinning, xterm.js terminal, WSL SSH manager.

About

Local-first SSH fleet manager and key lifecycle tool. Go + PostgreSQL + React. Encrypted key vault, bulk key rotation, authorized_keys scanning, live terminal, audit trail, and a loopback secret resolver. Self-hosted, no analytics.

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors