vssh 3.3.4__tar.gz → 3.3.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vssh
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server
5
5
  Author-email: MeshPOP <mpop@mpop.dev>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vssh"
7
- version = "3.3.4"
7
+ version = "3.3.6"
8
8
  description = "Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vssh
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: Secure SSH/SCP tool with Tailscale failover, P2P transport, and MCP server
5
5
  Author-email: MeshPOP <mpop@mpop.dev>
6
6
  License: MIT
@@ -35,7 +35,14 @@ def _load_vssh_secret():
35
35
  s = sf.read_text().strip()
36
36
  if s: return s
37
37
  conf = _load_vssh_config()
38
- return conf.get('SECRET', '')
38
+ s = conf.get('SECRET', '')
39
+ if s: return s
40
+ # Auto-generate on first install
41
+ import secrets as _sec
42
+ s = _sec.token_hex(16)
43
+ VSSH_DIR_CONF.mkdir(parents=True, exist_ok=True)
44
+ sf.write_text(s)
45
+ return s
39
46
 
40
47
  VSSH_CONFIG = _load_vssh_config()
41
48
  SECRET = _load_vssh_secret()
@@ -2961,6 +2968,57 @@ def server():
2961
2968
  result = subprocess.run(cmdline, shell=True, capture_output=True, timeout=60)
2962
2969
  conn.sendall(result.stdout + result.stderr + b'__END__')
2963
2970
 
2971
+ elif cmd == 'PTY_SESSION':
2972
+ # PTY_SESSION:auth:rows:cols → full bash PTY relay
2973
+ try:
2974
+ rows = int(parts[2]) if len(parts) > 2 else 24
2975
+ cols = int(parts[3]) if len(parts) > 3 else 80
2976
+ except (IndexError, ValueError):
2977
+ rows, cols = 24, 80
2978
+ conn.sendall(b'PTY_OK\n')
2979
+ print(f'[PTY] {addr[0]} {rows}x{cols}')
2980
+ import pty as _pty, select as _sel, fcntl as _fcntl
2981
+ import termios as _ter, struct as _stru
2982
+ mfd, sfd = _pty.openpty()
2983
+ _fcntl.ioctl(sfd, _ter.TIOCSWINSZ,
2984
+ _stru.pack('HHHH', rows, cols, 0, 0))
2985
+ proc = subprocess.Popen(
2986
+ ['/bin/bash', '-l'],
2987
+ stdin=sfd, stdout=sfd, stderr=sfd,
2988
+ preexec_fn=os.setsid, close_fds=True
2989
+ )
2990
+ os.close(sfd)
2991
+ cfd = conn.fileno()
2992
+ try:
2993
+ while proc.poll() is None:
2994
+ r, _, _ = _sel.select([cfd, mfd], [], [], 0.05)
2995
+ if cfd in r:
2996
+ data = conn.recv(4096)
2997
+ if not data:
2998
+ break
2999
+ try:
3000
+ os.write(mfd, data)
3001
+ except OSError:
3002
+ break
3003
+ if mfd in r:
3004
+ try:
3005
+ data = os.read(mfd, 4096)
3006
+ if data:
3007
+ conn.sendall(data)
3008
+ except OSError:
3009
+ break
3010
+ finally:
3011
+ try:
3012
+ proc.terminate()
3013
+ proc.wait(timeout=2)
3014
+ except Exception:
3015
+ pass
3016
+ try:
3017
+ os.close(mfd)
3018
+ except Exception:
3019
+ pass
3020
+ print(f'[PTY] {addr[0]} session ended')
3021
+
2964
3022
  elif cmd == 'INFO':
2965
3023
  conn.sendall(b'OK\n')
2966
3024
 
@@ -3279,6 +3337,108 @@ def server():
3279
3337
  except KeyboardInterrupt:
3280
3338
  print('\nStopped')
3281
3339
 
