Skip to content

Commit 5d69d64

Browse files
committed
Feat: run the demo in the background on uncommon ports, with a preflight
Improve the local demo UX from three angles. install-demo.sh now starts the proxy in the background instead of foreground: nohup + redirected output + a pidfile, then it waits until the forward proxy is actually listening (or surfaces the log tail if it died) before returning to the prompt. It prints the viewer / agent commands and a stop command, and — because it now regains control — the agent command carries the absolute CA path, so the old "run from the same directory" caveat is gone. It also runs a best-effort preflight (lsof, then nc) on the demo ports before downloading, aborting with a clear message if one is taken (e.g. the demo is already running). Move the demo listeners to uncommon loopback ports to cut collision odds: the old 8081 / 9094 / 9093 overlap with common dev and observability tools. Forward proxy -> 127.0.0.1:47600, session API -> 127.0.0.1:47601, stats -> 127.0.0.1:47602. Pinning stats also de-wildcards it (it defaulted to :9093 on all interfaces). Health stays on :9091 (hardcoded, non-fatal on conflict). The port constants live in both demo.go and install-demo.sh; a comment in each notes the coupling. Rework the root README quickstart into three tight steps on the new ports: install + start (one backgrounded command), open the viewer (abctl), and send an agent's traffic through it (Claude Code). No CA prose — the env var just appears in the command — and no build-from-source fallback line. Test: shellcheck clean; go build/test/vet green (the demo test asserts the new loopback ports). Ran the branch binary --demo — binds 47600/47601/47602 on loopback, no transparent listener. Ran install-demo.sh end to end on darwin/arm64: preflight aborts on an occupied port; otherwise it backgrounds the proxy, writes the pidfile, detects readiness, prints the instructions, and returns to the shell with the proxy still running (killable via the pidfile). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Hai Huang <huang195@gmail.com>
1 parent 4e01283 commit 5d69d64

4 files changed

Lines changed: 105 additions & 41 deletions

File tree

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,30 @@ It ships as a single binary; the identity and access layer is **AuthBridge**, an
1212

1313
## Quick start (local, no Kubernetes)
1414

15-
See an AI agent's egressLLM, MCP, and A2A calls — decrypted and parsed live on your laptop. No cluster, no Keycloak, no SPIRE.
15+
Watch an AI agent's trafficits model, tool, and agent-to-agent calls — decrypted and parsed live on your laptop.
1616

17-
1. **Install and start it.** One line installs the `abctl` and `authbridge-proxy` binaries (macOS/Linux) and starts the local demo. On first run it mints a demo CA under `./cortex-ca` and logs the `NODE_EXTRA_CA_CERTS=…` line to trust it:
17+
1. **Install and start the demo** (macOS/Linux). Downloads two small binaries and starts the proxy in the background:
1818

1919
```sh
2020
curl -fsSL https://raw.githubusercontent.com/rossoctl/cortex/main/authbridge/install-demo.sh | sh
2121
```
2222

23-
_(Prefer to inspect first? Read [`install-demo.sh`](./authbridge/install-demo.sh) — or build from source: `cd authbridge/cmd/abctl && go build .` then `cd ../authbridge-proxy && go build . && ./authbridge-proxy --demo`.)_
24-
25-
2. **Open the session viewer** in another terminal:
23+
2. **Open the live viewer** in another terminal:
2624

2725
```sh
28-
abctl --endpoint http://localhost:9094
26+
abctl --endpoint http://localhost:47601
2927
```
3028

31-
3. **Run your agent through it** — e.g. Claude Code (from the same directory, so `./cortex-ca` resolves — or use the absolute path the proxy logged):
29+
3. **Send an agent's traffic through it** — e.g. Claude Code, from the directory where you started the demo:
3230

3331
```sh
34-
HTTPS_PROXY=http://localhost:8081 \
32+
HTTPS_PROXY=http://localhost:47600 \
3533
NODE_EXTRA_CA_CERTS="$PWD/cortex-ca/ca.crt" \
3634
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
3735
claude
3836
```
3937

40-
Its LLM, MCP, and A2A calls appear live in `abctl`, decrypted and parsed.
41-
42-
> Observe-only: the parsers *observe* traffic; nothing is enforced. The self-signed demo CA is for local use — in Kubernetes the CA is a mounted cert-manager Secret.
38+
Its calls stream into `abctl`, decrypted and parsed.
4339

4440
## Running on Kubernetes
4541

