vssh 3.3.7__tar.gz → 3.3.9__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.7
3
+ Version: 3.3.9
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.7"
7
+ version = "3.3.9"
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.7
3
+ Version: 3.3.9
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
@@ -12,6 +12,8 @@ from pathlib import Path
12
12
  from concurrent.futures import ThreadPoolExecutor, as_completed
13
13
  from typing import Tuple, Optional
14
14
 
15
+ __version__ = '3.3.9'
16
+
15
17
  # ========== Standalone Config (no mpop dependency) ==========
16
18
  VSSH_DIR_CONF = Path.home() / '.vssh'
17
19
 
@@ -3339,113 +3341,157 @@ def server():
3339
3341
 
3340
3342
 
3341
3343
  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
+ """Install/upgrade vssh as a system daemon. Idempotent safe to re-run."""
3345
+ import sys as _sys, os as _os, platform as _plat, subprocess as _sp, shutil as _sh
3344
3346
 
3345
3347
  python_exe = _sys.executable
3346
- vssh_py = _os.path.abspath(__file__)
3347
3348
  secret = _load_vssh_secret()
3349
+ sf = VSSH_DIR_CONF / 'secret'
3350
+ secret_src = 'env' if _os.environ.get('VSSH_SECRET') else ('file' if sf.exists() else 'auto-generated')
3348
3351
 
3349
3352
  print(f"[vssh install]")
3350
3353
  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'})")
3354
+ print(f" secret : {secret[:6]}...{secret[-4:]} ({secret_src})")
3353
3355
 
3356
+ # --- Step 1: Upgrade pip package ---
3357
+ print(f" pip : upgrading vssh...")
3358
+ pip_args = [python_exe, '-m', 'pip', 'install', 'vssh', '--upgrade', '-q']
3354
3359
  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
