1
0

stop-server.sh 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #!/usr/bin/env bash
  2. # Stop the brainstorm server and clean up
  3. # Usage: stop-server.sh <session_dir>
  4. #
  5. # Kills the server process. Only deletes session directory if it's
  6. # under /tmp (ephemeral). Persistent directories (.superpowers/) are
  7. # kept so mockups can be reviewed later.
  8. SESSION_DIR="$1"
  9. if [[ -z "$SESSION_DIR" ]]; then
  10. echo '{"error": "Usage: stop-server.sh <session_dir>"}'
  11. exit 1
  12. fi
  13. STATE_DIR="${SESSION_DIR}/state"
  14. PID_FILE="${STATE_DIR}/server.pid"
  15. # Confirm a PID is actually our brainstorm server (node running server.cjs),
  16. # not a reused/unrelated process whose PID was recycled into a stale pid file.
  17. is_brainstorm_server() {
  18. kill -0 "$1" 2>/dev/null || return 1
  19. local command_line
  20. command_line="$(ps -p "$1" -o command= 2>/dev/null || true)"
  21. if [[ -z "$command_line" ]]; then
  22. command_line="$(ps -f -p "$1" 2>/dev/null | sed '1d' || true)"
  23. fi
  24. case "$command_line" in
  25. *node*server.cjs*) ;;
  26. *) return 1 ;;
  27. esac
  28. # Stronger check: if we recorded the bound port and lsof is available, require
  29. # the PID to be the process actually LISTENING on this session's port. This
  30. # rules out an unrelated `node ... server.cjs` (another project, an editor task
  31. # runner, a different session) that happened to recycle the stale PID.
  32. local info="${STATE_DIR}/server-info"
  33. if [[ -f "$info" ]] && command -v lsof >/dev/null 2>&1; then
  34. local port
  35. port=$(sed -n 's/.*"port":\([0-9][0-9]*\).*/\1/p' "$info" | head -1)
  36. if [[ -n "$port" ]]; then
  37. [[ "$(lsof -nP -iTCP:"$port" -sTCP:LISTEN -t 2>/dev/null | head -1)" == "$1" ]] || return 1
  38. fi
  39. fi
  40. return 0
  41. }
  42. if [[ -f "$PID_FILE" ]]; then
  43. pid=$(cat "$PID_FILE")
  44. # Refuse to signal a PID we can't prove is our server. A stale pid file may
  45. # point at an unrelated process after a reboot/PID wraparound.
  46. if ! is_brainstorm_server "$pid"; then
  47. rm -f "$PID_FILE"
  48. echo '{"status": "stale_pid"}'
  49. exit 0
  50. fi
  51. # Try to stop gracefully, fallback to force if still alive
  52. kill "$pid" 2>/dev/null || true
  53. # Wait for graceful shutdown (up to ~2s)
  54. for _ in {1..20}; do
  55. if ! kill -0 "$pid" 2>/dev/null; then
  56. break
  57. fi
  58. sleep 0.1
  59. done
  60. # If still running, escalate to SIGKILL
  61. if kill -0 "$pid" 2>/dev/null; then
  62. kill -9 "$pid" 2>/dev/null || true
  63. # Give SIGKILL a moment to take effect
  64. sleep 0.1
  65. fi
  66. if kill -0 "$pid" 2>/dev/null; then
  67. echo '{"status": "failed", "error": "process still running"}'
  68. exit 1
  69. fi
  70. rm -f "$PID_FILE" "${STATE_DIR}/server.log"
  71. # Only delete ephemeral /tmp directories
  72. if [[ "$SESSION_DIR" == /tmp/* ]]; then
  73. rm -rf "$SESSION_DIR"
  74. fi
  75. echo '{"status": "stopped"}'
  76. else
  77. echo '{"status": "not_running"}'
  78. fi