authbridge/cmd/authbridge-proxy/demo.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ const demoCADirDefault = "cortex-ca"
1616
// the LLM / MCP / A2A parsers, so an agent's egress is decrypted and parsed.
1717
// Kept in sync with the root README.
1818
//
19-
// The listeners the demo actually uses are pinned to loopback: this runs on a
20-
// laptop, so a wildcard bind would expose an open forward proxy and the
21-
// unauthenticated session API (which carries decrypted bodies and any injected
22-
// tokens) to the LAN. The preset only fills empty addresses, so these explicit
23-
// values win. The enforce-redirect transparent listener isn't used here (no
24-
// iptables) and main.go skips starting it under --demo.
19+
// Every listener the demo uses is pinned to loopback on an uncommon port. This
20+
// runs on a laptop, so (a) a wildcard bind would expose an open forward proxy,
21+
// the stats endpoint, and the unauthenticated session API (which carries
22+
// decrypted bodies and any injected tokens) to the LAN, and (b) the usual
23+
// 8081/909x ports collide with common dev tools. The preset only fills empty
24+
// addresses, so these explicit values win — keep them in sync with the ports
25+
// the installer probes and prints (authbridge/install-demo.sh). The
26+
// enforce-redirect transparent listener isn't used here (no iptables) and
27+
// main.go skips starting it under --demo.
2528
//
2629
// The YAML body is flush-left on purpose — a raw string literal preserves
2730
// leading whitespace, so indenting these lines in source would corrupt the YAML.
@@ -32,8 +35,10 @@ func demoConfigYAML(caDir string) string {
3235
mode: proxy-sidecar
3336
listener:
3437
roles: [forward]
35-
forward_proxy_addr: 127.0.0.1:8081
36-
session_api_addr: 127.0.0.1:9094
38+
forward_proxy_addr: 127.0.0.1:47600
39+
session_api_addr: 127.0.0.1:47601
40+
stats:
41+
address: 127.0.0.1:47602
3742
tls_bridge:
3843
mode: enabled
3944
ca_dir: "` + caDir + `"

authbridge/cmd/authbridge-proxy/demo_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,19 @@ func TestDemoConfig_WriteLoadsAndValidates(t *testing.T) {
4141
t.Errorf("expected forward-only roles, got %v", roles)
4242
}
4343

44-
// The listeners the demo uses must bind loopback, never a wildcard that
45-
// would expose an open forward proxy or the unauthenticated session API
46-
// (decrypted bodies + injected tokens) to the LAN. The transparent listener
47-
// isn't started under --demo (main.go gates it), so it's not asserted here.
48-
if got := cfg.Listener.ForwardProxyAddr; got != "127.0.0.1:8081" {
49-
t.Errorf("ForwardProxyAddr = %q, want loopback 127.0.0.1:8081", got)
44+
// The listeners the demo uses must bind loopback on the uncommon ports the
45+
// installer probes/prints, never a wildcard that would expose an open forward
46+
// proxy, the stats endpoint, or the unauthenticated session API (decrypted
47+
// bodies + injected tokens) to the LAN. The transparent listener isn't started
48+
// under --demo (main.go gates it), so it's not asserted here.
49+
if got := cfg.Listener.ForwardProxyAddr; got != "127.0.0.1:47600" {
50+
t.Errorf("ForwardProxyAddr = %q, want loopback 127.0.0.1:47600", got)
5051
}
51-
if got := cfg.Listener.SessionAPIAddr; got != "127.0.0.1:9094" {
52-
t.Errorf("SessionAPIAddr = %q, want loopback 127.0.0.1:9094", got)
52+
if got := cfg.Listener.SessionAPIAddr; got != "127.0.0.1:47601" {
53+
t.Errorf("SessionAPIAddr = %q, want loopback 127.0.0.1:47601", got)
54+
}
55+
if got := cfg.Stats.StatsAddress; got != "127.0.0.1:47602" {
56+
t.Errorf("Stats.StatsAddress = %q, want loopback 127.0.0.1:47602", got)
5357
}
5458

5559
if cfg.TLSBridge == nil {

authbridge/install-demo.sh

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
#
66
# Detects your OS/arch, downloads the prebuilt `abctl` and `authbridge-proxy`
77
# binaries for the newest release, verifies their SHA-256 checksums, installs
8-
# them to ~/.local/bin, and starts `authbridge-proxy --demo` (Ctrl-C to stop).
8+
# them to ~/.local/bin, and starts the demo in the background — then prints the
9+
# commands to watch traffic and point an agent at it, plus how to stop it.
910
# macOS + Linux, amd64 + arm64. No cluster, Keycloak, or SPIRE needed.
1011
#
1112
# Environment:
@@ -37,6 +38,25 @@ sha_check() {
3738
fi
3839
}
3940

41+
# Demo listener ports — loopback, and deliberately uncommon to avoid colliding
42+
# with common dev tools. Keep in sync with the demo config in
43+
# authbridge/cmd/authbridge-proxy/demo.go.
44+
DEMO_FORWARD_PORT=47600
45+
DEMO_SESSION_PORT=47601
46+
DEMO_STATS_PORT=47602
47+
48+
# port_in_use exits 0 if something is already listening on the given loopback
49+
# port. Best-effort: uses lsof, then nc; if neither exists, it assumes free.
50+
port_in_use() {
51+
if command -v lsof >/dev/null 2>&1; then
52+
lsof -nP -iTCP@127.0.0.1:"$1" -sTCP:LISTEN >/dev/null 2>&1
53+
elif command -v nc >/dev/null 2>&1; then
54+
nc -z 127.0.0.1 "$1" >/dev/null 2>&1
55+
else
56+
return 1
57+
fi
58+
}
59+
4060
# --- detect platform ---
4161
os=$(uname -s)
4262
case "$os" in
@@ -52,6 +72,15 @@ case "$arch" in
5272
*) die "unsupported architecture: $arch (supported: amd64, arm64)" ;;
5373
esac
5474

75+
# --- preflight: fail early (before downloading) if a demo port is taken ---
76+
if [ "${AUTHBRIDGE_INSTALL_ONLY:-}" != "1" ]; then
77+
for p in "$DEMO_FORWARD_PORT" "$DEMO_SESSION_PORT" "$DEMO_STATS_PORT"; do
78+
if port_in_use "$p"; then
79+
die "port ${p} is already in use. Is the demo already running (see ./cortex-ca/demo.pid)? Otherwise free the port, or change the ports in ./cortex-ca/demo.yaml, then re-run."
80+
fi
81+
done
82+
fi
83+
5584
# --- resolve the release tag ---
5685
# `releases/latest` excludes prereleases, and the project ships prereleases, so
5786
# list releases (newest first) and take the first tag_name instead.
@@ -104,8 +133,9 @@ fi
104133
rm -rf "$tmp"
105134
trap - EXIT
106135

107-
# --- report + next steps ---
136+
# --- report ---
108137
proxy="${BIN_DIR}/authbridge-proxy"
138+
ca_dir="$(pwd)/cortex-ca" # matches demoCADirDefault in demo.go
109139
case ":${PATH}:" in
110140
*":${BIN_DIR}:"*) abctl_cmd="abctl" proxy_cmd="authbridge-proxy" ;;
111141
*) abctl_cmd="${BIN_DIR}/abctl" proxy_cmd="$proxy" ;;
@@ -116,25 +146,54 @@ info "Installed abctl and authbridge-proxy (${version}) to ${BIN_DIR}"
116146
case ":${PATH}:" in
117147
*":${BIN_DIR}:"*) ;;
118148
*)
119-
warn "${BIN_DIR} is not on your PATH; the commands below use full paths."
149+
warn "${BIN_DIR} is not on your PATH."
120150
warn "Add it for future sessions: export PATH=\"${BIN_DIR}:\$PATH\""
121151
;;
122152
esac
123-
info ""
124-
info "In two more terminals (from this directory, so ./cortex-ca resolves):"
125-
info " View the live session: ${abctl_cmd} --endpoint http://localhost:9094"
126-
info ' Run your agent, e.g. Claude Code:'
127-
# $PWD is intentionally literal here — printed for the user's shell to expand
128-
# when they paste the command, not expanded by this script.
129-
# shellcheck disable=SC2016
130-
info ' HTTPS_PROXY=http://localhost:8081 NODE_EXTRA_CA_CERTS="$PWD/cortex-ca/ca.crt" CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude'
131-
info ""
132153

