#!/bin/bash
set -uo pipefail
WORK_HOME=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
BACKEND_DIR="${WORK_HOME}/agent-studio/backend"
FRONTEND_DIR="${WORK_HOME}/agent-studio/frontend"
TARGET_ENV_FILE="${WORK_HOME}/agent-studio/.env"
ENV_EXAMPLE_FILE="${WORK_HOME}/agent-studio/.env.example"
LOG_FILE="${WORK_HOME}/setup.log"
PROGRESS_FILE="${WORK_HOME}/.setup_progress"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
local LEVEL=$1
shift
local MSG="$*"
local TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
local LOG_MSG="[${TIMESTAMP}] [${LEVEL}] ${MSG}"
case "$LEVEL" in
ERROR)
echo -e "${RED}${LOG_MSG}${NC}" >&2
;;
WARN)
echo -e "${YELLOW}${LOG_MSG}${NC}"
;;
SUCCESS)
echo -e "${GREEN}${LOG_MSG}${NC}"
;;
INFO)
echo -e "${BLUE}${LOG_MSG}${NC}"
;;
*)
echo "${LOG_MSG}"
;;
esac
echo "${LOG_MSG}" >> "$LOG_FILE" 2>/dev/null || true
}
save_progress() {
local STEP=$1
echo "$STEP" > "$PROGRESS_FILE" 2>/dev/null || true
log "INFO" "Progress saved: $STEP"
}
read_progress() {
if [ -f "$PROGRESS_FILE" ]; then
cat "$PROGRESS_FILE" 2>/dev/null || echo ""
else
echo ""
fi
}
clear_progress() {
rm -f "$PROGRESS_FILE" 2>/dev/null || true
}
find_compatible_python() {
local candidates=("python3.13" "python3.12" "python3.11" "python3")
local cmd ver major minor
for cmd in "${candidates[@]}"; do
if command -v "$cmd" > /dev/null 2>&1; then
ver=$("$cmd" -c 'import sys,math; v=sys.version_info; print(f"{v[0]}.{v[1]}.{v[2]}")' 2>/dev/null || echo "")
major=${ver%%.*}
minor=${ver#*.}; minor=${minor%%.*}
if [ "$major" = "3" ] && [ "$minor" -ge 11 ] && [ "$minor" -le 13 ]; then
echo "$cmd"
return 0
fi
fi
done
return 1
}
ensure_python_compatible() {
if [ -n "${PYTHON_CMD:-}" ]; then
return 0
fi
local cmd
cmd=$(find_compatible_python) || cmd=""
if [ -n "$cmd" ]; then
PYTHON_CMD="$cmd"
log "INFO" "Compatible Python found: $($cmd -V 2>/dev/null || echo "$cmd")"
return 0
fi
if command -v brew > /dev/null 2>&1; then
log "INFO" "Python not found, installing Python 3.11 via Homebrew..."
if ! retry_execute 1 5 "Install Python 3.11" "brew install python@3.11"; then
error_exit "Install Python 3.11 failed; re-run the script or install Python 3.11 manually"
fi
PYTHON_CMD="python3.11"
log "SUCCESS" "Python 3.11 installed, using: $PYTHON_CMD"
return 0
fi
error_exit "Python not found; re-run the script or install Python 3.11 manually"
}
retry_execute() {
local MAX_RETRIES=${1:-3}
local RETRY_DELAY=${2:-5}
local STEP_NAME="$3"
shift 3
local COMMAND="$*"
local ATTEMPT=1
local LAST_ERROR=1
while [ $ATTEMPT -le $MAX_RETRIES ]; do
log "INFO" "[Attempt $ATTEMPT/$MAX_RETRIES] Running: $STEP_NAME"
eval "$COMMAND" 2>&1
local err=$?
if [ "$err" -eq 0 ]; then
log "SUCCESS" "$STEP_NAME completed"
return 0
fi
LAST_ERROR=$err
log "WARN" "$STEP_NAME failed (exit ${LAST_ERROR}), retrying in ${RETRY_DELAY}s..."
if [ $ATTEMPT -lt $MAX_RETRIES ]; then
sleep $RETRY_DELAY
fi
ATTEMPT=$((ATTEMPT + 1))
done
log "ERROR" "$STEP_NAME failed after $MAX_RETRIES attempts"
LAST_ERROR=${LAST_ERROR:-1}
return $LAST_ERROR
}
error_exit() {
local MSG="$1"
local RECOVERY_HINT="${2:-}"
log "ERROR" "========================================="
log "ERROR" "Deployment failed: $MSG"
if [ -n "$RECOVERY_HINT" ]; then
log "ERROR" ""
log "ERROR" "Recovery suggestion:"
echo -e "${YELLOW}$RECOVERY_HINT${NC}" >&2
fi
log "ERROR" "========================================="
log "ERROR" "See log file: $LOG_FILE"
log "ERROR" "Progress saved; re-run the script to continue"
exit 1
}
load_environments() {
if [ -s "$HOME/.nvm/nvm.sh" ]; then
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 2>/dev/null || true
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" 2>/dev/null || true
fi
if [ -d "$HOME/.pyenv" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [[ ":$PATH:" != *":$PYENV_ROOT/bin:"* ]]; then
export PATH="$PYENV_ROOT/bin:$PATH"
fi
if command -v pyenv &> /dev/null; then
eval "$(pyenv init -)" 2>/dev/null || true
eval "$(pyenv init --path)" 2>/dev/null || true
fi
fi
if [ -s "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/miniconda3/etc/profile.d/conda.sh" 2>/dev/null || true
elif [ -s "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/anaconda3/etc/profile.d/conda.sh" 2>/dev/null || true
elif [ -s "/opt/conda/etc/profile.d/conda.sh" ]; then
source "/opt/conda/etc/profile.d/conda.sh" 2>/dev/null || true
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
if [ -d "/root/.local/bin" ] && [[ ":$PATH:" != *":/root/.local/bin:"* ]]; then
export PATH="/root/.local/bin:$PATH"
fi
}
check_command() {
local CMD="${1:-}"
load_environments
if ! command -v "${CMD:-}" &> /dev/null; then
case "${CMD:-}" in
node|npm)
if [ -s "$HOME/.nvm/nvm.sh" ]; then
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 2>/dev/null || true
fi
;;
uv)
for PY_BIN in "$HOME/Library/Python/"*/bin/uv; do
if [ -f "$PY_BIN" ]; then
PY_BIN_DIR=$(dirname "$PY_BIN")
if [[ ":$PATH:" != *":$PY_BIN_DIR:"* ]]; then
export PATH="$PY_BIN_DIR:$PATH"
fi
break
fi
done
;;
python3.11)
load_environments
if [ -d "$HOME/.pyenv" ]; then
export PYENV_ROOT="$HOME/.pyenv"
if [[ ":$PATH:" != *":$PYENV_ROOT/bin:"* ]]; then
export PATH="$PYENV_ROOT/bin:$PATH"
fi
if command -v pyenv &> /dev/null; then
eval "$(pyenv init -)" 2>/dev/null || true
eval "$(pyenv init --path)" 2>/dev/null || true
fi
if [ -d "$PYENV_ROOT/shims" ] && [[ ":$PATH:" != *":$PYENV_ROOT/shims:"* ]]; then
export PATH="$PYENV_ROOT/shims:$PATH"
fi
for pyenv_python in "$PYENV_ROOT"/versions/*/bin/python3.11; do
if [ -f "$pyenv_python" ]; then
PYENV_BIN_DIR=$(dirname "$pyenv_python")
if [[ ":$PATH:" != *":$PYENV_BIN_DIR:"* ]]; then
export PATH="$PYENV_BIN_DIR:$PATH"
fi
break
fi
done
fi
if [ -s "$HOME/miniconda3/envs/py311/bin/python3.11" ]; then
load_environments
if [ ! -f "/usr/local/bin/python3.11" ] && [ -f "$HOME/miniconda3/envs/py311/bin/python3.11" ]; then
log "WARN" "python3.11 in conda env but symlink missing"
fi
fi
if [ -f "/opt/homebrew/bin/python3.11" ] && [[ ":$PATH:" != *":/opt/homebrew/bin:"* ]]; then
export PATH="/opt/homebrew/bin:$PATH"
elif [ -f "/usr/local/bin/python3.11" ] && [[ ":$PATH:" != *":/usr/local/bin:"* ]]; then
export PATH="/usr/local/bin:$PATH"
fi
;;
esac
if ! command -v "${CMD:-}" &> /dev/null; then
CMD_VALUE="${CMD:-unknown}"
ERROR_MSG="Required command '${CMD_VALUE}' not found"
RECOVERY_MSG="Install ${CMD_VALUE} and re-run the script"
error_exit "$ERROR_MSG" "$RECOVERY_MSG"
fi
fi
}
check_file() {
local FILE=$1
local CREATE_IF_NOT_EXIST=${2:-false}
if [ ! -f "$FILE" ]; then
if [ "$CREATE_IF_NOT_EXIST" = true ]; then
log "WARN" "File $FILE not found, creating empty file"
mkdir -p "$(dirname "$FILE")" && touch "$FILE" || {
error_exit "Failed to create $FILE" \
"Check directory permissions: $(dirname "$FILE")"
}
else
error_exit "File $FILE not found, cannot continue" \
"Ensure code is fetched or create the file manually"
fi
fi
}
check_dir() {
local DIR=$1
if [ ! -d "$DIR" ]; then
error_exit "Directory $DIR not found, cannot continue" \
"Ensure code is fetched or create directory: mkdir -p $DIR"
fi
}
check_script_permission() {
local SCRIPT=$1
if [ -f "$SCRIPT" ] && [ ! -x "$SCRIPT" ]; then
log "WARN" "Script $SCRIPT missing execute permission, fixing..."
chmod +x "$SCRIPT" || {
log "WARN" "Could not add execute permission to $SCRIPT, will run with bash"
return 1
}
log "SUCCESS" "Execute permission added to $SCRIPT"
fi
}
check_mysql_service() {
if brew services list 2>/dev/null | grep -q "mysql.*started"; then
return 0
fi
if pgrep -x mysqld > /dev/null 2>&1 || pgrep -f "mysqld" > /dev/null 2>&1; then
return 0
fi
if lsof -i :3306 > /dev/null 2>&1; then
return 0
fi
return 1
}
start_mysql_service() {
log "INFO" "Starting MySQL service..."
if command -v brew &> /dev/null; then
if brew services start mysql 2>/dev/null; then
log "SUCCESS" "MySQL service started (via Homebrew)"
sleep 3
return 0
fi
fi
local MYSQLD_PATH=""
if [ -f "/opt/homebrew/bin/mysqld_safe" ]; then
MYSQLD_PATH="/opt/homebrew/bin/mysqld_safe"
elif [ -f "/usr/local/bin/mysqld_safe" ]; then
MYSQLD_PATH="/usr/local/bin/mysqld_safe"
elif [ -f "/usr/local/mysql/bin/mysqld_safe" ]; then
MYSQLD_PATH="/usr/local/mysql/bin/mysqld_safe"
fi
if [ -n "$MYSQLD_PATH" ] && [ -x "$MYSQLD_PATH" ]; then
log "INFO" "Starting MySQL service directly..."
nohup "$MYSQLD_PATH" > /dev/null 2>&1 &
sleep 3
if check_mysql_service; then
log "SUCCESS" "MySQL service started"
return 0
fi
fi
log "WARN" "Could not start MySQL service automatically"
return 1
}
verify_mysql_command() {
local MYSQL_CMD="$1"
if [ -f "$MYSQL_CMD" ] && [ -x "$MYSQL_CMD" ]; then
if "$MYSQL_CMD" --version > /dev/null 2>&1; then
return 0
fi
fi
if command -v "$MYSQL_CMD" &> /dev/null; then
if "$MYSQL_CMD" --version > /dev/null 2>&1; then
return 0
fi
fi
return 1
}
check_and_install_greenlet() {
local BACKEND_DIR="$1"
if [[ "$(uname -s)" != "Darwin" ]]; then
return 0
fi
log "INFO" "Checking greenlet module (Apple Silicon compatibility)"
cd "$BACKEND_DIR" || {
log "WARN" "Cannot enter backend dir, skipping greenlet check"
return 0
}
if [ ! -f ".venv/bin/activate" ]; then
log "WARN" "Venv not found, skipping greenlet check"
return 0
fi
local WAS_IN_VENV=false
if [ -n "${VIRTUAL_ENV:-}" ]; then
WAS_IN_VENV=true
log "INFO" "In venv, deactivating to install greenlet"
deactivate 2>/dev/null || true
fi
local GREENLET_INSTALLED=false
if source .venv/bin/activate 2>/dev/null; then
if python3 -c "import greenlet" 2>/dev/null; then
GREENLET_INSTALLED=true
log "SUCCESS" "greenlet module already installed"
fi
deactivate 2>/dev/null || true
fi
if [ "$GREENLET_INSTALLED" = false ]; then
log "WARN" "greenlet not installed, installing..."
log "INFO" "Some Apple Silicon Mac Python builds lack greenlet in stdlib"
if [ -n "${VIRTUAL_ENV:-}" ]; then
deactivate 2>/dev/null || true
fi
check_command "uv"
log "INFO" "Installing greenlet via uv add..."
if ! retry_execute 3 10 "Install greenlet" "uv add greenlet"; then
log "ERROR" "greenlet install failed"
error_exit "greenlet installation failed" \
"1. Check network\n\
2. Check uv: uv --version\n\
3. Manual: cd $BACKEND_DIR && deactivate && uv add greenlet && source .venv/bin/activate"
fi
log "SUCCESS" "greenlet installed"
if source .venv/bin/activate 2>/dev/null; then
if python3 -c "import greenlet" 2>/dev/null; then
log "SUCCESS" "greenlet module verified"
else
log "WARN" "greenlet verification failed after install, continuing"
fi
deactivate 2>/dev/null || true
fi
fi
return 0
}
test_mysql_connection() {
local DB_USER="$1"
local DB_PASSWORD="$2"
local DB_HOST="${3:-localhost}"
local DB_PORT="${4:-3306}"
if [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ]; then
return 1
fi
local MYSQL_CMD_BIN=$(find_mysql_command)
if [ -z "$MYSQL_CMD_BIN" ]; then
MYSQL_CMD_BIN="mysql"
fi
if [ "$MYSQL_CMD_BIN" != "mysql" ]; then
local MYSQL_BIN_DIR=$(dirname "$MYSQL_CMD_BIN")
if [[ ":$PATH:" != *":$MYSQL_BIN_DIR:"* ]]; then
export PATH="$MYSQL_BIN_DIR:$PATH"
if command -v mysql &> /dev/null && verify_mysql_command "mysql" 2>/dev/null; then
MYSQL_CMD_BIN="mysql"
fi
fi
fi
export MYSQL_PWD="$DB_PASSWORD"
if [ "$MYSQL_CMD_BIN" = "mysql" ]; then
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -e "SELECT 1;" > /dev/null 2>&1
local RESULT=$?
else
"$MYSQL_CMD_BIN" -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -e "SELECT 1;" > /dev/null 2>&1
local RESULT=$?
fi
unset MYSQL_PWD
return $RESULT
}
read_env_value() {
local key="$1"
local default_value="${2:-}"
local env_file="${3:-$TARGET_ENV_FILE}"
if [ ! -f "$env_file" ]; then
echo "$default_value"
return
fi
local value=$(grep "^${key}=" "$env_file" 2>/dev/null | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -z "$value" ]; then
echo "$default_value"
return
fi
if [[ "$value" =~ ^\".*\"$ ]]; then
value="${value#\"}"
value="${value%\"}"
elif [[ "$value" =~ ^\'.*\'$ ]]; then
value="${value#\'}"
value="${value%\'}"
fi
echo "$value"
}
find_mysql_command() {
if command -v mysql &> /dev/null; then
if verify_mysql_command "mysql"; then
echo "mysql"
return 0
fi
fi
local MYSQL_PATHS=(
"/opt/homebrew/bin/mysql"
"/usr/local/bin/mysql"
"/usr/local/mysql/bin/mysql"
"/Applications/XAMPP/xamppfiles/bin/mysql"
)
for mysql_path in "${MYSQL_PATHS[@]}"; do
if [ -f "$mysql_path" ] && [ -x "$mysql_path" ]; then
if verify_mysql_command "$mysql_path"; then
echo "$mysql_path"
return 0
fi
fi
done
if [ -d "/usr/local" ]; then
local found_mysql=$(find /usr/local -maxdepth 3 -type f -name "mysql" -path "*/mysql*/bin/mysql" 2>/dev/null | head -1)
if [ -n "$found_mysql" ] && [ -x "$found_mysql" ]; then
if verify_mysql_command "$found_mysql"; then
echo "$found_mysql"
return 0
fi
fi
fi
if [ -d "/System/Volumes/Data/usr/local" ]; then
local found_mysql=$(find /System/Volumes/Data/usr/local -maxdepth 3 -type f -name "mysql" -path "*/mysql*/bin/mysql" 2>/dev/null | head -1)
if [ -n "$found_mysql" ] && [ -x "$found_mysql" ]; then
if verify_mysql_command "$found_mysql"; then
echo "$found_mysql"
return 0
fi
fi
fi
return 1
}
install_mysql_with_homebrew() {
if ! command -v brew &> /dev/null; then
log "ERROR" "Homebrew not installed, cannot install MySQL automatically"
echo -e "${RED}Error: Homebrew not installed${NC}"
echo -e "${YELLOW}Install Homebrew first, or install MySQL manually:${NC}"
echo " 1. Install Homebrew: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
echo " 2. Or use MySQL official package: https://dev.mysql.com/downloads/mysql/"
echo " 3. After installation, re-run this script: ./setup.sh"
return 1
fi
log "INFO" "Installing MySQL via Homebrew..."
echo -e "${YELLOW}Installing MySQL via Homebrew (may take a few minutes)...${NC}"
if brew install mysql 2>&1; then
log "SUCCESS" "MySQL installed"
log "INFO" "Starting MySQL service..."
brew services start mysql 2>/dev/null || {
log "WARN" "MySQL service failed to start; run manually: brew services start mysql"
}
sleep 3
if ! command -v mysql &> /dev/null; then
if [ -f "/opt/homebrew/bin/mysql" ]; then
export PATH="/opt/homebrew/bin:$PATH"
log "INFO" "MySQL path added to PATH: /opt/homebrew/bin"
elif [ -f "/usr/local/bin/mysql" ]; then
export PATH="/usr/local/bin:$PATH"
log "INFO" "MySQL path added to PATH: /usr/local/bin"
fi
fi
if command -v mysql &> /dev/null || [ -f "/opt/homebrew/bin/mysql" ] || [ -f "/usr/local/bin/mysql" ]; then
log "SUCCESS" "MySQL installed, command available"
return 0
else
log "WARN" "MySQL installed but may not be in PATH; reopen terminal or add PATH manually"
return 0
fi
else
log "ERROR" "MySQL installation failed"
return 1
fi
}
configure_mysql_database() {
echo ""
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}MySQL database configuration${NC}"
echo -e "${BLUE}========================================${NC}"
if ! ensure_mysql_service_running; then
return 1
fi
if ! get_mysql_root_connection; then
return 1
fi
create_mysql_user
}
ensure_mysql_service_running() {
if check_mysql_service; then
log "SUCCESS" "MySQL service is running"
return 0
fi
log "WARN" "MySQL service not running, attempting to start..."
if start_mysql_service; then
sleep 2
if check_mysql_service; then
log "SUCCESS" "MySQL service started"
return 0
fi
fi
echo -e "${YELLOW}Could not start MySQL service automatically${NC}"
echo "Please start MySQL service manually:"
if command -v brew &> /dev/null; then
echo " brew services start mysql"
else
echo " sudo systemctl start mysql # or according to your system"
fi
echo ""
read -p "Have you started MySQL service manually? (y/n, default y): " SERVICE_STARTED
if [[ "${SERVICE_STARTED:-y}" = "y" ]]; then
sleep 2
if check_mysql_service; then
log "SUCCESS" "MySQL service is running"
return 0
fi
fi
log "ERROR" "MySQL service not running"
return 1
}
get_mysql_root_connection() {
echo ""
echo -e "${YELLOW}Connecting to MySQL as root...${NC}"
if mysql -u root -e "SELECT 1;" &>/dev/null; then
MYSQL_CMD="mysql -u root"
log "SUCCESS" "MySQL connected"
return 0
fi
echo -e "${YELLOW}MySQL root access required${NC}"
MAX_PASSWORD_ATTEMPTS=3
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_PASSWORD_ATTEMPTS ]; do
read -sp "Enter MySQL root password (or press Enter to skip and configure later): " ROOT_PASSWORD
echo
if [ -z "$ROOT_PASSWORD" ]; then
log "ERROR" "Could not connect as MySQL root"
return 1
fi
if MYSQL_PWD="$ROOT_PASSWORD" mysql -u root -e "SELECT 1;" &>/dev/null; then
MYSQL_CMD="MYSQL_PWD='$ROOT_PASSWORD' mysql -u root"
log "SUCCESS" "Connected with password"
return 0
fi
ATTEMPT=$((ATTEMPT + 1))
log "ERROR" "Wrong password"
if [ $ATTEMPT -lt $MAX_PASSWORD_ATTEMPTS ]; then
echo -e "${YELLOW}$((MAX_PASSWORD_ATTEMPTS - ATTEMPT)) attempt(s) left, try again${NC}"
else
log "ERROR" "Wrong password after ${MAX_PASSWORD_ATTEMPTS} attempts"
return 1
fi
done
return 1
}
create_mysql_user() {
echo ""
echo -e "${BLUE}Creating MySQL user (auto mode)${NC}"
NEW_DB_USER="openjiuwen"
NEW_DB_PASSWORD="openjiuwen2026"
echo ""
echo -e "${YELLOW}Creating databases and user (openjiuwen)...${NC}"
SQL_COMMANDS="
CREATE DATABASE IF NOT EXISTS openjiuwen_agent;
CREATE DATABASE IF NOT EXISTS openjiuwen_ops;
CREATE USER IF NOT EXISTS '${NEW_DB_USER}'@'localhost' IDENTIFIED BY '${NEW_DB_PASSWORD}';
ALTER USER '${NEW_DB_USER}'@'localhost' IDENTIFIED BY '${NEW_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON openjiuwen_agent.* TO '${NEW_DB_USER}'@'localhost';
GRANT ALL PRIVILEGES ON openjiuwen_ops.* TO '${NEW_DB_USER}'@'localhost';
FLUSH PRIVILEGES;
"
if ! eval "$MYSQL_CMD -e \"$SQL_COMMANDS\"" 2>/dev/null; then
log "ERROR" "Failed to create database or user"
log "ERROR" "Check MySQL version and password policy, or create user/grant manually and retry"
return 1
fi
update_env_file "$NEW_DB_USER" "$NEW_DB_PASSWORD"
echo ""
echo -e "${GREEN}✅ Configuration complete${NC}"
echo -e "${GREEN} - User: ${NEW_DB_USER}${NC}"
echo -e "${GREEN} - Password: ${NEW_DB_PASSWORD}${NC}"
echo -e "${GREEN} - Databases: openjiuwen_agent, openjiuwen_ops${NC}"
return 0
}
update_env_file() {
local user="$1"
local password="$2"
if [ ! -f "$TARGET_ENV_FILE" ]; then
log "ERROR" ".env file not found"
return 1
fi
if [[ "$(uname -s)" == "Darwin" ]]; then
sed -i '' "/^DB_USER=/d" "$TARGET_ENV_FILE"
sed -i '' "/^DB_PASSWORD=/d" "$TARGET_ENV_FILE"
else
sed -i "/^DB_USER=/d" "$TARGET_ENV_FILE"
sed -i "/^DB_PASSWORD=/d" "$TARGET_ENV_FILE"
fi
echo "DB_USER=$user" >> "$TARGET_ENV_FILE"
echo "DB_PASSWORD=$password" >> "$TARGET_ENV_FILE"
log "SUCCESS" ".env updated: DB_USER=$user"
}
wait_process() {
local PID="${1:-}"
local NAME="${2:-}"
local TIMEOUT=30
local COUNT=0
PID="${PID:-}"
NAME="${NAME:-}"
if [ -z "$PID" ]; then
log "ERROR" "${NAME:-unknown}: PID not set"
return 1
fi
if ps -p "$PID" > /dev/null 2>&1; then
log "SUCCESS" "${NAME:-unknown} already running (PID: ${PID})"
return 0
fi
log "INFO" "Waiting for ${NAME:-unknown} to start (PID: ${PID})..."
while [ $COUNT -lt $TIMEOUT ]; do
if ps -p "$PID" > /dev/null 2>&1; then
log "SUCCESS" "${NAME:-unknown} started (PID: ${PID})"
return 0
fi
sleep 1
COUNT=$((COUNT + 1))
done
log "WARN" "${NAME:-unknown} start timeout (no process after ${TIMEOUT}s)"
return 1
}
get_process_tree() {
local PID=${1:-}
local PIDS=""
if [ -z "$PID" ] || ! ps -p "$PID" > /dev/null 2>&1; then
echo ""
return 0
fi
PIDS="$PID"
local CHILDREN=$(pgrep -P "$PID" 2>/dev/null || echo "")
if [ -n "$CHILDREN" ]; then
for CHILD in $CHILDREN; do
GRANDCHILDREN=$(get_process_tree "$CHILD")
if [ -n "$GRANDCHILDREN" ]; then
PIDS="$PIDS $GRANDCHILDREN"
fi
done
fi
echo "$PIDS"
}
stop_process_tree() {
local PID=${1:-}
local NAME=${2:-}
if [ -z "$PID" ] || ! ps -p "$PID" > /dev/null 2>&1; then
return 0
fi
local ALL_PIDS=$(get_process_tree "$PID")
for P in $ALL_PIDS; do
if [ "$P" != "$PID" ] && ps -p "$P" > /dev/null 2>&1; then
kill "$P" 2>/dev/null || true
fi
done
sleep 1
kill "$PID" 2>/dev/null || true
sleep 2
if ps -p "$PID" > /dev/null 2>&1; then
log "WARN" "$NAME did not respond to SIGTERM, forcing stop in 3s..."
sleep 3
if ps -p "$PID" > /dev/null 2>&1; then
log "WARN" "Force stopping $NAME and child processes..."
for P in $ALL_PIDS; do
if ps -p "$P" > /dev/null 2>&1; then
kill -9 "$P" 2>/dev/null || true
fi
done
fi
fi
sleep 1
if ! ps -p "$PID" > /dev/null 2>&1; then
return 0
else
return 1
fi
}
find_pid_by_port() {
local PORT=$1
local PID=""
if command -v lsof &> /dev/null; then
PID=$(lsof -ti:$PORT 2>/dev/null | head -n 1 || echo "")
elif command -v netstat &> /dev/null; then
PID=$(netstat -tlnp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1 | head -n 1 || echo "")
elif command -v ss &> /dev/null; then
PID=$(ss -tlnp 2>/dev/null | grep ":$PORT " | grep -oP "pid=\K\d+" | head -n 1 || echo "")
fi
echo "$PID"
}
get_backend_port() {
local LOG_FILE=$1
local PID_FILE="${2:-}"
local DEFAULT_PORT=${3:-8000}
local PORT=""
if [ -n "$PID_FILE" ] && [ -f "$PID_FILE" ]; then
local PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
PID="${PID:-}"
if [ -n "$PID" ] && ps -p "$PID" > /dev/null 2>&1; then
PORT=$(find_port_by_pid "$PID")
if [ -n "$PORT" ] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; then
echo "$PORT"
return
fi
fi
fi
if [ -f "$LOG_FILE" ]; then
PORT_FROM_LOG=$(grep -E "(0\.0\.0\.0|localhost|127\.0\.0\.1):[0-9]+" "$LOG_FILE" 2>/dev/null | sed -n 's/.*\(0\.0\.0\.0\|localhost\|127\.0\.0\.1\):\([0-9]\+\).*/\2/p' | tail -n 1 || echo "")
if [ -n "$PORT_FROM_LOG" ] && [ "$PORT_FROM_LOG" -ge 1 ] && [ "$PORT_FROM_LOG" -le 65535 ]; then
PORT="$PORT_FROM_LOG"
fi
fi
if [ -z "$PORT" ]; then
if [ -f "${WORK_HOME}/agent-studio/.env" ]; then
ENV_PORT=$(grep -E "^SERVER_PORT=|^PORT=" "${WORK_HOME}/agent-studio/.env" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" | head -n 1 || echo "")
if [ -n "$ENV_PORT" ] && [ "$ENV_PORT" -ge 1 ] && [ "$ENV_PORT" -le 65535 ]; then
PORT="$ENV_PORT"
fi
fi
fi
if [ -z "$PORT" ]; then
PORT="$DEFAULT_PORT"
fi
echo "$PORT"
}
find_port_by_pid() {
local PID=$1
local PORT=""
if [ -z "$PID" ] || ! ps -p "$PID" > /dev/null 2>&1; then
echo ""
return
fi
if command -v lsof &> /dev/null; then
PORT=$(lsof -Pan -p "$PID" -iTCP -sTCP:LISTEN 2>/dev/null | awk 'NR>1 {print $9}' | sed 's/.*://' | head -n 1 || echo "")
if [ -z "$PORT" ] && command -v pgrep &> /dev/null; then
local CHILD_PIDS=$(pgrep -P "$PID" 2>/dev/null || echo "")
for CHILD_PID in $CHILD_PIDS; do
local CHILD_PORT=$(lsof -Pan -p "$CHILD_PID" -iTCP -sTCP:LISTEN 2>/dev/null | awk 'NR>1 {print $9}' | sed 's/.*://' | head -n 1 || echo "")
if [ -n "$CHILD_PORT" ] && [ "$CHILD_PORT" -ge 1 ] && [ "$CHILD_PORT" -le 65535 ]; then
PORT="$CHILD_PORT"
break
fi
done
fi
fi
echo "$PORT"
}
get_frontend_port() {
local LOG_FILE=$1
local PID_FILE="${2:-}"
local DEFAULT_PORT=${3:-3000}
local PORT=""
if [ -n "$PID_FILE" ] && [ -f "$PID_FILE" ]; then
local PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
PID="${PID:-}"
if [ -n "$PID" ] && ps -p "$PID" > /dev/null 2>&1; then
PORT=$(find_port_by_pid "$PID")
if [ -n "$PORT" ] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; then
echo "$PORT"
return
fi
fi
fi
if [ -f "$LOG_FILE" ]; then
PORT_FROM_LOG=$(grep -E "(Local:|Network:|➜)" "$LOG_FILE" 2>/dev/null | sed -n 's/.*http:\/\/[^:]*:\([0-9]\+\).*/\1/p' | tail -n 1 || echo "")
if [ -n "$PORT_FROM_LOG" ] && [ "$PORT_FROM_LOG" -ge 1 ] && [ "$PORT_FROM_LOG" -le 65535 ]; then
PORT="$PORT_FROM_LOG"
else
PORT_FROM_LOG=$(grep -E ":[0-9]+/" "$LOG_FILE" 2>/dev/null | sed -n 's/.*:\([0-9]\+\).*/\1/p' | tail -n 1 || echo "")
if [ -n "$PORT_FROM_LOG" ] && [ "$PORT_FROM_LOG" -ge 1000 ] && [ "$PORT_FROM_LOG" -le 65535 ]; then
PORT="$PORT_FROM_LOG"
fi
fi
fi
if [ -z "$PORT" ]; then
if [ -f "${WORK_HOME}/agent-studio/frontend/vite.config.js" ] || [ -f "${WORK_HOME}/agent-studio/frontend/vite.config.ts" ]; then
VITE_CONFIG="${WORK_HOME}/agent-studio/frontend/vite.config.js"
[ ! -f "$VITE_CONFIG" ] && VITE_CONFIG="${WORK_HOME}/agent-studio/frontend/vite.config.ts"
if [ -f "$VITE_CONFIG" ]; then
CONFIG_PORT=$(grep -E "port:\s*[0-9]+" "$VITE_CONFIG" 2>/dev/null | sed -n 's/.*port:\s*\([0-9]\+\).*/\1/p' | head -n 1 || echo "")
if [ -n "$CONFIG_PORT" ] && [ "$CONFIG_PORT" -ge 1 ] && [ "$CONFIG_PORT" -le 65535 ]; then
PORT="$CONFIG_PORT"
fi
fi
fi
fi
if [ -z "$PORT" ]; then
PORT="$DEFAULT_PORT"
fi
echo "$PORT"
}
resolve_service_status() {
BACKEND_PID_FILE="${WORK_HOME}/backend.pid"
FRONTEND_PID_FILE="${WORK_HOME}/frontend.pid"
BACKEND_LOG="${WORK_HOME}/backend.log"
FRONTEND_LOG="${WORK_HOME}/frontend.log"
LOCAL_IP=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -n 1 || echo "localhost")
if [ -z "$LOCAL_IP" ] || [ "$LOCAL_IP" = "127.0.0.1" ] || [ "$LOCAL_IP" = "localhost" ]; then
LOCAL_IP=$(route get default 2>/dev/null | grep interface | awk '{print $2}' | xargs ifconfig 2>/dev/null | grep "inet " | awk '{print $2}' | head -n 1 || echo "localhost")
fi
LOCAL_IP="${LOCAL_IP:-localhost}"
BACKEND_PORT=$(get_backend_port "$BACKEND_LOG" "$BACKEND_PID_FILE" "8000")
FRONTEND_PORT=$(get_frontend_port "$FRONTEND_LOG" "$FRONTEND_PID_FILE" "3000")
BACKEND_PID=""
FRONTEND_PID=""
if [ -f "$BACKEND_PID_FILE" ]; then
_pid=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "")
_pid="${_pid:-}"
if [ -n "$_pid" ] && ps -p "$_pid" > /dev/null 2>&1; then
if ps -p "$_pid" -o command= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
BACKEND_PID="$_pid"
fi
fi
fi
if [ -z "$BACKEND_PID" ]; then
_port_pid=$(find_pid_by_port "$BACKEND_PORT")
_port_pid="${_port_pid:-}"
if [ -n "$_port_pid" ] && ps -p "$_port_pid" > /dev/null 2>&1; then
if ps -p "$_port_pid" -o command= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
BACKEND_PID="$_port_pid"
echo "$BACKEND_PID" > "$BACKEND_PID_FILE" 2>/dev/null || true
fi
fi
fi
if [ -z "$BACKEND_PID" ] && command -v pgrep &> /dev/null; then
for _pid in $(pgrep -f "python.*main\.py|uvicorn.*main" 2>/dev/null | grep -v "^$$" || true); do
if ps -p "$_pid" -o command= 2>/dev/null | grep -qE "(main\.py|uvicorn)" 2>/dev/null; then
if ps -p "$_pid" -o command= 2>/dev/null | grep -q "$BACKEND_DIR" 2>/dev/null; then
BACKEND_PID="$_pid"
echo "$BACKEND_PID" > "$BACKEND_PID_FILE" 2>/dev/null || true
break
fi
fi
done
fi
if [ -n "$BACKEND_PID" ] && ps -p "$BACKEND_PID" > /dev/null 2>&1; then
_actual=$(find_port_by_pid "$BACKEND_PID")
if [ -n "$_actual" ] && [ "$_actual" -ge 1 ] && [ "$_actual" -le 65535 ]; then
BACKEND_PORT="$_actual"
fi
fi
if [ -f "$FRONTEND_PID_FILE" ]; then
_pid=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
_pid="${_pid:-}"
if [ -n "$_pid" ] && ps -p "$_pid" > /dev/null 2>&1; then
if ps -p "$_pid" -o command= 2>/dev/null | grep -qE "(node|vite|npm.*dev)" 2>/dev/null; then
FRONTEND_PID="$_pid"
fi
fi
fi
if [ -z "$FRONTEND_PID" ]; then
_port_pid=$(find_pid_by_port "$FRONTEND_PORT")
_port_pid="${_port_pid:-}"
if [ -n "$_port_pid" ] && ps -p "$_port_pid" > /dev/null 2>&1; then
if ps -p "$_port_pid" -o command= 2>/dev/null | grep -qE "(node|vite|npm.*dev)" 2>/dev/null; then
FRONTEND_PID="$_port_pid"
echo "$FRONTEND_PID" > "$FRONTEND_PID_FILE" 2>/dev/null || true
fi
fi
fi
if [ -z "$FRONTEND_PID" ] && command -v pgrep &> /dev/null; then
for _pid in $(pgrep -f "npm.*dev|vite|node.*frontend" 2>/dev/null | grep -v "^$$" || true); do
if ps -p "$_pid" -o command= 2>/dev/null | grep -qE "(frontend|vite|npm.*dev)" 2>/dev/null; then
_node_port=$(find_pid_by_port "$FRONTEND_PORT")
_node_port="${_node_port:-}"
if [ -n "$_node_port" ] && [ "$_node_port" = "$_pid" ]; then
FRONTEND_PID="$_pid"
echo "$FRONTEND_PID" > "$FRONTEND_PID_FILE" 2>/dev/null || true
break
fi
fi
done
fi
if [ -n "$FRONTEND_PID" ] && ps -p "$FRONTEND_PID" > /dev/null 2>&1; then
_actual=$(find_port_by_pid "$FRONTEND_PID")
if [ -n "$_actual" ] && [ "$_actual" -ge 1 ] && [ "$_actual" -le 65535 ]; then
FRONTEND_PORT="$_actual"
fi
fi
}
check_status() {
resolve_service_status
echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Service status${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${YELLOW}Backend:${NC}"
if [ -n "$BACKEND_PID" ] && ps -p "$BACKEND_PID" > /dev/null 2>&1; then
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: ${BACKEND_PID}"
echo -e " Port: ${BACKEND_PORT}"
echo -e " Local: ${GREEN}http://localhost:${BACKEND_PORT}${NC}"
echo -e " Network: ${GREEN}http://${LOCAL_IP}:${BACKEND_PORT}${NC}"
echo -e " API docs: ${GREEN}http://localhost:${BACKEND_PORT}/api/docs${NC}"
echo -e " Health: ${GREEN}http://localhost:${BACKEND_PORT}/api/health${NC}"
else
echo -e " Status: ${RED}Not running${NC}"
if [ -f "$BACKEND_PID_FILE" ]; then
_old=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "")
_old="${_old:-}"
[ -n "$_old" ] && echo -e " Note: PID file exists but process not found (PID: ${_old})"
else
echo -e " Note: PID file not found"
fi
fi
echo ""
echo -e "${YELLOW}Frontend:${NC}"
if [ -n "$FRONTEND_PID" ] && ps -p "$FRONTEND_PID" > /dev/null 2>&1; then
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: ${FRONTEND_PID}"
echo -e " Port: ${FRONTEND_PORT}"
echo -e " Local: ${GREEN}http://localhost:${FRONTEND_PORT}${NC}"
echo -e " Network: ${GREEN}http://${LOCAL_IP}:${FRONTEND_PORT}${NC}"
else
echo -e " Status: ${RED}Not running${NC}"
if [ -f "$FRONTEND_PID_FILE" ]; then
_old=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
_old="${_old:-}"
[ -n "$_old" ] && echo -e " Note: PID file exists but process not found (PID: ${_old})"
else
echo -e " Note: PID file not found"
fi
fi
echo ""
echo -e "${BLUE}========================================${NC}"
echo ""
echo -e "${YELLOW}Log files:${NC}"
[ -f "$BACKEND_LOG" ] && echo -e " Backend: ${BACKEND_LOG}"
[ -f "$FRONTEND_LOG" ] && echo -e " Frontend: ${FRONTEND_LOG}"
return 0
}
stop_services() {
log "INFO" "===== Stopping services ====="
resolve_service_status
local STOPPED=0
local B_PID=""
local B_PORT=""
local B_PORT_PID=""
local F_PID=""
local F_PORT=""
local F_PORT_PID=""
B_PID="${BACKEND_PID:-}"
B_PORT="${BACKEND_PORT:-8000}"
if [ -z "${B_PID:-}" ] || ! ps -p "${B_PID:-}" > /dev/null 2>&1; then
B_PORT_PID=$(find_pid_by_port "$B_PORT" 2>/dev/null || echo "")
B_PORT_PID="${B_PORT_PID:-}"
B_PID="$B_PORT_PID"
fi
if [ -n "${B_PID:-}" ] && ps -p "${B_PID:-}" > /dev/null 2>&1; then
log "INFO" "Stopping backend (PID: ${B_PID:-})..."
if stop_process_tree "${B_PID:-}" "Backend"; then
log "SUCCESS" "Backend stopped (PID: ${B_PID:-})"
[ -n "${BACKEND_PID_FILE:-}" ] && [ -f "$BACKEND_PID_FILE" ] && rm -f "$BACKEND_PID_FILE"
STOPPED=$((STOPPED + 1))
else
log "ERROR" "Failed to stop backend (PID: ${B_PID:-})"
fi
else
log "INFO" "No running backend found"
[ -n "${BACKEND_PID_FILE:-}" ] && [ -f "$BACKEND_PID_FILE" ] && rm -f "$BACKEND_PID_FILE"
fi
F_PID="${FRONTEND_PID:-}"
F_PORT="${FRONTEND_PORT:-3000}"
if [ -z "${F_PID:-}" ] || ! ps -p "${F_PID:-}" > /dev/null 2>&1; then
F_PORT_PID=$(find_pid_by_port "$F_PORT" 2>/dev/null || echo "")
F_PORT_PID="${F_PORT_PID:-}"
F_PID="$F_PORT_PID"
fi
if [ -n "${F_PID:-}" ] && ps -p "${F_PID:-}" > /dev/null 2>&1; then
log "INFO" "Stopping frontend (PID: ${F_PID:-})..."
if stop_process_tree "${F_PID:-}" "Frontend"; then
log "SUCCESS" "Frontend stopped (PID: ${F_PID:-})"
[ -n "${FRONTEND_PID_FILE:-}" ] && [ -f "$FRONTEND_PID_FILE" ] && rm -f "$FRONTEND_PID_FILE"
STOPPED=$((STOPPED + 1))
else
log "ERROR" "Failed to stop frontend (PID: ${F_PID:-})"
fi
else
log "INFO" "No running frontend found"
[ -n "${FRONTEND_PID_FILE:-}" ] && [ -f "$FRONTEND_PID_FILE" ] && rm -f "$FRONTEND_PID_FILE"
fi
if [ "$STOPPED" -gt 0 ]; then
log "SUCCESS" "Stopped $STOPPED service(s)"
else
log "INFO" "No running services to stop"
fi
return 0
}
show_help() {
cat << EOF
Usage: ${0} [options]
One-step deploy Agent-Studio; supports DB type and code branch.
Options:
--db_type=<type> Database type: mysql (default), sqlite
- --db_type=mysql: set DB_TYPE in .env to mysql
- --db_type=sqlite: set DB_TYPE in .env to sqlite
--branch=<name> Git branch to fetch, default: main
- --branch=main: fetch main (default)
- --branch=develop: fetch develop
- --branch=<other>: fetch specified branch
--stop Gracefully stop backend and frontend
--restart Restart backend and frontend (no reinstall deps/keys)
--status Show service status and URLs
--help Show this help and exit
Examples:
${0} # Default: DB_TYPE=mysql, branch=main
${0} --db_type=sqlite # Use sqlite, main branch
${0} --branch=develop # Use mysql, develop branch
${0} --db_type=sqlite --branch=develop # Use sqlite, develop branch
${0} --stop # Stop services
${0} --restart # Restart services
${0} --status # Show status and URLs
${0} --help # Show help
Work directory: ${WORK_HOME}
EOF
exit 0
}
DB_TYPE="mysql"
GIT_BRANCH="main"
ACTION="install"
for arg in "$@"; do
case "$arg" in
--help)
show_help
;;
--stop)
ACTION="stop"
;;
--restart)
ACTION="restart"
;;
--status)
ACTION="status"
;;
--db_type=*)
DB_TYPE="${arg#*=}"
if [[ $DB_TYPE != "mysql" && $DB_TYPE != "sqlite" ]]; then
error_exit "--db_type must be mysql or sqlite, got: $DB_TYPE"
fi
log "INFO" "Database type: $DB_TYPE"
;;
--branch=*)
GIT_BRANCH="${arg#*=}"
if [ -z "$GIT_BRANCH" ]; then
error_exit "--branch value cannot be empty"
fi
log "INFO" "Branch: $GIT_BRANCH"
;;
*)
error_exit "Invalid argument '$arg'; use --help for usage"
;;
esac
done
if [ "$ACTION" = "stop" ]; then
stop_services
exit 0
fi
if [ "$ACTION" = "status" ]; then
check_status
exit 0
fi
if [ "$ACTION" = "restart" ]; then
log "INFO" "===== Restarting services ====="
stop_services
log "INFO" "Waiting 2s before starting services..."
sleep 2
save_progress "deploy_frontend"
LAST_PROGRESS="deploy_frontend"
log "INFO" "Progress set to deploy_frontend; skipping install steps, starting services"
fi
log "INFO" "===== Starting Agent-Studio deployment ====="
log "INFO" "Work directory: ${WORK_HOME}"
log "INFO" "Log file: ${LOG_FILE}"
echo "=========================================" >> "$LOG_FILE"
echo "Deployment start time: $(date)" >> "$LOG_FILE"
echo "=========================================" >> "$LOG_FILE"
load_environments
log "INFO" "Environments loaded (NVM, Conda, user PATH)"
check_command "bash"
check_command "sed"
check_command "grep"
check_command "mkdir"
log "INFO" "Checking script permissions..."
for script in "check_curl.sh" "check_git.sh" "check_nodejs.sh" "check_python.sh" "fetch_codes.sh" "check_mysql.sh"; do
if [ -f "${WORK_HOME}/${script}" ]; then
check_script_permission "${WORK_HOME}/${script}"
fi
done
if [ "$ACTION" != "restart" ]; then
LAST_PROGRESS=$(read_progress)
if [ -n "$LAST_PROGRESS" ]; then
log "WARN" "Resuming from previous progress: $LAST_PROGRESS"
read -p "Resume from last interruption? (y/n, default y): " CONTINUE
if [[ "${CONTINUE:-y}" != "y" ]]; then
clear_progress
LAST_PROGRESS=""
log "INFO" "Progress cleared, starting fresh"
fi
else
LAST_PROGRESS=""
fi
else
LAST_PROGRESS=$(read_progress)
if [ -z "$LAST_PROGRESS" ]; then
LAST_PROGRESS="deploy_frontend"
fi
log "INFO" "Restart mode: skipping install steps, progress: $LAST_PROGRESS"
fi
STEP="check_tools"
if [ "$ACTION" = "restart" ]; then
log "INFO" "Skipping: base tools check (restart mode)"
elif [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
log "INFO" "===== Checking base tools ====="
for script in "check_curl.sh" "check_git.sh" "check_nodejs.sh" "check_python.sh"; do
SCRIPT_PATH="${WORK_HOME}/${script}"
check_file "$SCRIPT_PATH"
if ! bash "$SCRIPT_PATH" > /tmp/setup_${script}.log 2>&1; then
log "ERROR" "Failed to run $script"
echo -e "${RED}Error details:${NC}"
cat /tmp/setup_${script}.log
rm -f /tmp/setup_${script}.log
error_exit "Failed to run $script" \
"1. Check script permission: chmod +x $SCRIPT_PATH\n\
2. Inspect script content for issues\n\
3. Run script manually to debug: bash $SCRIPT_PATH"
else
if grep -qE "(已安装|安装成功|功能测试通过|未安装|安装失败|操作完成|installed|Install success|Test passed|not installed|Install failed|Done)" /tmp/setup_${script}.log 2>/dev/null; then
grep -E "(已安装|安装成功|功能测试通过|未安装|安装失败|installed|Install success|Test passed|not installed|Install failed)" /tmp/setup_${script}.log 2>/dev/null | head -2 || true
else
tail -1 /tmp/setup_${script}.log 2>/dev/null || true
fi
rm -f /tmp/setup_${script}.log
fi
done
save_progress "$STEP"
else
log "INFO" "Skipping: base tools check (done)"
fi
STEP="fetch_code"
if [ "$ACTION" = "restart" ]; then
log "INFO" "Skipping: code fetch (restart mode)"
if [ ! -d "${WORK_HOME}/agent-studio" ]; then
log "WARN" "Code directory missing, run full install first"
error_exit "Code directory missing, cannot restart" \
"Run full install first: ./setup.sh"
fi
elif [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
log "INFO" "===== Fetching code ====="
echo -e "${GREEN}[Progress] Base tools done, fetching code (branch: ${GIT_BRANCH})...${NC}"
FETCH_SCRIPT="${WORK_HOME}/fetch_codes.sh"
check_file "$FETCH_SCRIPT"
if ! retry_execute 3 10 "Fetch code" "bash '$FETCH_SCRIPT' '$GIT_BRANCH'"; then
error_exit "Code fetch failed" \
"1. Check network: ping -c 3 gitcode.com\n\
2. Check Git config: git config --list\n\
3. Fetch manually: cd $WORK_HOME && bash $FETCH_SCRIPT"
fi
check_dir "${WORK_HOME}/agent-studio"
check_dir "$BACKEND_DIR"
check_dir "$FRONTEND_DIR"
echo -e "${GREEN}[Progress] Code fetch done${NC}\n"
save_progress "$STEP"
else
log "INFO" "Skipping: code fetch (done)"
if [ ! -d "${WORK_HOME}/agent-studio" ]; then
log "WARN" "Code directory missing, re-fetching..."
LAST_PROGRESS=""
fi
fi
STEP="config_aes"
if [ "$ACTION" = "restart" ]; then
log "INFO" "Skipping AES config (restart mode)"
if [ -z "${SERVER_AES_MASTER_KEY_ENV:-}" ] && [ -f "$TARGET_ENV_FILE" ]; then
log "WARN" "AES key not set, will read from .env if present"
fi
elif [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
log "INFO" "===== Configuring AES key ====="
echo -e "${GREEN}[Progress] Configuring AES key...${NC}"
AES_SCRIPT="${WORK_HOME}/agent-studio/scripts/build_AES_master_key.sh"
check_file "$AES_SCRIPT"
log "INFO" "Running AES key script: $AES_SCRIPT"
your_aes_key=""
ATTEMPT=1
MAX_RETRIES=3
while [ $ATTEMPT -le $MAX_RETRIES ]; do
log "INFO" "[Attempt $ATTEMPT/$MAX_RETRIES] Generating AES key"
your_aes_key=$(bash "$AES_SCRIPT" 2>/dev/null || echo "")
if [ -n "$your_aes_key" ] && [ ${#your_aes_key} -gt 10 ]; then
log "SUCCESS" "AES key generated"
break
else
log "WARN" "AES key generation failed or invalid, retrying in 2s..."
if [ $ATTEMPT -lt $MAX_RETRIES ]; then
sleep 2
fi
ATTEMPT=$((ATTEMPT + 1))
fi
done
if [ -z "$your_aes_key" ] || [ ${#your_aes_key} -le 10 ]; then
error_exit "AES key generation failed after $MAX_RETRIES attempts" \
"1. Check script permission: chmod +x $AES_SCRIPT\n\
2. Check Python: python3.11 --version\n\
3. Run script manually: bash $AES_SCRIPT\n\
4. Inspect script: cat $AES_SCRIPT"
fi
export SERVER_AES_MASTER_KEY_ENV="$your_aes_key"
log "SUCCESS" "AES key set: ${your_aes_key:0:8}**** (masked)"
save_progress "$STEP"
else
log "INFO" "Skipping: AES key config (done)"
if [ -z "${SERVER_AES_MASTER_KEY_ENV:-}" ] && [ -f "$TARGET_ENV_FILE" ]; then
log "WARN" "AES key not set, will read from .env if present"
fi
fi
STEP="config_env"
if [ "${ACTION:-install}" = "restart" ]; then
log "INFO" "Skipping .env config (restart mode)"
elif [[ "${LAST_PROGRESS:-}" != "${STEP:-config_env}"* ]] || [[ -z "${LAST_PROGRESS:-}" ]]; then
log "INFO" "===== Configuring .env ====="
echo -e "${GREEN}[Progress] AES done, configuring .env...${NC}"
check_file "$ENV_EXAMPLE_FILE"
if [ -f "$TARGET_ENV_FILE" ]; then
BACKUP_ENV="${TARGET_ENV_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp "$TARGET_ENV_FILE" "$BACKUP_ENV" || log "WARN" "Failed to backup .env, continuing"
log "INFO" "Backed up .env to: $BACKUP_ENV"
fi
if ! cp "$ENV_EXAMPLE_FILE" "$TARGET_ENV_FILE" 2>/dev/null; then
error_exit "Failed to copy .env.example" \
"1. Check file permission: ls -l $ENV_EXAMPLE_FILE\n\
2. Check target dir: ls -ld $(dirname $TARGET_ENV_FILE)\n\
3. Copy manually: cp $ENV_EXAMPLE_FILE $TARGET_ENV_FILE"
fi
check_file "$TARGET_ENV_FILE"
OLD_MYSQL="DB_TYPE=mysql"
NEW_SQLITE="DB_TYPE=sqlite"
DB_TYPE_VALUE="${DB_TYPE:-mysql}"
log "INFO" "Setting DB type: $DB_TYPE_VALUE"
if [ "$DB_TYPE_VALUE" = "sqlite" ]; then
if [[ "$(uname -s)" == "Darwin" ]]; then
sed -i '' "s|${OLD_MYSQL}|${NEW_SQLITE}|g" "$TARGET_ENV_FILE"
else
sed -i "s|${OLD_MYSQL}|${NEW_SQLITE}|g" "$TARGET_ENV_FILE"
fi
else
if [[ "$(uname -s)" == "Darwin" ]]; then
sed -i '' "s|${NEW_SQLITE}|${OLD_MYSQL}|g" "$TARGET_ENV_FILE"
else
sed -i "s|${NEW_SQLITE}|${OLD_MYSQL}|g" "$TARGET_ENV_FILE"
fi
fi
DB_TYPE_ACTUAL_VALUE="not_found"
if [ -f "$TARGET_ENV_FILE" ]; then
TEMP_RESULT=$(grep '^DB_TYPE=' "$TARGET_ENV_FILE" 2>/dev/null | cut -d'=' -f2 || true)
if [ -n "${TEMP_RESULT:-}" ]; then
DB_TYPE_ACTUAL_VALUE="$TEMP_RESULT"
fi
fi
DB_TYPE_ACTUAL_VALUE="${DB_TYPE_ACTUAL_VALUE:-not_found}"
DB_TYPE_VALUE="${DB_TYPE_VALUE:-mysql}"
if [ "$DB_TYPE_ACTUAL_VALUE" != "$DB_TYPE_VALUE" ]; then
WARN_MSG="DB_TYPE may not have taken effect, current: ${DB_TYPE_ACTUAL_VALUE} (expected: ${DB_TYPE_VALUE})"
log "WARN" "$WARN_MSG"
else
SUCCESS_MSG="DB_TYPE configured: ${DB_TYPE_VALUE}"
log "SUCCESS" "$SUCCESS_MSG"
fi
save_progress "${STEP:-config_env}"
else
log "INFO" "Skipping: .env config (done)"
fi
STEP="deploy_backend"
if [ "$ACTION" = "restart" ]; then
log "INFO" "Skipping backend deps install (restart mode)"
if [ ! -f "${BACKEND_DIR}/.venv/bin/activate" ]; then
log "ERROR" "Backend venv missing, cannot restart"
error_exit "Backend venv missing, cannot restart" \
"Run full install first: ./setup.sh"
fi
cd "$BACKEND_DIR" 2>/dev/null || log "WARN" "Cannot enter backend dir, continuing"
elif [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
log "INFO" "===== Deploying backend ====="
echo -e "${GREEN}[Progress] .env done, deploying backend...${NC}"
if ! cd "$BACKEND_DIR" 2>/dev/null; then
error_exit "Failed to enter backend dir: $BACKEND_DIR" \
"Check that code was fetched: ls -la $WORK_HOME/agent-studio"
fi
load_environments
ensure_python_compatible
log "INFO" "Installing uv (using ${PYTHON_CMD} -m pip)"
if ! retry_execute 3 10 "Install uv" "${PYTHON_CMD} -m pip install uv --user"; then
log "WARN" "User install failed, trying global (may need sudo)"
if ! retry_execute 2 5 "Install uv globally" "${PYTHON_CMD} -m pip install uv"; then
error_exit "uv install failed" \
"1. Check network\n\
2. Check pip: ${PYTHON_CMD} -m pip --version\n\
3. Install manually: ${PYTHON_CMD} -m pip install uv"
fi
fi
check_command "uv"
log "INFO" "uv installed: $(uv --version 2>/dev/null || echo 'unknown')"
log "INFO" "Creating/checking uv venv (using ${PYTHON_CMD})"
if [ -d ".venv" ]; then
log "INFO" "Existing venv detected, recreating..."
if rm -rf .venv 2>/dev/null; then
log "SUCCESS" "Old venv removed"
else
log "WARN" "Could not remove old venv; run: rm -rf ${BACKEND_DIR}/.venv"
fi
fi
if ! retry_execute 2 5 "Create venv" "uv venv --python \"${PYTHON_CMD}\""; then
error_exit "Failed to create venv" \
"1. Check Python: ${PYTHON_CMD} --version\n\
2. Check disk: df -h\n\
3. Check dir permission: ls -ld ${BACKEND_DIR}\n\
4. Create manually: cd $BACKEND_DIR && uv venv --python \"${PYTHON_CMD}\""
fi
log "INFO" "Syncing dependencies (may take a few minutes)..."
if ! retry_execute 3 30 "Sync deps" "uv sync"; then
error_exit "Dependency sync failed" \
"1. Check network\n\
2. Check disk: df -h\n\
3. On repeated failure try: rm -rf ${BACKEND_DIR}/.venv && cd ${BACKEND_DIR} && uv venv --python \"${PYTHON_CMD}\" && uv sync\n\
4. See errors: cd $BACKEND_DIR && uv sync"
fi
check_and_install_greenlet "$BACKEND_DIR"
if ! mkdir -p logs/run 2>/dev/null; then
error_exit "Failed to create backend log dir" \
"Check dir permission: ls -ld $BACKEND_DIR"
fi
save_progress "$STEP"
else
log "INFO" "Skipping backend deploy (already done)"
cd "$BACKEND_DIR" 2>/dev/null || log "WARN" "Cannot enter backend dir, continuing"
fi
if [ "${DB_TYPE:-mysql}" = "mysql" ] && [ "${ACTION:-install}" != "restart" ]; then
log "INFO" "===== Checking MySQL config ====="
if [ ! -f "$TARGET_ENV_FILE" ]; then
error_exit ".env file not found" \
"Ensure .env is configured: $TARGET_ENV_FILE"
fi
MYSQL_CMD_BIN=""
MYSQL_CMD_BIN=$(find_mysql_command)
if [ -z "${MYSQL_CMD_BIN:-}" ]; then
log "WARN" "MySQL not found"
echo ""
echo -e "${YELLOW}MySQL not installed, will try Homebrew...${NC}"
log "INFO" "Installing MySQL via Homebrew..."
if install_mysql_with_homebrew; then
log "SUCCESS" "MySQL installed"
MYSQL_CMD_BIN=$(find_mysql_command)
if [ -z "${MYSQL_CMD_BIN:-}" ]; then
error_exit "MySQL installed but command not available; reopen terminal or check PATH" \
"1. Reopen terminal and re-run\n2. Or check: which mysql"
fi
else
error_exit "MySQL auto-install failed" \
"1. Check network\n2. Install manually: brew install mysql\n3. Re-run script after install"
fi
else
log "SUCCESS" "MySQL installed (path: ${MYSQL_CMD_BIN})"
fi
DB_HOST=$(read_env_value "DB_HOST" "localhost")
DB_PORT=$(read_env_value "DB_PORT" "3306")
DB_USER=$(read_env_value "DB_USER" "")
DB_PASSWORD=$(read_env_value "DB_PASSWORD" "")
DB_NAME=$(read_env_value "DB_NAME" "openjiuwen_agent")
NEED_CONFIG=false
CONFIG_REASON=""
if ! check_mysql_service; then
log "WARN" "MySQL service not running"
NEED_CONFIG=true
CONFIG_REASON="MySQL service not running"
else
log "SUCCESS" "MySQL service is running"
if [ -z "$DB_USER" ] || [ "$DB_USER" = "your_user_name" ] || \
[ -z "$DB_PASSWORD" ] || [ "$DB_PASSWORD" = "your_password" ]; then
NEED_CONFIG=true
CONFIG_REASON="DB config not set (placeholder or empty)"
log "WARN" "$CONFIG_REASON"
else
log "INFO" "DB config set, testing MySQL connection..."
if ! test_mysql_connection "$DB_USER" "$DB_PASSWORD" "$DB_HOST" "$DB_PORT"; then
NEED_CONFIG=true
CONFIG_REASON="MySQL connection test failed (user missing or wrong password)"
log "WARN" "$CONFIG_REASON"
else
log "SUCCESS" "MySQL connection OK"
fi
fi
fi
if [ "$NEED_CONFIG" = true ]; then
log "INFO" "MySQL config incorrect, auto-configuring..."
if ! configure_mysql_database; then
error_exit "MySQL config failed" \
"Configure DB manually or run: bash ${WORK_HOME}/check_mysql.sh"
fi
fi
fi
STEP="start_backend"
if [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
BACKEND_LOG="${WORK_HOME}/backend.log"
BACKEND_PID_FILE="${WORK_HOME}/backend.pid"
check_and_install_greenlet "$BACKEND_DIR"
OLD_PID=""
if [ -f "$BACKEND_PID_FILE" ]; then
OLD_PID=$(cat "$BACKEND_PID_FILE" 2>/dev/null || echo "")
OLD_PID="${OLD_PID:-}"
if [ -n "$OLD_PID" ] && ps -p "$OLD_PID" > /dev/null 2>&1; then
log "WARN" "Backend already running (PID: ${OLD_PID}), skipping start"
save_progress "$STEP"
else
log "INFO" "Cleaning old PID file"
rm -f "$BACKEND_PID_FILE"
fi
fi
if [ ! -f "$BACKEND_PID_FILE" ] || ! ps -p "$(cat "$BACKEND_PID_FILE" 2>/dev/null)" > /dev/null 2>&1; then
log "INFO" "Starting backend, log: $BACKEND_LOG"
cd "$BACKEND_DIR" || error_exit "Cannot enter backend dir"
if [ ! -f ".venv/bin/activate" ]; then
error_exit "Venv not found" \
"Re-run script or create manually: cd $BACKEND_DIR && uv venv"
fi
if [ "${DB_TYPE:-mysql}" = "mysql" ]; then
log "INFO" "Testing MySQL before starting backend..."
DB_HOST=$(read_env_value "DB_HOST" "localhost" "$TARGET_ENV_FILE")
DB_PORT=$(read_env_value "DB_PORT" "3306" "$TARGET_ENV_FILE")
DB_USER=$(read_env_value "DB_USER" "" "$TARGET_ENV_FILE")
DB_PASSWORD=$(read_env_value "DB_PASSWORD" "" "$TARGET_ENV_FILE")
if [ -n "$DB_USER" ] && [ -n "$DB_PASSWORD" ]; then
if ! test_mysql_connection "$DB_USER" "$DB_PASSWORD" "$DB_HOST" "$DB_PORT"; then
log "ERROR" "MySQL connection test failed, cannot start backend"
error_exit "MySQL connection test failed" \
"Check DB config: cat ${TARGET_ENV_FILE} | grep -E '^DB_'"
else
log "SUCCESS" "MySQL connection OK"
fi
else
log "WARN" "DB user/password not set, skipping connection test"
fi
fi
source .venv/bin/activate || error_exit "Failed to activate venv"
if [ -n "${SERVER_AES_MASTER_KEY_ENV:-}" ]; then
export SERVER_AES_MASTER_KEY_ENV
fi
python main.py > "$BACKEND_LOG" 2>&1 &
BACKEND_PID=$!
if [ -z "$BACKEND_PID" ]; then
log "ERROR" "Could not get backend PID"
error_exit "Backend failed to start" "See log: $BACKEND_LOG"
fi
echo "$BACKEND_PID" > "$BACKEND_PID_FILE"
sleep 3
BACKEND_PID="${BACKEND_PID:-}"
if [ -z "$BACKEND_PID" ]; then
log "ERROR" "Backend PID not set"
error_exit "Backend failed to start" "See log: $BACKEND_LOG"
fi
if ! ps -p "$BACKEND_PID" > /dev/null 2>&1; then
log "ERROR" "Backend process exited"
if [ -f "$BACKEND_LOG" ]; then
log "ERROR" "Backend log (last 10 lines):"
tail -n 10 "$BACKEND_LOG" 2>/dev/null || true
fi
rm -f "$BACKEND_PID_FILE"
error_exit "Backend failed to start" "See: tail -n 10 ${BACKEND_LOG}"
fi
if [ -f "$BACKEND_LOG" ]; then
if grep -q "Application startup failed" "$BACKEND_LOG" 2>/dev/null; then
log "ERROR" "Backend start failed (error detected)"
log "ERROR" "Backend log (last 10 lines):"
tail -n 10 "$BACKEND_LOG" 2>/dev/null || true
if ps -p "$BACKEND_PID" > /dev/null 2>&1; then
kill "$BACKEND_PID" 2>/dev/null || true
sleep 1
if ps -p "$BACKEND_PID" > /dev/null 2>&1; then
kill -9 "$BACKEND_PID" 2>/dev/null || true
fi
fi
rm -f "$BACKEND_PID_FILE"
error_exit "Backend failed to start" "See: tail -n 10 ${BACKEND_LOG}"
fi
fi
sleep 2
if ! ps -p "$BACKEND_PID" > /dev/null 2>&1; then
log "WARN" "Initial PID exited, looking for actual backend process..."
BACKEND_PORT=$(get_backend_port "$BACKEND_LOG" "$BACKEND_PID_FILE" "8000")
ACTUAL_PID=$(find_pid_by_port "$BACKEND_PORT")
if [ -n "$ACTUAL_PID" ] && ps -p "$ACTUAL_PID" > /dev/null 2>&1; then
if ps -p "$ACTUAL_PID" -o command= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
log "SUCCESS" "Found backend process (PID: ${ACTUAL_PID}, was: ${BACKEND_PID})"
BACKEND_PID="$ACTUAL_PID"
echo "$BACKEND_PID" > "$BACKEND_PID_FILE"
else
log "ERROR" "Backend exited and no valid process found"
if [ -f "$BACKEND_LOG" ]; then
log "ERROR" "Backend log (last 10 lines):"
tail -n 10 "$BACKEND_LOG" 2>/dev/null || true
fi
rm -f "$BACKEND_PID_FILE"
error_exit "Backend failed to start" "See: tail -n 10 ${BACKEND_LOG}"
fi
else
if command -v pgrep &> /dev/null; then
PYTHON_PIDS=$(pgrep -f "python.*main\.py|uvicorn.*main" 2>/dev/null | grep -v "^$$" || echo "")
for PYTHON_PID in $PYTHON_PIDS; do
if ps -p "$PYTHON_PID" -o command= 2>/dev/null | grep -qE "(main\.py|uvicorn)" 2>/dev/null; then
if ps -p "$PYTHON_PID" -o command= 2>/dev/null | grep -q "$BACKEND_DIR" 2>/dev/null; then
log "SUCCESS" "Found backend via process (PID: ${PYTHON_PID}, was: ${BACKEND_PID})"
BACKEND_PID="$PYTHON_PID"
echo "$BACKEND_PID" > "$BACKEND_PID_FILE"
break
fi
fi
done
fi
if ! ps -p "$BACKEND_PID" > /dev/null 2>&1; then
log "ERROR" "Backend exited and no valid process found"
if [ -f "$BACKEND_LOG" ]; then
log "ERROR" "Backend log (last 10 lines):"
tail -n 10 "$BACKEND_LOG" 2>/dev/null || true
fi
rm -f "$BACKEND_PID_FILE"
error_exit "Backend failed to start" "See: tail -n 10 ${BACKEND_LOG}"
fi
fi
fi
log "SUCCESS" "Backend started (PID: ${BACKEND_PID})"
save_progress "$STEP"
fi
else
log "INFO" "Skipping: backend start (done)"
fi
STEP="deploy_frontend"
if [ "$ACTION" = "restart" ]; then
log "INFO" "Skipping frontend deps install (restart mode)"
if [ ! -d "${FRONTEND_DIR}/node_modules" ]; then
log "ERROR" "Frontend deps not installed, cannot restart"
error_exit "Frontend deps not installed, cannot restart" \
"Run full install first: ./setup.sh"
fi
cd "$FRONTEND_DIR" 2>/dev/null || log "WARN" "Cannot enter frontend dir, continuing"
elif [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]]; then
log "INFO" "===== Deploying frontend ====="
echo -e "${GREEN}[Progress] Backend done, deploying frontend...${NC}"
if ! cd "$FRONTEND_DIR" 2>/dev/null; then
error_exit "Failed to enter frontend dir: $FRONTEND_DIR" \
"Check that code was fetched: ls -la $WORK_HOME/agent-studio"
fi
load_environments
check_command "node"
check_command "npm"
log "INFO" "Installing frontend deps (may take a few minutes)..."
if ! retry_execute 3 30 "Install frontend deps" "npm install"; then
error_exit "Frontend deps install failed" \
"1. Check network\n\
2. Clear npm cache and retry: npm cache clean --force && npm install\n\
3. Check disk: df -h\n\
4. Install manually: cd $FRONTEND_DIR && npm install"
fi
save_progress "$STEP"
else
log "INFO" "Skipping frontend deps install (already done)"
cd "$FRONTEND_DIR" 2>/dev/null || log "WARN" "Cannot enter frontend dir, continuing"
fi
STEP="start_frontend"
if [[ "$LAST_PROGRESS" != "$STEP"* ]] || [[ -z "$LAST_PROGRESS" ]] || [[ "$ACTION" = "restart" ]]; then
FRONTEND_LOG="${WORK_HOME}/frontend.log"
FRONTEND_PID_FILE="${WORK_HOME}/frontend.pid"
OLD_PID=""
if [ "$ACTION" = "restart" ]; then
if [ -f "$FRONTEND_PID_FILE" ]; then
OLD_PID=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
OLD_PID="${OLD_PID:-}"
if [ -n "$OLD_PID" ] && ps -p "$OLD_PID" > /dev/null 2>&1; then
log "WARN" "Frontend still running (PID: ${OLD_PID}), stopping..."
kill "${OLD_PID}" 2>/dev/null || true
sleep 2
if ps -p "${OLD_PID}" > /dev/null 2>&1; then
kill -9 "${OLD_PID}" 2>/dev/null || true
fi
fi
rm -f "$FRONTEND_PID_FILE"
fi
fi
OLD_PID=""
if [ -f "$FRONTEND_PID_FILE" ]; then
OLD_PID=$(cat "$FRONTEND_PID_FILE" 2>/dev/null || echo "")
OLD_PID="${OLD_PID:-}"
if [ -n "$OLD_PID" ] && ps -p "$OLD_PID" > /dev/null 2>&1; then
log "WARN" "Frontend already running (PID: ${OLD_PID}), skipping start"
save_progress "$STEP"
else
log "INFO" "Cleaning old PID file"
rm -f "$FRONTEND_PID_FILE"
fi
fi
if [ ! -f "$FRONTEND_PID_FILE" ] || ! ps -p "$(cat "$FRONTEND_PID_FILE" 2>/dev/null)" > /dev/null 2>&1; then
log "INFO" "Starting frontend, log: $FRONTEND_LOG"
cd "$FRONTEND_DIR" || error_exit "Cannot enter frontend dir"
npm run dev > "$FRONTEND_LOG" 2>&1 &
FRONTEND_PID=$!
if [ -z "$FRONTEND_PID" ]; then
log "ERROR" "Could not get frontend PID"
error_exit "Frontend failed to start" "See: $FRONTEND_LOG"
fi
echo "$FRONTEND_PID" > "$FRONTEND_PID_FILE"
sleep 3
FRONTEND_PID="${FRONTEND_PID:-}"
if [ -z "$FRONTEND_PID" ]; then
log "ERROR" "Frontend PID undefined"
error_exit "Frontend failed to start" "See: $FRONTEND_LOG"
fi
if ! wait_process "$FRONTEND_PID" "Frontend"; then
log "WARN" "Frontend start status abnormal, see: $FRONTEND_LOG"
log "WARN" "Last 10 lines:"
tail -n 10 "$FRONTEND_LOG" 2>/dev/null || true
else
FRONTEND_PID="${FRONTEND_PID:-}"
if [ -n "$FRONTEND_PID" ]; then
log "SUCCESS" "Frontend started (PID: ${FRONTEND_PID})"
else
log "SUCCESS" "Frontend started"
fi
fi
save_progress "$STEP"
fi
else
log "INFO" "Skipping frontend start (already done)"
fi
log "SUCCESS" "========================================="
log "SUCCESS" "===== Deployment complete ====="
log "SUCCESS" "========================================="
resolve_service_status
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
[ ! -f "$SCRIPT_PATH" ] && SCRIPT_PATH="${0}"
[[ "$SCRIPT_PATH" != /* ]] && SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$(basename "$SCRIPT_PATH")"
log "SUCCESS" "Backend:"
log "SUCCESS" " - Local: http://localhost:$BACKEND_PORT"
log "SUCCESS" " - Network: http://$LOCAL_IP:$BACKEND_PORT"
log "SUCCESS" "Frontend:"
log "SUCCESS" " - Local: http://localhost:$FRONTEND_PORT"
log "SUCCESS" " - Network: http://$LOCAL_IP:$FRONTEND_PORT"
log "SUCCESS" ""
clear_progress
log "SUCCESS" "Full log: $LOG_FILE"
log "SUCCESS" "========================================="
exit 0