Metadata-Version: 2.4
Name: vssh
Version: 3.7.3
Summary: Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server
Author-email: MeshPOP <mpop@mpop.dev>
License: MIT
Project-URL: Homepage, https://github.com/meshpop/vssh
Project-URL: Repository, https://github.com/meshpop/vssh
Keywords: ssh,scp,tailscale,p2p,vpn,mcp,remote
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Networking
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# vssh

**SSH alternative and mesh transport layer — zero dependencies, faster connections, built for fleets.**

```bash
pip install vssh
```

---

## Why vssh?

Managing multiple servers with standard SSH gets painful fast.

```bash
# SSH — the old way
ssh -i ~/.ssh/id_ed25519 root@192.168.1.10 "systemctl restart nginx"
ssh -i ~/.ssh/id_ed25519 root@192.168.1.11 "systemctl restart nginx"
ssh -i ~/.ssh/id_ed25519 root@192.168.1.12 "systemctl restart nginx"
# ...repeat for every server
```

```bash
# vssh — the new way
vssh exec web1 "systemctl restart nginx"
vssh exec web2 "systemctl restart nginx"
vssh exec web3 "systemctl restart nginx"

# Or run across all nodes at once (via MCP / scripting)
for node in web1 web2 web3; do vssh exec $node "systemctl restart nginx" & done
```

No key flags. No IP addresses. No repeated handshakes. Just node names.

---

## What is vssh?

vssh is a **standalone SSH alternative and mesh transport layer** that:

