feat(git-proxy): stop 只杀本 CLI 启的 fastgithub,并入 en0 主 LAN IP 供 Parallels 桥接
This commit is contained in:
@@ -112,6 +112,24 @@ def _restore_baseline(proxy: str, port: int | None) -> str:
|
|||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
|
|
||||||
|
def _primary_lan_ip() -> str | None:
|
||||||
|
"""Wi-Fi/Ethernet IPv4 (en0). Bridged Parallels guests use this, not *.*.*.2."""
|
||||||
|
if sys.platform != "darwin":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
out = subprocess.check_output(
|
||||||
|
["ipconfig", "getifaddr", "en0"],
|
||||||
|
text=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return None
|
||||||
|
ip = out.strip()
|
||||||
|
if not ip or ip.startswith("127."):
|
||||||
|
return None
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def _parallels_host_ips() -> list[str]:
|
def _parallels_host_ips() -> list[str]:
|
||||||
"""Mac IPs that Parallels guests use to reach this host (auto from ifconfig)."""
|
"""Mac IPs that Parallels guests use to reach this host (auto from ifconfig)."""
|
||||||
if sys.platform != "darwin":
|
if sys.platform != "darwin":
|
||||||
@@ -130,6 +148,9 @@ def _parallels_host_ips() -> list[str]:
|
|||||||
ip = match.group(1)
|
ip = match.group(1)
|
||||||
if ip not in ips:
|
if ip not in ips:
|
||||||
ips.append(ip)
|
ips.append(ip)
|
||||||
|
lan = _primary_lan_ip()
|
||||||
|
if lan and lan not in ips:
|
||||||
|
ips.append(lan)
|
||||||
return ips
|
return ips
|
||||||
|
|
||||||
|
|
||||||
@@ -483,6 +504,20 @@ def _collect_forward_pids(port: int) -> list[int]:
|
|||||||
return pids
|
return pids
|
||||||
|
|
||||||
|
|
||||||
|
def _should_kill_proxy_process(state: dict[str, Any] | None, name: str) -> bool:
|
||||||
|
"""Kill FastGitHub only if this CLI spawned it.
|
||||||
|
|
||||||
|
Native `fastgithub start` / CI `ensure-fastgithub.sh` / a leftover instance
|
||||||
|
must keep running when `mytoolkit git proxy stop` only tears down socat
|
||||||
|
and git config.
|
||||||
|
"""
|
||||||
|
if not state or state.get("name") != name:
|
||||||
|
return False
|
||||||
|
if state.get("adopted"):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _start_daemon(name: str, *, set_git: bool) -> None:
|
def _start_daemon(name: str, *, set_git: bool) -> None:
|
||||||
# Clear a proxy left pointing at a dead daemon by an earlier aborted run.
|
# Clear a proxy left pointing at a dead daemon by an earlier aborted run.
|
||||||
_restore_stale_git_proxy()
|
_restore_stale_git_proxy()
|
||||||
@@ -498,6 +533,7 @@ def _start_daemon(name: str, *, set_git: bool) -> None:
|
|||||||
old_https = _restore_baseline(old_https, port)
|
old_https = _restore_baseline(old_https, port)
|
||||||
proxy_pid: int | None = None
|
proxy_pid: int | None = None
|
||||||
forward_pids: list[int] = []
|
forward_pids: list[int] = []
|
||||||
|
adopted = False
|
||||||
|
|
||||||
if port is None:
|
if port is None:
|
||||||
click.secho(
|
click.secho(
|
||||||
@@ -526,10 +562,12 @@ def _start_daemon(name: str, *, set_git: bool) -> None:
|
|||||||
pids = _procs_matching(_pids_listening_on("127.0.0.1", port), name)
|
pids = _procs_matching(_pids_listening_on("127.0.0.1", port), name)
|
||||||
if pids:
|
if pids:
|
||||||
click.secho(
|
click.secho(
|
||||||
f"{name} already listening on 127.0.0.1:{port} (pid={pids[0]})",
|
f"{name} already listening on 127.0.0.1:{port} (pid={pids[0]}) "
|
||||||
|
"— adopting, stop will not kill it",
|
||||||
fg="cyan",
|
fg="cyan",
|
||||||
)
|
)
|
||||||
proxy_pid = pids[0]
|
proxy_pid = pids[0]
|
||||||
|
adopted = True
|
||||||
else:
|
else:
|
||||||
click.secho(
|
click.secho(
|
||||||
f"WARN: 127.0.0.1:{port} is held by an unknown process — "
|
f"WARN: 127.0.0.1:{port} is held by an unknown process — "
|
||||||
@@ -585,6 +623,7 @@ def _start_daemon(name: str, *, set_git: bool) -> None:
|
|||||||
"port": port,
|
"port": port,
|
||||||
"proxy_pid": proxy_pid,
|
"proxy_pid": proxy_pid,
|
||||||
"forward_pids": forward_pids,
|
"forward_pids": forward_pids,
|
||||||
|
"adopted": adopted,
|
||||||
"set_git": set_git,
|
"set_git": set_git,
|
||||||
"old_http": old_http if set_git else None,
|
"old_http": old_http if set_git else None,
|
||||||
"old_https": old_https if set_git else None,
|
"old_https": old_https if set_git else None,
|
||||||
@@ -623,6 +662,7 @@ def _stop_daemon(name: str | None) -> None:
|
|||||||
click.secho(f"{name} socat: nothing listening on guest IPs", fg="yellow")
|
click.secho(f"{name} socat: nothing listening on guest IPs", fg="yellow")
|
||||||
|
|
||||||
proxy_pids = _procs_matching(_pids_listening_on("127.0.0.1", port), name)
|
proxy_pids = _procs_matching(_pids_listening_on("127.0.0.1", port), name)
|
||||||
|
if _should_kill_proxy_process(state if state_matches else None, name):
|
||||||
if proxy_pids:
|
if proxy_pids:
|
||||||
_kill_pids(proxy_pids, name)
|
_kill_pids(proxy_pids, name)
|
||||||
else:
|
else:
|
||||||
@@ -631,6 +671,18 @@ def _stop_daemon(name: str | None) -> None:
|
|||||||
"(or port is held by an unrelated process — left untouched)",
|
"(or port is held by an unrelated process — left untouched)",
|
||||||
fg="yellow",
|
fg="yellow",
|
||||||
)
|
)
|
||||||
|
elif proxy_pids:
|
||||||
|
click.secho(
|
||||||
|
f"Leaving {name} pid={proxy_pids[0]} running "
|
||||||
|
"(started outside mytoolkit; stop only tears down socat/git)",
|
||||||
|
fg="cyan",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.secho(
|
||||||
|
f"{name} is not listening on 127.0.0.1:{port} "
|
||||||
|
"(or port is held by an unrelated process — left untouched)",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
click.secho(f"{name}: no local loopback daemon to stop", fg="yellow")
|
click.secho(f"{name}: no local loopback daemon to stop", fg="yellow")
|
||||||
|
|
||||||
@@ -930,6 +982,7 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None:
|
|||||||
(_pids_listening_on("127.0.0.1", port) or [None])[0]
|
(_pids_listening_on("127.0.0.1", port) or [None])[0]
|
||||||
),
|
),
|
||||||
"forward_pids": [p.pid for p in forward_procs if p.pid],
|
"forward_pids": [p.pid for p in forward_procs if p.pid],
|
||||||
|
"adopted": proxy_proc is None,
|
||||||
"set_git": True,
|
"set_git": True,
|
||||||
"old_http": old_http,
|
"old_http": old_http,
|
||||||
"old_https": old_https,
|
"old_https": old_https,
|
||||||
|
|||||||
@@ -85,3 +85,31 @@ def test_restore_stale_git_proxy_noop_when_alive(monkeypatch):
|
|||||||
)
|
)
|
||||||
assert _restore_stale_git_proxy() is False
|
assert _restore_stale_git_proxy() is False
|
||||||
assert restored == {}
|
assert restored == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parallels_host_ips_includes_lan(monkeypatch):
|
||||||
|
from mytoolkit.commands.git import _parallels_host_ips
|
||||||
|
|
||||||
|
monkeypatch.setattr("mytoolkit.commands.git.sys.platform", "darwin")
|
||||||
|
monkeypatch.delenv("XIAOHE_CI_PARALLELS_HOST_IP", raising=False)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"mytoolkit.commands.git.subprocess.check_output",
|
||||||
|
lambda *a, **k: "inet 10.211.55.2 netmask\n",
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("mytoolkit.commands.git._primary_lan_ip", lambda: "192.168.1.168")
|
||||||
|
assert _parallels_host_ips() == ["10.211.55.2", "192.168.1.168"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_kill_proxy_only_if_we_spawned():
|
||||||
|
from mytoolkit.commands.git import _should_kill_proxy_process
|
||||||
|
|
||||||
|
assert _should_kill_proxy_process(None, "fastgithub") is False
|
||||||
|
assert _should_kill_proxy_process({"name": "other"}, "fastgithub") is False
|
||||||
|
assert _should_kill_proxy_process(
|
||||||
|
{"name": "fastgithub", "adopted": True}, "fastgithub"
|
||||||
|
) is False
|
||||||
|
assert _should_kill_proxy_process(
|
||||||
|
{"name": "fastgithub", "adopted": False}, "fastgithub"
|
||||||
|
) is True
|
||||||
|
# Old state files without adopted: keep previous stop-kills-daemon behavior
|
||||||
|
assert _should_kill_proxy_process({"name": "fastgithub"}, "fastgithub") is True
|
||||||
|
|||||||
Reference in New Issue
Block a user