- """
3360
+ pip_args.append('--break-system-packages')
3361
+ r = _sp.run(pip_args, capture_output=True, text=True)
3362
+ if r.returncode == 0:
3363
+ print(f" pip : \u2713 ok")
3364
+ else:
3365
+ print(f" pip : \u2717 {r.stderr.strip()[:120]}")
3366
+
3367
+ # --- Step 2: Find installed vssh.py (after upgrade) ---
3368
+ import importlib, importlib.util as _ilu
3369
+ try:
3370
+ importlib.invalidate_caches()
3371
+ spec = _ilu.find_spec('vssh')
3372
+ installed_py = spec.origin if (spec and spec.origin) else _os.path.abspath(__file__)
3373
+ except Exception:
3374
+ installed_py = _os.path.abspath(__file__)
3375
+
3376
+ # --- Step 3: Copy vssh.py to /usr/local/bin/vssh.py ---
3377
+ target_py = '/usr/local/bin/vssh.py'
3378
+ try:
3379
+ _sh.copy2(installed_py, target_py)
3380
+ _os.chmod(target_py, 0o755)
3381
+ print(f" copied : {installed_py} \u2192 {target_py}")
3382
+ except PermissionError:
3383
+ print(f" warning : cannot write {target_py} (need root) — using {installed_py}")
3384
+ target_py = installed_py
3385
+
3386
+ # --- Step 4: Create /usr/local/bin/vssh wrapper with explicit python path ---
3387
+ wrapper = '/usr/local/bin/vssh'
3388
+ wrapper_txt = f'#!/bin/sh\nexec {python_exe} {target_py} "$@"\n'
3389
+ try:
3390
+ with open(wrapper, 'w') as _f:
3391
+ _f.write(wrapper_txt)
3392
+ _os.chmod(wrapper, 0o755)
3393
+ print(f" wrapper : {wrapper}")
3394
+ except PermissionError:
3395
+ print(f" warning : cannot write {wrapper} (need root)")
3396
+
3397
+ if _plat.system() == 'Linux':
3398
+ svc_lines = [
3399
+ "[Unit]", "Description=vssh daemon", "After=network.target", "",
3400
+ "[Service]", "Type=simple",
3401
+ f"ExecStart={python_exe} {target_py} server",
3402
+ f"Environment=\"VSSH_SECRET={secret}\"",
3403
+ "Restart=always", "RestartSec=5",
3404
+ "StandardOutput=journal", "StandardError=journal", "",
3405
+ "[Install]", "WantedBy=multi-user.target", "",
3406
+ ]
3407
+ service = '\n'.join(svc_lines)
3371
3408
  svc_path = '/etc/systemd/system/vssh.service'
3372
3409
  try:
3373
- with open(svc_path, 'w') as f:
3374
- f.write(service)
3375
- print(f" Created : {svc_path}")
3410
+ with open(svc_path, 'w') as _f:
3411
+ _f.write(service)
3412
+ print(f" service : {svc_path}")
3376
3413
  _sp.run(['systemctl', 'daemon-reload'], check=True, capture_output=True)
3377
3414
  _sp.run(['systemctl', 'enable', 'vssh'], check=True, capture_output=True)
3378
3415
  _sp.run(['systemctl', 'restart', 'vssh'], check=True, capture_output=True)
3379
3416
  import time; time.sleep(1)
3380
3417
  r = _sp.run(['systemctl', 'is-active', 'vssh'], capture_output=True, text=True)
3381
3418
  status = r.stdout.strip()
3382
- print(f" Service : {status}")
3419
+ print(f" status : {status}")
3383
3420
  if status == 'active':
3384
- print("[vssh install] Done. vssh daemon running.")
3421
+ print("[vssh install] \u2713 Done. vssh daemon running.")
3385
3422
  else:
3386
- print("[vssh install] Service not active — check: journalctl -u vssh -n 20")
3423
+ print("[vssh install] \u2717 Service not active — check: journalctl -u vssh -n 20")
3387
3424
  except PermissionError:
3388
- print("[vssh install] Need root. Run: sudo vssh install")
3425
+ print("[vssh install] \u2717 Need root. Run: sudo vssh install")
3389
3426
  except Exception as e:
3390
- print(f"[vssh install] Error: {e}")
3427
+ print(f"[vssh install] \u2717 Error: {e}")
3391
3428
 
3392
3429
  elif _plat.system() == 'Darwin':
3393
3430
  home = _os.path.expanduser('~')
3394
3431
  plist_dir = _os.path.join(home, 'Library', 'LaunchAgents')
3395
3432
  _os.makedirs(plist_dir, exist_ok=True)
3396
3433
  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
3434
+ plist_lines = [
3435
+ '<?xml version="1.0" encoding="UTF-8"?>',
3436
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
3437
+ '<plist version="1.0">',
3438
+ '<dict>',
3439
+ ' <key>Label</key>',
3440
+ ' <string>com.vssh.server</string>',
3441
+ ' <key>ProgramArguments</key>',
3442
+ ' <array>',
3443
+ f' <string>{python_exe}</string>',
3444
+ f' <string>{target_py}</string>',
3445
+ ' <string>server</string>',
3446
+ ' </array>',
3447
+ ' <key>EnvironmentVariables</key>',
3448
+ ' <dict>',
3449
+ ' <key>VSSH_SECRET</key>',
3450
+ f' <string>{secret}</string>',
3451
+ ' </dict>',
3452
+ ' <key>RunAtLoad</key>',
3453
+ ' <true/>',
3454
+ ' <key>KeepAlive</key>',
3455
+ ' <true/>',
3456
+ ' <key>StandardOutPath</key>',
3457
+ ' <string>/tmp/vssh.log</string>',
3458
+ ' <key>StandardErrorPath</key>',
3459
+ ' <string>/tmp/vssh.err</string>',
3460
+ '</dict>',
3461
+ '</plist>',
3462
+ ]
3463
+ plist = '\n'.join(plist_lines)
3464
+ # Also fix pip entry point if it exists (may use wrong Python)
3465
+ for ep in [_os.path.join(_os.path.dirname(python_exe), 'vssh'),
3466
+ '/opt/homebrew/bin/vssh', '/usr/local/bin/vssh']:
3467
+ if ep != wrapper and _os.path.exists(ep):
3468
+ try:
3469
+ _sh.copy2(wrapper, ep)
3470
+ _os.chmod(ep, 0o755)
3471
+ print(f" fixed : {ep}")
3472
+ except Exception:
3473
+ pass
3425
3474
  _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}")
3475
+ with open(plist_path, 'w') as _f:
3476
+ _f.write(plist)
3477
+ print(f" plist : {plist_path}")
3429
3478
  r = _sp.run(['launchctl', 'load', plist_path], capture_output=True, text=True)
3430
3479
  if r.returncode == 0:
3431
3480
  import time; time.sleep(1)
3432
- r2 = _sp.run(['pgrep', '-f', 'vssh.py server'], capture_output=True, text=True)
3481
+ r2 = _sp.run(['pgrep', '-f', f'{target_py} server'], capture_output=True, text=True)
3433
3482
  if r2.stdout.strip():
3434
- print("[vssh install] Done. vssh daemon running.")
3483
+ print("[vssh install] \u2713 Done. vssh daemon running.")
3435
3484
  else:
3436
- print("[vssh install] Process not found — check: cat /tmp/vssh.err")
3485
+ print("[vssh install] \u2717 Process not found — check: cat /tmp/vssh.err")
3437
3486
  else:
3438
- print(f"[vssh install] launchctl error: {r.stderr.strip()}")
3487
+ print(f"[vssh install] \u2717 launchctl error: {r.stderr.strip()}")
3439
3488
  else:
3440
- print(f"[vssh install] Unsupported OS: {_plat.system()}")
3489
+ print(f"[vssh install] \u2717 Unsupported OS: {_plat.system()}")
3441
3490
 
3442
3491
 
3443
3492
  def _get_vssh_version():
3444
- try:
3445
- from importlib.metadata import version as _v
3446
- return _v('vssh')
3447
- except Exception:
3448
- return '?'
3493
+ return __version__
3494
+
3449
3495
 
3450
3496
 
3451
3497
  def main():
@@ -3456,7 +3502,7 @@ def main():
3456
3502
  from importlib.metadata import version as _v
3457
3503
  print(f"vssh v{_v('vssh')}")
3458
3504
  except Exception:
3459
- print("vssh v3.3.2")
3505
+ print(f"vssh v{__version__}")
3460
3506
  return
3461
3507
  if not args:
3462
3508
  print('''vssh v3 - Distributed command & file transport daemon for server meshes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes