Fixing bridge to haskelseed

This commit is contained in:
2026-06-14 19:46:06 +02:00
parent d949f3e93e
commit 6eb0b1c52f
5 changed files with 178 additions and 35 deletions

7
.codex/config.toml Normal file
View File

@@ -0,0 +1,7 @@
[mcp_servers.ops-bridge]
command = "uv"
args = [
"run",
"python",
"src/bridge/mcp_server/server.py",
]

View File

@@ -1,13 +1,22 @@
.PHONY: test lint install mcp-http mcp-stop
.DEFAULT_GOAL := help
test:
.PHONY: help setup test lint install mcp-http mcp-stop
help: ## List available make targets
@awk 'BEGIN {FS = ":.*## "}; /^[a-zA-Z0-9_.-]+:.*## / {printf " %-16s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
setup: ## Sync dependencies and install the bridge CLI wrapper
uv sync --all-groups
uv tool install -e . --force
test: ## Run the test suite
uv run pytest
lint:
lint: ## Run ruff lint checks
uv run ruff check .
install:
uv tool install -e .
install: ## Install the bridge CLI wrapper
uv tool install -e . --force
mcp-http: ## Start MCP server in SSE mode (default port 8002)
BRIDGE_MCP_PORT=$${BRIDGE_MCP_PORT:-8002} uv run python src/bridge/mcp_server/server.py --http

View File

