Revert "Merge pull request #239 from manaflow-ai/issue-151-ssh-remote-port-proxying"

This reverts commit 78e4bd32ba, reversing
changes made to cf75da8f8a.
This commit is contained in:
Lawrence Chen 2026-03-12 14:45:58 -07:00
parent 78e4bd32ba
commit f7cbbad434
60 changed files with 1250 additions and 17140 deletions

View file

@ -1,20 +0,0 @@
FROM alpine:3.20
RUN apk add --no-cache openssh python3 iproute2 net-tools ncurses
RUN adduser -D -s /bin/sh dev \
&& mkdir -p /home/dev/.ssh /run/sshd /srv/www \
&& chown -R dev:dev /home/dev/.ssh \
&& chmod 700 /home/dev/.ssh \
&& echo "cmux-ssh-forward-ok" > /srv/www/index.html
RUN ssh-keygen -A
COPY sshd_config /etc/ssh/sshd_config
COPY run.sh /usr/local/bin/run.sh
COPY ws_echo.py /usr/local/bin/ws_echo.py
RUN chmod +x /usr/local/bin/run.sh
EXPOSE 22
CMD ["/usr/local/bin/run.sh"]

View file

@ -1,38 +0,0 @@
#!/bin/sh
set -eu
if [ -z "${AUTHORIZED_KEY:-}" ]; then
echo "AUTHORIZED_KEY is required" >&2
exit 1
fi
REMOTE_HTTP_PORT="${REMOTE_HTTP_PORT:-43173}"
REMOTE_WS_PORT="${REMOTE_WS_PORT:-43174}"
mkdir -p /home/dev/.ssh /root/.ssh /run/sshd
printf '%s\n' "$AUTHORIZED_KEY" > /home/dev/.ssh/authorized_keys
printf '%s\n' "$AUTHORIZED_KEY" > /root/.ssh/authorized_keys
chown -R dev:dev /home/dev/.ssh
chmod 700 /home/dev/.ssh
chmod 600 /home/dev/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
python3 -m http.server "$REMOTE_HTTP_PORT" --bind 127.0.0.1 --directory /srv/www >/tmp/http.log 2>&1 &
HTTP_PID=$!
python3 /usr/local/bin/ws_echo.py --host 127.0.0.1 --port "$REMOTE_WS_PORT" >/tmp/ws.log 2>&1 &
WS_PID=$!
sleep 0.2
if ! kill -0 "$HTTP_PID" 2>/dev/null; then
echo "HTTP fixture failed to start (see /tmp/http.log)" >&2
cat /tmp/http.log >&2 || true
exit 1
fi
if ! kill -0 "$WS_PID" 2>/dev/null; then
echo "WebSocket fixture failed to start (see /tmp/ws.log)" >&2
cat /tmp/ws.log >&2 || true
exit 1
fi
exec /usr/sbin/sshd -D -e

View file

@ -1,31 +0,0 @@
Port 22
Protocol 2
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM no
AuthorizedKeysFile .ssh/authorized_keys
PermitEmptyPasswords no
AcceptEnv TERM_PROGRAM TERM_PROGRAM_VERSION COLORTERM
X11Forwarding no
AllowTcpForwarding yes
AllowStreamLocalForwarding yes
StreamLocalBindUnlink yes
GatewayPorts no
PermitTunnel no
ClientAliveInterval 30
ClientAliveCountMax 2
PrintMotd no
PidFile /run/sshd.pid
Subsystem sftp /usr/lib/ssh/sftp-server

View file

