vssh 3.7.1__tar.gz → 3.7.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vssh-3.7.3/PKG-INFO +1030 -0
- vssh-3.7.3/README.md +1007 -0
- {vssh-3.7.1 → vssh-3.7.3}/pyproject.toml +1 -1
- vssh-3.7.3/vssh.egg-info/PKG-INFO +1030 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh.py +45 -17
- {vssh-3.7.1 → vssh-3.7.3}/vssh_mcp_server.py +44 -24
- vssh-3.7.1/PKG-INFO +0 -73
- vssh-3.7.1/README.md +0 -50
- vssh-3.7.1/vssh.egg-info/PKG-INFO +0 -73
- {vssh-3.7.1 → vssh-3.7.3}/LICENSE +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/setup.cfg +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh.egg-info/SOURCES.txt +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh.egg-info/dependency_links.txt +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh.egg-info/entry_points.txt +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh.egg-info/top_level.txt +0 -0
- {vssh-3.7.1 → vssh-3.7.3}/vssh_p2p.py +0 -0
vssh-3.7.3/PKG-INFO
ADDED
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vssh
|
|
3
|
+
Version: 3.7.3
|
|
4
|
+
Summary: Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server
|
|
5
|
+
Author-email: MeshPOP <mpop@mpop.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/meshpop/vssh
|
|
8
|
+
Project-URL: Repository, https://github.com/meshpop/vssh
|
|
9
|
+
Keywords: ssh,scp,tailscale,p2p,vpn,mcp,remote
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# vssh
|
|
25
|
+
|
|
26
|
+
**SSH alternative and mesh transport layer — zero dependencies, faster connections, built for fleets.**
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install vssh
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why vssh?
|
|
35
|
+
|
|
36
|
+
Managing multiple servers with standard SSH gets painful fast.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# SSH — the old way
|
|
40
|
+
ssh -i ~/.ssh/id_ed25519 root@192.168.1.10 "systemctl restart nginx"
|
|
41
|
+
ssh -i ~/.ssh/id_ed25519 root@192.168.1.11 "systemctl restart nginx"
|
|
42
|
+
ssh -i ~/.ssh/id_ed25519 root@192.168.1.12 "systemctl restart nginx"
|
|
43
|
+
# ...repeat for every server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# vssh — the new way
|
|
48
|
+
vssh exec web1 "systemctl restart nginx"
|
|
49
|
+
vssh exec web2 "systemctl restart nginx"
|
|
50
|
+
vssh exec web3 "systemctl restart nginx"
|
|
51
|
+
|
|
52
|
+
# Or run across all nodes at once (via MCP / scripting)
|
|
53
|
+
for node in web1 web2 web3; do vssh exec $node "systemctl restart nginx" & done
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
No key flags. No IP addresses. No repeated handshakes. Just node names.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## What is vssh?
|
|
61
|
+
|
|
62
|
+
vssh is a **standalone SSH alternative and mesh transport layer** that:
|
|
63
|
+
|
|
64
|
+
- Works **out of the box** after `pip install vssh` — no config files, no agents, no dependencies
|
|
65
|
+
- Auto-discovers servers from your [wire](https://github.com/meshpop/wire) VPN mesh or a static config file
|
|
66
|
+
- Authenticates via **HMAC-SHA256** shared secret instead of per-node key management
|
|
67
|
+
- Falls back to **Tailscale** automatically when the primary path is unreachable
|
|
68
|
+
- Runs a persistent **daemon** on each node, so connections are instant — no SSH handshake overhead
|
|
69
|
+
|
|
70
|
+
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.
|
|
71
|
+
|
|
72
|
+
If you don't use wire, vssh works standalone with a simple `~/.vssh/config` file listing your servers.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install vssh
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Pure Python stdlib — no external packages required. Works on Linux and macOS.
|
|
83
|
+
|
|
84
|
+
Start the daemon on each server:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Set shared secret (same on all nodes)
|
|
88
|
+
export VSSH_SECRET=your-shared-secret
|
|
89
|
+
|
|
90
|
+
# Start daemon
|
|
91
|
+
vssh server
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Or as a systemd service:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
vssh install # writes and enables /etc/systemd/system/vssh.service
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Authentication
|
|
103
|
+
|
|
104
|
+
vssh uses **HMAC-SHA256** for authentication. Every connection includes a time-based token:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
hmac_{timestamp}_{sha256(secret, timestamp)[:32]}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- The same `VSSH_SECRET` is shared across all nodes
|
|
111
|
+
- Tokens are valid for ±60 seconds (configurable clock skew window)
|
|
112
|
+
- No per-node keys, no certificates, no key rotation
|
|
113
|
+
- Secret is read from (in priority order):
|
|
114
|
+
1. `VSSH_SECRET` environment variable
|
|
115
|
+
2. `~/.vssh/secret` file
|
|
116
|
+
3. `SECRET=` entry in `~/.vssh/config`
|
|
117
|
+
4. Auto-generated on first install (`secrets.token_hex(16)`)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Connection Protocol
|
|
122
|
+
|
|
123
|
+
All vssh communication happens on **TCP port 48291** using a simple line-delimited text protocol. Every command follows the format:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
COMMAND:auth_token:...args...\n
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The server responds with `OK` (or `OK:size`) followed by the payload and `__END__` as a terminator.
|
|
130
|
+
|
|
131
|
+
| Protocol | Format | Direction |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| SSH exec | `SSH:token:command\n` | client → server |
|
|
134
|
+
| File upload | `PUT:token:path:size:md5\n` + data | client → server |
|
|
135
|
+
| File download | `GET:token:path\n` | client → server |
|
|
136
|
+
| RPC call | `RPC:token:method:payload_len\n` + JSON | client → server |
|
|
137
|
+
| Node info | `INFO:token\n` | client → server |
|
|
138
|
+
| PTY session | `PTY_SESSION:token:rows:cols\n` | client → server |
|
|
139
|
+
| Persistent session | `SESSION:token\n` | client → server |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## CLI Commands
|
|
144
|
+
|
|
145
|
+
### Status and Monitoring
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
vssh status # Connection status to all mesh nodes
|
|
149
|
+
vssh status --full # Status + full node info (disk, memory, load)
|
|
150
|
+
vssh stats # Transfer statistics (last 7 days)
|
|
151
|
+
vssh stats 30 # Transfer statistics for last 30 days
|
|
152
|
+
vssh history # Last 20 commands
|
|
153
|
+
vssh history 50 # Last 50 commands
|
|
154
|
+
vssh history put # Filter by operation (PUT, GET, SSH, RPC, SYNC)
|
|
155
|
+
vssh history 20 web1 # Last 20 commands for specific host
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Example `vssh status` output:**
|
|
159
|
+
```
|
|
160
|
+
vssh v3.7.2 - Connection Status
|
|
161
|
+
======================================================================
|
|
162
|
+
c1 10.99.248.51 ● online (12ms)
|
|
163
|
+
c2 10.99.14.187 ● online (11ms)
|
|
164
|
+
v5 10.99.171.140 ● online (local)
|
|
165
|
+
|
|
166
|
+
Total: 3/3 online
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Example `vssh status --full` output:**
|
|
170
|
+
```
|
|
171
|
+
vssh v3.7.2 - Cluster Status (full)
|
|
172
|
+
======================================================================
|
|
173
|
+
web1 10.99.1.10 ● online (8ms)
|
|
174
|
+
disk: 45% used (120GB / 250GB) mem: 4.2GB / 16GB load: 0.42
|
|
175
|
+
web2 10.99.1.11 ● online (9ms)
|
|
176
|
+
disk: 31% used mem: 2.1GB / 8GB load: 0.15
|
|
177
|
+
db1 10.99.1.20 ✗ offline
|
|
178
|
+
|
|
179
|
+
Total: 2/3 online
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Remote Execution
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
vssh <host> # Interactive terminal (PTY — like SSH)
|
|
188
|
+
vssh <host> "command" # Single command
|
|
189
|
+
vssh ssh <host> "command" # Explicit ssh subcommand
|
|
190
|
+
vssh session <host> # Persistent terminal session (PTY mode)
|
|
191
|
+
vssh session-test <host> # Test session latency (3 RPC calls)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Examples:**
|
|
195
|
+
```bash
|
|
196
|
+
vssh web1 # Open interactive shell
|
|
197
|
+
vssh web1 "uptime" # Run single command
|
|
198
|
+
vssh web1 "df -h && free -h" # Chain commands
|
|
199
|
+
vssh session web1 # Full PTY session (like SSH)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### File Transfer
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
vssh put <local> <host>:<remote> # Upload
|
|
208
|
+
vssh put -z <local> <host>:<remote> # Force compression
|
|
209
|
+
vssh put --resume <local> <host>:<remote> # Resume interrupted upload
|
|
210
|
+
vssh put --retry=3 <local> <host>:<remote> # Auto-retry on failure
|
|
211
|
+
vssh get <host>:<remote> <local> # Download
|
|
212
|
+
vssh get --retry=3 <host>:<remote> <local> # Download with retry
|
|
213
|
+
vssh sync <local_dir> <host>:<remote_dir> # Directory sync (8 parallel streams)
|
|
214
|
+
vssh mput <host>:<base_path> <file1> <file2> ... # Multi-file single connection
|
|
215
|
+
vssh fast <local> <host>:<remote> # Auto-select best method
|
|
216
|
+
vssh p2p <local> <host>:<remote> # P2P direct (NAT hole-punch)
|
|
217
|
+
vssh rsync <local> <host>:<remote> # Delta sync (only changed blocks)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Transfer flags:**
|
|
221
|
+
| Flag | Description |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `-z` / `--compress` | Force zlib compression |
|
|
224
|
+
| `--no-compress` | Disable auto-compression |
|
|
225
|
+
| `--resume` | Resume interrupted transfer (for files >100MB) |
|
|
226
|
+
| `--retry=N` | Retry N times on network failure |
|
|
227
|
+
| `--limit=X` | Rate limit (e.g. `--limit=10MB`) |
|
|
228
|
+
| `--no-lan` | Disable LAN-direct optimization |
|
|
229
|
+
|
|
230
|
+
**Examples:**
|
|
231
|
+
```bash
|
|
232
|
+
vssh put ./app.tar.gz web1:/opt/app.tar.gz # Upload
|
|
233
|
+
vssh get web1:/var/log/app.log ./app.log # Download
|
|
234
|
+
vssh sync ./config/ web1:/etc/app/ # Sync directory
|
|
235
|
+
vssh mput web1:/backup/ file1.txt file2.txt file3.log # Multi-file
|
|
236
|
+
vssh rsync ./src/ web1:/opt/src/ # Delta sync (only changes)
|
|
237
|
+
vssh p2p ./dataset.tar.gz web2:/data/ # P2P bypass VPN relay
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
vssh auto-compresses text files (`.py`, `.js`, `.json`, `.log`, `.md`, etc.) and skips upload for files with matching MD5.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### Speed Test
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
vssh speed-test <host> # Transfer speed test (10MB default)
|
|
248
|
+
vssh speed-test <host> --size=50 # Custom size in MB
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
→ 54.2 MB/s (WireGuard mesh, direct)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### Pipes (stdin/stdout)
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# Pipe stdin to remote file
|
|
261
|
+
cat local.log | vssh pipe-up web1:/var/log/backup.log
|
|
262
|
+
|
|
263
|
+
# Pipe remote command to stdout
|
|
264
|
+
vssh pipe-down web1:"journalctl -u nginx -n 100" | grep ERROR
|
|
265
|
+
|
|
266
|
+
# Auto-detect: stdin present → upload, otherwise → download
|
|
267
|
+
cat data.csv | vssh pipe web1:/data/input.csv
|
|
268
|
+
vssh pipe web1:"cat /etc/os-release" > os-info.txt
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Node Info
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
vssh info <host> # Full JSON node info (OS, IPs, vsshd version, etc.)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"hostname": "web1",
|
|
282
|
+
"os": "Linux-5.15.0",
|
|
283
|
+
"vssh_version": "3.7.2",
|
|
284
|
+
"local_ips": ["10.99.1.10"],
|
|
285
|
+
"public_ip": "203.0.113.10",
|
|
286
|
+
"load": [0.42, 0.38, 0.31]
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### Daemon Management
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
vssh server # Start daemon (foreground)
|
|
296
|
+
vssh install # Install as systemd service (Linux) or launchd (macOS)
|
|
297
|
+
vssh up # Start via systemd
|
|
298
|
+
vssh down # Stop daemon
|
|
299
|
+
vssh restart # Restart daemon
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## RPC System
|
|
305
|
+
|
|
306
|
+
RPC is vssh's structured remote call interface. Unlike SSH exec (raw shell), RPC returns typed JSON results — no screen scraping needed.
|
|
307
|
+
|
|
308
|
+
### Protocol
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
Client → Server:
|
|
312
|
+
RPC:{token}:{method}:{payload_length}\n
|
|
313
|
+
{JSON payload (payload_length bytes)}
|
|
314
|
+
|
|
315
|
+
Server → Client:
|
|
316
|
+
OK:{response_length}\n
|
|
317
|
+
{JSON response (response_length bytes)}
|
|
318
|
+
__END__
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If the method takes no arguments, `payload_length` is `0` and no payload follows.
|
|
322
|
+
|
|
323
|
+
### CLI Usage
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
vssh rpc <host> <method> # Call with no arguments
|
|
327
|
+
vssh rpc <host> <method> '{"key":"value"}' # Call with JSON payload
|
|
328
|
+
vssh rpc-list <host> # List all available methods
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### RPC Methods Reference
|
|
332
|
+
|
|
333
|
+
#### `get_gpu` — GPU Status
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
vssh rpc g1 get_gpu
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Response:**
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"available": true,
|
|
343
|
+
"gpus": [
|
|
344
|
+
{
|
|
345
|
+
"name": "NVIDIA RTX 4090",
|
|
346
|
+
"memory_total": 24576,
|
|
347
|
+
"memory_used": 8192,
|
|
348
|
+
"memory_free": 16384,
|
|
349
|
+
"utilization": 43,
|
|
350
|
+
"temperature": 71
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
If no GPU: `{ "available": false, "error": "nvidia-smi failed" }`
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
#### `get_disk` — Disk Usage
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
vssh rpc web1 get_disk
|
|
364
|
+
vssh rpc web1 get_disk '{"path": "/data"}'
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Payload:** `{ "path": "/" }` (default: `/`)
|
|
368
|
+
|
|
369
|
+
**Response:**
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"path": "/",
|
|
373
|
+
"total": 107374182400,
|
|
374
|
+
"used": 48318382080,
|
|
375
|
+
"free": 59055800320,
|
|
376
|
+
"percent": 45
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
#### `get_memory` — Memory Usage
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
vssh rpc web1 get_memory
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Response:**
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"total": 17179869184,
|
|
392
|
+
"used": 4508876800,
|
|
393
|
+
"free": 12670992384,
|
|
394
|
+
"available": 12670992384
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
All values in bytes.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
#### `get_load` — System Load Average
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
vssh rpc web1 get_load
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Response:**
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"load1": 0.42,
|
|
412
|
+
"load5": 0.38,
|
|
413
|
+
"load15": 0.31,
|
|
414
|
+
"processes": "2/412"
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
#### `get_processes` — Top Processes
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
vssh rpc web1 get_processes
|
|
424
|
+
vssh rpc web1 get_processes '{"n": 5, "sort": "mem"}'
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Payload:**
|
|
428
|
+
| Field | Type | Default | Description |
|
|
429
|
+
|---|---|---|---|
|
|
430
|
+
| `n` | int | 10 | Number of processes to return |
|
|
431
|
+
| `sort` | string | `"cpu"` | Sort by `"cpu"` or `"mem"` |
|
|
432
|
+
|
|
433
|
+
**Response:**
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"processes": [
|
|
437
|
+
{
|
|
438
|
+
"user": "root",
|
|
439
|
+
"pid": 1234,
|
|
440
|
+
"cpu": 45.2,
|
|
441
|
+
"mem": 2.1,
|
|
442
|
+
"command": "/usr/bin/python3 train.py --epochs 100"
|
|
443
|
+
}
|
|
444
|
+
]
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
#### `get_logs` — Read Logs
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
vssh rpc web1 get_logs '{"service": "nginx", "lines": 20}'
|
|
454
|
+
vssh rpc web1 get_logs '{"service": "syslog"}'
|
|
455
|
+
vssh rpc web1 get_logs '{"service": "ollama", "lines": 50}'
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Payload:**
|
|
459
|
+
| Field | Type | Default | Description |
|
|
460
|
+
|---|---|---|---|
|
|
461
|
+
| `service` | string | `"syslog"` | Service name or log type |
|
|
462
|
+
| `lines` | int | 50 | Number of lines to return |
|
|
463
|
+
|
|
464
|
+
**Built-in log paths:**
|
|
465
|
+
| Service key | Log file |
|
|
466
|
+
|---|---|
|
|
467
|
+
| `syslog` | `/var/log/syslog` |
|
|
468
|
+
| `auth` | `/var/log/auth.log` |
|
|
469
|
+
| `nginx` | `/var/log/nginx/access.log` |
|
|
470
|
+
| `nginx_error` | `/var/log/nginx/error.log` |
|
|
471
|
+
| `docker` | `/var/log/docker.log` |
|
|
472
|
+
| *(any other)* | `journalctl -u {service} -n {lines}` |
|
|
473
|
+
|
|
474
|
+
**Response:**
|
|
475
|
+
```json
|
|
476
|
+
{
|
|
477
|
+
"service": "nginx",
|
|
478
|
+
"path": "/var/log/nginx/access.log",
|
|
479
|
+
"lines": ["192.168.1.1 - - [01/Mar/2025:12:00:00 +0000] \"GET /api/health\" 200", "..."]
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
#### `restart_service` — Restart a Service
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
vssh rpc web1 restart_service '{"service": "nginx"}'
|
|
489
|
+
vssh rpc web1 restart_service '{"service": "ollama"}'
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Payload:** `{ "service": "nginx" }`
|
|
493
|
+
|
|
494
|
+
**Allowed services:** `nginx`, `docker`, `ollama`, `chromadb`, `postgresql`, `redis`, `server-agent`
|
|
495
|
+
|
|
496
|
+
**Response:**
|
|
497
|
+
```json
|
|
498
|
+
{ "success": true, "service": "nginx" }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Or on failure:
|
|
502
|
+
```json
|
|
503
|
+
{ "success": false, "error": "Job for nginx.service failed" }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Permission:** `admin` (requires `VSSH_SECRET` match)
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
#### `service_status` — Check Service Status
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
vssh rpc web1 service_status '{"service": "nginx"}'
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Response:**
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"service": "nginx",
|
|
520
|
+
"status": "active",
|
|
521
|
+
"active": true
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
#### `list_services` — List Running Services
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
vssh rpc web1 list_services
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Response:**
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"services": [
|
|
537
|
+
{ "name": "nginx", "load": "loaded", "active": "active", "sub": "running" },
|
|
538
|
+
{ "name": "docker", "load": "loaded", "active": "active", "sub": "running" },
|
|
539
|
+
{ "name": "vssh", "load": "loaded", "active": "active", "sub": "running" }
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
#### `docker_containers` — List Docker Containers
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
vssh rpc web1 docker_containers
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**Response:**
|
|
553
|
+
```json
|
|
554
|
+
{
|
|
555
|
+
"containers": [
|
|
556
|
+
{ "name": "app", "status": "Up 2 hours", "image": "myapp:latest" },
|
|
557
|
+
{ "name": "redis", "status": "Up 5 days", "image": "redis:alpine" }
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
#### `file_read` — Read a File
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
vssh rpc web1 file_read '{"path": "/etc/nginx/nginx.conf"}'
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Payload:** `{ "path": "/path/to/file" }`
|
|
571
|
+
|
|
572
|
+
**Security:** Blocks access to `/etc/shadow`, `/etc/passwd`, `.ssh/`, `.gnupg/`, paths containing `secret`, `password`, or `credential`. Files over 1MB are rejected.
|
|
573
|
+
|
|
574
|
+
**Response:**
|
|
575
|
+
```json
|
|
576
|
+
{
|
|
577
|
+
"path": "/etc/nginx/nginx.conf",
|
|
578
|
+
"size": 1024,
|
|
579
|
+
"content": "user nginx;\nworker_processes auto;\n..."
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
#### `file_write` — Write a File
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
vssh rpc web1 file_write '{"path": "/opt/app/config.json", "content": "{\"debug\": false}"}'
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Payload:**
|
|
592
|
+
| Field | Type | Description |
|
|
593
|
+
|---|---|---|
|
|
594
|
+
| `path` | string | Destination path |
|
|
595
|
+
| `content` | string | File content (text) |
|
|
596
|
+
|
|
597
|
+
**Security:** Blocks writing to system paths: `/etc/`, `/usr/`, `/bin/`, `/sbin/`, `/var/log/`, `/root/`.
|
|
598
|
+
|
|
599
|
+
**Response:**
|
|
600
|
+
```json
|
|
601
|
+
{ "success": true, "path": "/opt/app/config.json", "size": 16 }
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Permission:** `write`
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
#### `get_network_info` — Network Info
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
vssh rpc web1 get_network_info
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Response:**
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"local_ips": ["10.99.1.10", "192.168.1.50"],
|
|
618
|
+
"public_ip": "203.0.113.10"
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Used internally for LAN-direct optimization.
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
#### `p2p_signal` — P2P Signaling
|
|
627
|
+
|
|
628
|
+
Used internally by `vssh p2p` to coordinate NAT hole-punch. Exchanges external IP/port info between peers via the VPN mesh relay.
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
vssh rpc relay p2p_signal '{"external_ip":"203.0.113.1","external_port":48300,"local_port":48300}'
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
### RPC via Session
|
|
637
|
+
|
|
638
|
+
You can run multiple RPC calls on a single persistent connection using `vssh session`:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
vssh session web1
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Inside the session:
|
|
645
|
+
```
|
|
646
|
+
web1> rpc get_disk
|
|
647
|
+
web1> rpc get_memory
|
|
648
|
+
web1> rpc get_load
|
|
649
|
+
web1> rpc restart_service {"service": "nginx"}
|
|
650
|
+
web1> exit
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Or programmatically via the Python API:
|
|
654
|
+
```python
|
|
655
|
+
from vssh import VsshSession
|
|
656
|
+
|
|
657
|
+
with VsshSession("10.99.1.10") as sess:
|
|
658
|
+
disk = sess.rpc("get_disk")
|
|
659
|
+
mem = sess.rpc("get_memory")
|
|
660
|
+
load = sess.rpc("get_load")
|
|
661
|
+
# All on same TCP connection — no reconnect overhead
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Before / After: Managing a Fleet
|
|
667
|
+
|
|
668
|
+
### Before (SSH)
|
|
669
|
+
|
|
670
|
+
```bash
|
|
671
|
+
# Deploy new config to 10 servers
|
|
672
|
+
for ip in 10.0.1.1 10.0.1.2 10.0.1.3 10.0.1.4 10.0.1.5 \
|
|
673
|
+
10.0.1.6 10.0.1.7 10.0.1.8 10.0.1.9 10.0.1.10; do
|
|
674
|
+
scp -i ~/.ssh/id_rsa ./nginx.conf root@$ip:/etc/nginx/nginx.conf
|
|
675
|
+
ssh -i ~/.ssh/id_rsa root@$ip "nginx -t && systemctl reload nginx"
|
|
676
|
+
done
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
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.
|
|
680
|
+
|
|
681
|
+
### After (vssh)
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
# Deploy new config to 10 servers
|
|
685
|
+
for node in web{1..10}; do
|
|
686
|
+
vssh put $node ./nginx.conf /etc/nginx/nginx.conf
|
|
687
|
+
vssh exec $node "nginx -t && systemctl reload nginx"
|
|
688
|
+
done
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
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.
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## Connection Speed
|
|
696
|
+
|
|
697
|
+
vssh is measurably faster than SSH for repeated commands because:
|
|
698
|
+
|
|
699
|
+
| | SSH | vssh |
|
|
700
|
+
|---|---|---|
|
|
701
|
+
| Connection | TCP handshake + key exchange each time | Persistent daemon, HMAC auth only |
|
|
702
|
+
| Auth | RSA/Ed25519 challenge-response | HMAC-SHA256 token (microseconds) |
|
|
703
|
+
| Transport | TCP | UDP (WireGuard tunnel) |
|
|
704
|
+
| Name resolution | `/etc/hosts` or DNS | Wire mesh coordinator (auto) |
|
|
705
|
+
| Failover | Manual | Auto-failover to Tailscale |
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Auto-Discovery
|
|
710
|
+
|
|
711
|
+
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.
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
# After wire is set up, vssh knows all nodes automatically
|
|
715
|
+
vssh status # shows all mesh nodes immediately
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
For standalone use without wire, create `~/.vssh/config`:
|
|
719
|
+
|
|
720
|
+
```ini
|
|
721
|
+
web1=192.168.1.10
|
|
722
|
+
web2=192.168.1.11
|
|
723
|
+
web3=192.168.1.12
|
|
724
|
+
SECRET=your-shared-secret
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## Tailscale Failover
|
|
730
|
+
|
|
731
|
+
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.
|
|
732
|
+
|
|
733
|
+
The map is auto-generated on first run (by cross-referencing `tailscale status --json` with wire `/peers`) and refreshed every 24 hours.
|
|
734
|
+
|
|
735
|
+
Failover logic:
|
|
736
|
+
1. Try Wire VPN IP (2s fast path)
|
|
737
|
+
2. If timeout: wait 300ms, retry once (WireGuard handshake may be completing)
|
|
738
|
+
3. If still failing: switch to Tailscale IP, cache for 60s
|
|
739
|
+
4. After 60s: retry Wire — if recovered, clear failover cache
|
|
740
|
+
|
|
741
|
+
You can also configure the mapping manually in `~/.vssh/config`:
|
|
742
|
+
```ini
|
|
743
|
+
# TAILSCALE.<wire_ip> = <tailscale_ip>
|
|
744
|
+
TAILSCALE.10.99.1.10 = 100.64.0.1
|
|
745
|
+
TAILSCALE.10.99.1.11 = 100.64.0.2
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## MCP Integration (AI Agents)
|
|
751
|
+
|
|
752
|
+
vssh ships with an MCP server, letting AI agents (Claude, etc.) execute commands and transfer files across your fleet.
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"mcpServers": {
|
|
757
|
+
"vssh": { "command": "vssh-mcp" }
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### MCP Tools Reference
|
|
763
|
+
|
|
764
|
+
#### `vssh_status` — Fleet Status
|
|
765
|
+
|
|
766
|
+
Check connection status to all configured servers.
|
|
767
|
+
|
|
768
|
+
```json
|
|
769
|
+
// Input: (none)
|
|
770
|
+
|
|
771
|
+
// Output:
|
|
772
|
+
{
|
|
773
|
+
"summary": "3/3 online",
|
|
774
|
+
"servers": {
|
|
775
|
+
"web1": { "status": "online", "ip": "10.99.1.10", "latency_ms": 8.2 },
|
|
776
|
+
"web2": { "status": "online", "ip": "10.99.1.11", "latency_ms": 9.1 },
|
|
777
|
+
"db1": { "status": "offline", "ip": "10.99.1.20" }
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
#### `vssh_exec` — Execute Command
|
|
785
|
+
|
|
786
|
+
Run a shell command on a remote server.
|
|
787
|
+
|
|
788
|
+
```json
|
|
789
|
+
// Input:
|
|
790
|
+
{
|
|
791
|
+
"server": "web1",
|
|
792
|
+
"command": "systemctl restart nginx && systemctl is-active nginx"
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Output:
|
|
796
|
+
{
|
|
797
|
+
"server": "web1",
|
|
798
|
+
"command": "systemctl restart nginx && systemctl is-active nginx",
|
|
799
|
+
"output": "active"
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
#### `vssh_put` — Upload File
|
|
806
|
+
|
|
807
|
+
Upload a local file to a remote server.
|
|
808
|
+
|
|
809
|
+
```json
|
|
810
|
+
// Input:
|
|
811
|
+
{
|
|
812
|
+
"server": "web1",
|
|
813
|
+
"local_path": "/tmp/app.tar.gz",
|
|
814
|
+
"remote_path": "/opt/app.tar.gz"
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Output:
|
|
818
|
+
{
|
|
819
|
+
"status": "success",
|
|
820
|
+
"size": 52428800,
|
|
821
|
+
"speed_mbps": 54.2,
|
|
822
|
+
"elapsed": 0.97,
|
|
823
|
+
"remote_path": "/opt/app.tar.gz"
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
#### `vssh_get` — Download File
|
|
830
|
+
|
|
831
|
+
Download a file from a remote server.
|
|
832
|
+
|
|
833
|
+
```json
|
|
834
|
+
// Input:
|
|
835
|
+
{
|
|
836
|
+
"server": "web1",
|
|
837
|
+
"remote_path": "/var/log/app.log",
|
|
838
|
+
"local_path": "/tmp/app.log"
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Output:
|
|
842
|
+
{
|
|
843
|
+
"status": "success",
|
|
844
|
+
"size": 1048576,
|
|
845
|
+
"speed_mbps": 48.3,
|
|
846
|
+
"elapsed": 0.02,
|
|
847
|
+
"local_path": "/tmp/app.log"
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
#### `vssh_sync` — Sync Directory
|
|
854
|
+
|
|
855
|
+
Sync a directory from one server to another via local relay (download + re-upload).
|
|
856
|
+
|
|
857
|
+
```json
|
|
858
|
+
// Input:
|
|
859
|
+
{
|
|
860
|
+
"source": "web1",
|
|
861
|
+
"dest": "web2",
|
|
862
|
+
"path": "/etc/app"
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Output:
|
|
866
|
+
{
|
|
867
|
+
"source": "web1",
|
|
868
|
+
"dest": "web2",
|
|
869
|
+
"path": "/etc/app",
|
|
870
|
+
"total_files": 12,
|
|
871
|
+
"synced": 12,
|
|
872
|
+
"failed": 0,
|
|
873
|
+
"errors": []
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
---
|
|
878
|
+
|
|
879
|
+
#### `vssh_speed_test` — Transfer Speed Test
|
|
880
|
+
|
|
881
|
+
Test upload and download speed to a server.
|
|
882
|
+
|
|
883
|
+
```json
|
|
884
|
+
// Input:
|
|
885
|
+
{
|
|
886
|
+
"server": "web1",
|
|
887
|
+
"size_mb": 10
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Output:
|
|
891
|
+
{
|
|
892
|
+
"server": "web1",
|
|
893
|
+
"size_mb": 10,
|
|
894
|
+
"upload_mbps": 54.2,
|
|
895
|
+
"download_mbps": 48.3,
|
|
896
|
+
"latency_ms": 9.1
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
#### `vssh_p2p_status` — P2P Status
|
|
903
|
+
|
|
904
|
+
Check NAT hole-punch capability.
|
|
905
|
+
|
|
906
|
+
```json
|
|
907
|
+
// Output:
|
|
908
|
+
{
|
|
909
|
+
"p2p_available": true,
|
|
910
|
+
"external_ip": "203.0.113.1",
|
|
911
|
+
"external_port": 48310,
|
|
912
|
+
"nat_type": "cone"
|
|
913
|
+
}
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
#### `vssh_tunnel` — SSH Tunnel (Port Forwarding)
|
|
919
|
+
|
|
920
|
+
Create an SSH tunnel to access a remote service locally.
|
|
921
|
+
|
|
922
|
+
```json
|
|
923
|
+
// Input:
|
|
924
|
+
{
|
|
925
|
+
"server": "db1",
|
|
926
|
+
"local_port": 5433,
|
|
927
|
+
"remote_port": 5432,
|
|
928
|
+
"remote_host": "localhost"
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Output:
|
|
932
|
+
{
|
|
933
|
+
"status": "running",
|
|
934
|
+
"pid": 12345,
|
|
935
|
+
"server": "db1",
|
|
936
|
+
"local_port": 5433,
|
|
937
|
+
"remote_host": "localhost",
|
|
938
|
+
"remote_port": 5432,
|
|
939
|
+
"access": "localhost:5433",
|
|
940
|
+
"usage": "Connect to localhost:5432 on db1 via localhost:5433",
|
|
941
|
+
"close_cmd": "kill 12345"
|
|
942
|
+
}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
947
|
+
#### `vssh_keys` — Key Management
|
|
948
|
+
|
|
949
|
+
Show configured secrets and available servers.
|
|
950
|
+
|
|
951
|
+
```json
|
|
952
|
+
// Output:
|
|
953
|
+
{
|
|
954
|
+
"secret_configured": true,
|
|
955
|
+
"secret_preview": "a1b2c3d4...",
|
|
956
|
+
"key_directory": "/Users/you/.vssh",
|
|
957
|
+
"keys": [
|
|
958
|
+
{ "name": "secret", "size": 32, "permissions": "600" },
|
|
959
|
+
{ "name": "tailscale_map", "size": 412, "permissions": "644" }
|
|
960
|
+
],
|
|
961
|
+
"servers_with_vssh": ["web1", "web2", "web3"]
|
|
962
|
+
}
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
---
|
|
966
|
+
|
|
967
|
+
### MCP Usage Examples
|
|
968
|
+
|
|
969
|
+
**Fleet health check:**
|
|
970
|
+
```
|
|
971
|
+
vssh_status → check all servers
|
|
972
|
+
vssh_exec(web1, "df -h / | tail -1") → check disk
|
|
973
|
+
vssh_exec(web1, "free -m | grep Mem") → check memory
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
**Deploy update across fleet:**
|
|
977
|
+
```
|
|
978
|
+
vssh_put(web1, "/tmp/app-v2.tar.gz", "/opt/app.tar.gz")
|
|
979
|
+
vssh_exec(web1, "cd /opt && tar xf app.tar.gz && systemctl restart app")
|
|
980
|
+
vssh_exec(web1, "systemctl is-active app")
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
**Access database locally via tunnel:**
|
|
984
|
+
```
|
|
985
|
+
vssh_tunnel(db1, local_port=5433, remote_port=5432)
|
|
986
|
+
→ Connect your DB client to localhost:5433
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
---
|
|
990
|
+
|
|
991
|
+
## Architecture
|
|
992
|
+
|
|
993
|
+
```
|
|
994
|
+
┌─────────────────────────────────────────────┐
|
|
995
|
+
│ Your Fleet │
|
|
996
|
+
│ │
|
|
997
|
+
│ ┌──────┐ ┌──────┐ ┌──────┐ │
|
|
998
|
+
│ │ web1 │ │ web2 │ │ web3 │ ... │
|
|
999
|
+
│ │vsshd │ │vsshd │ │vsshd │ │
|
|
1000
|
+
│ └──┬───┘ └──┬───┘ └──┬───┘ │
|
|
1001
|
+
│ └───────────┴───────────┘ │
|
|
1002
|
+
│ WireGuard mesh │
|
|
1003
|
+
│ (wire coordinator) │
|
|
1004
|
+
└──────────────────┬──────────────────────────┘
|
|
1005
|
+
│
|
|
1006
|
+
┌──────┴──────┐
|
|
1007
|
+
│ vssh client │ ← your machine
|
|
1008
|
+
│ (Mac/Linux) │
|
|
1009
|
+
└─────────────┘
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
vssh sits at **Layer 2 (Transport)** of the MeshPOP stack:
|
|
1013
|
+
|
|
1014
|
+
- **Layer 1** — [wire](https://github.com/meshpop/wire): WireGuard mesh VPN
|
|
1015
|
+
- **Layer 2** — vssh: authenticated transport (this project)
|
|
1016
|
+
- **Layer 3** — mpop: AI agent orchestration
|
|
1017
|
+
|
|
1018
|
+
Each layer is independently installable and usable on its own.
|
|
1019
|
+
|
|
1020
|
+
---
|
|
1021
|
+
|
|
1022
|
+
## Links
|
|
1023
|
+
|
|
1024
|
+
- Wire VPN: [github.com/meshpop/wire](https://github.com/meshpop/wire)
|
|
1025
|
+
- Main project: [github.com/meshpop/mpop](https://github.com/meshpop/mpop)
|
|
1026
|
+
- PyPI: [pypi.org/project/vssh](https://pypi.org/project/vssh/)
|
|
1027
|
+
|
|
1028
|
+
## License
|
|
1029
|
+
|
|
1030
|
+
MIT
|