Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server
Project description
vssh
Fast SSH alternative for server fleets — no key management, instant connections, AI-assisted.
pip install vssh
No external dependencies. Pure Python standard library. Works on Linux and macOS.
Why vssh?
The problem with SSH at scale
SSH is the industry standard for remote access — but it was designed for single-server use. Managing a fleet with SSH means:
Key management sprawl:
# Adding a new admin to 20 servers
for server in web{1..10} db{1..5} worker{1..5}; do
ssh-copy-id -i ~/.ssh/newadmin.pub root@$server
done
# When they leave: remove the key from all 20 servers
# Key rotation: touch every server again
IP address bookkeeping:
# You have to remember or look up IPs constantly
ssh root@10.0.1.42 # which server is this again?
ssh -i ~/.ssh/id_rsa root@10.0.1.42 "df -h"
# Different key for different servers? Different user? Port?
Connection overhead: SSH does a full TLS-style handshake on every connection — key exchange, algorithm negotiation, authentication. For a fleet-wide operation this adds up to seconds of overhead per server.
No unified view: There's no built-in way to see which servers are reachable right now, or to run commands across multiple servers with clean output.
Before vssh
# Deploy nginx config to 10 servers and reload
for ip in 10.0.1.10 10.0.1.11 10.0.1.12 10.0.1.13 10.0.1.14 \
10.0.1.15 10.0.1.16 10.0.1.17 10.0.1.18 10.0.1.19; do
scp -i ~/.ssh/id_rsa ./nginx.conf root@$ip:/etc/nginx/nginx.conf
ssh -i ~/.ssh/id_rsa root@$ip "nginx -t && systemctl reload nginx"
done
# ~200-400ms per server just for connection setup
# Must track IPs, keys, users manually
# No visibility into which succeeded or failed
After vssh
# Same operation with vssh
for node in web{1..10}; do
vssh put ./nginx.conf $node:/etc/nginx/nginx.conf
vssh $node "nginx -t && systemctl reload nginx"
done
# Names auto-discovered from wire mesh
# One shared secret — no per-server keys
# Persistent daemon — connections are instant
Comparison
| SSH | vssh | |
|---|---|---|
| Authentication | Per-server public keys | One shared HMAC secret |
| Key management | Add/remove on every server | Change one secret, done |
| Connection setup | Full handshake every time (~200-400ms) | HMAC token check (~1ms) |
| Server discovery | IP addresses / /etc/hosts / DNS | Wire mesh auto-discovery |
| Fleet health view | None built-in | vssh status shows all nodes |
| File transfer | scp (new connection each time) |
Persistent connection, resumable |
| AI management | ✗ | ✓ MCP integration |
| Dependencies | OpenSSH (system) | None (pure Python) |
Architecture
vssh has two parts: a daemon (vsshd) running on each server, and a client (vssh) you run from your machine.
┌─────────────────────────────────────────────┐
│ Your Server Fleet │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ web1 │ │ web2 │ │ db1 │ │
│ │vsshd │ │vsshd │ │vsshd │ ... │
│ │:48291│ │:48291│ │:48291│ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ └───────────┴───────────┘ │
│ WireGuard mesh (wire) │
└──────────────────┬──────────────────────────┘
│ TCP :48291
┌──────┴──────┐
│ vssh client │ ← your machine
│ (Mac/Linux) │
└─────────────┘
All communication happens on TCP port 48291 using a simple line-delimited protocol with HMAC-SHA256 authentication.
vssh is Layer 2 of the MeshPOP stack:
Layer 3 mpop Fleet orchestration — monitor, manage, automate
Layer 2 vssh Authenticated transport — remote exec, file transfer ← this
Layer 1 wire Encrypted mesh VPN — connects all nodes
Each layer is independently installable. vssh works without wire (use a static config file) and without mpop.
Installation
Install vssh
pip install vssh
This installs:
vssh— CLI clientvsshd/vssh server— daemonvssh-mcp— MCP server for AI integration
Pure Python standard library — no external packages required.
Start the daemon on each server
Set the same shared secret on every server (environment variable or file):
# Option 1: environment variable
export VSSH_SECRET=your-shared-secret-here
vssh server
# Option 2: secret file
echo "your-shared-secret-here" > ~/.vssh/secret
chmod 600 ~/.vssh/secret
vssh server
The secret can be any string. Use a long random value:
python3 -c "import secrets; print(secrets.token_hex(32))"
Install as a system service (recommended)
# Set the secret first, then:
vssh install # writes /etc/systemd/system/vssh.service and enables it
The service starts automatically on boot.
Configure the client (your machine)
If you use wire mesh VPN, vssh discovers all nodes automatically — no config needed.
For standalone use without wire, create ~/.vssh/config:
# Server names and IPs
web1=192.168.1.10
web2=192.168.1.11
db1=192.168.1.20
# Shared secret (same as on servers)
SECRET=your-shared-secret-here
Authentication
vssh uses HMAC-SHA256 — a time-based token derived from the shared secret:
token = hmac_{timestamp}_{sha256(secret + timestamp)[:32]}
Every command includes this token. The server verifies it against its own secret. Tokens are valid for ±60 seconds (clock skew window).
One secret to manage all servers. To revoke access, change the secret on all servers. No key files to track, no per-user certificates.
Secret is read from (priority order):
VSSH_SECRETenvironment variable~/.vssh/secretfileSECRET=entry in~/.vssh/config
CLI Reference
Fleet status
vssh status # Connection status to all mesh nodes
vssh status --full # Status + disk / memory / load per node
vssh v3.7.4 - Cluster Status (full)
======================================================================
web1 10.99.1.10 ● online (8ms)
disk: 45% used (120GB / 250GB) mem: 4.2GB / 16GB load: 0.42
web2 10.99.1.11 ● online (9ms)
disk: 31% used mem: 2.1GB / 8GB load: 0.15
db1 10.99.1.20 ✗ offline
Total: 2/3 online
Remote execution
vssh <host> # Interactive terminal (PTY — like SSH)
vssh <host> "command" # Run a single command
vssh session <host> # Persistent PTY session
# Examples
vssh web1 # Open interactive shell on web1
vssh web1 "uptime" # Check uptime
vssh web1 "df -h && free -h" # Chain commands
vssh db1 "systemctl status postgresql"
File transfer
# Upload
vssh put <local> <host>:<remote>
vssh put -z <local> <host>:<remote> # Force compression
vssh put --resume <local> <host>:<remote> # Resume interrupted upload
# Download
vssh get <host>:<remote> <local>
vssh get --retry=3 <host>:<remote> <local> # Retry on failure
# Directory sync
vssh sync <local_dir> <host>:<remote_dir> # 8 parallel streams
# Delta sync (only changed blocks — like rsync)
vssh rsync <local> <host>:<remote>
# Multiple files in one connection
vssh mput <host>:<base_path> file1 file2 file3
# Examples
vssh put ./app.tar.gz web1:/opt/app.tar.gz
vssh get web1:/var/log/app.log ./app.log
vssh sync ./config/ web1:/etc/app/
vssh rsync ./src/ web1:/opt/src/
vssh auto-compresses text files (.py, .js, .json, .log, etc.) and skips upload if remote MD5 matches.
Speed test
vssh speed-test web1 # 10MB test
vssh speed-test web1 --size=50
Pipe operations
# Upload stdin to remote file
cat data.csv | vssh pipe-up web1:/data/input.csv
# Download remote command output to stdout
vssh pipe-down web1:"journalctl -u nginx -n 100" | grep ERROR
Daemon management
vssh server # Start daemon (foreground)
vssh install # Install as systemd (Linux) or launchd (macOS) service
vssh up # Start via systemd
vssh down # Stop daemon
vssh restart # Restart daemon
Node info
vssh info <host> # OS, IPs, vsshd version, load
History and stats
vssh history # Last 20 commands
vssh history 50 # Last 50
vssh history put # Filter by operation (SSH, PUT, GET, RPC, SYNC)
vssh stats # Transfer stats (last 7 days)
vssh stats 30 # Last 30 days
RPC — Structured Remote Calls
RPC is vssh's structured interface for getting typed JSON data from remote servers — no screen scraping, no parsing shell output.
vssh rpc <host> <method> # No arguments
vssh rpc <host> <method> '{"key":"value"}' # With JSON payload
vssh rpc-list <host> # List available methods
Available RPC methods
| Method | Description |
|---|---|
get_disk |
Disk usage (path optional) |
get_memory |
Memory usage in bytes |
get_load |
Load average (1m / 5m / 15m) |
get_processes |
Top processes by CPU or memory |
get_gpu |
GPU VRAM, utilization, temperature |
get_logs |
Read log file or journalctl output |
get_network_info |
Local and public IPs |
list_services |
Running systemd services |
service_status |
Check if a service is active |
restart_service |
Restart nginx / docker / ollama / postgresql / redis |
docker_containers |
List Docker containers and status |
file_read |
Read a file (with security restrictions) |
file_write |
Write a file (system paths blocked) |
# Examples
vssh rpc web1 get_disk
vssh rpc web1 get_disk '{"path": "/data"}'
vssh rpc web1 get_memory
vssh rpc web1 get_processes '{"n": 5, "sort": "mem"}'
vssh rpc web1 get_gpu
vssh rpc web1 get_logs '{"service": "nginx", "lines": 50}'
vssh rpc web1 restart_service '{"service": "nginx"}'
Tailscale Failover
If a node's primary VPN IP (wire) is unreachable, vssh automatically retries via Tailscale:
- Try wire VPN IP (2s timeout)
- Retry once after 300ms (WireGuard handshake may be completing)
- Fall back to Tailscale IP, cache for 60s
- After 60s: retry wire — if recovered, clear failover cache
The failover map (~/.vssh/tailscale_map) is built automatically by cross-referencing tailscale status with the wire peer list. No manual configuration needed.
AI Management via MCP
vssh ships with an MCP server that lets AI agents (Claude, etc.) manage your fleet through natural language — from installation through daily operations.
Setup
pip install vssh # installs vssh-mcp automatically
Add to Claude config (~/.claude/settings.json):
{
"mcpServers": {
"vssh": { "command": "vssh-mcp" }
}
}
What the AI can do
Once connected, you can ask Claude in plain language:
"Check which servers are online and show me their disk usage" "Deploy the new config to all web servers and reload nginx" "Show me the last 50 nginx error log lines from web1" "What's using the most memory on db1?" "Sync the /etc/app/ config directory from web1 to web2" "Run a speed test to web1"
The AI handles the vssh commands, interprets the results, and tells you what it found.
MCP Tools Reference
| Tool | Description |
|---|---|
vssh_status |
Fleet status — all nodes, online/offline, latency |
vssh_exec |
Run a shell command on a remote server |
vssh_put |
Upload a file to a remote server |
vssh_get |
Download a file from a remote server |
vssh_sync |
Sync a directory between two servers |
vssh_speed_test |
Measure upload/download speed to a server |
vssh_tunnel |
Create a port-forwarding tunnel |
vssh_keys |
Show configured secrets and available servers |
Example AI workflow — deploy update
You: "Deploy app-v2.tar.gz to all web servers and restart the app service"
AI:
1. vssh_status → check which web servers are online
2. vssh_put(web1, /tmp/app-v2.tar.gz, /opt/app.tar.gz)
3. vssh_exec(web1, "cd /opt && tar xf app.tar.gz && systemctl restart app")
4. vssh_exec(web1, "systemctl is-active app")
5. Repeats for web2, web3...
6. Reports: "Deployed to 3/3 servers. All app services active."
Links
- Wire VPN: github.com/meshpop/wire
- Fleet orchestration: github.com/meshpop/mpop
- PyPI: pypi.org/project/vssh
License
MIT — MeshPOP
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vssh-3.7.5.tar.gz.
File metadata
- Download URL: vssh-3.7.5.tar.gz
- Upload date:
- Size: 48.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0673c0ad3cbec39b00258801f2628a828c1c5507bd879c35ed868564303e1a7f
|
|
| MD5 |
26a7dbdcfbb1d7a47ce503ceb4f749fa
|
|
| BLAKE2b-256 |
b1e79295671e8b21edee7f690fb0e43a371128c3ac0f8d14e7ef1434b9f8c688
|
File details
Details for the file vssh-3.7.5-py3-none-any.whl.
File metadata
- Download URL: vssh-3.7.5-py3-none-any.whl
- Upload date:
- Size: 49.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28f0ff1f378c1f0a57eb020f1193e91ac862a499af9657eaad40595a5a5e341c
|
|
| MD5 |
1edebc76a21ab787a96865dc1c5b794b
|
|
| BLAKE2b-256 |
09b0ccc30d42a519fbb1419670aaea5ecbf8384f494dcfd4e2a3d90262ade125
|