mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Fix dev env port resolution and improve multi-worktree support (#2025)
## Summary
- Fix dev environment port isolation so OTel collector, MongoDB, and ClickHouse resolve to the correct worktree-specific ports
- Add slot-specific session cookies so multiple worktrees on localhost don't interfere with each other's login sessions
- Share NX build cache across worktrees for faster builds
- Add worktrunk project config for automated worktree lifecycle management
## Changes
### Port resolution fix
**Root `.env`**: Replace self-referential `HDX_DEV_*` vars (e.g. `HDX_DEV_OTEL_HTTP_PORT=${HDX_DEV_OTEL_HTTP_PORT:-4318}`) with plain default values. The `${VAR:-default}` syntax caused `dotenv-expand` infinite recursion, preventing `dev-env.sh` port overrides from taking effect.
**`packages/api/.env.development`**: Remove redundant self-referential port declarations and `:-default` fallbacks from `${HDX_DEV_*}` references. Ports now come exclusively from `dev-env.sh` exports or root `.env` defaults.
**`packages/app/.env.development`**: Set `HYPERDX_API_KEY` to `super-secure-ingestion-api-key` (matching the API/collector) instead of a placeholder. Remove self-referential port declarations.
**`packages/app/src/config.ts`**: Add `process.env.OTEL_EXPORTER_OTLP_ENDPOINT` as fallback for `HDX_COLLECTOR_URL` so the browser OTel SDK picks up the correct collector endpoint.
### Session cookie isolation
**`packages/api/src/api-app.ts`**: Use `connect.sid.<slot>` as the session cookie name in dev mode so multiple worktrees on `localhost` maintain independent sessions. Guarded behind `config.IS_DEV && process.env.HDX_DEV_SLOT` — production uses the default `connect.sid`.
### Shared NX build cache
**`scripts/dev-env.sh`**: Set `NX_CACHE_DIRECTORY=~/.config/hyperdx/nx-cache` so all worktrees share a single content-hash-based build cache. Unchanged packages get cache hits regardless of worktree; changed packages rebuild correctly.
### Worktrunk project config
**`.config/wt.toml`**: New project config for [worktrunk](https://worktrunk.dev) (`wt`) worktree lifecycle hooks:
- `pre-start`: Symlink `node_modules/` from primary worktree (instant, no copy)
- `post-start`: Copy `.env.local` from primary worktree
- `post-remove`: Tear down Docker stacks (`dev-down`, `dev-int-down`, `dev-e2e-down`) for the removed worktree's slot
This commit is contained in:
parent
958ab61d51
commit
dea1b669e6
7 changed files with 55 additions and 23 deletions
27
.config/wt.toml
Normal file
27
.config/wt.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# HyperDX worktrunk project config
|
||||
# See: https://worktrunk.dev
|
||||
#
|
||||
# This file is committed and shared with the team. It configures lifecycle
|
||||
# hooks that run when creating/switching/removing worktrees via `wt`.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pre-start: blocking — symlink node_modules from primary worktree for
|
||||
# instant dependency access. If the branch needs different deps:
|
||||
# rm node_modules && yarn install
|
||||
# ---------------------------------------------------------------------------
|
||||
pre-start = "ln -sf {{ primary_worktree_path }}/node_modules {{ worktree_path }}/node_modules"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# post-start: background tasks
|
||||
# ---------------------------------------------------------------------------
|
||||
[post-start]
|
||||
env = "cp {{ primary_worktree_path }}/.env.local {{ worktree_path }}/.env.local 2>/dev/null && echo 'Copied .env.local' || true"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# post-remove: clean up all Docker resources for this worktree's slot
|
||||
# (dev stack, integration tests, E2E tests)
|
||||
# ---------------------------------------------------------------------------
|
||||
[post-remove]
|
||||
dev-down = "make dev-down 2>/dev/null || true"
|
||||
int-down = "make dev-int-down 2>/dev/null || true"
|
||||
e2e-down = "make dev-e2e-down 2>/dev/null || true"
|
||||
16
.env
16
.env
|
|
@ -29,14 +29,14 @@ HYPERDX_OPAMP_PORT=${HYPERDX_OPAMP_PORT:-4320}
|
|||
HYPERDX_BASE_PATH=
|
||||
|
||||
# Docker service ports (overridden by scripts/dev-env.sh for isolation)
|
||||
HDX_DEV_MONGO_PORT=${HDX_DEV_MONGO_PORT:-27017}
|
||||
HDX_DEV_CH_HTTP_PORT=${HDX_DEV_CH_HTTP_PORT:-8123}
|
||||
HDX_DEV_CH_NATIVE_PORT=${HDX_DEV_CH_NATIVE_PORT:-9000}
|
||||
HDX_DEV_OTEL_HEALTH_PORT=${HDX_DEV_OTEL_HEALTH_PORT:-13133}
|
||||
HDX_DEV_OTEL_GRPC_PORT=${HDX_DEV_OTEL_GRPC_PORT:-4317}
|
||||
HDX_DEV_OTEL_HTTP_PORT=${HDX_DEV_OTEL_HTTP_PORT:-4318}
|
||||
HDX_DEV_OTEL_METRICS_PORT=${HDX_DEV_OTEL_METRICS_PORT:-8888}
|
||||
HDX_DEV_OTEL_JSON_HTTP_PORT=${HDX_DEV_OTEL_JSON_HTTP_PORT:-14318}
|
||||
HDX_DEV_MONGO_PORT=27017
|
||||
HDX_DEV_CH_HTTP_PORT=8123
|
||||
HDX_DEV_CH_NATIVE_PORT=9000
|
||||
HDX_DEV_OTEL_HEALTH_PORT=13133
|
||||
HDX_DEV_OTEL_GRPC_PORT=4317
|
||||
HDX_DEV_OTEL_HTTP_PORT=4318
|
||||
HDX_DEV_OTEL_METRICS_PORT=8888
|
||||
HDX_DEV_OTEL_JSON_HTTP_PORT=14318
|
||||
|
||||
# Otel/Clickhouse config
|
||||
HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE=default
|
||||
|
|
|
|||
|
|
@ -1,29 +1,23 @@
|
|||
HYPERDX_API_KEY="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# Ports are overridden by scripts/dev-env.sh for worktree isolation
|
||||
HYPERDX_API_PORT=${HYPERDX_API_PORT:-8000}
|
||||
HYPERDX_OPAMP_PORT=${HYPERDX_OPAMP_PORT:-4320}
|
||||
HYPERDX_APP_PORT=${HYPERDX_APP_PORT:-8080}
|
||||
# Ports come from scripts/dev-env.sh (worktree isolation) or root .env (defaults)
|
||||
HYPERDX_LOG_LEVEL=debug
|
||||
EXPRESS_SESSION_SECRET="hyperdx is cool 👋"
|
||||
FRONTEND_URL="http://localhost:${HYPERDX_APP_PORT}"
|
||||
HDX_NODE_ADVANCED_NETWORK_CAPTURE=1
|
||||
HDX_NODE_BETA_MODE=1
|
||||
HDX_NODE_CONSOLE_CAPTURE=1
|
||||
HYPERDX_API_KEY=${HYPERDX_API_KEY}
|
||||
HYPERDX_LOG_LEVEL=${HYPERDX_LOG_LEVEL}
|
||||
MINER_API_URL="http://localhost:5123"
|
||||
MONGO_URI="mongodb://localhost:${HDX_DEV_MONGO_PORT:-27017}/hyperdx"
|
||||
MONGO_URI="mongodb://localhost:${HDX_DEV_MONGO_PORT}/hyperdx"
|
||||
NODE_ENV=development
|
||||
OTEL_SERVICE_NAME="hdx-oss-dev-api"
|
||||
OTEL_RESOURCE_ATTRIBUTES="service.version=dev"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT:-4318}"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT}"
|
||||
PORT=${HYPERDX_API_PORT}
|
||||
OPAMP_PORT=${HYPERDX_OPAMP_PORT}
|
||||
REDIS_URL=redis://localhost:6379
|
||||
USAGE_STATS_ENABLED=false
|
||||
NODE_OPTIONS="--max-http-header-size=131072"
|
||||
ENABLE_SWAGGER=true
|
||||
DEFAULT_CONNECTIONS=[{"name":"Local ClickHouse","host":"http://localhost:${HDX_DEV_CH_HTTP_PORT:-8123}","username":"default","password":""}]
|
||||
DEFAULT_CONNECTIONS=[{"name":"Local ClickHouse","host":"http://localhost:${HDX_DEV_CH_HTTP_PORT}","username":"default","password":""}]
|
||||
DEFAULT_SOURCES=[{"from":{"databaseName":"default","tableName":"otel_logs"},"kind":"log","timestampValueExpression":"TimestampTime","name":"Logs","displayedTimestampValueExpression":"Timestamp","implicitColumnExpression":"Body","serviceNameExpression":"ServiceName","bodyExpression":"Body","eventAttributesExpression":"LogAttributes","resourceAttributesExpression":"ResourceAttributes","defaultTableSelectExpression":"Timestamp,ServiceName,SeverityText,Body","severityTextExpression":"SeverityText","traceIdExpression":"TraceId","spanIdExpression":"SpanId","connection":"Local ClickHouse","traceSourceId":"Traces","sessionSourceId":"Sessions","metricSourceId":"Metrics"},{"from":{"databaseName":"default","tableName":"otel_traces"},"kind":"trace","timestampValueExpression":"Timestamp","name":"Traces","displayedTimestampValueExpression":"Timestamp","implicitColumnExpression":"SpanName","serviceNameExpression":"ServiceName","eventAttributesExpression":"SpanAttributes","resourceAttributesExpression":"ResourceAttributes","defaultTableSelectExpression":"Timestamp,ServiceName,StatusCode,round(Duration/1e6),SpanName","traceIdExpression":"TraceId","spanIdExpression":"SpanId","durationExpression":"Duration","durationPrecision":9,"parentSpanIdExpression":"ParentSpanId","spanNameExpression":"SpanName","spanKindExpression":"SpanKind","statusCodeExpression":"StatusCode","statusMessageExpression":"StatusMessage","connection":"Local ClickHouse","logSourceId":"Logs","sessionSourceId":"Sessions","metricSourceId":"Metrics"},{"from":{"databaseName":"default","tableName":""},"kind":"metric","timestampValueExpression":"TimeUnix","name":"Metrics","resourceAttributesExpression":"ResourceAttributes","metricTables":{"gauge":"otel_metrics_gauge","histogram":"otel_metrics_histogram","sum":"otel_metrics_sum","_id":"682586a8b1f81924e628e808","id":"682586a8b1f81924e628e808"},"connection":"Local ClickHouse","logSourceId":"Logs","traceSourceId":"Traces","sessionSourceId":"Sessions"},{"from":{"databaseName":"default","tableName":"hyperdx_sessions"},"kind":"session","timestampValueExpression":"TimestampTime","name":"Sessions","displayedTimestampValueExpression":"Timestamp","implicitColumnExpression":"Body","serviceNameExpression":"ServiceName","bodyExpression":"Body","eventAttributesExpression":"LogAttributes","resourceAttributesExpression":"ResourceAttributes","defaultTableSelectExpression":"Timestamp,ServiceName,SeverityText,Body","severityTextExpression":"SeverityText","traceIdExpression":"TraceId","spanIdExpression":"SpanId","connection":"Local ClickHouse","logSourceId":"Logs","traceSourceId":"Traces","metricSourceId":"Metrics"},{"from":{"databaseName":"otel_json","tableName":"otel_logs"},"kind":"log","timestampValueExpression":"Timestamp","name":"JSON Logs","displayedTimestampValueExpression":"Timestamp","implicitColumnExpression":"Body","serviceNameExpression":"ServiceName","bodyExpression":"Body","eventAttributesExpression":"LogAttributes","resourceAttributesExpression":"ResourceAttributes","defaultTableSelectExpression":"Timestamp,ServiceName,SeverityText,Body","severityTextExpression":"SeverityText","traceIdExpression":"TraceId","spanIdExpression":"SpanId","connection":"Local ClickHouse","traceSourceId":"JSON Traces","metricSourceId":"JSON Metrics"},{"from":{"databaseName":"otel_json","tableName":"otel_traces"},"kind":"trace","timestampValueExpression":"Timestamp","name":"JSON Traces","displayedTimestampValueExpression":"Timestamp","implicitColumnExpression":"SpanName","serviceNameExpression":"ServiceName","eventAttributesExpression":"SpanAttributes","resourceAttributesExpression":"ResourceAttributes","defaultTableSelectExpression":"Timestamp,ServiceName,StatusCode,round(Duration/1e6),SpanName","traceIdExpression":"TraceId","spanIdExpression":"SpanId","durationExpression":"Duration","durationPrecision":9,"parentSpanIdExpression":"ParentSpanId","spanNameExpression":"SpanName","spanKindExpression":"SpanKind","statusCodeExpression":"StatusCode","statusMessageExpression":"StatusMessage","connection":"Local ClickHouse","logSourceId":"JSON Logs","metricSourceId":"JSON Metrics"},{"from":{"databaseName":"otel_json","tableName":""},"kind":"metric","timestampValueExpression":"TimeUnix","name":"JSON Metrics","resourceAttributesExpression":"ResourceAttributes","metricTables":{"gauge":"otel_metrics_gauge","histogram":"otel_metrics_histogram","sum":"otel_metrics_sum"},"connection":"Local ClickHouse","logSourceId":"JSON Logs","traceSourceId":"JSON Traces"}]
|
||||
INGESTION_API_KEY="super-secure-ingestion-api-key"
|
||||
HYPERDX_API_KEY=$INGESTION_API_KEY
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ import passport from './utils/passport';
|
|||
const app: express.Application = express();
|
||||
|
||||
const sess: session.SessionOptions & { cookie: session.CookieOptions } = {
|
||||
// Use a slot-specific cookie name in dev so multiple worktrees on localhost
|
||||
// don't overwrite each other's session cookies.
|
||||
...(config.IS_DEV && process.env.HDX_DEV_SLOT
|
||||
? { name: `connect.sid.${process.env.HDX_DEV_SLOT}` }
|
||||
: {}),
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
secret: config.EXPRESS_SESSION_SECRET,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
HYPERDX_API_KEY="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# Ports are overridden by scripts/dev-env.sh for worktree isolation
|
||||
HYPERDX_API_PORT=${HYPERDX_API_PORT:-8000}
|
||||
HYPERDX_APP_PORT=${HYPERDX_APP_PORT:-8080}
|
||||
HYPERDX_API_KEY="super-secure-ingestion-api-key"
|
||||
# Ports come from scripts/dev-env.sh (worktree isolation) or root .env (defaults)
|
||||
SERVER_URL="http://localhost:${HYPERDX_API_PORT}"
|
||||
NODE_ENV=development
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT:-4318}"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:${HDX_DEV_OTEL_HTTP_PORT}"
|
||||
OTEL_SERVICE_NAME="hdx-oss-dev-app"
|
||||
PORT=${HYPERDX_APP_PORT}
|
||||
NODE_OPTIONS="--max-http-header-size=131072"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const HDX_EXPORTER_ENABLED =
|
|||
(process.env.HDX_EXPORTER_ENABLED ?? 'true') === 'true';
|
||||
export const HDX_COLLECTOR_URL =
|
||||
process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT ??
|
||||
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
|
||||
'http://localhost:4318';
|
||||
export const IS_DEV = NODE_ENV === 'development';
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@ HDX_DEV_OTEL_JSON_HTTP_PORT=$((31100 + HDX_DEV_SLOT))
|
|||
# --- Docker Compose project name (unique per slot) ---
|
||||
HDX_DEV_PROJECT="hdx-dev-${HDX_DEV_SLOT}"
|
||||
|
||||
# --- Shared NX build cache across all worktrees ---
|
||||
# NX cache is content-hash based so changed files get cache misses (correct
|
||||
# behavior). Unchanged packages reuse cached output regardless of worktree.
|
||||
NX_CACHE_DIRECTORY="${HOME}/.config/hyperdx/nx-cache"
|
||||
mkdir -p "$NX_CACHE_DIRECTORY"
|
||||
|
||||
# Export everything
|
||||
export HDX_DEV_SLOT
|
||||
export HDX_DEV_BRANCH
|
||||
|
|
@ -75,6 +81,7 @@ export HDX_DEV_OTEL_HTTP_PORT
|
|||
export HDX_DEV_OTEL_METRICS_PORT
|
||||
export HDX_DEV_OTEL_JSON_HTTP_PORT
|
||||
export HDX_DEV_PROJECT
|
||||
export NX_CACHE_DIRECTORY
|
||||
|
||||
# --- Clean up stale Next.js state from previous sessions ---
|
||||
# Nuke the entire .next directory to avoid stale webpack bundles, lock files,
|
||||
|
|
|
|||
Loading…
Reference in a new issue