Skip to content

feat(scripts): one-shot local dev launcher (#70) - #71

Closed
obchain wants to merge 2 commits into
mainfrom
feat/70-dev-stack
Closed

obchain wants to merge 2 commits into
mainfrom
feat/70-dev-stack

Conversation

@obchain

@obchain obchain commented May 27, 2026

Copy link
Copy Markdown
Owner

Summary

scripts/dev-stack.sh — one-shot launcher for the full M1 stack plus the Next.js dev server. Sibling of e2e-smoke.sh but inverted: no assertions, no auto-teardown, just boot every service in dependency order, print a single ready URL, and block until Ctrl-C.

Closes #70.

What it does

docker compose up -d               →   clickhouse + redis (volumes preserved)
healthcheck poll                   →   /ping 200 + redis PING PONG
cargo build --release              →   ingestion + engine binaries
go build ./cmd/api                 →   api binary
spawn ingestion, engine, api       →   background, logs → /tmp/volx-dev-*/...
spawn pnpm dev (frontend)          →   background, logs → /tmp/volx-dev-*/frontend.log
poll /v1/health                    →   api ready
poll http://localhost:FRONTEND_PORT →   next dev ready
print READY block with URLs        →   landing + chart + api endpoints
wait on PIDs (poll loop)           →   blocks until any service dies or Ctrl-C
EXIT trap                          →   reap rust+go+node by name, compose down

Ready URLs

URL Purpose
http://localhost:${FRONTEND_PORT} landing page
http://localhost:${FRONTEND_PORT}/chart/bvol live chart (visual demo target)
http://localhost:${API_PORT}/v1/health API liveness
http://localhost:${API_PORT}/v1/index/bvol/latest latest BVOL JSON

Configuration

Env Default Notes
LOGS 0 1 tails all four service logs to stdout, each line prefixed [service]
SKIP_BUILD 0 1 skips cargo + go compile; fails fast if binaries missing
FRONTEND_PORT 3000 Override when 3000 is busy (Docker Desktop binds it on some setups)
API_PORT 8080
CLICKHOUSE_HTTP_PORT 8123

Design choices

  • Volumes preserved by default. Inverse of the smoke script — re-runs keep yesterday's ClickHouse history. No down -v in startup or teardown.
  • Frontend runs via pnpm dev, not pnpm build && start. Hot module reload, ~2 s cold start vs ~30 s for a production bundle. Matches the day-to-day inner loop.
  • PID polling instead of wait -n. wait -n is bash 4+; macOS ships bash 3.2 by default. A 5-second poll over the recorded child PIDs catches unexpected exits with no perceptible latency.
  • Process reap by name. cargo run parents fork-exec the compiled binary, and next dev spawns workers that don't inherit the SIGTERM from killing the pnpm wrapper. The teardown trap pkill -fs each binary name explicitly before falling back to recorded PIDs and a lsof port sweep.
  • Logs in a single mktemp dir. Always preserved on exit so a post-mortem doesn't require restarting the stack.

File map

File Role
scripts/dev-stack.sh 212 lines bash 3.2-compatible orchestrator

Dependencies

Tool Purpose
docker (compose v2) clickhouse + redis
cargo (Rust ≥ 1.85) ingestion + engine build
go (1.25) api build
pnpm (9.x) frontend dev server
curl health probes
lsof, pkill teardown port sweep + name-based reap

Out of scope

  • Hot-reload of rust/go services (would need cargo-watch / air; orthogonal)
  • Production-mode frontend (pnpm build && pnpm start) — dev mode chosen for HMR
  • Browser auto-open (varies macOS/linux, leave to the operator)
  • Wiring into CI — this script is interactive by design; the assertion path is e2e-smoke.sh and GitHub Actions: CI (lint + test, no deploy) #28 will wire that into Actions
  • Multi-venue boot (OKX, Bybit) — M2 territory; the script will grow ingestion-okx / ingestion-bybit spawn lines when those crates land

Test plan

  • bash -n scripts/dev-stack.sh clean on macOS bash 3.2
  • Live boot on the dev box: all five services reach READY, browser at /chart/bvol paints the live chart, Ctrl-C tears down with no orphans
  • Re-run immediately after teardown — second run hits the warm caches and reaches READY in <30 s
  • LOGS=1 streams every service log with [service] prefix
  • SKIP_BUILD=1 skips both build phases when binaries exist; fails fast when they do not

Live verification deferred per operator decision — will run before/at merge. Two un-checked test-plan items are smoke (clean boot + clean teardown); the third (LOGS=1) and fourth (SKIP_BUILD=1) are flag paths to exercise on second run.

obchain added 2 commits May 27, 2026 15:27
Sibling of e2e-smoke.sh. Boots the same M1 pipeline plus the Next.js dev
server, prints a single ready URL, and blocks on Ctrl-C. No assertions —
this is a developer ergonomics script for the visual demo + day-to-day
inner loop.

Volumes are preserved across runs (ClickHouse history persists). LOGS=1
multiplexes all four service logs to stdout with a [service] prefix.
SKIP_BUILD=1 skips cargo + go compile when binaries are already fresh.
PID polling is used in place of wait -n so the script runs on macOS
bash 3.2.

Teardown reaps rust + go + node processes by name and brings the compose
stack down (without -v) so the next run starts hot.
HIGH-1: Next.js worker grandchildren survived teardown. The recorded
PID is the pnpm wrapper; pkill -f "next dev" / "next-server" misses the
worker tree whose argv doesn't contain either string. Teardown now walks
two layers of children under the tracked FRONTEND_PID before falling
back to pkill + the port sweep.

HIGH-2: A background service crashing during the readiness wait was
silently swallowed (bash pipefail doesn't apply to backgrounded jobs).
Added check_services_alive between every wait_until so a dead ingestion
or engine fails the boot loud instead of greeting the operator with
READY plus a dark chart.

MED-1: Added pre-flight check_port_free for API_PORT + FRONTEND_PORT.
A stale process on the same port can return 200 to the health probe and
let our fresh binary die on EADDRINUSE unnoticed.

MED-2: Guarded teardown with _TEARING_DOWN so the trap can't fire twice
when SIGINT delivers and the in-trap `exit` triggers EXIT. Previously
re-entered, ran compose down twice, and clobbered the propagated rc.

MED-3: Teardown now prunes /tmp/volx-dev-* older than 2 days. The
launcher runs many times per day; /tmp would grow unbounded otherwise.

LOW findings either match the e2e-smoke sibling for consistency
(redundant CHILD_PIDS expansion, narrow lsof sweep) or do not apply
(volx-normalizer is library, not binary).
@obchain

obchain commented May 27, 2026

Copy link
Copy Markdown
Owner Author

Review r1 — addressed in fixup c195e05

Severity Finding Status
HIGH-1 Next.js worker grandchildren survive teardown (pkill -f misses workers whose argv lacks "next") Fixed — pgrep -P walks two layers under tracked FRONTEND_PID before pkill fallback
HIGH-2 Service crash during readiness wait silently swallowed (background exit doesn't trip pipefail) Fixed — check_services_alive between every wait_until
MED-1 No port pre-flight; stale process on same port can serve the health probe Fixed — check_port_free for API_PORT + FRONTEND_PORT after compose-up
MED-2 EXIT trap re-fires when SIGINT handler calls exit → double compose down, clobbered rc Fixed — _TEARING_DOWN latch
MED-3 /tmp/volx-dev-* log dirs accumulate unbounded Fixed — teardown prunes -mtime +2
LOW-1 lsof sweep narrow Skipped — matches sibling e2e-smoke.sh
LOW-2 Redundant CHILD_PIDS[@]:- fallback Skipped — same as smoke, harmless
LOW-3 Missing volx-normalizer pkill N/A — normalizer is library inside ingestion, not a separate binary

Live verification still pending per operator decision.

@obchain obchain closed this May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scripts/dev-stack.sh — one-shot local dev launcher (full stack + frontend, keep-alive)

1 participant