3340
+
3341
+ def _cmd_install(args):
3342
+ """Install vssh as a system daemon (systemd on Linux, LaunchAgent on macOS)."""
3343
+ import sys as _sys, os as _os, platform as _plat, subprocess as _sp, stat as _stat
3344
+
3345
+ python_exe = _sys.executable
3346
+ vssh_py = _os.path.abspath(__file__)
3347
+ secret = _load_vssh_secret()
3348
+
3349
+ print(f"[vssh install]")
3350
+ print(f" python : {python_exe}")
3351
+ print(f" vssh.py : {vssh_py}")
3352
+ print(f" secret : {secret[:6]}...{secret[-4:]} ({'auto-generated' if not _os.environ.get('VSSH_SECRET') else 'from env'})")
3353
+
3354
+ if _plat.system() == 'Linux':
3355
+ service = f"""[Unit]
3356
+ Description=vssh daemon
3357
+ After=network.target
3358
+
3359
+ [Service]
3360
+ Type=simple
3361
+ ExecStart={python_exe} {vssh_py} server
3362
+ Environment="VSSH_SECRET={secret}"
3363
+ Restart=always
3364
+ RestartSec=5
3365
+ StandardOutput=journal
3366
+ StandardError=journal
3367
+
3368
+ [Install]
3369
+ WantedBy=multi-user.target
3370
+ """
3371
+ svc_path = '/etc/systemd/system/vssh.service'
3372
+ try:
3373
+ with open(svc_path, 'w') as f:
3374
+ f.write(service)
3375
+ print(f" Created : {svc_path}")
3376
+ _sp.run(['systemctl', 'daemon-reload'], check=True, capture_output=True)
3377
+ _sp.run(['systemctl', 'enable', 'vssh'], check=True, capture_output=True)
3378
+ _sp.run(['systemctl', 'restart', 'vssh'], check=True, capture_output=True)
3379
+ import time; time.sleep(1)
3380
+ r = _sp.run(['systemctl', 'is-active', 'vssh'], capture_output=True, text=True)
3381
+ status = r.stdout.strip()
3382
+ print(f" Service : {status}")
3383
+ if status == 'active':
3384
+ print("[vssh install] ✓ Done. vssh daemon running.")
3385
+ else:
3386
+ print("[vssh install] ✗ Service not active — check: journalctl -u vssh -n 20")
3387
+ except PermissionError:
3388
+ print("[vssh install] ✗ Need root. Run: sudo vssh install")
3389
+ except Exception as e:
3390
+ print(f"[vssh install] ✗ Error: {e}")
3391
+
3392
+ elif _plat.system() == 'Darwin':
3393
+ home = _os.path.expanduser('~')
3394
+ plist_dir = _os.path.join(home, 'Library', 'LaunchAgents')
3395
+ _os.makedirs(plist_dir, exist_ok=True)
3396
+ plist_path = _os.path.join(plist_dir, 'com.vssh.server.plist')
3397
+ plist = f"""<?xml version="1.0" encoding="UTF-8"?>
3398
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3399
+ <plist version="1.0">
3400
+ <dict>
3401
+ <key>Label</key>
3402
+ <string>com.vssh.server</string>
3403
+ <key>ProgramArguments</key>
3404
+ <array>
3405
+ <string>{python_exe}</string>
3406
+ <string>{vssh_py}</string>
3407
+ <string>server</string>
3408
+ </array>
3409
+ <key>EnvironmentVariables</key>
3410
+ <dict>
3411
+ <key>VSSH_SECRET</key>
3412
+ <string>{secret}</string>
3413
+ </dict>
3414
+ <key>RunAtLoad</key>
3415
+ <true/>
3416
+ <key>KeepAlive</key>
3417
+ <true/>
3418
+ <key>StandardOutPath</key>
3419
+ <string>/tmp/vssh.log</string>
3420
+ <key>StandardErrorPath</key>
3421
+ <string>/tmp/vssh.err</string>
3422
+ </dict>
3423
+ </plist>"""
3424
+ # Unload existing if any
3425
+ _sp.run(['launchctl', 'unload', plist_path], capture_output=True)
3426
+ with open(plist_path, 'w') as f:
3427
+ f.write(plist)
3428
+ print(f" Created : {plist_path}")
3429
+ r = _sp.run(['launchctl', 'load', plist_path], capture_output=True, text=True)
3430
+ if r.returncode == 0:
3431
+ import time; time.sleep(1)
3432
+ r2 = _sp.run(['pgrep', '-f', 'vssh.py server'], capture_output=True, text=True)
3433
+ if r2.stdout.strip():
3434
+ print("[vssh install] ✓ Done. vssh daemon running.")
3435
+ else:
3436
+ print("[vssh install] ✗ Process not found — check: cat /tmp/vssh.err")
3437
+ else:
3438
+ print(f"[vssh install] ✗ launchctl error: {r.stderr.strip()}")
3439
+ else:
3440
+ print(f"[vssh install] ✗ Unsupported OS: {_plat.system()}")
3441
+
3282
3442
  def main():
3283
3443
  args = sys.argv[1:]
3284
3444
  # Handle --version and --help flags
@@ -3594,6 +3754,8 @@ Env: VSSH_SECRET
3594
3754
  sys.exit(0 if result['fail'] == 0 else 1)
3595
3755
  elif cmd == 'server':
3596
3756
  server()
3757
+ elif cmd == 'install':
3758
+ _cmd_install(args)
3597
3759
  elif cmd == 'help' or cmd == '-h' or cmd == '--help':
3598
3760
  main_args_bak = sys.argv[1:]
3599
3761
  sys.argv = sys.argv[:1]
@@ -3615,7 +3777,7 @@ Env: VSSH_SECRET
3615
3777
  result = {'name': name, 'ip': ip, 'online': False, 'latency': 0}
3616
3778
  try:
3617
3779
  s = _sock.socket(_sock.AF_INET, _sock.SOCK_STREAM)
3618
- s.settimeout(3)
3780
+ s.settimeout(1)
3619
3781
  t0 = time.time()
3620
3782
  s.connect((ip, 48291))
3621
3783
  result['latency'] = (time.time() - t0) * 1000
@@ -3649,9 +3811,9 @@ Env: VSSH_SECRET
3649
3811
  return result
3650
3812
 
3651
3813
  if full_mode:
3652
- print("vssh v3.2.2 - Cluster Status (full)")
3814
+ print(f"vssh v{_get_vssh_version()} - Cluster Status (full)")
3653
3815
  else:
3654
- print("vssh v3.2.2 - Connection Status")
3816
+ print(f"vssh v{_get_vssh_version()} - Connection Status")
3655
3817
  print("=" * 70)
3656
3818
 
3657
3819
  online = 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes