Select the types of activity you want to include in your feed.
Simplify server startup script
- Replace the interactive server TUI with a direct `serve.sh` wrapper - Update README usage examples to match the new port-based startup flow - Stop tracking the stale server PID file in `.gitignore`
···1717### Start the server
18181919```bash
2020-./scripts/server-tui.sh
2020+./serve.sh
2121```
22222323-This opens an interactive menu where you can:
2323+This runs:
24242525-| Option | What it does |
2626-|--------|-------------|
2727-| **Start server** | Launches a Python HTTP server on a port you choose (default `8080`) |
2828-| **Stop managed server** | Gracefully kills the server UXET started |
2929-| **Status** | Shows whether the server is running, on which port, and its PID |
3030-| **Show log tail** | Prints the last 20 lines of the server log |
3131-| **Quit** | Exits the menu (the server keeps running in the background) |
2525+```bash
2626+python3 -m http.server 8080
2727+```
32283333-Once the server is up, open the URL it prints (usually `http://127.0.0.1:8080`) in your browser.
2929+You can also choose a different port:
3030+3131+```bash
3232+./serve.sh 9000
3333+```
3434+3535+Or via an environment variable:
3636+3737+```bash
3838+UXET_PORT=9000 ./serve.sh
3939+```
4040+4141+Once the server is up, open the URL in your browser, usually `http://127.0.0.1:8080`.
34423543> **Requires:** `python3` (used for `python3 -m http.server`). That's it.
3644···6876The `data-win` attribute defines when the test ends automatically. Supported formats:
69777078- `selector:.some-class` — fires when the CSS selector matches a visible element.
7171-- `text:Some text` — fires when the specified text appears in the page.7979+- `text:Some text` — fires when the specified text appears in the page.
-439
scripts/server-tui.sh
···11-#!/usr/bin/env bash
22-33-set -u
44-55-ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
66-DEFAULT_PORT="${UXET_PORT:-8080}"
77-STATE_FILE="$ROOT_DIR/.uxet-server.state"
88-LOG_FILE="$ROOT_DIR/.uxet-server.log"
99-START_TIMEOUT_SECONDS=5
1010-STOP_TIMEOUT_SECONDS=5
1111-1212-STATE_PID=""
1313-STATE_PORT=""
1414-STATE_START_TS=""
1515-STATE_ROOT_DIR=""
1616-STATE_CMD=""
1717-1818-server_url() {
1919- printf 'http://127.0.0.1:%s\n' "$1"
2020-}
2121-2222-prompt_for_port() {
2323- local input
2424- while true; do
2525- read -r -p "Port [$DEFAULT_PORT]: " input
2626- if [[ -z "${input:-}" ]]; then
2727- input="$DEFAULT_PORT"
2828- fi
2929-3030- if validate_port "$input"; then
3131- printf '%s\n' "$input"
3232- return 0
3333- fi
3434-3535- printf 'Invalid port. Enter a number between 1 and 65535.\n'
3636- done
3737-}
3838-3939-validate_port() {
4040- local port="$1"
4141- [[ "$port" =~ ^[0-9]+$ ]] || return 1
4242- (( port >= 1 && port <= 65535 ))
4343-}
4444-4545-write_state() {
4646- local pid="$1"
4747- local port="$2"
4848-4949- cat >"$STATE_FILE" <<EOF
5050-PID=$pid
5151-PORT=$port
5252-START_TS=$(date +%s)
5353-ROOT_DIR=$ROOT_DIR
5454-CMD=python3 -m http.server $port
5555-EOF
5656-5757- STATE_PID="$pid"
5858- STATE_PORT="$port"
5959- STATE_START_TS="$(date +%s)"
6060- STATE_ROOT_DIR="$ROOT_DIR"
6161- STATE_CMD="python3 -m http.server $port"
6262-}
6363-6464-remove_state() {
6565- rm -f "$STATE_FILE"
6666- STATE_PID=""
6767- STATE_PORT=""
6868- STATE_START_TS=""
6969- STATE_ROOT_DIR=""
7070- STATE_CMD=""
7171-}
7272-7373-load_state() {
7474- STATE_PID=""
7575- STATE_PORT=""
7676- STATE_START_TS=""
7777- STATE_ROOT_DIR=""
7878- STATE_CMD=""
7979-8080- if [[ ! -f "$STATE_FILE" ]]; then
8181- return 1
8282- fi
8383-8484- while IFS='=' read -r key value; do
8585- case "$key" in
8686- PID) STATE_PID="$value" ;;
8787- PORT) STATE_PORT="$value" ;;
8888- START_TS) STATE_START_TS="$value" ;;
8989- ROOT_DIR) STATE_ROOT_DIR="$value" ;;
9090- CMD) STATE_CMD="$value" ;;
9191- esac
9292- done <"$STATE_FILE"
9393-9494- return 0
9595-}
9696-9797-get_listening_pids() {
9898- local port="$1"
9999- lsof -nP -t -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | awk '!seen[$0]++'
100100-}
101101-102102-get_pid_cmdline() {
103103- local pid="$1"
104104- [[ -r "/proc/$pid/cmdline" ]] || return 1
105105- tr '\0' ' ' <"/proc/$pid/cmdline" | sed 's/[[:space:]]\+$//'
106106-}
107107-108108-get_pid_cwd() {
109109- local pid="$1"
110110- [[ -L "/proc/$pid/cwd" ]] || return 1
111111- readlink -f "/proc/$pid/cwd"
112112-}
113113-114114-describe_pid() {
115115- local pid="$1"
116116- local cmdline cwd
117117-118118- cmdline="$(get_pid_cmdline "$pid" 2>/dev/null || true)"
119119- cwd="$(get_pid_cwd "$pid" 2>/dev/null || true)"
120120-121121- printf 'PID: %s\n' "$pid"
122122- printf 'CWD: %s\n' "${cwd:-unknown}"
123123- printf 'CMD: %s\n' "${cmdline:-unknown}"
124124-}
125125-126126-is_pid_listening_on_port() {
127127- local pid="$1"
128128- local port="$2"
129129- local listening_pid
130130-131131- while IFS= read -r listening_pid; do
132132- [[ "$listening_pid" == "$pid" ]] && return 0
133133- done < <(get_listening_pids "$port")
134134-135135- return 1
136136-}
137137-138138-is_managed_server() {
139139- local pid="$1"
140140- local port="$2"
141141- local cmdline cwd
142142-143143- [[ -n "$pid" && -n "$port" ]] || return 1
144144- [[ "$STATE_PID" == "$pid" ]] || return 1
145145- [[ "$STATE_PORT" == "$port" ]] || return 1
146146- is_pid_listening_on_port "$pid" "$port" || return 1
147147-148148- cwd="$(get_pid_cwd "$pid" 2>/dev/null || true)"
149149- [[ "$cwd" == "$ROOT_DIR" ]] || return 1
150150- [[ -n "$STATE_ROOT_DIR" && "$STATE_ROOT_DIR" != "$ROOT_DIR" ]] && return 1
151151-152152- cmdline="$(get_pid_cmdline "$pid" 2>/dev/null || true)"
153153- [[ "$cmdline" == *python3* ]] || return 1
154154- [[ "$cmdline" == *"-m http.server"* ]] || return 1
155155- [[ "$cmdline" == *" $port"* || "$cmdline" == *" $port "* ]] || return 1
156156-157157- return 0
158158-}
159159-160160-find_managed_server() {
161161- local pid
162162-163163- load_state || return 1
164164- validate_port "$STATE_PORT" || return 1
165165-166166- for pid in $(get_listening_pids "$STATE_PORT"); do
167167- if is_managed_server "$pid" "$STATE_PORT"; then
168168- printf '%s\n' "$pid"
169169- return 0
170170- fi
171171- done
172172-173173- return 1
174174-}
175175-176176-find_unmanaged_listener() {
177177- local port="$1"
178178- local managed_pid pid
179179-180180- managed_pid="$(find_managed_server 2>/dev/null || true)"
181181- while IFS= read -r pid; do
182182- [[ -z "$pid" ]] && continue
183183- if [[ -n "$managed_pid" && "$pid" == "$managed_pid" ]]; then
184184- continue
185185- fi
186186- printf '%s\n' "$pid"
187187- return 0
188188- done < <(get_listening_pids "$port")
189189-190190- return 1
191191-}
192192-193193-wait_for_port_state() {
194194- local port="$1"
195195- local expected_state="$2"
196196- local timeout="$3"
197197- local remaining="$timeout"
198198-199199- while (( remaining > 0 )); do
200200- if [[ "$expected_state" == "listening" ]]; then
201201- if get_listening_pids "$port" >/dev/null && [[ -n "$(get_listening_pids "$port")" ]]; then
202202- return 0
203203- fi
204204- else
205205- if [[ -z "$(get_listening_pids "$port")" ]]; then
206206- return 0
207207- fi
208208- fi
209209- sleep 1
210210- ((remaining--))
211211- done
212212-213213- return 1
214214-}
215215-216216-start_server() {
217217- local port listener_pid managed_pid pid
218218-219219- if ! command -v python3 >/dev/null 2>&1; then
220220- printf 'python3 is required but was not found.\n'
221221- return 1
222222- fi
223223-224224- port="$(prompt_for_port)"
225225- DEFAULT_PORT="$port"
226226-227227- managed_pid="$(find_managed_server 2>/dev/null || true)"
228228- if [[ -n "$managed_pid" ]]; then
229229- printf 'Managed UXET server already running.\n'
230230- printf 'URL: %s\n' "$(server_url "$STATE_PORT")"
231231- printf 'Log: %s\n' "$LOG_FILE"
232232- describe_pid "$managed_pid"
233233- return 0
234234- fi
235235-236236- listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)"
237237- if [[ -n "$listener_pid" ]]; then
238238- printf 'Port %s is already occupied by an unmanaged process.\n' "$port"
239239- describe_pid "$listener_pid"
240240- printf 'Refusing to replace it.\n'
241241- return 1
242242- fi
243243-244244- if load_state && [[ -n "$STATE_PORT" ]]; then
245245- if [[ -z "$(get_listening_pids "$STATE_PORT")" ]]; then
246246- printf 'Removing stale state file.\n'
247247- remove_state
248248- fi
249249- fi
250250-251251- printf 'Starting UXET server on %s\n' "$(server_url "$port")"
252252- nohup bash -c 'cd "$1" && exec python3 -m http.server "$2"' _ "$ROOT_DIR" "$port" >>"$LOG_FILE" 2>&1 &
253253- pid=$!
254254-255255- write_state "$pid" "$port"
256256-257257- if wait_for_port_state "$port" listening "$START_TIMEOUT_SECONDS" && is_managed_server "$pid" "$port"; then
258258- printf 'Server started.\n'
259259- printf 'URL: %s\n' "$(server_url "$port")"
260260- printf 'Log: %s\n' "$LOG_FILE"
261261- describe_pid "$pid"
262262- return 0
263263- fi
264264-265265- printf 'Failed to verify startup.\n'
266266- if kill -0 "$pid" 2>/dev/null; then
267267- kill "$pid" 2>/dev/null || true
268268- sleep 1
269269- kill -9 "$pid" 2>/dev/null || true
270270- fi
271271- remove_state
272272- printf 'Check log: %s\n' "$LOG_FILE"
273273- return 1
274274-}
275275-276276-stop_server() {
277277- local managed_pid listener_pid port remaining
278278-279279- managed_pid="$(find_managed_server 2>/dev/null || true)"
280280-281281- if [[ -n "$managed_pid" ]]; then
282282- port="$STATE_PORT"
283283- printf 'Stopping managed UXET server on %s\n' "$(server_url "$port")"
284284- kill "$managed_pid" 2>/dev/null || true
285285-286286- remaining="$STOP_TIMEOUT_SECONDS"
287287- while (( remaining > 0 )); do
288288- if ! is_pid_listening_on_port "$managed_pid" "$port" && ! kill -0 "$managed_pid" 2>/dev/null; then
289289- remove_state
290290- printf 'Server stopped.\n'
291291- return 0
292292- fi
293293- sleep 1
294294- ((remaining--))
295295- done
296296-297297- if is_pid_listening_on_port "$managed_pid" "$port" || kill -0 "$managed_pid" 2>/dev/null; then
298298- printf 'Server did not exit cleanly, sending SIGKILL.\n'
299299- kill -9 "$managed_pid" 2>/dev/null || true
300300- fi
301301-302302- remaining="$STOP_TIMEOUT_SECONDS"
303303- while (( remaining > 0 )); do
304304- if ! is_pid_listening_on_port "$managed_pid" "$port" && ! kill -0 "$managed_pid" 2>/dev/null; then
305305- if [[ -z "$(get_listening_pids "$port")" ]]; then
306306- remove_state
307307- printf 'Server stopped.\n'
308308- return 0
309309- fi
310310- break
311311- fi
312312- sleep 1
313313- ((remaining--))
314314- done
315315-316316- printf 'Shutdown failed verification.\n'
317317- listener_pid="$(get_listening_pids "$port" | head -n 1)"
318318- if [[ -n "$listener_pid" ]]; then
319319- printf 'Port %s is still occupied.\n' "$port"
320320- describe_pid "$listener_pid"
321321- fi
322322- return 1
323323- fi
324324-325325- if load_state && [[ -n "$STATE_PORT" ]]; then
326326- port="$STATE_PORT"
327327- if [[ -z "$(get_listening_pids "$port")" ]]; then
328328- printf 'State file is stale. Removing it.\n'
329329- remove_state
330330- printf 'Server is not running.\n'
331331- return 0
332332- fi
333333-334334- listener_pid="$(get_listening_pids "$port" | head -n 1)"
335335- if [[ -n "$listener_pid" ]]; then
336336- printf 'A process is listening on %s but it is not a verified UXET-managed server.\n' "$(server_url "$port")"
337337- describe_pid "$listener_pid"
338338- printf 'Refusing to stop it.\n'
339339- return 1
340340- fi
341341- fi
342342-343343- port="$DEFAULT_PORT"
344344- listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)"
345345- if [[ -n "$listener_pid" ]]; then
346346- printf 'Port %s is occupied by an unmanaged process.\n' "$port"
347347- describe_pid "$listener_pid"
348348- printf 'Refusing to stop it.\n'
349349- return 1
350350- fi
351351-352352- printf 'Server is not running.\n'
353353- return 0
354354-}
355355-356356-show_status() {
357357- local managed_pid listener_pid port
358358-359359- managed_pid="$(find_managed_server 2>/dev/null || true)"
360360- if [[ -n "$managed_pid" ]]; then
361361- printf 'Status: running (managed)\n'
362362- printf 'URL: %s\n' "$(server_url "$STATE_PORT")"
363363- printf 'Log: %s\n' "$LOG_FILE"
364364- printf 'Root: %s\n' "$ROOT_DIR"
365365- describe_pid "$managed_pid"
366366- return 0
367367- fi
368368-369369- if load_state && [[ -n "$STATE_PORT" ]]; then
370370- port="$STATE_PORT"
371371- listener_pid="$(get_listening_pids "$port" | head -n 1)"
372372- if [[ -z "$listener_pid" ]]; then
373373- printf 'Status: stale state file\n'
374374- printf 'Expected port: %s\n' "$port"
375375- printf 'Log: %s\n' "$LOG_FILE"
376376- printf 'Root: %s\n' "$ROOT_DIR"
377377- return 0
378378- fi
379379-380380- printf 'Status: port occupied by unmanaged process\n'
381381- printf 'Port: %s\n' "$port"
382382- printf 'URL: %s\n' "$(server_url "$port")"
383383- printf 'Log: %s\n' "$LOG_FILE"
384384- printf 'Root: %s\n' "$ROOT_DIR"
385385- printf 'State file does not match the live listener.\n'
386386- describe_pid "$listener_pid"
387387- return 0
388388- fi
389389-390390- port="$DEFAULT_PORT"
391391- listener_pid="$(find_unmanaged_listener "$port" 2>/dev/null || true)"
392392- if [[ -n "$listener_pid" ]]; then
393393- printf 'Status: port occupied by unmanaged process\n'
394394- printf 'Port: %s\n' "$port"
395395- printf 'URL: %s\n' "$(server_url "$port")"
396396- printf 'Log: %s\n' "$LOG_FILE"
397397- printf 'Root: %s\n' "$ROOT_DIR"
398398- describe_pid "$listener_pid"
399399- return 0
400400- fi
401401-402402- printf 'Status: stopped\n'
403403- printf 'Default URL: %s\n' "$(server_url "$port")"
404404- printf 'Log: %s\n' "$LOG_FILE"
405405- printf 'Root: %s\n' "$ROOT_DIR"
406406-}
407407-408408-show_log_tail() {
409409- if [[ ! -f "$LOG_FILE" ]]; then
410410- printf 'No log file yet.\n'
411411- return 0
412412- fi
413413-414414- printf '\nLast 20 log lines:\n\n'
415415- tail -n 20 "$LOG_FILE"
416416- printf '\n'
417417-}
418418-419419-main_menu() {
420420- while true; do
421421- printf '\nUXET Server Control\n'
422422- printf 'Repo: %s\n' "$ROOT_DIR"
423423- printf 'Safety: only verified UXET-managed servers will be stopped.\n\n'
424424-425425- PS3="Choose an action: "
426426- select action in "Start server" "Stop managed server" "Status" "Show log tail" "Quit"; do
427427- case "$REPLY" in
428428- 1) start_server; break ;;
429429- 2) stop_server; break ;;
430430- 3) show_status; break ;;
431431- 4) show_log_tail; break ;;
432432- 5) exit 0 ;;
433433- *) printf 'Invalid choice.\n'; break ;;
434434- esac
435435- done
436436- done
437437-}
438438-439439-main_menu