Capstone project. I'm ngl it's vibe-coded and it's only here so I can mess around with it
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Harden server TUI process management

- Replace pid file tracking with validated state
- Detect managed vs unmanaged listeners before stop/start
- Add startup and shutdown verification with clearer status output

+366 -54
+366 -54
scripts/server-tui.sh
··· 4 4 5 5 ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 6 6 DEFAULT_PORT="${UXET_PORT:-8080}" 7 - PID_FILE="$ROOT_DIR/.uxet-server.pid" 7 + STATE_FILE="$ROOT_DIR/.uxet-server.state" 8 8 LOG_FILE="$ROOT_DIR/.uxet-server.log" 9 + START_TIMEOUT_SECONDS=5 10 + STOP_TIMEOUT_SECONDS=5 11 + 12 + STATE_PID="" 13 + STATE_PORT="" 14 + STATE_START_TS="" 15 + STATE_ROOT_DIR="" 16 + STATE_CMD="" 9 17 10 18 server_url() { 11 19 printf 'http://127.0.0.1:%s\n' "$1" 12 20 } 13 21 14 - read_port() { 22 + prompt_for_port() { 15 23 local input 16 - read -r -p "Port [$DEFAULT_PORT]: " input 17 - if [[ -n "${input:-}" ]]; then 18 - DEFAULT_PORT="$input" 19 - fi 24 + while true; do 25 + read -r -p "Port [$DEFAULT_PORT]: " input 26 + if [[ -z "${input:-}" ]]; then 27 + input="$DEFAULT_PORT" 28 + fi 29 + 30 + if validate_port "$input"; then 31 + printf '%s\n' "$input" 32 + return 0 33 + fi 34 + 35 + printf 'Invalid port. Enter a number between 1 and 65535.\n' 36 + done 37 + } 38 + 39 + validate_port() { 40 + local port="$1" 41 + [[ "$port" =~ ^[0-9]+$ ]] || return 1 42 + (( port >= 1 && port <= 65535 )) 43 + } 44 + 45 + write_state() { 46 + local pid="$1" 47 + local port="$2" 48 + 49 + cat >"$STATE_FILE" <<EOF 50 + PID=$pid 51 + PORT=$port 52 + START_TS=$(date +%s) 53 + ROOT_DIR=$ROOT_DIR 54 + CMD=python3 -m http.server $port 55 + EOF 56 + 57 + STATE_PID="$pid" 58 + STATE_PORT="$port" 59 + STATE_START_TS="$(date +%s)" 60 + STATE_ROOT_DIR="$ROOT_DIR" 61 + STATE_CMD="python3 -m http.server $port" 62 + } 63 + 64 + remove_state() { 65 + rm -f "$STATE_FILE" 66 + STATE_PID="" 67 + STATE_PORT="" 68 + STATE_START_TS="" 69 + STATE_ROOT_DIR="" 70 + STATE_CMD="" 20 71 } 21 72 22 - server_running() { 23 - if [[ ! -f "$PID_FILE" ]]; then 73 + load_state() { 74 + STATE_PID="" 75 + STATE_PORT="" 76 + STATE_START_TS="" 77 + STATE_ROOT_DIR="" 78 + STATE_CMD="" 79 + 80 + if [[ ! -f "$STATE_FILE" ]]; then 24 81 return 1 25 82 fi 26 83 84 + while IFS='=' read -r key value; do 85 + case "$key" in 86 + PID) STATE_PID="$value" ;; 87 + PORT) STATE_PORT="$value" ;; 88 + START_TS) STATE_START_TS="$value" ;; 89 + ROOT_DIR) STATE_ROOT_DIR="$value" ;; 90 + CMD) STATE_CMD="$value" ;; 91 + esac 92 + done <"$STATE_FILE" 93 + 94 + return 0 95 + } 96 + 97 + get_listening_pids() { 98 + local port="$1" 99 + lsof -nP -t -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | awk '!seen[$0]++' 100 + } 101 + 102 + get_pid_cmdline() { 103 + local pid="$1" 104 + [[ -r "/proc/$pid/cmdline" ]] || return 1 105 + tr '\0' ' ' <"/proc/$pid/cmdline" | sed 's/[[:space:]]\+$//' 106 + } 107 + 108 + get_pid_cwd() { 109 + local pid="$1" 110 + [[ -L "/proc/$pid/cwd" ]] || return 1 111 + readlink -f "/proc/$pid/cwd" 112 + } 113 + 114 + describe_pid() { 115 + local pid="$1" 116 + local cmdline cwd 117 + 118 + cmdline="$(get_pid_cmdline "$pid" 2>/dev/null || true)" 119 + cwd="$(get_pid_cwd "$pid" 2>/dev/null || true)" 120 + 121 + printf 'PID: %s\n' "$pid" 122 + printf 'CWD: %s\n' "${cwd:-unknown}" 123 + printf 'CMD: %s\n' "${cmdline:-unknown}" 124 + } 125 + 126 + is_pid_listening_on_port() { 127 + local pid="$1" 128 + local port="$2" 129 + local listening_pid 130 + 131 + while IFS= read -r listening_pid; do 132 + [[ "$listening_pid" == "$pid" ]] && return 0 133 + done < <(get_listening_pids "$port") 134 + 135 + return 1 136 + } 137 + 138 + is_managed_server() { 139 + local pid="$1" 140 + local port="$2" 141 + local cmdline cwd 142 + 143 + [[ -n "$pid" && -n "$port" ]] || return 1 144 + [[ "$STATE_PID" == "$pid" ]] || return 1 145 + [[ "$STATE_PORT" == "$port" ]] || return 1 146 + is_pid_listening_on_port "$pid" "$port" || return 1 147 + 148 + cwd="$(get_pid_cwd "$pid" 2>/dev/null || true)" 149 + [[ "$cwd" == "$ROOT_DIR" ]] || return 1 150 + [[ -n "$STATE_ROOT_DIR" && "$STATE_ROOT_DIR" != "$ROOT_DIR" ]] && return 1 151 + 152 + cmdline="$(get_pid_cmdline "$pid" 2>/dev/null || true)" 153 + [[ "$cmdline" == *python3* ]] || return 1 154 + [[ "$cmdline" == *"-m http.server"* ]] || return 1 155 + [[ "$cmdline" == *" $port"* || "$cmdline" == *" $port "* ]] || return 1 156 + 157 + return 0 158 + } 159 + 160 + find_managed_server() { 27 161 local pid 28 - pid="$(<"$PID_FILE")" 29 - [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null 162 + 163 + load_state || return 1 164 + validate_port "$STATE_PORT" || return 1 165 + 166 + for pid in $(get_listening_pids "$STATE_PORT"); do 167 + if is_managed_server "$pid" "$STATE_PORT"; then 168 + printf '%s\n' "$pid" 169 + return 0 170 + fi 171 + done 172 + 173 + return 1 174 + } 175 + 176 + find_unmanaged_listener() { 177 + local port="$1" 178 + local managed_pid pid 179 + 180 + managed_pid="$(find_managed_server 2>/dev/null || true)" 181 + while IFS= read -r pid; do 182 + [[ -z "$pid" ]] && continue 183 + if [[ -n "$managed_pid" && "$pid" == "$managed_pid" ]]; then 184 + continue 185 + fi 186 + printf '%s\n' "$pid" 187 + return 0 188 + done < <(get_listening_pids "$port") 189 + 190 + return 1 191 + } 192 + 193 + wait_for_port_state() { 194 + local port="$1" 195 + local expected_state="$2" 196 + local timeout="$3" 197 + local remaining="$timeout" 198 + 199 + while (( remaining > 0 )); do 200 + if [[ "$expected_state" == "listening" ]]; then 201 + if get_listening_pids "$port" >/dev/null && [[ -n "$(get_listening_pids "$port")" ]]; then 202 + return 0 203 + fi 204 + else 205 + if [[ -z "$(get_listening_pids "$port")" ]]; then 206 + return 0 207 + fi 208 + fi 209 + sleep 1 210 + ((remaining--)) 211 + done 212 + 213 + return 1 30 214 } 31 215 32 216 start_server() { 33 - if server_running; then 34 - printf 'Server already running at %s (pid %s)\n' "$(server_url "$DEFAULT_PORT")" "$(cat "$PID_FILE")" 35 - return 217 + local port listener_pid managed_pid pid 218 + 219 + if ! command -v python3 >/dev/null 2>&1; then 220 + printf 'python3 is required but was not found.\n' 221 + return 1 36 222 fi 37 223 38 - read_port 224 + port="$(prompt_for_port)" 225 + DEFAULT_PORT="$port" 226 + 227 + managed_pid="$(find_managed_server 2>/dev/null || true)" 228 + if [[ -n "$managed_pid" ]]; then 229 + printf 'Managed UXET server already running.\n' 230 + printf 'URL: %s\n' "$(server_url "$STATE_PORT")" 231 + printf 'Log: %s\n' "$LOG_FILE" 232 + describe_pid "$managed_pid" 233 + return 0 234 + fi 235 + 236 + listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)" 237 + if [[ -n "$listener_pid" ]]; then 238 + printf 'Port %s is already occupied by an unmanaged process.\n' "$port" 239 + describe_pid "$listener_pid" 240 + printf 'Refusing to replace it.\n' 241 + return 1 242 + fi 243 + 244 + if load_state && [[ -n "$STATE_PORT" ]]; then 245 + if [[ -z "$(get_listening_pids "$STATE_PORT")" ]]; then 246 + printf 'Removing stale state file.\n' 247 + remove_state 248 + fi 249 + fi 39 250 40 - printf 'Starting UXET server on %s\n' "$(server_url "$DEFAULT_PORT")" 41 - ( 42 - cd "$ROOT_DIR" && 43 - nohup python3 -m http.server "$DEFAULT_PORT" >"$LOG_FILE" 2>&1 & 44 - echo $! >"$PID_FILE" 45 - ) 251 + printf 'Starting UXET server on %s\n' "$(server_url "$port")" 252 + nohup bash -c 'cd "$1" && exec python3 -m http.server "$2"' _ "$ROOT_DIR" "$port" >>"$LOG_FILE" 2>&1 & 253 + pid=$! 46 254 47 - sleep 1 255 + write_state "$pid" "$port" 48 256 49 - if server_running; then 50 - printf 'Server started. Log: %s\n' "$LOG_FILE" 51 - else 52 - printf 'Failed to start server. Check %s\n' "$LOG_FILE" 53 - rm -f "$PID_FILE" 257 + if wait_for_port_state "$port" listening "$START_TIMEOUT_SECONDS" && is_managed_server "$pid" "$port"; then 258 + printf 'Server started.\n' 259 + printf 'URL: %s\n' "$(server_url "$port")" 260 + printf 'Log: %s\n' "$LOG_FILE" 261 + describe_pid "$pid" 262 + return 0 263 + fi 264 + 265 + printf 'Failed to verify startup.\n' 266 + if kill -0 "$pid" 2>/dev/null; then 267 + kill "$pid" 2>/dev/null || true 268 + sleep 1 269 + kill -9 "$pid" 2>/dev/null || true 54 270 fi 271 + remove_state 272 + printf 'Check log: %s\n' "$LOG_FILE" 273 + return 1 55 274 } 56 275 57 276 stop_server() { 58 - if ! server_running; then 59 - printf 'Server is not running.\n' 60 - rm -f "$PID_FILE" 61 - return 277 + local managed_pid listener_pid port remaining 278 + 279 + managed_pid="$(find_managed_server 2>/dev/null || true)" 280 + 281 + if [[ -n "$managed_pid" ]]; then 282 + port="$STATE_PORT" 283 + printf 'Stopping managed UXET server on %s\n' "$(server_url "$port")" 284 + kill "$managed_pid" 2>/dev/null || true 285 + 286 + remaining="$STOP_TIMEOUT_SECONDS" 287 + while (( remaining > 0 )); do 288 + if ! is_pid_listening_on_port "$managed_pid" "$port" && ! kill -0 "$managed_pid" 2>/dev/null; then 289 + remove_state 290 + printf 'Server stopped.\n' 291 + return 0 292 + fi 293 + sleep 1 294 + ((remaining--)) 295 + done 296 + 297 + if is_pid_listening_on_port "$managed_pid" "$port" || kill -0 "$managed_pid" 2>/dev/null; then 298 + printf 'Server did not exit cleanly, sending SIGKILL.\n' 299 + kill -9 "$managed_pid" 2>/dev/null || true 300 + fi 301 + 302 + remaining="$STOP_TIMEOUT_SECONDS" 303 + while (( remaining > 0 )); do 304 + if ! is_pid_listening_on_port "$managed_pid" "$port" && ! kill -0 "$managed_pid" 2>/dev/null; then 305 + if [[ -z "$(get_listening_pids "$port")" ]]; then 306 + remove_state 307 + printf 'Server stopped.\n' 308 + return 0 309 + fi 310 + break 311 + fi 312 + sleep 1 313 + ((remaining--)) 314 + done 315 + 316 + printf 'Shutdown failed verification.\n' 317 + listener_pid="$(get_listening_pids "$port" | head -n 1)" 318 + if [[ -n "$listener_pid" ]]; then 319 + printf 'Port %s is still occupied.\n' "$port" 320 + describe_pid "$listener_pid" 321 + fi 322 + return 1 62 323 fi 63 324 64 - local pid 65 - pid="$(<"$PID_FILE")" 66 - printf 'Stopping server pid %s\n' "$pid" 67 - kill "$pid" 2>/dev/null || true 325 + if load_state && [[ -n "$STATE_PORT" ]]; then 326 + port="$STATE_PORT" 327 + if [[ -z "$(get_listening_pids "$port")" ]]; then 328 + printf 'State file is stale. Removing it.\n' 329 + remove_state 330 + printf 'Server is not running.\n' 331 + return 0 332 + fi 68 333 69 - for _ in 1 2 3 4 5; do 70 - if ! kill -0 "$pid" 2>/dev/null; then 71 - break 334 + listener_pid="$(get_listening_pids "$port" | head -n 1)" 335 + if [[ -n "$listener_pid" ]]; then 336 + printf 'A process is listening on %s but it is not a verified UXET-managed server.\n' "$(server_url "$port")" 337 + describe_pid "$listener_pid" 338 + printf 'Refusing to stop it.\n' 339 + return 1 72 340 fi 73 - sleep 1 74 - done 341 + fi 75 342 76 - if kill -0 "$pid" 2>/dev/null; then 77 - printf 'Server did not exit cleanly, sending SIGKILL.\n' 78 - kill -9 "$pid" 2>/dev/null || true 343 + port="$DEFAULT_PORT" 344 + listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)" 345 + if [[ -n "$listener_pid" ]]; then 346 + printf 'Port %s is occupied by an unmanaged process.\n' "$port" 347 + describe_pid "$listener_pid" 348 + printf 'Refusing to stop it.\n' 349 + return 1 79 350 fi 80 351 81 - rm -f "$PID_FILE" 82 - printf 'Server stopped.\n' 352 + printf 'Server is not running.\n' 353 + return 0 83 354 } 84 355 85 356 show_status() { 86 - if server_running; then 87 - printf 'Status: running\n' 88 - printf 'PID: %s\n' "$(cat "$PID_FILE")" 89 - printf 'URL: %s\n' "$(server_url "$DEFAULT_PORT")" 357 + local managed_pid listener_pid port 358 + 359 + managed_pid="$(find_managed_server 2>/dev/null || true)" 360 + if [[ -n "$managed_pid" ]]; then 361 + printf 'Status: running (managed)\n' 362 + printf 'URL: %s\n' "$(server_url "$STATE_PORT")" 363 + printf 'Log: %s\n' "$LOG_FILE" 364 + printf 'Root: %s\n' "$ROOT_DIR" 365 + describe_pid "$managed_pid" 366 + return 0 367 + fi 368 + 369 + if load_state && [[ -n "$STATE_PORT" ]]; then 370 + port="$STATE_PORT" 371 + listener_pid="$(get_listening_pids "$port" | head -n 1)" 372 + if [[ -z "$listener_pid" ]]; then 373 + printf 'Status: stale state file\n' 374 + printf 'Expected port: %s\n' "$port" 375 + printf 'Log: %s\n' "$LOG_FILE" 376 + printf 'Root: %s\n' "$ROOT_DIR" 377 + return 0 378 + fi 379 + 380 + printf 'Status: port occupied by unmanaged process\n' 381 + printf 'Port: %s\n' "$port" 382 + printf 'URL: %s\n' "$(server_url "$port")" 90 383 printf 'Log: %s\n' "$LOG_FILE" 91 - else 92 - printf 'Status: stopped\n' 93 - printf 'Default URL: %s\n' "$(server_url "$DEFAULT_PORT")" 384 + printf 'Root: %s\n' "$ROOT_DIR" 385 + printf 'State file does not match the live listener.\n' 386 + describe_pid "$listener_pid" 387 + return 0 94 388 fi 389 + 390 + port="$DEFAULT_PORT" 391 + listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)" 392 + if [[ -n "$listener_pid" ]]; then 393 + printf 'Status: port occupied by unmanaged process\n' 394 + printf 'Port: %s\n' "$port" 395 + printf 'URL: %s\n' "$(server_url "$port")" 396 + printf 'Log: %s\n' "$LOG_FILE" 397 + printf 'Root: %s\n' "$ROOT_DIR" 398 + describe_pid "$listener_pid" 399 + return 0 400 + fi 401 + 402 + printf 'Status: stopped\n' 403 + printf 'Default URL: %s\n' "$(server_url "$port")" 404 + printf 'Log: %s\n' "$LOG_FILE" 405 + printf 'Root: %s\n' "$ROOT_DIR" 95 406 } 96 407 97 408 show_log_tail() { 98 409 if [[ ! -f "$LOG_FILE" ]]; then 99 410 printf 'No log file yet.\n' 100 - return 411 + return 0 101 412 fi 102 413 103 414 printf '\nLast 20 log lines:\n\n' ··· 108 419 main_menu() { 109 420 while true; do 110 421 printf '\nUXET Server Control\n' 111 - printf 'Repo: %s\n\n' "$ROOT_DIR" 422 + printf 'Repo: %s\n' "$ROOT_DIR" 423 + printf 'Safety: only verified UXET-managed servers will be stopped.\n\n' 112 424 113 425 PS3="Choose an action: " 114 - select action in "Start server" "Stop server" "Status" "Show log tail" "Quit"; do 426 + select action in "Start server" "Stop managed server" "Status" "Show log tail" "Quit"; do 115 427 case "$REPLY" in 116 428 1) start_server; break ;; 117 429 2) stop_server; break ;;