vssh 3.7.0__tar.gz → 3.7.2__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.7.0
3
+ Version: 3.7.2
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.7.0"
7
+ version = "3.7.2"
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.7.0
3
+ Version: 3.7.2
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
@@ -81,7 +81,17 @@ def _load_tailscale_map() -> dict:
81
81
  pass
82
82
 
83
83
  # 3. Auto-detect: cross-reference `tailscale status --json` + wire /peers
84
- if not ts_map:
84
+ # Re-run if: no map yet, OR tailscale_map file is older than 24 hours
85
+ _stale = False
86
+ if ts_map and ts_file.exists():
87
+ try:
88
+ _age = time.time() - ts_file.stat().st_mtime
89
+ if _age > 86400: # 24 hours
90
+ _stale = True
91
+ except OSError:
92
+ pass
93
+
94
+ if not ts_map or _stale:
85
95
  try:
86
96
  import urllib.request as _ur, subprocess as _sp2
87
97
  ts_out = _sp2.run(['tailscale', 'status', '--json'],
@@ -96,18 +106,32 @@ def _load_tailscale_map() -> dict:
96
106
  ips = [ip for ip in peer.get('TailscaleIPs', []) if ':' not in ip]
97
107
  if name and ips:
98
108
  ts_host[name] = ips[0]
99
- for srv_ip in ['10.99.85.143', '158.247.247.115']:
109
+ detected = {}
110
+ for srv_url in WIRE_SERVERS:
100
111
  try:
101
- with _ur.urlopen(f'http://{srv_ip}:8790/peers', timeout=3) as r:
112
+ with _ur.urlopen(f'{srv_url}/peers', timeout=3) as r:
102
113
  for p in json.loads(r.read()).get('peers', []):
103
114
  name = p.get('node_name', '').lower()
104
115
  wire_ip = p.get('vpn_ip', '')
105
116
  ts_ip = ts_host.get(name)
106
117
  if wire_ip and ts_ip:
107
- ts_map[wire_ip] = ts_ip
118
+ detected[wire_ip] = ts_ip
108
119
  break
109
120
  except Exception:
110
121
  continue
122
+ if detected:
123
+ ts_map.update(detected)
124
+ # Save auto-detected map to file for future runs
125
+ try:
126
+ VSSH_DIR_CONF.mkdir(parents=True, exist_ok=True)
127
+ lines = [f"{wire_ip} {ts_ip}\n"
128
+ for wire_ip, ts_ip in sorted(detected.items())]
129
+ ts_file.write_text(
130
+ "# Auto-generated by vssh — wire VPN IP → Tailscale IP\n"
131
+ + "".join(lines)
132
+ )
133
+ except OSError:
134
+ pass
111
135
  except Exception:
112
136
  pass
113
137
 
@@ -531,19 +555,23 @@ def check_transfer_safety(host: str) -> bool:
531
555
  # Dynamically read from /etc/wire/config.json (server_url field)
532
556
  # Falls back to hardcoded list only if config is missing
533
557
  def _load_wire_servers() -> list:
558
+ """Return list of wire coordinator base URLs (e.g. ['http://1.2.3.4:8790']).
559
+ Reads from /etc/wire/config.json or ~/.wire/config.json — no hardcoded IPs.
560
+ """
534
561
  import json as _json, re as _re
535
- try:
536
- with open("/etc/wire/config.json") as _f:
537
- _cfg = _json.load(_f)
538
- _url = _cfg.get("server_url", "")
539
- _m = _re.match(r'https?://([^:/]+)', _url)
540
- if _m:
541
- return [_m.group(1)]
542
- except (OSError, ValueError, KeyError):
543
- pass
562
+ for _cfg_path in ["/etc/wire/config.json",
563
+ os.path.expanduser("~/.wire/config.json")]:
564
+ try:
565
+ with open(_cfg_path) as _f:
566
+ _cfg = _json.load(_f)
567
+ _url = _cfg.get("server_url", "").rstrip("/")
568
+ if _re.match(r'https?://', _url):
569
+ return [_url]
570
+ except (OSError, ValueError, KeyError):
571
+ continue
544
572
  return []
545
573
 
546
- WIRE_SERVERS = _load_wire_servers()
574
+ WIRE_SERVERS = _load_wire_servers() # list of coordinator base URLs
547
575
  _name_cache = {} # name -> vpn_ip
548
576
  _name_cache_time = 0
549
577
 
@@ -568,7 +596,7 @@ def resolve_name(host: str) -> str:
568
596
  import urllib.request
569
597
  for srv_ip in WIRE_SERVERS:
570
598
  try:
571
- req = urllib.request.Request(f"http://{srv_ip}:8790/peers")
599
+ req = urllib.request.Request(f"{srv_ip}/peers")
572
600
  with urllib.request.urlopen(req, timeout=3) as resp:
573
601
  data = json.loads(resp.read())
574
602
  peers = data.get("peers", [])
@@ -3978,7 +4006,7 @@ Env: VSSH_SECRET
3978
4006
  import urllib.request as _ureq
3979
4007
  for _srv_ip in WIRE_SERVERS:
3980
4008
  try:
3981
- with _ureq.urlopen(f"http://{_srv_ip}:8790/peers", timeout=3) as _r:
4009
+ with _ureq.urlopen(f"{_srv_ip}/peers", timeout=3) as _r:
3982
4010
  _peers = json.loads(_r.read()).get("peers", [])
3983
4011
  for _p in _peers:
3984
4012
  _nn = _p.get("node_name", "")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes