start-server.sh 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env bash
  2. # Start the brainstorm server and output connection info
  3. # Usage: start-server.sh [--project-dir <path>] [--host <bind-host>] [--url-host <display-host>] [--foreground] [--background]
  4. #
  5. # Starts server on a random high port, outputs JSON with URL.
  6. # Each session gets its own directory to avoid conflicts.
  7. #
  8. # Options:
  9. # --project-dir <path> Store session files under <path>/.superpowers/brainstorm/
  10. # instead of /tmp. Files persist after server stops.
  11. # --host <bind-host> Host/interface to bind (default: 127.0.0.1).
  12. # Use 0.0.0.0 in remote/containerized environments.
  13. # --url-host <host> Hostname shown in returned URL JSON.
  14. # --foreground Run server in the current terminal (no backgrounding).
  15. # --background Force background mode (overrides Codex auto-foreground).
  16. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  17. # Parse arguments
  18. PROJECT_DIR=""
  19. FOREGROUND="false"
  20. FORCE_BACKGROUND="false"
  21. BIND_HOST="127.0.0.1"
  22. URL_HOST=""
  23. while [[ $# -gt 0 ]]; do
  24. case "$1" in
  25. --project-dir)
  26. PROJECT_DIR="$2"
  27. shift 2
  28. ;;
  29. --host)
  30. BIND_HOST="$2"
  31. shift 2
  32. ;;
  33. --url-host)
  34. URL_HOST="$2"
  35. shift 2
  36. ;;
  37. --foreground|--no-daemon)
  38. FOREGROUND="true"
  39. shift
  40. ;;
  41. --background|--daemon)
  42. FORCE_BACKGROUND="true"
  43. shift
  44. ;;
  45. *)
  46. echo "{\"error\": \"Unknown argument: $1\"}"
  47. exit 1
  48. ;;
  49. esac
  50. done
  51. if [[ -z "$URL_HOST" ]]; then
  52. if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then
  53. URL_HOST="localhost"
  54. else
  55. URL_HOST="$BIND_HOST"
  56. fi
  57. fi
  58. # Some environments reap detached/background processes. Auto-foreground when detected.
  59. if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then
  60. FOREGROUND="true"
  61. fi
  62. # Windows/Git Bash reaps nohup background processes. Auto-foreground when detected.
  63. if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then
  64. case "${OSTYPE:-}" in
  65. msys*|cygwin*|mingw*) FOREGROUND="true" ;;
  66. esac
  67. if [[ -n "${MSYSTEM:-}" ]]; then
  68. FOREGROUND="true"
  69. fi
  70. fi
  71. # Generate unique session directory
  72. SESSION_ID="$$-$(date +%s)"
  73. if [[ -n "$PROJECT_DIR" ]]; then
  74. SCREEN_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
  75. else
  76. SCREEN_DIR="/tmp/brainstorm-${SESSION_ID}"
  77. fi
  78. PID_FILE="${SCREEN_DIR}/.server.pid"
  79. LOG_FILE="${SCREEN_DIR}/.server.log"
  80. # Create fresh session directory
  81. mkdir -p "$SCREEN_DIR"
  82. # Kill any existing server
  83. if [[ -f "$PID_FILE" ]]; then
  84. old_pid=$(cat "$PID_FILE")
  85. kill "$old_pid" 2>/dev/null
  86. rm -f "$PID_FILE"
  87. fi
  88. cd "$SCRIPT_DIR"
  89. # Resolve the harness PID (grandparent of this script).
  90. # $PPID is the ephemeral shell the harness spawned to run us — it dies
  91. # when this script exits. The harness itself is $PPID's parent.
  92. OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')"
  93. if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
  94. OWNER_PID="$PPID"
  95. fi
  96. # On Windows/MSYS2, the MSYS2 PID namespace is invisible to Node.js.
  97. # Skip owner-PID monitoring — the 30-minute idle timeout prevents orphans.
  98. case "${OSTYPE:-}" in
  99. msys*|cygwin*|mingw*) OWNER_PID="" ;;
  100. esac
  101. # Foreground mode for environments that reap detached/background processes.
  102. if [[ "$FOREGROUND" == "true" ]]; then
  103. echo "$$" > "$PID_FILE"
  104. env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
  105. exit $?
  106. fi
  107. # Start server, capturing output to log file
  108. # Use nohup to survive shell exit; disown to remove from job table
  109. nohup env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 &
  110. SERVER_PID=$!
  111. disown "$SERVER_PID" 2>/dev/null
  112. echo "$SERVER_PID" > "$PID_FILE"
  113. # Wait for server-started message (check log file)
  114. for i in {1..50}; do
  115. if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then
  116. # Verify server is still alive after a short window (catches process reapers)
  117. alive="true"
  118. for _ in {1..20}; do
  119. if ! kill -0 "$SERVER_PID" 2>/dev/null; then
  120. alive="false"
  121. break
  122. fi
  123. sleep 0.1
  124. done
  125. if [[ "$alive" != "true" ]]; then
  126. echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}"
  127. exit 1
  128. fi
  129. grep "server-started" "$LOG_FILE" | head -1
  130. exit 0
  131. fi
  132. sleep 0.1
  133. done
  134. # Timeout - server didn't start
  135. echo '{"error": "Server failed to start within 5 seconds"}'
  136. exit 1