diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:39:59 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:39:59 -0700 |
| commit | 0a6fa2a6566a6d20b9cd587f1d188869971871c5 (patch) | |
| tree | dd55166f736dbd21ce1d32702ad37982170902e7 /scripts/stack.sh | |
| parent | dcb417cbf251a427861b2cbeb50e7f6a9f06f212 (diff) | |
systemd
Diffstat (limited to 'scripts/stack.sh')
| -rwxr-xr-x | scripts/stack.sh | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/scripts/stack.sh b/scripts/stack.sh new file mode 100755 index 0000000..83f3920 --- /dev/null +++ b/scripts/stack.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +RUN_DIR="$ROOT_DIR/.run" +LOG_DIR="$RUN_DIR/logs" + +BACKEND_PID_FILE="$RUN_DIR/backend.pid" +FRONTEND_PID_FILE="$RUN_DIR/frontend.pid" +BACKEND_LOG="$LOG_DIR/backend.log" +FRONTEND_LOG="$LOG_DIR/frontend.log" + +BACKEND_HOST="${BACKEND_HOST:-127.0.0.1}" +BACKEND_PORT="${BACKEND_PORT:-8001}" +FRONTEND_HOST="${FRONTEND_HOST:-127.0.0.1}" +FRONTEND_PORT="${FRONTEND_PORT:-3001}" +API_BASE_URL="${NEXT_PUBLIC_API_BASE_URL:-http://${BACKEND_HOST}:${BACKEND_PORT}}" + +is_running() { + local pid_file="$1" + if [[ -f "$pid_file" ]]; then + local pid + pid="$(cat "$pid_file")" + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + return 0 + fi + rm -f "$pid_file" + fi + return 1 +} + +require_file() { + local path="$1" + local label="$2" + if [[ ! -e "$path" ]]; then + echo "$label not found: $path" >&2 + exit 1 + fi +} + +cmd_start() { + require_file "$ROOT_DIR/backend/.venv/bin/uvicorn" "Backend virtualenv executable" + require_file "$ROOT_DIR/frontend/node_modules" "Frontend dependencies" + mkdir -p "$LOG_DIR" + + if is_running "$BACKEND_PID_FILE"; then + echo "Backend already running on PID $(cat "$BACKEND_PID_FILE")" + else + ( + cd "$ROOT_DIR/backend" + exec .venv/bin/uvicorn app.main:app --reload --host "$BACKEND_HOST" --port "$BACKEND_PORT" + ) >"$BACKEND_LOG" 2>&1 & + echo $! >"$BACKEND_PID_FILE" + echo "Started backend on http://${BACKEND_HOST}:${BACKEND_PORT} (PID $(cat "$BACKEND_PID_FILE"))" + fi + + if is_running "$FRONTEND_PID_FILE"; then + echo "Frontend already running on PID $(cat "$FRONTEND_PID_FILE")" + else + ( + cd "$ROOT_DIR/frontend" + export NEXT_PUBLIC_API_BASE_URL="$API_BASE_URL" + exec npm run dev -- --hostname "$FRONTEND_HOST" --port "$FRONTEND_PORT" + ) >"$FRONTEND_LOG" 2>&1 & + echo $! >"$FRONTEND_PID_FILE" + echo "Started frontend on http://${FRONTEND_HOST}:${FRONTEND_PORT} (PID $(cat "$FRONTEND_PID_FILE"))" + fi + + cat <<EOF + +Stack status +- Frontend: http://${FRONTEND_HOST}:${FRONTEND_PORT} +- Backend: http://${BACKEND_HOST}:${BACKEND_PORT} +- Logs: $LOG_DIR +EOF +} + +stop_process() { + local name="$1" + local pid_file="$2" + if [[ ! -f "$pid_file" ]]; then + echo "$name not running" + return + fi + + local pid + pid="$(cat "$pid_file")" + + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + for _ in {1..20}; do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 0.25 + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + echo "Stopped $name (PID $pid)" + else + echo "$name pid file was stale" + fi + + rm -f "$pid_file" +} + +cmd_stop() { + stop_process "frontend" "$FRONTEND_PID_FILE" + stop_process "backend" "$BACKEND_PID_FILE" +} + +cmd_restart() { + cmd_stop + cmd_start +} + +cmd_status() { + if is_running "$BACKEND_PID_FILE"; then + echo "backend running PID $(cat "$BACKEND_PID_FILE") http://${BACKEND_HOST}:${BACKEND_PORT}" + else + echo "backend stopped" + fi + + if is_running "$FRONTEND_PID_FILE"; then + echo "frontend running PID $(cat "$FRONTEND_PID_FILE") http://${FRONTEND_HOST}:${FRONTEND_PORT}" + else + echo "frontend stopped" + fi +} + +case "${1:-}" in + start) cmd_start ;; + stop) cmd_stop ;; + restart) cmd_restart ;; + status) cmd_status ;; + *) + echo "Usage: $(basename "$0") {start|stop|restart|status}" >&2 + exit 1 + ;; +esac |