- Works **out of the box** after `pip install vssh` — no config files, no agents, no dependencies
- Auto-discovers servers from your [wire](https://github.com/meshpop/wire) VPN mesh or a static config file
- Authenticates via **HMAC-SHA256** shared secret instead of per-node key management
- Falls back to **Tailscale** automatically when the primary path is unreachable
- Runs a persistent **daemon** on each node, so connections are instant — no SSH handshake overhead

If you already use WireGuard mesh (via `wire`), vssh is the transport layer that sits on top and gives you fast, authenticated, name-based access to every node in the mesh.

If you don't use wire, vssh works standalone with a simple `~/.vssh/config` file listing your servers.

---

## Install

```bash
pip install vssh
```

Pure Python stdlib — no external packages required. Works on Linux and macOS.

Start the daemon on each server:

```bash
# Set shared secret (same on all nodes)
export VSSH_SECRET=your-shared-secret

# Start daemon
vssh server
```

Or as a systemd service:

```bash
vssh install   # writes and enables /etc/systemd/system/vssh.service
```

---

## Authentication

vssh uses **HMAC-SHA256** for authentication. Every connection includes a time-based token:

```
hmac_{timestamp}_{sha256(secret, timestamp)[:32]}
```

- The same `VSSH_SECRET` is shared across all nodes
- Tokens are valid for ±60 seconds (configurable clock skew window)
- No per-node keys, no certificates, no key rotation
- Secret is read from (in priority order):
  1. `VSSH_SECRET` environment variable
  2. `~/.vssh/secret` file
  3. `SECRET=` entry in `~/.vssh/config`
  4. Auto-generated on first install (`secrets.token_hex(16)`)

---

## Connection Protocol

All vssh communication happens on **TCP port 48291** using a simple line-delimited text protocol. Every command follows the format:

```
COMMAND:auth_token:...args...\n
```

The server responds with `OK` (or `OK:size`) followed by the payload and `__END__` as a terminator.

| Protocol | Format | Direction |
|---|---|---|
| SSH exec | `SSH:token:command\n` | client → server |
| File upload | `PUT:token:path:size:md5\n` + data | client → server |
| File download | `GET:token:path\n` | client → server |
| RPC call | `RPC:token:method:payload_len\n` + JSON | client → server |
| Node info | `INFO:token\n` | client → server |
| PTY session | `PTY_SESSION:token:rows:cols\n` | client → server |
| Persistent session | `SESSION:token\n` | client → server |

---

## CLI Commands

### Status and Monitoring

```bash
vssh status              # Connection status to all mesh nodes
vssh status --full       # Status + full node info (disk, memory, load)
vssh stats               # Transfer statistics (last 7 days)
vssh stats 30            # Transfer statistics for last 30 days
vssh history             # Last 20 commands
vssh history 50          # Last 50 commands
vssh history put         # Filter by operation (PUT, GET, SSH, RPC, SYNC)
vssh history 20 web1     # Last 20 commands for specific host
```

**Example `vssh status` output:**
```
vssh v3.7.2 - Connection Status
======================================================================
  c1     10.99.248.51       ● online  (12ms)
  c2     10.99.14.187       ● online  (11ms)
  v5     10.99.171.140      ● online  (local)

Total: 3/3 online
```

**Example `vssh status --full` output:**
```
vssh v3.7.2 - 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

```bash
vssh <host>              # Interactive terminal (PTY — like SSH)
vssh <host> "command"    # Single command
vssh ssh <host> "command"  # Explicit ssh subcommand
vssh session <host>      # Persistent terminal session (PTY mode)
vssh session-test <host> # Test session latency (3 RPC calls)
```

**Examples:**
```bash
vssh web1                          # Open interactive shell
vssh web1 "uptime"                 # Run single command
vssh web1 "df -h && free -h"       # Chain commands
vssh session web1                  # Full PTY session (like SSH)
```

---

### File Transfer

```bash
vssh put  <local> <host>:<remote>           # Upload
vssh put  -z <local> <host>:<remote>        # Force compression
vssh put  --resume <local> <host>:<remote>  # Resume interrupted upload
vssh put  --retry=3 <local> <host>:<remote> # Auto-retry on failure
vssh get  <host>:<remote> <local>           # Download
vssh get  --retry=3 <host>:<remote> <local> # Download with retry
vssh sync <local_dir> <host>:<remote_dir>   # Directory sync (8 parallel streams)
vssh mput <host>:<base_path> <file1> <file2> ...  # Multi-file single connection
vssh fast <local> <host>:<remote>           # Auto-select best method
vssh p2p  <local> <host>:<remote>           # P2P direct (NAT hole-punch)
vssh rsync <local> <host>:<remote>          # Delta sync (only changed blocks)
```

**Transfer flags:**
| Flag | Description |
|---|---|
| `-z` / `--compress` | Force zlib compression |
| `--no-compress` | Disable auto-compression |
| `--resume` | Resume interrupted transfer (for files >100MB) |
| `--retry=N` | Retry N times on network failure |
| `--limit=X` | Rate limit (e.g. `--limit=10MB`) |
| `--no-lan` | Disable LAN-direct optimization |

**Examples:**
```bash
vssh put  ./app.tar.gz  web1:/opt/app.tar.gz    # Upload
vssh get  web1:/var/log/app.log ./app.log        # Download
vssh sync ./config/ web1:/etc/app/              # Sync directory
vssh mput web1:/backup/ file1.txt file2.txt file3.log   # Multi-file
vssh rsync ./src/ web1:/opt/src/                # Delta sync (only changes)
vssh p2p  ./dataset.tar.gz web2:/data/          # P2P bypass VPN relay
```

vssh auto-compresses text files (`.py`, `.js`, `.json`, `.log`, `.md`, etc.) and skips upload for files with matching MD5.

---

### Speed Test

```bash
vssh speed-test <host>          # Transfer speed test (10MB default)
vssh speed-test <host> --size=50 # Custom size in MB
```

```
→ 54.2 MB/s  (WireGuard mesh, direct)
```

---

### Pipes (stdin/stdout)

```bash
# Pipe stdin to remote file
cat local.log | vssh pipe-up web1:/var/log/backup.log

# Pipe remote command to stdout
vssh pipe-down web1:"journalctl -u nginx -n 100" | grep ERROR

# Auto-detect: stdin present → upload, otherwise → download
cat data.csv | vssh pipe web1:/data/input.csv
vssh pipe web1:"cat /etc/os-release" > os-info.txt
```

---

### Node Info

```bash
vssh info <host>    # Full JSON node info (OS, IPs, vsshd version, etc.)
```

```json
{
  "hostname": "web1",
  "os": "Linux-5.15.0",
  "vssh_version": "3.7.2",
  "local_ips": ["10.99.1.10"],
  "public_ip": "203.0.113.10",
  "load": [0.42, 0.38, 0.31]
}
```

---

### Daemon Management

```bash
vssh server          # Start daemon (foreground)
vssh install         # Install as systemd service (Linux) or launchd (macOS)
vssh up              # Start via systemd
vssh down            # Stop daemon
vssh restart         # Restart daemon
```

---

## RPC System

RPC is vssh's structured remote call interface. Unlike SSH exec (raw shell), RPC returns typed JSON results — no screen scraping needed.

### Protocol

```
Client → Server:
  RPC:{token}:{method}:{payload_length}\n
  {JSON payload (payload_length bytes)}

Server → Client:
  OK:{response_length}\n
  {JSON response (response_length bytes)}
  __END__
```

If the method takes no arguments, `payload_length` is `0` and no payload follows.

### CLI Usage

```bash
vssh rpc <host> <method>              # Call with no arguments
vssh rpc <host> <method> '{"key":"value"}'  # Call with JSON payload
vssh rpc-list <host>                  # List all available methods
```

### RPC Methods Reference

#### `get_gpu` — GPU Status

```bash
vssh rpc g1 get_gpu
```

**Response:**
```json
{
  "available": true,
  "gpus": [
    {
      "name": "NVIDIA RTX 4090",
      "memory_total": 24576,
      "memory_used": 8192,
      "memory_free": 16384,
      "utilization": 43,
      "temperature": 71
    }
  ]
}
```

If no GPU: `{ "available": false, "error": "nvidia-smi failed" }`

---

#### `get_disk` — Disk Usage

```bash
vssh rpc web1 get_disk
vssh rpc web1 get_disk '{"path": "/data"}'
```

**Payload:** `{ "path": "/" }` (default: `/`)

**Response:**
```json
{
  "path": "/",
  "total": 107374182400,
  "used": 48318382080,
  "free": 59055800320,
  "percent": 45
}
```

---

#### `get_memory` — Memory Usage

```bash
vssh rpc web1 get_memory
```

**Response:**
```json
{
  "total": 17179869184,
  "used": 4508876800,
  "free": 12670992384,
  "available": 12670992384
}
```

All values in bytes.

---

#### `get_load` — System Load Average

```bash
vssh rpc web1 get_load
```

**Response:**
```json
{
  "load1": 0.42,
  "load5": 0.38,
  "load15": 0.31,
  "processes": "2/412"
}
```

---

#### `get_processes` — Top Processes

```bash
vssh rpc web1 get_processes
vssh rpc web1 get_processes '{"n": 5, "sort": "mem"}'
```

**Payload:**
| Field | Type | Default | Description |
|---|---|---|---|
| `n` | int | 10 | Number of processes to return |
| `sort` | string | `"cpu"` | Sort by `"cpu"` or `"mem"` |

**Response:**
```json
{
  "processes": [
    {
      "user": "root",
      "pid": 1234,
      "cpu": 45.2,
      "mem": 2.1,
      "command": "/usr/bin/python3 train.py --epochs 100"
    }
  ]
}
```

---

#### `get_logs` — Read Logs

```bash
vssh rpc web1 get_logs '{"service": "nginx", "lines": 20}'
vssh rpc web1 get_logs '{"service": "syslog"}'
vssh rpc web1 get_logs '{"service": "ollama", "lines": 50}'
```

**Payload:**
| Field | Type | Default | Description |
|---|---|---|---|
| `service` | string | `"syslog"` | Service name or log type |
| `lines` | int | 50 | Number of lines to return |

**Built-in log paths:**
| Service key | Log file |
|---|---|
| `syslog` | `/var/log/syslog` |
| `auth` | `/var/log/auth.log` |
| `nginx` | `/var/log/nginx/access.log` |
| `nginx_error` | `/var/log/nginx/error.log` |
| `docker` | `/var/log/docker.log` |
| *(any other)* | `journalctl -u {service} -n {lines}` |

**Response:**
```json
{
  "service": "nginx",
  "path": "/var/log/nginx/access.log",
  "lines": ["192.168.1.1 - - [01/Mar/2025:12:00:00 +0000] \"GET /api/health\" 200", "..."]
}
```

---

#### `restart_service` — Restart a Service

```bash
vssh rpc web1 restart_service '{"service": "nginx"}'
vssh rpc web1 restart_service '{"service": "ollama"}'
```

**Payload:** `{ "service": "nginx" }`

**Allowed services:** `nginx`, `docker`, `ollama`, `chromadb`, `postgresql`, `redis`, `server-agent`

**Response:**
```json
{ "success": true, "service": "nginx" }
```

Or on failure:
```json
{ "success": false, "error": "Job for nginx.service failed" }
```

**Permission:** `admin` (requires `VSSH_SECRET` match)

---

#### `service_status` — Check Service Status

```bash
vssh rpc web1 service_status '{"service": "nginx"}'
```

**Response:**
```json
{
  "service": "nginx",
  "status": "active",
  "active": true
}
```

---

#### `list_services` — List Running Services

```bash
vssh rpc web1 list_services
```

**Response:**
```json
{
  "services": [
    { "name": "nginx", "load": "loaded", "active": "active", "sub": "running" },
    { "name": "docker", "load": "loaded", "active": "active", "sub": "running" },
    { "name": "vssh", "load": "loaded", "active": "active", "sub": "running" }
  ]
}
```

---

#### `docker_containers` — List Docker Containers

```bash
vssh rpc web1 docker_containers
```

**Response:**
```json
{
  "containers": [
    { "name": "app", "status": "Up 2 hours", "image": "myapp:latest" },
    { "name": "redis", "status": "Up 5 days", "image": "redis:alpine" }
  ]
}
```

---

#### `file_read` — Read a File

```bash
vssh rpc web1 file_read '{"path": "/etc/nginx/nginx.conf"}'
```

**Payload:** `{ "path": "/path/to/file" }`

**Security:** Blocks access to `/etc/shadow`, `/etc/passwd`, `.ssh/`, `.gnupg/`, paths containing `secret`, `password`, or `credential`. Files over 1MB are rejected.

**Response:**
```json
{
  "path": "/etc/nginx/nginx.conf",
  "size": 1024,
  "content": "user nginx;\nworker_processes auto;\n..."
}
```

---

#### `file_write` — Write a File

```bash
vssh rpc web1 file_write '{"path": "/opt/app/config.json", "content": "{\"debug\": false}"}'
```

**Payload:**
| Field | Type | Description |
|---|---|---|
| `path` | string | Destination path |
| `content` | string | File content (text) |

**Security:** Blocks writing to system paths: `/etc/`, `/usr/`, `/bin/`, `/sbin/`, `/var/log/`, `/root/`.

**Response:**
```json
{ "success": true, "path": "/opt/app/config.json", "size": 16 }
```

**Permission:** `write`

---

#### `get_network_info` — Network Info

```bash
vssh rpc web1 get_network_info
```

**Response:**
```json
{
  "local_ips": ["10.99.1.10", "192.168.1.50"],
  "public_ip": "203.0.113.10"
}
```

Used internally for LAN-direct optimization.

---

#### `p2p_signal` — P2P Signaling

Used internally by `vssh p2p` to coordinate NAT hole-punch. Exchanges external IP/port info between peers via the VPN mesh relay.

```bash
vssh rpc relay p2p_signal '{"external_ip":"203.0.113.1","external_port":48300,"local_port":48300}'
```

---

### RPC via Session

You can run multiple RPC calls on a single persistent connection using `vssh session`:

```bash
vssh session web1
```

Inside the session:
```
web1> rpc get_disk
web1> rpc get_memory
web1> rpc get_load
web1> rpc restart_service {"service": "nginx"}
web1> exit
```

Or programmatically via the Python API:
```python
from vssh import VsshSession

with VsshSession("10.99.1.10") as sess:
    disk = sess.rpc("get_disk")
    mem  = sess.rpc("get_memory")
    load = sess.rpc("get_load")
    # All on same TCP connection — no reconnect overhead
```

---

## Before / After: Managing a Fleet

### Before (SSH)

```bash
# Deploy new config to 10 servers
for ip in 10.0.1.1 10.0.1.2 10.0.1.3 10.0.1.4 10.0.1.5 \
          10.0.1.6 10.0.1.7 10.0.1.8 10.0.1.9 10.0.1.10; 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
```

Problems: must track IPs manually, full SSH handshake on every connection (~200-400ms each), key management across all nodes, no unified view of which nodes are reachable.

### After (vssh)

```bash
# Deploy new config to 10 servers
for node in web{1..10}; do
  vssh put $node ./nginx.conf /etc/nginx/nginx.conf
  vssh exec $node "nginx -t && systemctl reload nginx"
done
```

Benefits: node names auto-discovered from wire mesh, persistent daemon — connections are instant, one shared secret across all nodes, `vssh status` shows full fleet health at a glance.

---

## Connection Speed

vssh is measurably faster than SSH for repeated commands because:

| | SSH | vssh |
|---|---|---|
| Connection | TCP handshake + key exchange each time | Persistent daemon, HMAC auth only |
| Auth | RSA/Ed25519 challenge-response | HMAC-SHA256 token (microseconds) |
| Transport | TCP | UDP (WireGuard tunnel) |
| Name resolution | `/etc/hosts` or DNS | Wire mesh coordinator (auto) |
| Failover | Manual | Auto-failover to Tailscale |

---

## Auto-Discovery

When used with [wire](https://github.com/meshpop/wire) mesh VPN, vssh reads the wire coordinator URL from `/etc/wire/config.json` and queries `/peers` for the current node list — no config file needed.

```bash
# After wire is set up, vssh knows all nodes automatically
vssh status    # shows all mesh nodes immediately
```

For standalone use without wire, create `~/.vssh/config`:

```ini
web1=192.168.1.10
web2=192.168.1.11
web3=192.168.1.12
SECRET=your-shared-secret
```

---

## Tailscale Failover

vssh automatically detects your Tailscale peers and builds a fallback map (`~/.vssh/tailscale_map`). If a node's primary wire IP is unreachable, vssh transparently retries via Tailscale — no intervention needed.

The map is auto-generated on first run (by cross-referencing `tailscale status --json` with wire `/peers`) and refreshed every 24 hours.

Failover logic:
1. Try Wire VPN IP (2s fast path)
2. If timeout: wait 300ms, retry once (WireGuard handshake may be completing)
3. If still failing: switch to Tailscale IP, cache for 60s
4. After 60s: retry Wire — if recovered, clear failover cache

You can also configure the mapping manually in `~/.vssh/config`:
```ini
# TAILSCALE.<wire_ip> = <tailscale_ip>
TAILSCALE.10.99.1.10 = 100.64.0.1
TAILSCALE.10.99.1.11 = 100.64.0.2
```

---

## MCP Integration (AI Agents)

vssh ships with an MCP server, letting AI agents (Claude, etc.) execute commands and transfer files across your fleet.

```json
{
  "mcpServers": {
    "vssh": { "command": "vssh-mcp" }
  }
}
```

### MCP Tools Reference

#### `vssh_status` — Fleet Status

Check connection status to all configured servers.

```json
// Input: (none)

// Output:
{
  "summary": "3/3 online",
  "servers": {
    "web1": { "status": "online", "ip": "10.99.1.10", "latency_ms": 8.2 },
    "web2": { "status": "online", "ip": "10.99.1.11", "latency_ms": 9.1 },
    "db1":  { "status": "offline", "ip": "10.99.1.20" }
  }
}
```

---

#### `vssh_exec` — Execute Command

Run a shell command on a remote server.

```json
// Input:
{
  "server": "web1",
  "command": "systemctl restart nginx && systemctl is-active nginx"
}

// Output:
{
  "server": "web1",
  "command": "systemctl restart nginx && systemctl is-active nginx",
  "output": "active"
}
```

---

#### `vssh_put` — Upload File

Upload a local file to a remote server.

```json
// Input:
{
  "server": "web1",
  "local_path": "/tmp/app.tar.gz",
  "remote_path": "/opt/app.tar.gz"
}

// Output:
{
  "status": "success",
  "size": 52428800,
  "speed_mbps": 54.2,
  "elapsed": 0.97,
  "remote_path": "/opt/app.tar.gz"
}
```

---

#### `vssh_get` — Download File

Download a file from a remote server.

```json
// Input:
{
  "server": "web1",
  "remote_path": "/var/log/app.log",
  "local_path": "/tmp/app.log"
}

// Output:
{
  "status": "success",
  "size": 1048576,
  "speed_mbps": 48.3,
  "elapsed": 0.02,
  "local_path": "/tmp/app.log"
}
```

---

#### `vssh_sync` — Sync Directory

Sync a directory from one server to another via local relay (download + re-upload).

```json
// Input:
{
  "source": "web1",
  "dest": "web2",
  "path": "/etc/app"
}

// Output:
{
  "source": "web1",
  "dest": "web2",
  "path": "/etc/app",
  "total_files": 12,
  "synced": 12,
  "failed": 0,
  "errors": []
}
```

---

#### `vssh_speed_test` — Transfer Speed Test

Test upload and download speed to a server.

```json
// Input:
{
  "server": "web1",
  "size_mb": 10
}

// Output:
{
  "server": "web1",
  "size_mb": 10,
  "upload_mbps": 54.2,
  "download_mbps": 48.3,
  "latency_ms": 9.1
}
```

---

#### `vssh_p2p_status` — P2P Status

Check NAT hole-punch capability.

```json
// Output:
{
  "p2p_available": true,
  "external_ip": "203.0.113.1",
  "external_port": 48310,
  "nat_type": "cone"
}
```

---

#### `vssh_tunnel` — SSH Tunnel (Port Forwarding)

Create an SSH tunnel to access a remote service locally.

```json
// Input:
{
  "server": "db1",
  "local_port": 5433,
  "remote_port": 5432,
  "remote_host": "localhost"
}

// Output:
{
  "status": "running",
  "pid": 12345,
  "server": "db1",
  "local_port": 5433,
  "remote_host": "localhost",
  "remote_port": 5432,
  "access": "localhost:5433",
  "usage": "Connect to localhost:5432 on db1 via localhost:5433",
  "close_cmd": "kill 12345"
}
```

---

#### `vssh_keys` — Key Management

Show configured secrets and available servers.

```json
// Output:
{
  "secret_configured": true,
  "secret_preview": "a1b2c3d4...",
  "key_directory": "/Users/you/.vssh",
  "keys": [
    { "name": "secret", "size": 32, "permissions": "600" },
    { "name": "tailscale_map", "size": 412, "permissions": "644" }
  ],
  "servers_with_vssh": ["web1", "web2", "web3"]
}
```

---

### MCP Usage Examples

**Fleet health check:**
```
vssh_status → check all servers
vssh_exec(web1, "df -h / | tail -1") → check disk
vssh_exec(web1, "free -m | grep Mem") → check memory
```

**Deploy update across fleet:**
```
vssh_put(web1, "/tmp/app-v2.tar.gz", "/opt/app.tar.gz")
vssh_exec(web1, "cd /opt && tar xf app.tar.gz && systemctl restart app")
vssh_exec(web1, "systemctl is-active app")
```

**Access database locally via tunnel:**
```
vssh_tunnel(db1, local_port=5433, remote_port=5432)
→ Connect your DB client to localhost:5433
```

---

## Architecture

```
┌─────────────────────────────────────────────┐
│                 Your Fleet                  │
│                                             │
│  ┌──────┐    ┌──────┐    ┌──────┐           │
│  │ web1 │    │ web2 │    │ web3 │  ...      │
│  │vsshd │    │vsshd │    │vsshd │           │
│  └──┬───┘    └──┬───┘    └──┬───┘           │
│     └───────────┴───────────┘               │
│              WireGuard mesh                 │
│           (wire coordinator)                │
└──────────────────┬──────────────────────────┘
                   │
            ┌──────┴──────┐
            │  vssh client │  ← your machine
            │  (Mac/Linux) │
            └─────────────┘
```

vssh sits at **Layer 2 (Transport)** of the MeshPOP stack:

- **Layer 1** — [wire](https://github.com/meshpop/wire): WireGuard mesh VPN
- **Layer 2** — vssh: authenticated transport (this project)
- **Layer 3** — mpop: AI agent orchestration

Each layer is independently installable and usable on its own.

---

## Links

- Wire VPN: [github.com/meshpop/wire](https://github.com/meshpop/wire)
- Main project: [github.com/meshpop/mpop](https://github.com/meshpop/mpop)
- PyPI: [pypi.org/project/vssh](https://pypi.org/project/vssh/)

## License

MIT
