summaryrefslogtreecommitdiff
path: root/scripts/stack.sh
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-19 00:39:59 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-19 00:39:59 -0700
commit0a6fa2a6566a6d20b9cd587f1d188869971871c5 (patch)
treedd55166f736dbd21ce1d32702ad37982170902e7 /scripts/stack.sh
parentdcb417cbf251a427861b2cbeb50e7f6a9f06f212 (diff)
systemd
Diffstat (limited to 'scripts/stack.sh')
-rwxr-xr-xscripts/stack.sh142
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