@ -1,132 +0,0 @@
#!/usr/bin/env python3
"""Tiny WebSocket echo server for SSH proxy integration tests."""
from __future__ import annotations
import argparse
import base64
import hashlib
import socket
import struct
import threading
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def _recv_exact(conn: socket.socket, n: int) -> bytes:
data = bytearray()
while len(data) < n:
chunk = conn.recv(n - len(data))
if not chunk:
raise ConnectionError("unexpected EOF")
data.extend(chunk)
return bytes(data)
def _recv_until(conn: socket.socket, marker: bytes, limit: int = 8192) -> bytes:
data = bytearray()
while marker not in data:
chunk = conn.recv(1024)
if not chunk:
raise ConnectionError("unexpected EOF while reading headers")
data.extend(chunk)
if len(data) > limit:
raise ValueError("header too large")
return bytes(data)
def _read_frame(conn: socket.socket) -> tuple[int, bytes]:
first, second = _recv_exact(conn, 2)
opcode = first & 0x0F
masked = (second & 0x80) != 0
length = second & 0x7F
if length == 126:
length = struct.unpack("!H", _recv_exact(conn, 2))[0]
elif length == 127:
length = struct.unpack("!Q", _recv_exact(conn, 8))[0]
mask_key = _recv_exact(conn, 4) if masked else b""
payload = _recv_exact(conn, length) if length else b""
if masked and payload:
payload = bytes(b ^ mask_key[i % 4] for i, b in enumerate(payload))
return opcode, payload
def _send_frame(conn: socket.socket, opcode: int, payload: bytes) -> None:
first = 0x80 | (opcode & 0x0F)
length = len(payload)
if length < 126:
header = bytes([first, length])
elif length <= 0xFFFF:
header = bytes([first, 126]) + struct.pack("!H", length)
else:
header = bytes([first, 127]) + struct.pack("!Q", length)
conn.sendall(header + payload)
def handle_client(conn: socket.socket) -> None:
try:
request = _recv_until(conn, b"\r\n\r\n")
headers_raw = request.decode("utf-8", errors="replace").split("\r\n")
header_map: dict[str, str] = {}
for line in headers_raw[1:]:
if not line or ":" not in line:
continue
k, v = line.split(":", 1)
header_map[k.strip().lower()] = v.strip()
key = header_map.get("sec-websocket-key", "")
upgrade = header_map.get("upgrade", "").lower()
connection_hdr = header_map.get("connection", "").lower()
if not key or upgrade != "websocket" or "upgrade" not in connection_hdr:
conn.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
return
accept = base64.b64encode(hashlib.sha1((key + GUID).encode("utf-8")).digest()).decode("ascii")
response = (
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
f"Sec-WebSocket-Accept: {accept}\r\n"
"\r\n"
)
conn.sendall(response.encode("utf-8"))
while True:
opcode, payload = _read_frame(conn)
if opcode == 0x8: # close
_send_frame(conn, 0x8, b"")
return
if opcode == 0x9: # ping
_send_frame(conn, 0xA, payload)
continue
if opcode == 0x1: # text
_send_frame(conn, 0x1, payload)
continue
# ignore all other opcodes
finally:
try:
conn.close()
except Exception:
pass
def main() -> int:
parser = argparse.ArgumentParser(description="WebSocket echo server")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=43174)
args = parser.parse_args()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((args.host, args.port))
server.listen(16)
while True:
conn, _ = server.accept()
thread = threading.Thread(target=handle_client, args=(conn,), daemon=True)
thread.start()
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -32,17 +32,13 @@ def resolve_cmux_cli() -> str:
raise RuntimeError("Unable to find cmux CLI binary. Set CMUX_CLI_BIN.")
def run(cli_path: str, *args: str, timeout: float = 5.0) -> tuple[int, str, str]:
try:
proc = subprocess.run(
[cli_path, *args],
text=True,
capture_output=True,
check=False,
timeout=timeout,
)
except subprocess.TimeoutExpired:
return 124, "", f"timed out after {timeout:.1f}s"
def run(cli_path: str, *args: str) -> tuple[int, str, str]:
proc = subprocess.run(
[cli_path, *args],
text=True,
capture_output=True,
check=False,
)
return proc.returncode, proc.stdout.strip(), proc.stderr.strip()

View file

