From 6163ab98841680294e9563f3fbf901ff87400ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Fri, 31 Jul 2026 22:49:12 +0200 Subject: [PATCH] fix: streamline dev setup DX with auto-sourced env config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DX improvements: - dev-env.sh writes scripts/.env.dev with all config (including OPENSHELL_DIR) - Makefile auto-sources scripts/.env.dev via -include (zero manual exports) - make dev-full: one command starts infra + dashboard - make dev: auto-reads config from previous dev-env.sh start OPENSHELL_DIR resolution: - Checks env var first, then scripts/.env.dev, then prompts interactively - Offers to clone NVIDIA/OpenShell if no checkout exists - Persists the path to .env.dev for future runs Robust shutdown: - Graceful stop with 10s timeout, then SIGKILL - Detects and kills orphaned gateway processes on the expected port - Force-removes Keycloak containers regardless of state - Cleans up stale PID/log/config files (preserves PKI, DB, env config) Also: fix grpcurl to use proto descriptor instead of server reflection Assisted-By: 🤖 Claude Code --- .gitignore | 3 +- Makefile | 8 ++- README.md | 35 ++++++---- scripts/dev-env.sh | 163 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 170 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 73da34f..674576c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,10 @@ backend/bin/ .idea/ .vscode/ -# Dev environment state (generated certs, runtime artifacts) +# Dev environment state (generated certs, runtime artifacts, env config) scripts/.pki/ scripts/.state/ +scripts/.env.dev # Env files are never committed .env diff --git a/Makefile b/Makefile index 5c5db3f..665752c 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ PROTO_GRPC_OPTS := \ --go-grpc_opt=Minference.proto=$(GO_MODULE)/gen/inferencev1 \ --go-grpc_opt=Mopenshell.proto=$(GO_MODULE)/gen/openshellv1 +# Auto-source dev environment config if available (written by scripts/dev-env.sh) +-include scripts/.env.dev +export + .PHONY: setup proto dev dev-full dev-backend dev-frontend build build-frontend build-backend test lint typecheck clean setup: ## Install frontend deps and Go deps @@ -32,7 +36,7 @@ proto: ## Regenerate Go stubs from backend/proto/*.proto into backend/gen/ $(addprefix $(PROTO_DIR)/,$(PROTO_FILES)) cd backend && go mod tidy -dev-full: ## Start dev infrastructure (Keycloak + gateway) then frontend + BFF +dev-full: ## Start Keycloak + gateway, then frontend + BFF (one command) ./scripts/dev-env.sh start @$(MAKE) dev @@ -40,7 +44,7 @@ dev: ## Start frontend dev server (:3000) and Go BFF (:8080) @$(MAKE) -j2 dev-backend dev-frontend dev-backend: - cd backend && AUTH_DISABLED=$${AUTH_DISABLED:-true} go run ./cmd/server + cd backend && go run ./cmd/server dev-frontend: cd frontend && npm start diff --git a/README.md b/README.md index c0a1593..c3750f9 100644 --- a/README.md +++ b/README.md @@ -36,21 +36,13 @@ To test with real OIDC authentication against a local Keycloak and OpenShell gat ```bash make setup +export OPENSHELL_DIR=~/path/to/openshell # your OpenShell checkout +make dev-full # starts infra + dashboard +``` -# Point at your OpenShell source checkout -export OPENSHELL_DIR=~/path/to/openshell +That's it. `dev-full` starts Keycloak and the gateway (if not already running), writes a `scripts/.env.dev` config file, and launches the dashboard. On subsequent runs, `make dev` picks up the config automatically (no env vars needed). -# Start the infrastructure (Keycloak + gateway) -./scripts/dev-env.sh start - -# Run the dashboard with the printed env vars -export OPENSHELL_GATEWAY_URL=grpcs://localhost:17670 -export OIDC_ISSUER=http://localhost:8180/realms/openshell -export OIDC_CLIENT_ID=openshell-dashboard -export GATEWAY_CA_CERT=$(pwd)/scripts/.pki/ca.crt -export AUTH_DISABLED=false -make dev -``` +If `OPENSHELL_DIR` is not set, the script prompts interactively and offers to clone the repo for you. The chosen path is saved to `scripts/.env.dev` so you only configure it once. Open http://localhost:3000 and log in via Keycloak with one of the test users: @@ -60,7 +52,22 @@ Open http://localhost:3000 and log in via Keycloak with one of the test users: | `user@test` | `user` | Workspace member | | `user-b@test` | `user-b` | Workspace member | -The script is idempotent. Run `./scripts/dev-env.sh status` to check components, `stop` to tear down, or `rebuild-gateway` after pulling upstream changes. +### What `dev-full` starts + +| Component | How | Lifecycle | +|-----------|-----|-----------| +| Keycloak | Podman container (`openshell-keycloak`) on port 8180 | Runs until `dev-env.sh stop` | +| OpenShell gateway | Background process built from source, port 17670 (gRPCs) + 17671 (health) | Runs until `dev-env.sh stop` | +| Dashboard BFF | `go run` on port 8080 | Runs with `make dev`, Ctrl+C to stop | +| Dashboard frontend | Webpack dev server on port 3000 | Runs with `make dev`, Ctrl+C to stop | + +Keycloak and the gateway survive across `make dev` restarts. Stop them explicitly: + +```bash +./scripts/dev-env.sh stop # stops gateway + keycloak, cleans up orphans +./scripts/dev-env.sh status # check what's running +./scripts/dev-env.sh rebuild-gateway # rebuild after upstream changes +``` ## Configuration diff --git a/scripts/dev-env.sh b/scripts/dev-env.sh index 9508961..1aeea45 100755 --- a/scripts/dev-env.sh +++ b/scripts/dev-env.sh @@ -5,15 +5,65 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" PKI_DIR="$SCRIPT_DIR/.pki" STATE_DIR="$SCRIPT_DIR/.state" -if [ -z "${OPENSHELL_DIR:-}" ]; then - echo "ERROR: OPENSHELL_DIR is not set." >&2 - echo "Set it to the path of your OpenShell source checkout:" >&2 - echo " export OPENSHELL_DIR=~/path/to/openshell" >&2 - exit 1 -fi -OPENSHELL_DIR="${OPENSHELL_DIR}" +ENV_FILE="$SCRIPT_DIR/.env.dev" + +resolve_openshell_dir() { + if [ -n "${OPENSHELL_DIR:-}" ]; then + return 0 + fi + + if [ -f "$ENV_FILE" ]; then + local saved + saved=$(grep '^OPENSHELL_DIR=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-) + if [ -n "$saved" ] && [ -d "$saved" ]; then + OPENSHELL_DIR="$saved" + return 0 + fi + fi + + echo "" + echo "OpenShell source directory not configured." + echo "" + echo "Where is your OpenShell checkout?" + echo "" + echo " 1) Enter a path" + echo " 2) Clone from GitHub into ./openshell (next to this project)" + echo "" + printf "Choice [1/2]: " + read -r choice + + case "$choice" in + 2) + local clone_dir="$PROJECT_DIR/../openshell" + if [ -d "$clone_dir" ] && [ -f "$clone_dir/Cargo.toml" ]; then + echo "Found existing checkout at $clone_dir" + OPENSHELL_DIR="$(cd "$clone_dir" && pwd)" + else + echo "Cloning NVIDIA/OpenShell..." + git clone https://github.com/NVIDIA/OpenShell.git "$clone_dir" 2>&1 + OPENSHELL_DIR="$(cd "$clone_dir" && pwd)" + fi + ;; + *) + printf "Path to OpenShell source: " + read -r user_path + user_path="${user_path/#\~/$HOME}" + if [ ! -d "$user_path" ] || [ ! -f "$user_path/Cargo.toml" ]; then + error "Not a valid OpenShell checkout: $user_path" + exit 1 + fi + OPENSHELL_DIR="$(cd "$user_path" && pwd)" + ;; + esac + + echo "OPENSHELL_DIR=$OPENSHELL_DIR" >> "$ENV_FILE" 2>/dev/null || true + export OPENSHELL_DIR +} + +OPENSHELL_DIR="${OPENSHELL_DIR:-}" KEYCLOAK_PORT="${KEYCLOAK_PORT:-8180}" KEYCLOAK_CONTAINER="openshell-keycloak" +PODMAN_NETWORK="openshell-dev" GATEWAY_GRPC_PORT=17670 GATEWAY_HTTP_PORT=17671 GATEWAY_PID_FILE="$STATE_DIR/gateway.pid" @@ -128,7 +178,7 @@ generate_pki() { -subj "/CN=localhost" 2>/dev/null cat > "$PKI_DIR/server/san.cnf" </dev/null 2>&1; then + return 0 + fi + podman network create --driver bridge "$PODMAN_NETWORK" >/dev/null 2>&1 + info "Podman network '$PODMAN_NETWORK' created" +} + generate_gateway_config() { mkdir -p "$STATE_DIR" local podman_socket podman_socket=$(detect_podman_socket) + ensure_podman_network + + local jwt_dir="$STATE_DIR/jwt" + if [ ! -f "$jwt_dir/signing.pem" ]; then + mkdir -p "$jwt_dir" + (umask 077; openssl genpkey -algorithm Ed25519 -out "$jwt_dir/signing.pem" 2>/dev/null) + openssl pkey -in "$jwt_dir/signing.pem" -pubout -out "$jwt_dir/public.pem" 2>/dev/null + openssl rand -hex 16 > "$jwt_dir/kid" + fi + cat > "$GATEWAY_CONFIG_FILE" </dev/null; then local result + local proto_import="$OPENSHELL_DIR/proto" result=$(grpcurl -H "Authorization: Bearer $admin_token" \ -cacert "$PKI_DIR/ca.crt" \ + -import-path "$proto_import" -proto openshell.proto \ -d '{"name": "default"}' \ "localhost:${GATEWAY_GRPC_PORT}" \ openshell.v1.OpenShell/CreateWorkspace 2>&1 || true) @@ -517,19 +593,36 @@ create_default_workspace() { fi } +write_env_file() { + cat > "$ENV_FILE" </dev/null || true + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + local waited=0 + while kill -0 "$pid" 2>/dev/null && [ "$waited" -lt 10 ]; do + sleep 1 + waited=$((waited + 1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + warn "Gateway killed forcefully (PID $pid)" + else + info "Gateway stopped (PID $pid)" + fi + else + info "Gateway PID $pid already gone (stale PID file)" + fi rm -f "$GATEWAY_PID_FILE" - info "Gateway stopped (PID $pid)" - else - info "Gateway not running" fi - if keycloak_is_running; then - podman stop "$KEYCLOAK_CONTAINER" >/dev/null 2>&1 || true - podman rm "$KEYCLOAK_CONTAINER" >/dev/null 2>&1 || true + # Check for orphaned gateway processes on the expected port + local orphan_pid + orphan_pid=$(lsof -ti :"$GATEWAY_GRPC_PORT" 2>/dev/null || true) + if [ -n "$orphan_pid" ]; then + kill "$orphan_pid" 2>/dev/null || true + warn "Killed orphaned process on port $GATEWAY_GRPC_PORT (PID $orphan_pid)" + fi + + # Keycloak: force remove regardless of state (handles stopped, running, or broken containers) + if podman container exists "$KEYCLOAK_CONTAINER" 2>/dev/null; then + podman stop "$KEYCLOAK_CONTAINER" 2>/dev/null || true + podman rm -f "$KEYCLOAK_CONTAINER" 2>/dev/null || true info "Keycloak stopped and removed" else - podman rm -f "$KEYCLOAK_CONTAINER" >/dev/null 2>&1 || true info "Keycloak not running" fi + # Clean up stale state files (preserve PKI, DB, and env config) + rm -f "$GATEWAY_PID_FILE" "$GATEWAY_LOG_FILE" "$GATEWAY_CONFIG_FILE" + info "State files cleaned" + echo "" } @@ -608,6 +726,7 @@ cmd_status() { } cmd_rebuild_gateway() { + resolve_openshell_dir step "Rebuilding gateway" if gateway_is_running; then