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