fix(native): make remote gxserver tunnel survive SSH multiplexing#41
Draft
gsxdsm wants to merge 2 commits into
Draft
fix(native): make remote gxserver tunnel survive SSH multiplexing#41gsxdsm wants to merge 2 commits into
gsxdsm wants to merge 2 commits into
Conversation
Remote connections failed at the tunnel stage on every attempt: `ssh -N -L` attached to a persistent ControlMaster opened by an earlier gxserver SSH call, handed off its forward, and exited 0 immediately — tearing down the local listener so the health probe could never connect. - Pin the tunnel to a dedicated connection (ControlMaster=no, ControlPath=none) so `-N` stays in the foreground holding its own -L forward. - Add ServerAliveInterval/ServerAliveCountMax/TCPKeepAlive so an established tunnel is not silently dropped by idle NAT/firewall timeouts. - Store the remote token and saved SSH password in the file/login keychain (kSecUseDataProtectionKeychain=false) so ad-hoc-signed dev builds without a keychain-access-groups entitlement stop failing with errSecMissingEntitlement; surface the real OSStatus in the keychainFailed toast. - Add tunnel diagnostics: richer tunnelFailed detail (ssh stderr / last health HTTP / probe error) and a remote-gxserver-tunnel-debug.log recording the ssh argv and per-attempt outcome. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…non-fatal Ad-hoc-signed dev builds get a new code signature each rebuild, so a token item written by a previous build stays ACL-bound to that build. SecItemDelete then cannot remove it and SecItemAdd reports errSecDuplicateItem, which aborted the whole connect with keychainFailed even though the connection was otherwise healthy. - On errSecDuplicateItem, SecItemUpdate the existing item's value in place. - The token is read fresh over SSH on every connect and is never read back from Keychain, so persisting it is best-effort: on any remaining Keychain error, log the OSStatus and continue to the tunnel instead of returning keychainFailed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Remote machine connections failed at the tunnel stage on every attempt. The debug trail showed
ssh -N -L …exiting status 0 with empty stderr within ~0.3s, repeatedly:A manual
ssh -N -L 50000:127.0.0.1:58744 eclipxe@host+curl …/api/health/serverreturned HTTP 200, proving the server, token, protocol, and forwarding were all fine.Root cause
SSH connection multiplexing. When
~/.ssh/configenablesControlMaster/ControlPath(ssh -O check→ "Master running"), an earlier gxserver SSH call (token read) opens a persistent master; the tunnelssh -N -Lthen attaches as a slave, hands its forward to the master, and exits 0 immediately — taking its local-Llistener down with it. The health probe then can't connect →tunnelFailed, deterministically.Fix
ControlMaster=no,ControlPath=none) so-Nstays foreground and owns its-Lforward. (the actual blocker)ServerAliveInterval=15,ServerAliveCountMax=3,TCPKeepAlive=yes) so an established tunnel isn't silently dropped by idle NAT/firewall timeouts.kSecUseDataProtectionKeychain=false). Ad-hoc-signed Debug builds have nokeychain-access-groupsentitlement, so the data-protection keychain rejectedSecItemAddwitherrSecMissingEntitlement. Also surface the realOSStatusin thekeychainFailedtoast.tunnelFaileddetail (ssh stderr / last health HTTP / probe error) and a~/.ghostex/logs/remote-gxserver-tunnel-debug.logrecording the ssh argv and per-attempt outcome.Testing
./build-ghostex-host.shsucceeds (Debug, arm64)./api/health/serverprobe returns 200 through the SSH-Lforward.Notes / follow-ups
keychain-access-groupsentitlement is still the proper path.RemoteGxserverClient.swift.🤖 Generated with Claude Code
Update — added commit: resilient/non-fatal token Keychain store. Ad-hoc dev builds get a new signature each rebuild, so a token item written by a prior build stays ACL-bound to it;
SecItemDeletecan't remove it andSecItemAddthen returnserrSecDuplicateItem, which aborted connect withkeychainFailed. Now: on duplicate,SecItemUpdatein place; and since the token is read fresh over SSH every connect and never read back from Keychain, any remaining store error is logged (OSStatus) and the connection proceeds instead of failing.