@@ -1,6 +1,7 @@
"""End-to-end tunnel diagnostics for OpsBridge."""
from __future__ import annotations
import socket
import subprocess
import time
from dataclasses import dataclass
@@ -13,6 +14,38 @@ from bridge.models import BridgeState, TunnelConfig
from bridge.state import StateManager, _pid_alive
def _remote_port_probe_command(remote_port: int) -> str:
"""Build a portable remote shell probe for a listening TCP port."""
return (
f"port={remote_port}; "
"if command -v ss >/dev/null 2>&1; then "
"ss -tnlp 2>/dev/null | grep -q \":$port \" && echo ok || echo closed; "
"elif command -v netstat >/dev/null 2>&1; then "
"netstat -tnlp 2>/dev/null | "
"grep -q \"[.:]$port[[:space:]]\" && echo ok || echo closed; "
"else "
"hex=$(printf '%04X' \"$port\"); "
"awk -v p=\":$hex\" "
"'NR > 1 && $4 == \"0A\" && index($2, p) { found = 1 } "
"END { print found ? \"ok\" : \"closed\" }' "
"/proc/net/tcp /proc/net/tcp6 2>/dev/null; "
"fi"
)
def _probe_local_port(local_port: int) -> str:
"""Check whether the local side of an SSH -L tunnel is accepting TCP."""
try:
with socket.create_connection(("127.0.0.1", local_port), timeout=5):
return "listening"
except ConnectionRefusedError:
return "closed"
except socket.timeout:
return "error:timeout"
except OSError as e:
return f"error:{e}"
@dataclass
class TunnelCheckResult:
tunnel: str
@@ -52,7 +85,10 @@ def check_tunnel(cfg: TunnelConfig, state_mgr: StateManager) -> TunnelCheckResul
and ssh_process != "ok"
)
# 3. SSH probe for remote port
# 3. Port probe: reverse tunnels listen remotely; local tunnels listen here.
if cfg.direction == "local":
remote_port = _probe_local_port(cfg.local_port)
else:
key_path = str(Path(cfg.ssh_key).expanduser())
cmd = [
"ssh",
@@ -61,7 +97,7 @@ def check_tunnel(cfg: TunnelConfig, state_mgr: StateManager) -> TunnelCheckResul
"-o", "ConnectTimeout=5",
"-o", "StrictHostKeyChecking=accept-new",
f"{cfg.ssh_user}@{cfg.host}",
f"ss -tnlp 2>/dev/null | grep -q ':{cfg.remote_port} ' && echo ok || echo closed",
_remote_port_probe_command(cfg.remote_port),
]
try:
proc = subprocess.run(

View File

@@ -6,7 +6,11 @@ from unittest.mock import MagicMock, patch
import pytest
from bridge.diagnostics import check_all_tunnels, check_tunnel
from bridge.diagnostics import (
_remote_port_probe_command,
check_all_tunnels,
check_tunnel,
)
from bridge.models import BridgeState, TunnelConfig
from bridge.state import StateManager
@@ -32,6 +36,14 @@ def state_mgr(tmp_path):
class TestCheckTunnel:
def test_remote_port_probe_has_minimal_host_fallback(self):
"""Remote probe supports minimal hosts without ss/netstat."""
command = _remote_port_probe_command(18000)
assert "command -v ss" in command
assert "command -v netstat" in command
assert "/proc/net/tcp" in command
assert "/proc/net/tcp6" in command
def test_no_pid(self, tcfg, state_mgr):
"""No PID file → ssh_process='no_pid', ok=False."""
with patch("bridge.diagnostics.subprocess.run") as mock_run:
@@ -83,6 +95,29 @@ class TestCheckTunnel:
assert result.remote_port == "closed"
assert result.ok is False
def test_local_direction_checks_local_port(self, tcfg, state_mgr):
"""Local tunnels verify the local listener instead of a remote -R port."""
local_cfg = TunnelConfig(
name="local-tunnel",
host="haskelseed.local",
remote_port=1234,
local_port=11234,
ssh_user="root",
ssh_key="~/.ssh/id_ops",
actor="adm-bernd",
direction="local",
)
state_mgr.write_pid("local-tunnel", 12345)
with (
patch("bridge.diagnostics._pid_alive", return_value=True),
patch("bridge.diagnostics._probe_local_port", return_value="listening"),
patch("bridge.diagnostics.subprocess.run") as mock_run,
):
result = check_tunnel(local_cfg, state_mgr)
mock_run.assert_not_called()
assert result.remote_port == "listening"
assert result.ok is True
def test_ssh_timeout(self, tcfg, state_mgr):
"""SSH probe timeout → remote_port='error:timeout'."""
state_mgr.write_pid("test-tunnel", 12345)

View File

@@ -0,0 +1,56 @@
---
id: ADHOC-2026-06-14
type: workplan
title: "Ad hoc ops-bridge fixes for 2026-06-14"
domain: custodian
repo: ops-bridge
status: finished
owner: codex
topic_slug: ops-bridge
created: "2026-06-14"
updated: "2026-06-14"
state_hub_workstream_id: "fbc2ef7e-626f-4c6a-bdf8-c69bf29097ce"
---
## Fix haskelseed bridge diagnostics
```task
id: ADHOC-2026-06-14-T01
status: done
priority: medium
state_hub_task_id: "ffe6b8d8-889c-4ec4-8b64-00b77f86e39f"
```
`haskelseed` is an Alpine host without `ss`, so `bridge check` reported
reverse tunnel ports as closed even while SSH reverse listeners were present.
Updated diagnostics to fall back from `ss` to `netstat` and then
`/proc/net/tcp`/`tcp6`. Also fixed local-direction diagnostics so
`nix-daemon-haskelseed` checks the local `-L` listener instead of probing a
remote reverse port.
Verification:
- `state-hub-haskelseed` responded through `127.0.0.1:18000/state/health`.
- `bridge check --json` reported all configured tunnels `ok: true`.
- `python3 -m pytest tests/test_cli.py tests/test_diagnostics.py` passed.
## Make default target safe and add setup
```task
id: ADHOC-2026-06-14-T02
status: done
priority: medium
state_hub_task_id: "3b932955-0d75-4b95-9821-92bfa2dadbd0"
```
Changed `make` to default to a help listing that only shows targets with
`##` comments. Added `make setup` to run `uv sync --all-groups` and reinstall
the editable `bridge` CLI wrapper through `uv tool install -e . --force`.
Verification:
- `uv sync --all-groups` succeeded and installed the project environment.
- `make` listed targets only and did not run tests or setup.
- `make setup` succeeded and installed the `bridge` executable.
- `make test` passed all 235 tests.
- `make lint` passed.