feat(git-proxy): stop 只杀本 CLI 启的 fastgithub,并入 en0 主 LAN IP 供 Parallels 桥接

This commit is contained in:
Zhengshou Lai
2026-08-22 13:41:27 +08:00
parent 1b109fa6c3
commit 34c4fc45f7
2 changed files with 84 additions and 3 deletions
+56 -3
View File
@@ -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,8 +662,21 @@ 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 proxy_pids: if _should_kill_proxy_process(state if state_matches else None, name):
_kill_pids(proxy_pids, name) if proxy_pids:
_kill_pids(proxy_pids, name)
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",
)
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: else:
click.secho( click.secho(
f"{name} is not listening on 127.0.0.1:{port} " f"{name} is not listening on 127.0.0.1:{port} "
@@ -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,
+28
View File
@@ -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