#!/bin/bash
service_state_file() {
printf '%s/services.state' "${WORK_HOME:?}"
}
_service_state_seed_legacy() {
local out="$1"
local key file v
while IFS=: read -r key file; do
[ -f "${WORK_HOME}/${file}" ] || continue
v=$(grep -E '^[0-9]+$' "${WORK_HOME}/${file}" 2>/dev/null | head -1)
[ -n "$v" ] && printf '%s:%s\n' "$key" "$v" >> "$out"
done <<'EOF'
runtime_pid:runtime.pid
runtime_port:runtime.port
backend_pid:backend.pid
backend_port:backend.port
frontend_pid:frontend.pid
frontend_port:frontend.port
EOF
}
_service_state_remove_legacy_files() {
rm -f "${WORK_HOME}/runtime.pid" "${WORK_HOME}/runtime.port" \
"${WORK_HOME}/backend.pid" "${WORK_HOME}/backend.port" \
"${WORK_HOME}/frontend.pid" "${WORK_HOME}/frontend.port"
}
_service_state_sort_and_write() {
local src="$1"
local dest="$2"
local tmp="${dest}.new.$$"
: > "$tmp"
local k line
for k in runtime_port runtime_pid backend_port backend_pid frontend_port frontend_pid; do
line=$(grep "^${k}:" "$src" 2>/dev/null | head -1)
if [ -n "$line" ]; then
printf '%s\n' "$line" >> "$tmp"
fi
done
if [ ! -s "$tmp" ]; then
rm -f "$tmp" "$dest"
return 0
fi
mv "$tmp" "$dest"
}
service_state_get() {
local key="$1"
local f val
f=$(service_state_file)
if [ -f "$f" ]; then
val=$(grep "^${key}:" "$f" 2>/dev/null | head -1)
if [ -n "$val" ]; then
val="${val#*:}"
val="${val#"${val%%[![:space:]]*}"}"
val="${val%"${val##*[![:space:]]}"}"
printf '%s\n' "$val"
return 0
fi
return 1
fi
case "$key" in
runtime_pid|runtime_port|backend_pid|backend_port|frontend_pid|frontend_port)
local leg
leg=$(echo "$key" | tr '_' '.')
if [ -f "${WORK_HOME}/${leg}" ]; then
val=$(grep -E '^[0-9]+$' "${WORK_HOME}/${leg}" 2>/dev/null | head -1)
if [ -n "$val" ]; then
printf '%s\n' "$val"
return 0
fi
fi
;;
esac
return 1
}
service_state_set() {
local f
f=$(service_state_file)
local tmp="${WORK_HOME}/.services.state.merge.$$"
if [ -f "$f" ]; then
cp "$f" "$tmp"
else
: > "$tmp"
_service_state_seed_legacy "$tmp"
fi
local arg key val
for arg in "$@"; do
key="${arg%%=*}"
val="${arg#*=}"
[ "$key" = "$arg" ] && continue
grep -v "^${key}:" "$tmp" > "${tmp}.n" && mv "${tmp}.n" "$tmp"
if [ -n "$val" ]; then
printf '%s:%s\n' "$key" "$val" >> "$tmp"
fi
done
if [ ! -s "$tmp" ]; then
rm -f "$tmp" "$f"
_service_state_remove_legacy_files
return 0
fi
_service_state_sort_and_write "$tmp" "$f"
rm -f "$tmp" "${tmp}.n"
_service_state_remove_legacy_files
}
service_state_unset() {
local f
f=$(service_state_file)
[ -f "$f" ] || return 0
local tmp="${WORK_HOME}/.services.state.unset.$$"
cp "$f" "$tmp"
local k
for k in "$@"; do
grep -v "^${k}:" "$tmp" > "${tmp}.n" 2>/dev/null && mv "${tmp}.n" "$tmp"
done
if [ ! -s "$tmp" ]; then
rm -f "$tmp" "$f"
return 0
fi
_service_state_sort_and_write "$tmp" "$f"
rm -f "$tmp" "${tmp}.n"
}
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 "")
fi
if [ -z "$PID" ] && command -v ss &> /dev/null; then
PID=$(ss -tlnp 2>/dev/null | grep ":$PORT " | grep -oP "pid=\K\d+" | head -n 1 || echo "")
fi
if [ -z "$PID" ] && command -v netstat &> /dev/null; then
PID=$(netstat -tlnp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1 | head -n 1 || echo "")
if [ -z "$PID" ]; then
PID=$(netstat -tunlp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1 | head -n 1 || echo "")
fi
fi
echo "$PID"
}
wait_port_ready() {
local PORT=$1
local NAME=$2
local TIMEOUT=${3:-3}
local COUNT=0
local PID=""
log "INFO" "Waiting for $NAME to listen on port $PORT..."
while [ $COUNT -lt $TIMEOUT ]; do
PID=$(find_pid_by_port "$PORT")
if [ -n "$PID" ]; then
log "SUCCESS" "$NAME is listening on port $PORT (PID: $PID)"
return 0
fi
sleep 1
COUNT=$((COUNT + 1))
done
log "WARN" "$NAME startup timeout (port $PORT not listening within ${TIMEOUT}s)"
return 1
}
get_process_tree() {
local PID=$1
local PIDS="$PID"
if [ -z "$PID" ] || ! ps -p "$PID" > /dev/null 2>&1; then
echo ""
return 0
fi
local CHILDREN
CHILDREN=$(pgrep -P "$PID" 2>/dev/null || echo "")
if [ -n "$CHILDREN" ]; then
for CHILD in $CHILDREN; do
local GRANDCHILDREN
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
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
}
get_backend_port() {
local LOG_FILE=$1
local DEFAULT_PORT=${2:-8000}
local PORT="$DEFAULT_PORT"
local ST_PORT
ST_PORT=$(service_state_get backend_port || true)
if [ -n "$ST_PORT" ] && [ "$ST_PORT" -ge 1 ] && [ "$ST_PORT" -le 65535 ] 2>/dev/null; then
PORT="$ST_PORT"
fi
if [ -f "$LOG_FILE" ]; then
PORT_FROM_LOG=$(grep -oP "(0\.0\.0\.0|localhost|127\.0\.0\.1):\K\d+" "$LOG_FILE" 2>/dev/null | 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 [ -f "${WORK_HOME}/agent-studio/.env" ]; then
ENV_PORT=$(grep -E "^BACKEND_PORT=|^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
echo "$PORT"
}
get_frontend_port() {
local LOG_FILE=$1
local DEFAULT_PORT=${2:-3000}
local PORT="$DEFAULT_PORT"
local ST_PORT ENV_FE
ST_PORT=$(service_state_get frontend_port || true)
if [ -n "$ST_PORT" ] && [ "$ST_PORT" -ge 1 ] && [ "$ST_PORT" -le 65535 ] 2>/dev/null; then
PORT="$ST_PORT"
fi
if [ -f "${TARGET_ENV_FILE:-}" ]; then
ENV_FE=$(grep -E "^FRONTEND_PORT=" "${TARGET_ENV_FILE}" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" | head -n 1 || echo "")
if [ -n "$ENV_FE" ] && [ "$ENV_FE" -ge 1 ] && [ "$ENV_FE" -le 65535 ]; then
PORT="$ENV_FE"
fi
fi
if [ -f "$LOG_FILE" ]; then
PORT_FROM_LOG=$(grep -E "(Local:|Network:)" "$LOG_FILE" 2>/dev/null | grep -oP "http://[^:]+:\K\d+" | 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
echo "$PORT"
}
check_status() {
local BACKEND_LOG FRONTEND_LOG RUNTIME_LOG SERVICE_STATE_PATH
BACKEND_LOG="${WORK_HOME}/backend.log"
FRONTEND_LOG="${WORK_HOME}/frontend.log"
RUNTIME_LOG="${WORK_HOME}/runtime.log"
SERVICE_STATE_PATH=$(service_state_file)
LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
if [ -z "$LOCAL_IP" ] || [ "$LOCAL_IP" = "127.0.0.1" ]; then
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}' || echo "localhost")
fi
echo -e "${YELLOW}Frontend Service:${NC}"
FRONTEND_PID=""
FRONTEND_PORT=$(get_frontend_port "$FRONTEND_LOG" "3000")
PID_FROM_STATE=""
PID_FROM_STATE=$(service_state_get frontend_pid || true)
PORT_PID=$(find_pid_by_port "$FRONTEND_PORT")
if [ -n "$PORT_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
if ps -p "$PORT_PID" -o cmd= 2>/dev/null | grep -qE "(node|vite|npm.*dev)" 2>/dev/null; then
FRONTEND_PID="$PORT_PID"
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: $FRONTEND_PID"
if [ -n "$PID_FROM_STATE" ] && [ "$PID_FROM_STATE" != "$FRONTEND_PID" ]; then
echo -e " ${YELLOW}Warning: services.state frontend_pid does not match port process${NC}"
fi
fi
fi
if [ -z "$FRONTEND_PID" ] && [ -n "$PID_FROM_STATE" ]; then
if ps -p "$PID_FROM_STATE" > /dev/null 2>&1; then
if ps -p "$PID_FROM_STATE" -o cmd= 2>/dev/null | grep -qE "(node|vite|npm.*dev)" 2>/dev/null; then
FRONTEND_PID="$PID_FROM_STATE"
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: $FRONTEND_PID"
echo -e " ${YELLOW}Warning: Could not detect by port, port config may be incorrect${NC}"
fi
fi
fi
if [ -z "$FRONTEND_PID" ] && command -v pgrep &> /dev/null; then
NODE_PIDS=$(pgrep -f "npm.*dev|vite|node.*frontend" 2>/dev/null | grep -v "^$$" || echo "")
for NODE_PID in $NODE_PIDS; do
if ps -p "$NODE_PID" -o cmd= 2>/dev/null | grep -qE "(frontend|vite|npm.*dev)" 2>/dev/null; then
FRONTEND_PID="$NODE_PID"
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: $FRONTEND_PID"
echo -e " ${YELLOW}Warning: Could not detect by port${NC}"
break
fi
done
fi
if [ -n "$FRONTEND_PID" ] && ps -p "$FRONTEND_PID" > /dev/null 2>&1; then
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 [ -n "$PID_FROM_STATE" ]; then
echo -e " Note: services.state has frontend_pid but process not found (frontend_pid: $PID_FROM_STATE)"
else
echo -e " Note: frontend_pid not recorded in services.state"
fi
fi
echo -e " Log File: ${GREEN}${FRONTEND_LOG}${NC}"
echo ""
echo -e "${YELLOW}Backend Service:${NC}"
BACKEND_PID=""
BACKEND_PORT=$(get_backend_port "$BACKEND_LOG" "8000")
BACKEND_PID=$(service_state_get backend_pid || true)
if [ -n "$BACKEND_PID" ] && ps -p "$BACKEND_PID" > /dev/null 2>&1; then
if ps -p "$BACKEND_PID" -o cmd= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: $BACKEND_PID"
else
BACKEND_PID=""
fi
else
BACKEND_PID=""
fi
if [ -z "$BACKEND_PID" ]; then
PORT_PID=$(find_pid_by_port "$BACKEND_PORT")
if [ -n "$PORT_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
if ps -p "$PORT_PID" -o cmd= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
echo -e " Status: ${GREEN}Running (detected by port)${NC}"
echo -e " PID: $PORT_PID"
echo -e " ${YELLOW}Warning: backend_pid not in services.state or stale${NC}"
BACKEND_PID="$PORT_PID"
fi
fi
fi
if [ -n "$BACKEND_PID" ] && ps -p "$BACKEND_PID" > /dev/null 2>&1; then
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}"
OLD_PID=$(service_state_get backend_pid || true)
if [ -n "$OLD_PID" ]; then
echo -e " Note: services.state has backend_pid but process not found (backend_pid: $OLD_PID)"
else
echo -e " Note: backend_pid not recorded in services.state"
fi
fi
echo -e " Log File: ${GREEN}${BACKEND_LOG}${NC}"
echo ""
echo -e "${YELLOW}Runtime Service:${NC}"
RUNTIME_PID_VAL=""
RUNTIME_PORT_VAL=""
RUNTIME_PORT_VAL=$(service_state_get runtime_port || true)
RUNTIME_PID_VAL=$(service_state_get runtime_pid || true)
if [ -n "$RUNTIME_PID_VAL" ] && ps -p "$RUNTIME_PID_VAL" > /dev/null 2>&1; then
echo -e " Status: ${GREEN}Running${NC}"
echo -e " PID: $RUNTIME_PID_VAL"
if [ -n "$RUNTIME_PORT_VAL" ]; then
echo -e " Local: ${GREEN}http://localhost:${RUNTIME_PORT_VAL}${NC}"
echo -e " Docs: ${GREEN}http://localhost:${RUNTIME_PORT_VAL}/docs${NC}"
else
echo -e " ${YELLOW}Note: runtime_port not in services.state${NC}"
fi
elif [ -n "$RUNTIME_PORT_VAL" ]; then
PORT_PID=$(find_pid_by_port "$RUNTIME_PORT_VAL")
if [ -n "$PORT_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
echo -e " Status: ${GREEN}Running (detected by port)${NC}"
echo -e " PID: $PORT_PID"
echo -e " Local: ${GREEN}http://localhost:${RUNTIME_PORT_VAL}${NC}"
else
echo -e " Status: ${RED}Not Running${NC}"
echo -e " Note: runtime_port in state but nothing listening on port ${RUNTIME_PORT_VAL}"
fi
else
echo -e " Status: ${RED}Not Running${NC}"
if [ -n "$RUNTIME_PID_VAL" ]; then
echo -e " Note: services.state has runtime_pid but process not found (runtime_pid: $RUNTIME_PID_VAL)"
else
echo -e " Note: runtime_pid not recorded in services.state"
fi
fi
echo -e " Log File: ${GREEN}${RUNTIME_LOG}${NC}"
echo ""
echo -e "${YELLOW}Services state file:${NC}"
echo -e " ${GREEN}${SERVICE_STATE_PATH}${NC}"
echo ""
echo -e "${YELLOW}Manage Service:${NC}"
echo -e " Stop Services: ${GREEN}./setup.sh --stop${NC}"
echo -e " Start Services: ${GREEN}./setup.sh --start${NC}"
echo -e " Restart Services: ${GREEN}./setup.sh --restart${NC}"
echo -e " Check Status: ${GREEN}./setup.sh --status${NC}"
return 0
}
stop_services() {
log "INFO" "===== Stopping services ====="
local STOPPED=0
BACKEND_PID=""
BACKEND_PID=$(service_state_get backend_pid || true)
if [ -n "$BACKEND_PID" ] && ps -p "$BACKEND_PID" > /dev/null 2>&1; then
log "INFO" "Stopping backend service (PID: $BACKEND_PID)..."
if stop_process_tree "$BACKEND_PID" "Backend service"; then
log "SUCCESS" "Backend service stopped (PID: $BACKEND_PID)"
service_state_unset backend_pid backend_port
BACKEND_PID=""
STOPPED=$((STOPPED + 1))
else
log "ERROR" "Failed to stop backend service (PID: $BACKEND_PID)"
fi
elif [ -n "$BACKEND_PID" ]; then
log "WARN" "Backend not running (services.state has backend_pid but process not found)"
service_state_unset backend_pid backend_port
BACKEND_PID=""
fi
BACKEND_PORT=$(get_backend_port "${WORK_HOME}/backend.log" "8000")
PORT_PID=$(find_pid_by_port "$BACKEND_PORT")
if [ -n "$PORT_PID" ] && [ "$PORT_PID" != "$BACKEND_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
if ps -p "$PORT_PID" -o cmd= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
log "WARN" "Port $BACKEND_PORT still in use by backend (PID: $PORT_PID), stopping..."
if stop_process_tree "$PORT_PID" "Backend (port $BACKEND_PORT)"; then
log "SUCCESS" "Stopped backend process on port $BACKEND_PORT"
service_state_unset backend_pid backend_port
STOPPED=$((STOPPED + 1))
fi
fi
fi
FRONTEND_PID=""
FRONTEND_PID=$(service_state_get frontend_pid || true)
if [ -n "$FRONTEND_PID" ] && ps -p "$FRONTEND_PID" > /dev/null 2>&1; then
log "INFO" "Stopping frontend service (PID: $FRONTEND_PID)..."
if stop_process_tree "$FRONTEND_PID" "Frontend service"; then
log "SUCCESS" "Frontend service stopped (PID: $FRONTEND_PID)"
service_state_unset frontend_pid frontend_port
FRONTEND_PID=""
STOPPED=$((STOPPED + 1))
else
log "ERROR" "Failed to stop frontend service (PID: $FRONTEND_PID)"
fi
elif [ -n "$FRONTEND_PID" ]; then
log "WARN" "Frontend not running (services.state has frontend_pid but process not found)"
service_state_unset frontend_pid frontend_port
FRONTEND_PID=""
fi
FRONTEND_PORT=$(get_frontend_port "${WORK_HOME}/frontend.log" "3000")
PORT_PID=$(find_pid_by_port "$FRONTEND_PORT")
if [ -n "$PORT_PID" ] && [ "$PORT_PID" != "$FRONTEND_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
if ps -p "$PORT_PID" -o cmd= 2>/dev/null | grep -qE "(node|vite|npm.*dev)" 2>/dev/null; then
log "WARN" "Port $FRONTEND_PORT still in use by frontend (PID: $PORT_PID), stopping..."
if stop_process_tree "$PORT_PID" "Frontend (port $FRONTEND_PORT)"; then
log "SUCCESS" "Stopped frontend process on port $FRONTEND_PORT"
service_state_unset frontend_pid frontend_port
STOPPED=$((STOPPED + 1))
fi
fi
fi
if command -v pgrep &> /dev/null; then
NODE_PIDS=$(pgrep -f "npm.*dev|vite|node.*frontend" 2>/dev/null | grep -v "^$$" || echo "")
if [ -n "$NODE_PIDS" ]; then
for NODE_PID in $NODE_PIDS; do
if ps -p "$NODE_PID" -o cmd= 2>/dev/null | grep -qE "(frontend|vite|npm.*dev)" 2>/dev/null; then
if [ "$NODE_PID" != "$FRONTEND_PID" ] && [ "$NODE_PID" != "$PORT_PID" ]; then
log "WARN" "Possible frontend process detected (PID: $NODE_PID), stopping..."
if stop_process_tree "$NODE_PID" "Frontend (orphan)"; then
log "SUCCESS" "Stopped orphan frontend process (PID: $NODE_PID)"
service_state_unset frontend_pid frontend_port
STOPPED=$((STOPPED + 1))
fi
fi
fi
done
fi
fi
RUNTIME_PID_VAL=""
RUNTIME_PID_VAL=$(service_state_get runtime_pid || true)
RUNTIME_PORT_VAL=""
RUNTIME_PORT_VAL=$(service_state_get runtime_port || true)
if [ -n "$RUNTIME_PID_VAL" ] && ps -p "$RUNTIME_PID_VAL" > /dev/null 2>&1; then
log "INFO" "Stopping runtime service (PID: $RUNTIME_PID_VAL)..."
if stop_process_tree "$RUNTIME_PID_VAL" "Runtime service"; then
log "SUCCESS" "Runtime service stopped (PID: $RUNTIME_PID_VAL)"
STOPPED=$((STOPPED + 1))
else
log "ERROR" "Failed to stop runtime service (PID: $RUNTIME_PID_VAL)"
fi
elif [ -n "$RUNTIME_PID_VAL" ]; then
log "WARN" "Runtime not running (services.state has runtime_pid but process not found)"
fi
if [ -n "$RUNTIME_PORT_VAL" ]; then
PORT_PID=$(find_pid_by_port "$RUNTIME_PORT_VAL")
if [ -n "$PORT_PID" ] && ps -p "$PORT_PID" > /dev/null 2>&1; then
if ps -p "$PORT_PID" -o cmd= 2>/dev/null | grep -qE "(uvicorn|openjiuwen_runtime)" 2>/dev/null; then
log "WARN" "Port $RUNTIME_PORT_VAL still in use by runtime (PID: $PORT_PID), stopping..."
if stop_process_tree "$PORT_PID" "Runtime (port $RUNTIME_PORT_VAL)"; then
log "SUCCESS" "Stopped runtime process on port $RUNTIME_PORT_VAL"
STOPPED=$((STOPPED + 1))
fi
fi
fi
fi
service_state_unset runtime_pid runtime_port
log "INFO" "Cleared runtime_pid / runtime_port in services.state"
if [ $STOPPED -gt 0 ]; then
log "SUCCESS" "Stopped $STOPPED service(s)"
else
log "INFO" "No running services to stop"
fi
return 0
}
start_runtime_service() {
log "INFO" "===== Starting Runtime Service ====="
local RUNTIME_SERVER_DIR RUNTIME_ENV_FILE RUNTIME_LOG RUNTIME_RUN_SCRIPT
RUNTIME_SERVER_DIR="${RUNTIME_DIR:?}/server"
RUNTIME_ENV_FILE="${RUNTIME_SERVER_DIR}/.env"
RUNTIME_LOG="${WORK_HOME}/runtime.log"
RUNTIME_RUN_SCRIPT="${RUNTIME_DIR:?}/scripts/run-server.sh"
check_dir "$RUNTIME_SERVER_DIR"
check_file "$RUNTIME_ENV_FILE"
check_file "$RUNTIME_RUN_SCRIPT"
: > "$RUNTIME_LOG"
log "INFO" "Starting runtime server by run-server.sh in background, log file: $RUNTIME_LOG"
cd "${RUNTIME_DIR}" || error_exit "Cannot cd to runtime directory: ${RUNTIME_DIR}"
log "INFO" "Running command: bash ./scripts/run-server.sh"
nohup bash ./scripts/run-server.sh >> "$RUNTIME_LOG" 2>&1 &
local LAUNCH_PID=$!
log "INFO" "Runtime server process started (pid: $LAUNCH_PID)"
local RuntimePort="" RuntimePid="$LAUNCH_PID" i
for ((i = 1; i <= 45; i++)); do
sleep 1
if ! ps -p "$LAUNCH_PID" > /dev/null 2>&1; then
log "ERROR" "Runtime service process exited unexpectedly"
error_exit "Runtime failed to stay running; see log: $RUNTIME_LOG" \
"tail -n 50 $RUNTIME_LOG"
fi
break
done
if [ -n "${TARGET_ENV_FILE:-}" ] && [ -f "${TARGET_ENV_FILE}" ]; then
RuntimePort=$(grep '^RUNTIME_PORT=' "${TARGET_ENV_FILE}" 2>/dev/null | head -n 1 | cut -d'=' -f2 | tr -d '\r' | tr -d '"' | tr -d "'" || true)
if [[ ! "$RuntimePort" =~ ^[0-9]+$ ]]; then
RuntimePort=""
fi
fi
service_state_unset runtime_port
service_state_set "runtime_pid=$RuntimePid"
if [ -n "$RuntimePort" ]; then
service_state_set "runtime_port=$RuntimePort"
log "INFO" "Saved runtime_pid / runtime_port to services.state (pid: $RuntimePid, port: $RuntimePort)"
log "SUCCESS" "Runtime service started in background: http://localhost:$RuntimePort"
else
log "INFO" "Saved runtime_pid to services.state (pid: $RuntimePid)"
log "SUCCESS" "Runtime service started in background"
fi
}
start_backend() {
local BACKEND_LOG="${WORK_HOME}/backend.log"
if [ ! -d "$BACKEND_DIR" ]; then
log "ERROR" "Backend directory not found: $BACKEND_DIR"
error_exit "Backend directory not found, cannot start service" \
"Run full install first: ./setup.sh"
fi
if [ ! -f "$TARGET_ENV_FILE" ]; then
log "ERROR" ".env file not found: $TARGET_ENV_FILE"
error_exit ".env file not found, cannot start service" \
"Run full install first: ./setup.sh"
fi
if [ -z "${SERVER_AES_MASTER_KEY_ENV:-}" ] && [ -f "$TARGET_ENV_FILE" ]; then
local AES_KEY_LINE
AES_KEY_LINE=$(grep '^SERVER_AES_MASTER_KEY=' "$TARGET_ENV_FILE" 2>/dev/null | head -n 1)
if [ -n "$AES_KEY_LINE" ]; then
SERVER_AES_MASTER_KEY_ENV=$(echo "$AES_KEY_LINE" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
log "INFO" "Reading AES key from .env file"
fi
fi
if [ -z "${SERVER_AES_MASTER_KEY_ENV:-}" ]; then
log "WARN" "AES key not found, generating temporary key (recommend running full install)"
if command -v python3.11 &> /dev/null; then
local AES_SCRIPT="${WORK_HOME}/agent-studio/scripts/build_AES_master_key.sh"
if [ -f "$AES_SCRIPT" ]; then
SERVER_AES_MASTER_KEY_ENV=$(bash "$AES_SCRIPT" 2>/dev/null || echo "")
fi
fi
if [ -z "${SERVER_AES_MASTER_KEY_ENV:-}" ]; then
SERVER_AES_MASTER_KEY_ENV=$(openssl rand -base64 32 2>/dev/null || echo "")
fi
fi
export SERVER_AES_MASTER_KEY_ENV
log "INFO" "AES key set: ${SERVER_AES_MASTER_KEY_ENV:0:8}**** (partially hidden)"
local BACKEND_PORT_START
BACKEND_PORT_START=$(get_backend_port "$BACKEND_LOG" "8000")
local OLD_PID
OLD_PID=$(service_state_get backend_pid || true)
if [ -n "$OLD_PID" ]; then
local PORT_PID_NOW
PORT_PID_NOW=$(find_pid_by_port "$BACKEND_PORT_START")
if [ -n "$PORT_PID_NOW" ] && [ "$OLD_PID" = "$PORT_PID_NOW" ]; then
if ps -p "$OLD_PID" > /dev/null 2>&1 && ps -p "$OLD_PID" -o cmd= 2>/dev/null | grep -qE "(python.*main\.py|uvicorn|fastapi)" 2>/dev/null; then
log "WARN" "Backend already running (PID: $OLD_PID, port: $BACKEND_PORT_START), skipping start"
return 0
fi
fi
log "INFO" "Removing stale backend_pid / backend_port from services.state"
service_state_unset backend_pid backend_port
fi
log "INFO" "Starting backend service, log file: $BACKEND_LOG"
cd "$BACKEND_DIR" || error_exit "Cannot cd to backend directory"
if [ ! -f ".venv/bin/activate" ]; then
error_exit "Virtual environment not found" \
"Run full install first: ./setup.sh"
fi
source .venv/bin/activate || error_exit "Failed to activate virtual environment"
if [ -n "${SERVER_AES_MASTER_KEY_ENV:-}" ]; then
export SERVER_AES_MASTER_KEY_ENV
fi
local CONFIG_BACKEND_PORT
CONFIG_BACKEND_PORT=$(grep '^BACKEND_PORT=' "$TARGET_ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" || echo "8000")
if [ -n "$CONFIG_BACKEND_PORT" ] && [ "$CONFIG_BACKEND_PORT" -ge 1 ] && [ "$CONFIG_BACKEND_PORT" -le 65535 ]; then
export BACKEND_PORT="$CONFIG_BACKEND_PORT"
log "INFO" "Using backend port from .env: $BACKEND_PORT"
fi
python main.py > "$BACKEND_LOG" 2>&1 &
local LAUNCH_PID=$!
service_state_set "backend_pid=$LAUNCH_PID"
sleep 2
sleep 3
local BACKEND_PORT
BACKEND_PORT=$(get_backend_port "$BACKEND_LOG" "8000")
if ! wait_port_ready "$BACKEND_PORT" "Backend service" 3; then
log "WARN" "Backend startup abnormal, see log: $BACKEND_LOG"
log "WARN" "Last 20 lines of log:"
tail -n 20 "$BACKEND_LOG" 2>/dev/null || true
else
local PORT_PID
PORT_PID=$(find_pid_by_port "$BACKEND_PORT")
if [ -n "$PORT_PID" ]; then
service_state_set "backend_pid=$PORT_PID" "backend_port=$BACKEND_PORT"
log "INFO" "Saved backend_pid / backend_port to services.state (port: $BACKEND_PORT)"
log "SUCCESS" "Backend started (PID: $PORT_PID, port: $BACKEND_PORT)"
fi
fi
}
start_frontend() {
local FRONTEND_LOG="${WORK_HOME}/frontend.log"
if [ ! -d "$FRONTEND_DIR" ]; then
log "ERROR" "Frontend directory not found: $FRONTEND_DIR"
error_exit "Frontend directory not found, cannot start service" \
"Run full install first: ./setup.sh"
fi
local OLD_PID
OLD_PID=$(service_state_get frontend_pid || true)
if [ -n "$OLD_PID" ] && ps -p "$OLD_PID" > /dev/null 2>&1; then
log "WARN" "Frontend already running (PID: $OLD_PID), skipping start"
return 0
fi
if [ -n "$OLD_PID" ]; then
log "INFO" "Removing stale frontend_pid / frontend_port from services.state"
service_state_unset frontend_pid frontend_port
fi
log "INFO" "Starting frontend service, log file: $FRONTEND_LOG"
cd "$FRONTEND_DIR" || error_exit "Cannot cd to frontend directory"
if [ ! -d "node_modules" ]; then
error_exit "Frontend dependencies not installed, cannot start service" \
"Run full install first: ./setup.sh"
fi
load_environments
check_command "node"
check_command "npm"
npm run dev > "$FRONTEND_LOG" 2>&1 &
local NPM_PID=$!
service_state_set "frontend_pid=$NPM_PID"
sleep 3
local FRONTEND_PORT
FRONTEND_PORT=$(get_frontend_port "$FRONTEND_LOG" "3000")
if ! wait_port_ready "$FRONTEND_PORT" "Frontend service" 3; then
log "WARN" "Frontend startup abnormal, see log: $FRONTEND_LOG"
log "WARN" "Last 10 lines of log:"
tail -n 10 "$FRONTEND_LOG" 2>/dev/null || true
else
local PORT_PID
PORT_PID=$(find_pid_by_port "$FRONTEND_PORT")
if [ -n "$PORT_PID" ]; then
service_state_set "frontend_pid=$PORT_PID" "frontend_port=$FRONTEND_PORT"
log "INFO" "Saved frontend_pid / frontend_port to services.state (port: $FRONTEND_PORT)"
log "SUCCESS" "Frontend started (PID: $PORT_PID, port: $FRONTEND_PORT)"
fi
fi
}
start_services() {
log "INFO" "===== Starting services ====="
local RUNTIME_SERVER_DIR RUNTIME_ENV_FILE
RUNTIME_SERVER_DIR="${RUNTIME_DIR:?}/server"
RUNTIME_ENV_FILE="${RUNTIME_SERVER_DIR}/.env"
if [ -d "$RUNTIME_SERVER_DIR" ] && [ -f "$RUNTIME_ENV_FILE" ]; then
start_runtime_service
else
log "WARN" "Runtime not installed or incomplete (need server/.env), skipping runtime start"
fi
start_backend
start_frontend
log "SUCCESS" "Services started"
check_status
return 0
}
restart_services() {
log "INFO" "===== Restarting services ====="
log "INFO" "Stopping services..."
stop_services
log "INFO" "Waiting 2s before starting..."
sleep 2
log "INFO" "Starting services..."
start_services
log "SUCCESS" "Services restarted"
return 0
}