From 755157ab6c009cbe69e794ec9d6e12cb2193bfaf Mon Sep 17 00:00:00 2001 From: Zhengshou Lai Date: Tue, 11 Aug 2026 10:32:47 +0800 Subject: [PATCH] fix(git): start FastGitHub before Parallels socat forwards. Binding socat first occupied :38457 and made FastGitHub fail to listen. --- mytoolkit/commands/git.py | 111 +++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/mytoolkit/commands/git.py b/mytoolkit/commands/git.py index ea91560..bd01534 100644 --- a/mytoolkit/commands/git.py +++ b/mytoolkit/commands/git.py @@ -256,8 +256,9 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None: If no COMMAND is given, tries to run a program with the same name as the proxy. If that program is not found, sets the proxy and waits for Ctrl-C. - For loopback proxies (e.g. fastgithub on 127.0.0.1), also opens socat - forwards on auto-detected Parallels host IPs so Windows/Linux VMs can connect. + For loopback proxies (e.g. fastgithub on 127.0.0.1), start the proxy + process first, then socat-forward auto-detected Parallels host IPs so + Windows/Linux VMs can connect. Socat must not bind before the proxy. """ proxies = _get_proxies() if name not in proxies: @@ -265,7 +266,6 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None: click.echo(f"Available: {', '.join(proxies.keys())}", err=True) sys.exit(1) - # Save current state old_http = _get_git_proxy("http.proxy") old_https = _get_git_proxy("https.proxy") @@ -280,20 +280,21 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None: forward_procs: list[subprocess.Popen] = [] port = _proxy_loopback_port(proxy_url) - if port is not None: - forward_procs = _start_parallels_forwards(port) + proxy_proc: subprocess.Popen | None = None - # Determine what to run - already_up = port is not None and _port_listening_on("127.0.0.1", port) cmd_to_run = list(command) if command else None - if cmd_to_run is None: - if already_up: - click.secho(f"{name} already listening on :{port}", fg="cyan") - elif shutil.which(name): - cmd_to_run = [name] + if cmd_to_run is None and shutil.which(name): + cmd_to_run = [name] - def cleanup(): + def cleanup() -> None: _stop_procs(forward_procs) + if proxy_proc is not None and proxy_proc.poll() is None: + proxy_proc.send_signal(signal.SIGINT) + try: + proxy_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proxy_proc.terminate() + proxy_proc.wait() if old_http == "(not set)": _unset_git_proxy("http.proxy") else: @@ -312,27 +313,65 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None: ) click.secho("Git proxy restored", fg="green") - if cmd_to_run: - click.secho(f"Running: {' '.join(cmd_to_run)}", fg="cyan") - click.secho("Press Ctrl-C to stop and auto-unset proxy\n", fg="cyan") - proc = None - try: - proc = subprocess.Popen(cmd_to_run) - proc.wait() - except KeyboardInterrupt: - if proc is not None: - proc.send_signal(signal.SIGINT) - try: - proc.wait(timeout=5) - except subprocess.TimeoutExpired: - proc.terminate() - proc.wait() - finally: - cleanup() - if proc is not None: - sys.exit(proc.returncode) - sys.exit(0) - else: + try: + # Loopback proxy (fastgithub): ensure 127.0.0.1:port is up BEFORE socat. + if port is not None: + if _port_listening_on("127.0.0.1", port): + click.secho( + f"{name} already listening on 127.0.0.1:{port}", fg="cyan" + ) + elif cmd_to_run: + click.secho(f"Running: {' '.join(cmd_to_run)}", fg="cyan") + proxy_proc = subprocess.Popen(cmd_to_run) + ready = False + for _ in range(60): + if _port_listening_on("127.0.0.1", port): + ready = True + break + if proxy_proc.poll() is not None: + break + time.sleep(0.25) + if not ready: + click.secho( + f"ERROR: {name} failed to listen on 127.0.0.1:{port}", + fg="red", + err=True, + ) + sys.exit(1) + else: + click.secho( + f"ERROR: nothing on 127.0.0.1:{port} and `{name}` not found", + fg="red", + err=True, + ) + sys.exit(1) + + forward_procs = _start_parallels_forwards(port) + click.secho( + "Press Ctrl-C to stop and auto-unset proxy\n", fg="cyan" + ) + try: + if proxy_proc is not None: + proxy_proc.wait() + else: + signal.pause() + except KeyboardInterrupt: + pass + sys.exit(proxy_proc.returncode if proxy_proc is not None else 0) + + # Non-loopback proxy: original behavior. + if cmd_to_run: + click.secho(f"Running: {' '.join(cmd_to_run)}", fg="cyan") + click.secho( + "Press Ctrl-C to stop and auto-unset proxy\n", fg="cyan" + ) + try: + proxy_proc = subprocess.Popen(cmd_to_run) + proxy_proc.wait() + except KeyboardInterrupt: + pass + sys.exit(proxy_proc.returncode if proxy_proc is not None else 0) + click.secho( "Proxy active. Press Ctrl-C to stop and auto-unset proxy", fg="cyan" ) @@ -340,5 +379,5 @@ def proxy_run(name: str, command: tuple[str, ...]) -> None: signal.pause() except KeyboardInterrupt: pass - finally: - cleanup() + finally: + cleanup()