stop-server.sh 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. SERVER_ID_FILE="${STATE_DIR}/server-instance-id"
  16. mark_stopped() {
  17. local reason="$1"
  18. rm -f "${STATE_DIR}/server-info"
  19. printf '{"reason":"%s","timestamp":%s}\n' "$reason" "$(date +%s)" > "${STATE_DIR}/server-stopped"
  20. }
  21. read_expected_server_id() {
  22. [[ -f "$SERVER_ID_FILE" ]] || return 1
  23. local id
  24. id="$(tr -d '\r\n' < "$SERVER_ID_FILE" 2>/dev/null || true)"
  25. [[ "$id" =~ ^[A-Za-z0-9_-]{32,64}$ ]] || return 1
  26. printf '%s\n' "$id"
  27. }
  28. command_line_for_pid() {
  29. local pid="$1"
  30. if [[ -r "/proc/$pid/cmdline" ]]; then
  31. tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null || true
  32. return 0
  33. fi
  34. ps -ww -p "$pid" -o command= 2>/dev/null || ps -f -p "$pid" 2>/dev/null | sed '1d' || true
  35. }
  36. command_has_server_id() {
  37. local pid="$1"
  38. local expected="$2"
  39. local expected_arg="--brainstorm-server-id=$expected"
  40. if [[ -r "/proc/$pid/cmdline" ]]; then
  41. local arg
  42. while IFS= read -r -d '' arg || [[ -n "$arg" ]]; do
  43. [[ "$arg" == "$expected_arg" ]] && return 0
  44. done < "/proc/$pid/cmdline"
  45. return 1
  46. fi
  47. local command_line
  48. command_line="$(command_line_for_pid "$pid")"
  49. [[ -n "$command_line" ]] || return 1
  50. case " $command_line " in
  51. *" $expected_arg "*) return 0 ;;
  52. *) return 1 ;;
  53. esac
  54. }
  55. # Confirm a PID has this session's per-start instance id, not just a familiar
  56. # process name. Ambiguous or legacy metadata fails closed as stale_pid.
  57. is_brainstorm_server() {
  58. kill -0 "$1" 2>/dev/null || return 1
  59. local expected_id
  60. expected_id="$(read_expected_server_id)" || return 1
  61. command_has_server_id "$1" "$expected_id" || return 1
  62. return 0
  63. }
  64. if [[ -f "$PID_FILE" ]]; then
  65. pid=$(cat "$PID_FILE")
  66. # Refuse to signal a PID we can't prove is our server. A stale pid file may
  67. # point at an unrelated process after a reboot/PID wraparound.
  68. if ! is_brainstorm_server "$pid"; then
  69. rm -f "$PID_FILE" "$SERVER_ID_FILE"
  70. mark_stopped "stale_pid"
  71. echo '{"status": "stale_pid"}'
  72. exit 0
  73. fi
  74. # Try to stop gracefully, fallback to force if still alive
  75. kill "$pid" 2>/dev/null || true
  76. # Wait for graceful shutdown (up to ~2s)
  77. for _ in {1..20}; do
  78. if ! kill -0 "$pid" 2>/dev/null; then
  79. break
  80. fi
  81. sleep 0.1
  82. done
  83. # If still running, escalate to SIGKILL
  84. if kill -0 "$pid" 2>/dev/null; then
  85. kill -9 "$pid" 2>/dev/null || true
  86. # Give SIGKILL a moment to take effect
  87. sleep 0.1
  88. fi
  89. if kill -0 "$pid" 2>/dev/null; then
  90. echo '{"status": "failed", "error": "process still running"}'
  91. exit 1
  92. fi
  93. rm -f "$PID_FILE" "$SERVER_ID_FILE" "${STATE_DIR}/server.log"
  94. mark_stopped "stop-server.sh"
  95. # Only delete ephemeral /tmp directories
  96. if [[ "$SESSION_DIR" == /tmp/* ]]; then
  97. rm -rf "$SESSION_DIR"
  98. fi
  99. echo '{"status": "stopped"}'
  100. else
  101. echo '{"status": "not_running"}'
  102. fi