@ -1,65 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
OUTPUT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-remote-assets-test.XXXXXX")"
trap 'rm -rf "$OUTPUT_DIR"' EXIT
"$ROOT_DIR/scripts/build_remote_daemon_release_assets.sh" \
--version "0.62.0-test" \
--release-tag "v0.62.0-test" \
--repo "manaflow-ai/cmux" \
--output-dir "$OUTPUT_DIR" >/dev/null
for asset in \
cmuxd-remote-darwin-arm64 \
cmuxd-remote-darwin-amd64 \
cmuxd-remote-linux-arm64 \
cmuxd-remote-linux-amd64 \
cmuxd-remote-checksums.txt \
cmuxd-remote-manifest.json
do
if [[ ! -f "$OUTPUT_DIR/$asset" ]]; then
echo "FAIL: missing asset $asset" >&2
exit 1
fi
done
python3 - <<'PY' "$OUTPUT_DIR/cmuxd-remote-manifest.json" "$OUTPUT_DIR/cmuxd-remote-checksums.txt"
import json
import sys
from pathlib import Path
manifest_path = Path(sys.argv[1])
checksums_path = Path(sys.argv[2])
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
expected_targets = {
("darwin", "arm64"),
("darwin", "amd64"),
("linux", "arm64"),
("linux", "amd64"),
}
actual_targets = {(entry["goOS"], entry["goArch"]) for entry in manifest["entries"]}
if actual_targets != expected_targets:
raise SystemExit(f"FAIL: manifest targets {sorted(actual_targets)} != {sorted(expected_targets)}")
if manifest["appVersion"] != "0.62.0-test":
raise SystemExit(f"FAIL: unexpected appVersion {manifest['appVersion']}")
if manifest["releaseTag"] != "v0.62.0-test":
raise SystemExit(f"FAIL: unexpected releaseTag {manifest['releaseTag']}")
if not manifest["checksumsURL"].endswith("/cmuxd-remote-checksums.txt"):
raise SystemExit(f"FAIL: unexpected checksumsURL {manifest['checksumsURL']}")
checksum_lines = [line for line in checksums_path.read_text(encoding="utf-8").splitlines() if line.strip()]
if len(checksum_lines) != 4:
raise SystemExit(f"FAIL: expected 4 checksum lines, got {len(checksum_lines)}")
for entry in manifest["entries"]:
if not entry["downloadURL"].endswith("/" + entry["assetName"]):
raise SystemExit(f"FAIL: downloadURL mismatch for {entry['assetName']}")
if len(entry["sha256"]) != 64:
raise SystemExit(f"FAIL: invalid sha256 for {entry['assetName']}")
print("PASS: remote daemon release assets include all targets and manifest entries")
PY

View file

@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Regression test: sidebar context menu shows Copy SSH Error only when an SSH error exists."""
from __future__ import annotations
import subprocess
from pathlib import Path
def get_repo_root() -> Path:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return Path(result.stdout.strip())
return Path.cwd()
def require(content: str, needle: str, message: str, failures: list[str]) -> None:
if needle not in content:
failures.append(message)
def main() -> int:
repo_root = get_repo_root()
content_view_path = repo_root / "Sources" / "ContentView.swift"
if not content_view_path.exists():
print(f"FAIL: missing expected file: {content_view_path}")
return 1
content = content_view_path.read_text(encoding="utf-8")
failures: list[str] = []
require(
content,
"private var copyableSidebarSSHError: String?",
"Missing sidebar SSH error extraction helper",
failures,
)
require(
content,
'tab.statusEntries["remote.error"]?.value',
"Missing remote.error status fallback for copyable SSH error text",
failures,
)
require(
content,
"if let copyableSidebarSSHError {",
"Copy SSH Error menu entry is no longer conditionally gated",
failures,
)
require(
content,
'Button("Copy SSH Error")',
"Missing Copy SSH Error context menu button",
failures,
)
require(
content,
"copyTextToPasteboard(copyableSidebarSSHError)",
"Copy SSH Error button no longer writes the resolved error text",
failures,
)
if failures:
print("FAIL: sidebar copy SSH error context-menu regression(s) detected")
for failure in failures:
print(f"- {failure}")
return 1
print("PASS: sidebar Copy SSH Error context menu wiring is intact")
return 0
if __name__ == "__main__":
raise SystemExit(main())