Skill Tracer
Skills › Superpowers › brainstorming › stop-server.sh

stop-server.sh

120 lines · skills/brainstorming/scripts/stop-server.sh at 2ce8aa1

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