Files
ss-tools/run.sh
busya 7aa3bd52ef fix: add PYTHONUNBUFFERED=1 to run.sh to prevent stderr buffering
Without PYTHONUNBUFFERED, when stderr is piped through '2>&1 | sed',
Python fully buffers stderr output. JSON log lines are small and never
fill the buffer, so they never appear in real time (or at all for
sporadic HTTP request logs). Startup logs appeared because the buffer
flushed on process exit during Alembic migration.

Also kept --reload removed as it breaks stderr capture from child process.
2026-05-27 10:19:16 +03:00

235 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
# #region run [C:2] [TYPE Module]
# @PURPOSE: Utility script for run
# @COMPLEXITY: 1
# Project Launch Script
# Automates setup and concurrent execution of backend and frontend servers.
set -e
# Default configuration
BACKEND_PORT=${BACKEND_PORT:-8000}
FRONTEND_PORT=${FRONTEND_PORT:-5173}
SKIP_INSTALL=false
# Help message
show_help() {
echo "Usage: ./run.sh [options]"
echo ""
echo "Options:"
echo " --help Show this help message"
echo " --skip-install Skip dependency checks and installation"
echo ""
echo "Environment Variables:"
echo " BACKEND_PORT Port for the backend server (default: 8000)"
echo " FRONTEND_PORT Port for the frontend server (default: 5173)"
}
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--help) show_help; exit 0 ;;
--skip-install) SKIP_INSTALL=true ;;
*) echo "Unknown parameter passed: $1"; show_help; exit 1 ;;
esac
shift
done
echo "Starting Project Launch Script..."
# Environment validation
validate_env() {
echo "Validating environment..."
if ! command -v python3 &> /dev/null; then
echo "Error: python3 is not installed."
exit 1
fi
if ! python3 -c 'import sys; exit(0) if sys.version_info >= (3, 9) else exit(1)'; then
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Error: python3 version 3.9 or higher is required. Found $PYTHON_VERSION"
exit 1
fi
if ! command -v npm &> /dev/null; then
echo "Error: npm is not installed."
exit 1
fi
PYTHON_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
echo "Environment validation passed (Python $PYTHON_VERSION, npm $(npm -v))"
}
validate_env
# Database connectivity preflight
check_database() {
# Keep resolution order aligned with backend/src/core/database.py defaults.
local DB_URL="${DATABASE_URL:-${POSTGRES_URL:-postgresql+psycopg2://postgres:postgres@localhost:5432/ss_tools}}"
# SQLite does not require external service.
if [[ "$DB_URL" == sqlite* ]]; then
echo "Database preflight: sqlite detected, skipping PostgreSQL connectivity check."
return
fi
local DB_HOST DB_PORT
read -r DB_HOST DB_PORT < <(
python3 - "$DB_URL" <<'PY'
import sys
from urllib.parse import urlparse
url = sys.argv[1]
if "://" not in url:
print("localhost 5432")
raise SystemExit(0)
# Support SQLAlchemy schemes like postgresql+psycopg2://...
scheme, rest = url.split("://", 1)
parsed = urlparse(f"{scheme.split('+', 1)[0]}://{rest}")
host = parsed.hostname or "localhost"
port = parsed.port or 5432
print(f"{host} {port}")
PY
)
local check_cmd
check_cmd='import socket,sys; socket.create_connection((sys.argv[1], int(sys.argv[2])), timeout=1).close()'
if python3 -c "$check_cmd" "$DB_HOST" "$DB_PORT" >/dev/null 2>&1; then
echo "Database preflight: reachable at ${DB_HOST}:${DB_PORT}."
return
fi
echo "Database preflight: cannot connect to ${DB_HOST}:${DB_PORT}."
# For local development defaults, attempt to auto-start bundled PostgreSQL.
if [ "$DB_HOST" = "localhost" ] && [ "$DB_PORT" = "5432" ] && command -v docker >/dev/null 2>&1; then
if [ -f "docker-compose.yml" ]; then
echo "Attempting to start local PostgreSQL via docker compose (service: db)..."
docker compose up -d db || true
fi
fi
for _ in {1..20}; do
if python3 -c "$check_cmd" "$DB_HOST" "$DB_PORT" >/dev/null 2>&1; then
echo "Database preflight: reachable at ${DB_HOST}:${DB_PORT}."
return
fi
sleep 1
done
echo "Error: PostgreSQL is unavailable at ${DB_HOST}:${DB_PORT}."
echo "Run: docker compose up -d db"
echo "Or set DATABASE_URL/POSTGRES_URL to a reachable database."
exit 1
}
check_database
# Backend dependency management
setup_backend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping backend installation..."
return
fi
echo "Setting up backend..."
cd backend
if [ ! -d ".venv" ]; then
echo "Creating virtual environment..."
python3 -m venv .venv
fi
source .venv/bin/activate
if [ -f "requirements.txt" ]; then
echo "Installing backend dependencies..."
pip install -r requirements.txt
else
echo "Warning: backend/requirements.txt not found."
fi
cd ..
}
# Frontend dependency management
setup_frontend() {
if [ "$SKIP_INSTALL" = true ]; then
echo "Skipping frontend installation..."
return
fi
echo "Setting up frontend..."
cd frontend
if [ ! -d "node_modules" ]; then
echo "Installing frontend dependencies..."
npm install
else
echo "frontend/node_modules already exists. Skipping npm install."
fi
cd ..
}
setup_backend
setup_frontend
# Cleanup function for graceful shutdown
cleanup() {
echo ""
echo "Stopping services..."
if [ -n "$BACKEND_PID" ]; then
kill $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill $FRONTEND_PID 2>/dev/null || true
fi
echo "Services stopped."
exit 0
}
# Trap SIGINT (Ctrl+C)
trap cleanup SIGINT
# Start Backend
start_backend() {
echo -e "\033[0;34m[Backend]\033[0m Starting on port $BACKEND_PORT..."
cd backend
local -a uvicorn_env_args=()
if [ -f ".venv/bin/activate" ]; then
source .venv/bin/activate
else
echo -e "\033[0;31m[Backend]\033[0m Warning: .venv/bin/activate not found. Attempting to run without venv."
fi
if [ -f ".env" ]; then
uvicorn_env_args=(--env-file .env)
fi
# PYTHONUNBUFFERED=1 prevents stderr buffering when piped through sed.
# Without it, JSON logs from superset_tools_app get stuck in the buffer
# and never appear in real time (or at all, for small log lines).
# NOTE: --reload also breaks stderr capture (subprocess child loses the pipe).
# For hot-reload during development, run uvicorn directly instead:
# cd backend && source .venv/bin/activate && uvicorn src.app:app --reload --port 8000
PYTHONUNBUFFERED=1 python3 -m uvicorn src.app:app --port "$BACKEND_PORT" "${uvicorn_env_args[@]}" 2>&1 | sed "s/^/$(echo -e '\033[0;34m[Backend]\033[0m ') /" &
BACKEND_PID=$!
cd ..
}
# Start Frontend
start_frontend() {
echo -e "\033[0;32m[Frontend]\033[0m Starting on port $FRONTEND_PORT..."
cd frontend
# Use a subshell to prefix output
npm run dev -- --port "$FRONTEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;32m[Frontend]\033[0m ') /" &
FRONTEND_PID=$!
cd ..
}
start_backend
start_frontend
echo "Services are running. Press Ctrl+C to stop."
wait
# [/DEF:run:Module]