133154
if [ "${AUTHBRIDGE_INSTALL_ONLY:-}" = "1" ]; then
155+
info ""
134156
info "Install-only mode. Start the demo with: ${proxy_cmd} --demo"
135157
exit 0
136158
fi
137159

138-
info "Starting the demo proxy (Ctrl-C to stop)..."
160+
# --- start in the background, then wait until it's actually listening ---
161+
info ""
162+
info "Starting the demo in the background..."
163+
mkdir -p "$ca_dir"
164+
log="${ca_dir}/demo.log"
165+
pidfile="${ca_dir}/demo.pid"
166+
nohup "$proxy" --demo </dev/null >"$log" 2>&1 &
167+
demo_pid=$!
168+
echo "$demo_pid" >"$pidfile"
169+
170+
# Don't claim success blindly: wait for the forward proxy to bind, or surface the
171+
# failure (e.g. a lost port race) with the log tail instead of a silent exit.
172+
ready=0
173+
i=0
174+
while [ "$i" -lt 50 ]; do
175+
if ! kill -0 "$demo_pid" 2>/dev/null; then
176+
warn "the demo exited during startup — last log lines:"
177+
tail -n 15 "$log" >&2 || true
178+
die "demo failed to start (full log: ${log})"
179+
fi
180+
if grep -q "name=forward-proxy" "$log" 2>/dev/null; then
181+
ready=1
182+
break
183+
fi
184+
sleep 0.2
185+
i=$((i + 1))
186+
done
187+
[ "$ready" -eq 1 ] || warn "demo is still starting after ~10s; check ${log} (pid ${demo_pid})"
188+
189+
info ""
190+
info "Cortex demo is running (pid ${demo_pid}). Logs: ${log}"
191+
info ""
192+
info " Watch traffic: ${abctl_cmd} --endpoint http://localhost:${DEMO_SESSION_PORT}"
193+
info " Send traffic through it (e.g. Claude Code):"
194+
info " HTTPS_PROXY=http://localhost:${DEMO_FORWARD_PORT} \\"
195+
info " NODE_EXTRA_CA_CERTS=${ca_dir}/ca.crt \\"
196+
info " CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude"
197+
info ""
198+
info " Stop the demo: kill ${demo_pid} (or: kill \$(cat ${pidfile}))"
139199
info ""
140-
exec "$proxy" --demo

0 commit comments

Comments
 (0)