vssh 3.3.4__tar.gz → 3.3.7__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.7
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.7"
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.7
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,117 @@ 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
+
3442
+
3443
+ def _get_vssh_version():
3444
+ try:
3445
+ from importlib.metadata import version as _v
3446
+ return _v('vssh')
3447
+ except Exception:
3448
+ return '?'
3449
+
3450
+
3282
3451
  def main():
3283
3452
  args = sys.argv[1:]
3284
3453
  # Handle --version and --help flags
@@ -3594,6 +3763,8 @@ Env: VSSH_SECRET
3594
3763
  sys.exit(0 if result['fail'] == 0 else 1)
3595
3764
  elif cmd == 'server':
3596
3765
  server()
3766
+ elif cmd == 'install':
3767
+ _cmd_install(args)
3597
3768
  elif cmd == 'help' or cmd == '-h' or cmd == '--help':
3598
3769
  main_args_bak = sys.argv[1:]
3599
3770
  sys.argv = sys.argv[:1]
@@ -3615,7 +3786,7 @@ Env: VSSH_SECRET
3615
3786
  result = {'name': name, 'ip': ip, 'online': False, 'latency': 0}
3616
3787
  try:
3617
3788
  s = _sock.socket(_sock.AF_INET, _sock.SOCK_STREAM)
3618
- s.settimeout(3)
3789
+ s.settimeout(1)
3619
3790
  t0 = time.time()
3620
3791
  s.connect((ip, 48291))
3621
3792
  result['latency'] = (time.time() - t0) * 1000
@@ -3649,9 +3820,9 @@ Env: VSSH_SECRET
3649
3820
  return result
3650
3821
 
3651
3822
  if full_mode:
3652
- print("vssh v3.2.2 - Cluster Status (full)")
3823
+ print(f"vssh v{_get_vssh_version()} - Cluster Status (full)")
3653
3824
  else:
3654
- print("vssh v3.2.2 - Connection Status")
3825
+ print(f"vssh v{_get_vssh_version()} - Connection Status")
3655
3826
  print("=" * 70)
3656
3827
 
3657
3828
  online = 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes