release: bump version to 0.66.0
This commit is contained in:
477
planet.sh
477
planet.sh
@@ -115,10 +115,19 @@ FRONTEND_RUNTIME_BIN="${FRONTEND_RUNTIME_BIN:-}"
|
||||
FRONTEND_RUNTIME_SOURCE="${FRONTEND_RUNTIME_SOURCE:-}"
|
||||
PLANET_STATE_DIR="${PLANET_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/planet}"
|
||||
PLANET_CACHE_DIR="${PLANET_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/planet}"
|
||||
PLANET_UV_TUNA_INDEX_URL="${PLANET_UV_TUNA_INDEX_URL:-https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/}"
|
||||
BACKEND_PID_FILE="$PLANET_STATE_DIR/backend.pid"
|
||||
BACKEND_LOG_FILE="$PLANET_STATE_DIR/backend.log"
|
||||
FRONTEND_PID_FILE="$PLANET_STATE_DIR/frontend.pid"
|
||||
FRONTEND_LOG_FILE="$PLANET_STATE_DIR/frontend.log"
|
||||
FRONTEND_CA_SERVER_PID_FILE="$PLANET_STATE_DIR/frontend_ca_server.pid"
|
||||
FRONTEND_CA_SERVER_LOG_FILE="$PLANET_STATE_DIR/frontend_ca_server.log"
|
||||
FRONTEND_CERT_DIR="$PLANET_STATE_DIR/certs"
|
||||
FRONTEND_CA_CERT_FILE="$FRONTEND_CERT_DIR/planet-dev-ca.crt"
|
||||
FRONTEND_CA_KEY_FILE="$FRONTEND_CERT_DIR/planet-dev-ca.key"
|
||||
FRONTEND_HTTPS_CERT_FILE="$FRONTEND_CERT_DIR/planet-dev-server.crt"
|
||||
FRONTEND_HTTPS_KEY_FILE="$FRONTEND_CERT_DIR/planet-dev-server.key"
|
||||
FRONTEND_CA_DOWNLOAD_PORT="${FRONTEND_CA_DOWNLOAD_PORT:-8088}"
|
||||
PLANET_PORT_STATE_FILE="$PLANET_STATE_DIR/ports.env"
|
||||
FRONTEND_VITE_ENTRY="$SCRIPT_DIR/frontend/node_modules/vite/bin/vite.js"
|
||||
MOTION_AGENT_PID_FILE="$PLANET_STATE_DIR/motion_agent.pid"
|
||||
@@ -249,6 +258,21 @@ write_pid_file() {
|
||||
chmod 600 "$pid_file" 2>/dev/null || true
|
||||
}
|
||||
|
||||
start_detached_command() {
|
||||
local log_file="$1"
|
||||
shift
|
||||
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
setsid "$@" </dev/null > "$log_file" 2>&1 &
|
||||
else
|
||||
nohup "$@" </dev/null > "$log_file" 2>&1 &
|
||||
fi
|
||||
|
||||
local detached_pid="$!"
|
||||
disown "$detached_pid" 2>/dev/null || true
|
||||
printf "%s" "$detached_pid"
|
||||
}
|
||||
|
||||
remove_pid_file() {
|
||||
local pid_file="$1"
|
||||
|
||||
@@ -261,6 +285,12 @@ http_ok() {
|
||||
curl -fsS --max-time "$HTTP_CHECK_MAX_TIME" "$url" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
https_ok_insecure() {
|
||||
local url="$1"
|
||||
|
||||
curl -kfsS --max-time "$HTTP_CHECK_MAX_TIME" "$url" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
log_matches() {
|
||||
local log_file="$1"
|
||||
shift
|
||||
@@ -286,20 +316,50 @@ read_port_state_value() {
|
||||
fi
|
||||
}
|
||||
|
||||
read_port_state_text_value() {
|
||||
local key="$1"
|
||||
local fallback="$2"
|
||||
local value=""
|
||||
|
||||
if [ -f "$PLANET_PORT_STATE_FILE" ]; then
|
||||
value="$(sed -nE "s/^${key}=([A-Za-z0-9_.:-]+)$/\\1/p" "$PLANET_PORT_STATE_FILE" | tail -n 1)"
|
||||
fi
|
||||
|
||||
if [ -n "$value" ]; then
|
||||
printf "%s" "$value"
|
||||
else
|
||||
printf "%s" "$fallback"
|
||||
fi
|
||||
}
|
||||
|
||||
current_frontend_scheme() {
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
printf "https"
|
||||
else
|
||||
printf "http"
|
||||
fi
|
||||
}
|
||||
|
||||
write_port_state() {
|
||||
local backend_port="$1"
|
||||
local frontend_port="$2"
|
||||
local ai_provider_port="$3"
|
||||
local motion_agent_port="$4"
|
||||
local frontend_scheme="${5:-$(current_frontend_scheme)}"
|
||||
|
||||
validate_port "$backend_port"
|
||||
validate_port "$frontend_port"
|
||||
validate_port "$ai_provider_port"
|
||||
validate_port "$motion_agent_port"
|
||||
case "$frontend_scheme" in
|
||||
http|https) ;;
|
||||
*) frontend_scheme="http" ;;
|
||||
esac
|
||||
|
||||
{
|
||||
printf "BACKEND_PORT=%s\n" "$backend_port"
|
||||
printf "FRONTEND_PORT=%s\n" "$frontend_port"
|
||||
printf "FRONTEND_SCHEME=%s\n" "$frontend_scheme"
|
||||
printf "AI_PROVIDER_PORT=%s\n" "$ai_provider_port"
|
||||
printf "MOTION_AGENT_PORT=%s\n" "$motion_agent_port"
|
||||
} > "$PLANET_PORT_STATE_FILE"
|
||||
@@ -699,12 +759,16 @@ log_lan_access_notes() {
|
||||
local frontend_port="$1"
|
||||
local backend_port="$2"
|
||||
local ai_provider_port="${3:-$AI_PROVIDER_PORT}"
|
||||
local frontend_scheme="${4:-http}"
|
||||
local recommended_lan_ip=""
|
||||
|
||||
if recommended_lan_ip="$(get_recommended_lan_ipv4)"; then
|
||||
log_note "推荐访问地址: http://${recommended_lan_ip}:${frontend_port}"
|
||||
log_note "推荐访问地址: ${frontend_scheme}://${recommended_lan_ip}:${frontend_port}"
|
||||
log_note "后端健康检查: http://${recommended_lan_ip}:${backend_port}/health"
|
||||
log_note "AI Provider 健康检查: http://${recommended_lan_ip}:${ai_provider_port}/health"
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
log_note "手机 CA 证书下载: http://${recommended_lan_ip}:${FRONTEND_CA_DOWNLOAD_PORT}/planet-dev-ca.crt"
|
||||
fi
|
||||
else
|
||||
log_note "前端已对局域网开放,请使用本机局域网 IP 访问 :${frontend_port}"
|
||||
log_note "后端已对局域网开放,请使用本机局域网 IP 访问 :${backend_port}/health"
|
||||
@@ -1045,6 +1109,133 @@ run_with_retry() {
|
||||
return 1
|
||||
}
|
||||
|
||||
run_uv_sync_with_mirror_fallback() {
|
||||
local log_file="$1"
|
||||
|
||||
if run_with_retry \
|
||||
"$DEPENDENCY_INSTALL_MAX_RETRIES" \
|
||||
"$DEPENDENCY_INSTALL_RETRY_INTERVAL" \
|
||||
"uv sync 默认源失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次,准备切换清华源" \
|
||||
"uv sync --frozen" \
|
||||
run_command_quiet_unless_verbose "$log_file" uv sync --frozen --group dev; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
configure_uv_tuna_index
|
||||
set_wait_detail "已写入 uv.toml 清华源,重新执行 uv sync"
|
||||
|
||||
run_with_retry \
|
||||
"$DEPENDENCY_INSTALL_MAX_RETRIES" \
|
||||
"$DEPENDENCY_INSTALL_RETRY_INTERVAL" \
|
||||
"uv 环境初始化失败,清华源重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次后仍失败" \
|
||||
"uv sync --frozen (清华源)" \
|
||||
run_command_quiet_unless_verbose "$log_file" uv sync --frozen --group dev
|
||||
}
|
||||
|
||||
configure_uv_tuna_index() {
|
||||
local uv_config="$SCRIPT_DIR/uv.toml"
|
||||
|
||||
if [ -f "$uv_config" ] &&
|
||||
grep -Fq 'name = "tsinghua"' "$uv_config" 2>/dev/null &&
|
||||
grep -Fq "$PLANET_UV_TUNA_INDEX_URL" "$uv_config" 2>/dev/null &&
|
||||
grep -Eq '^[[:space:]]*default[[:space:]]*=[[:space:]]*true' "$uv_config" 2>/dev/null; then
|
||||
log_note "uv.toml 已配置清华源,直接重试 uv sync"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$uv_config" ] && grep -Eq '^[[:space:]]*default[[:space:]]*=[[:space:]]*true' "$uv_config" 2>/dev/null; then
|
||||
log_warn "uv.toml 已存在默认 index,未覆盖用户配置"
|
||||
log_note "如需手动切换清华源,可添加 [[index]] name=\"tsinghua\" 并设置 default=true"
|
||||
return 0
|
||||
fi
|
||||
|
||||
{
|
||||
if [ -s "$uv_config" ]; then
|
||||
printf "\n"
|
||||
fi
|
||||
printf '[[index]]\n'
|
||||
printf 'name = "tsinghua"\n'
|
||||
printf 'url = "%s"\n' "$PLANET_UV_TUNA_INDEX_URL"
|
||||
printf 'default = true\n'
|
||||
} >> "$uv_config"
|
||||
|
||||
log_note "已向 uv.toml 添加清华 PyPI 源: ${PLANET_UV_TUNA_INDEX_URL}"
|
||||
}
|
||||
|
||||
install_system_package() {
|
||||
local package_name="$1"
|
||||
local log_file="$PLANET_STATE_DIR/system_dependency_install.log"
|
||||
local -a sudo_prefix
|
||||
|
||||
sudo_prefix=()
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
log_error "缺少 sudo,无法自动安装系统依赖: ${package_name}"
|
||||
return 1
|
||||
fi
|
||||
sudo_prefix=(sudo)
|
||||
fi
|
||||
|
||||
: > "$log_file"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (apt)"
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" apt-get update &&
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" apt-get install -y "$package_name"
|
||||
return $?
|
||||
fi
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (dnf)"
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" dnf install -y "$package_name"
|
||||
return $?
|
||||
fi
|
||||
if command -v yum >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (yum)"
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" yum install -y "$package_name"
|
||||
return $?
|
||||
fi
|
||||
if command -v pacman >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (pacman)"
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" pacman -Sy --noconfirm "$package_name"
|
||||
return $?
|
||||
fi
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (apk)"
|
||||
run_command_quiet_unless_verbose "$log_file" "${sudo_prefix[@]}" apk add --no-cache "$package_name"
|
||||
return $?
|
||||
fi
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
set_wait_detail "安装系统依赖 ${package_name} (brew)"
|
||||
run_command_quiet_unless_verbose "$log_file" brew install "$package_name"
|
||||
return $?
|
||||
fi
|
||||
|
||||
log_error "未识别系统包管理器,无法自动安装: ${package_name}"
|
||||
return 1
|
||||
}
|
||||
|
||||
ensure_system_command() {
|
||||
local command_name="$1"
|
||||
local package_name="$2"
|
||||
local label="$3"
|
||||
local log_file="$PLANET_STATE_DIR/system_dependency_install.log"
|
||||
|
||||
if command -v "$command_name" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_warn "缺少 ${label},准备自动安装 ${package_name}"
|
||||
if ! install_system_package "$package_name"; then
|
||||
tail -20 "$log_file" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
log_error "${label} 安装后仍不可用,请手动安装 ${package_name}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
format_wait_elapsed_seconds() {
|
||||
local completed_intervals="${1:-0}"
|
||||
local interval="${2:-1}"
|
||||
@@ -1326,6 +1517,10 @@ install_bun_if_needed() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! ensure_system_command unzip unzip "unzip"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set_wait_detail "未找到 bun,准备自动安装"
|
||||
mkdir -p "$HOME/.bun"
|
||||
|
||||
@@ -1360,12 +1555,7 @@ ensure_uv_backend_deps() {
|
||||
|
||||
if [ ! -x "$SCRIPT_DIR/.venv/bin/python" ]; then
|
||||
log_warn "未检测到 .venv,正在执行 uv sync"
|
||||
if ! run_with_retry \
|
||||
"$DEPENDENCY_INSTALL_MAX_RETRIES" \
|
||||
"$DEPENDENCY_INSTALL_RETRY_INTERVAL" \
|
||||
"uv 环境初始化失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次" \
|
||||
"uv sync --frozen" \
|
||||
run_command_quiet_unless_verbose "$log_file" uv sync --frozen --group dev; then
|
||||
if ! run_uv_sync_with_mirror_fallback "$log_file"; then
|
||||
tail -20 "$log_file" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
@@ -1414,12 +1604,7 @@ sync_python_deps() {
|
||||
: > "$log_file"
|
||||
|
||||
set_wait_detail "同步 Python 依赖"
|
||||
if ! run_with_retry \
|
||||
"$DEPENDENCY_INSTALL_MAX_RETRIES" \
|
||||
"$DEPENDENCY_INSTALL_RETRY_INTERVAL" \
|
||||
"uv 环境初始化失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次" \
|
||||
"uv sync --frozen" \
|
||||
run_command_quiet_unless_verbose "$log_file" uv sync --frozen --group dev; then
|
||||
if ! run_uv_sync_with_mirror_fallback "$log_file"; then
|
||||
tail -20 "$log_file" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
@@ -1712,15 +1897,26 @@ wait_for_frontend_ready() {
|
||||
render_wait_spinner "$WAIT_SPINNER_MESSAGE" "$WAIT_SPINNER_STEP"
|
||||
fi
|
||||
|
||||
if [ $(( tick % probe_every_ticks )) -eq 0 ] &&
|
||||
http_ok "http://localhost:${frontend_port}"; then
|
||||
if [ "$owns_wait_session" -eq 1 ]; then
|
||||
stop_wait_session
|
||||
finish_wait_spinner "前端已就绪"
|
||||
if [ $(( tick % probe_every_ticks )) -eq 0 ]; then
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
frontend_ready_ok() {
|
||||
https_ok_insecure "https://localhost:${frontend_port}"
|
||||
}
|
||||
else
|
||||
reset_wait_spinner_state
|
||||
frontend_ready_ok() {
|
||||
http_ok "http://localhost:${frontend_port}"
|
||||
}
|
||||
fi
|
||||
|
||||
if frontend_ready_ok; then
|
||||
if [ "$owns_wait_session" -eq 1 ]; then
|
||||
stop_wait_session
|
||||
finish_wait_spinner "前端已就绪"
|
||||
else
|
||||
reset_wait_spinner_state
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep 0.125
|
||||
@@ -1974,8 +2170,10 @@ start_backend_with_retry() {
|
||||
fi
|
||||
cd "$SCRIPT_DIR/backend"
|
||||
: > "$BACKEND_LOG_FILE"
|
||||
PYTHONPATH="$SCRIPT_DIR/backend" nohup uv run --frozen --project "$SCRIPT_DIR" python -m uvicorn app.main:app --host "$backend_bind_host" --port "$backend_port" --reload > "$BACKEND_LOG_FILE" 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
BACKEND_PID="$(start_detached_command "$BACKEND_LOG_FILE" \
|
||||
env PYTHONPATH="$SCRIPT_DIR/backend" \
|
||||
uv run --frozen --project "$SCRIPT_DIR" python -m uvicorn app.main:app \
|
||||
--host "$backend_bind_host" --port "$backend_port" --reload)"
|
||||
write_pid_file "$BACKEND_PID_FILE" "$BACKEND_PID"
|
||||
if [ "$VERBOSE" -eq 1 ]; then
|
||||
WAIT_VERBOSE_LOG_FILE="$BACKEND_LOG_FILE"
|
||||
@@ -2527,6 +2725,90 @@ report_backend_port_conflict_if_needed() {
|
||||
}
|
||||
|
||||
# Frontend lifecycle helpers
|
||||
cleanup_frontend_ca_server() {
|
||||
local tracked_pid=""
|
||||
|
||||
if [ -f "$FRONTEND_CA_SERVER_PID_FILE" ]; then
|
||||
if tracked_pid="$(read_pid_file "$FRONTEND_CA_SERVER_PID_FILE")"; then
|
||||
terminate_process_tree TERM "$tracked_pid"
|
||||
sleep 1
|
||||
terminate_process_tree KILL "$tracked_pid"
|
||||
fi
|
||||
remove_pid_file "$FRONTEND_CA_SERVER_PID_FILE"
|
||||
fi
|
||||
|
||||
pkill -f "python3 -m http.server ${FRONTEND_CA_DOWNLOAD_PORT} .*${FRONTEND_CERT_DIR}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
get_frontend_https_alt_names() {
|
||||
local candidate=""
|
||||
local alt_names="DNS:localhost,IP:127.0.0.1"
|
||||
|
||||
while read -r candidate; do
|
||||
[ -z "$candidate" ] && continue
|
||||
case ",${alt_names}," in
|
||||
*",IP:${candidate},"*) continue ;;
|
||||
esac
|
||||
alt_names="${alt_names},IP:${candidate}"
|
||||
done <<EOF
|
||||
$(hostname -I 2>/dev/null | tr ' ' '\n')
|
||||
EOF
|
||||
|
||||
printf "%s" "$alt_names"
|
||||
}
|
||||
|
||||
ensure_frontend_https_certs() {
|
||||
local common_name=""
|
||||
local alt_names=""
|
||||
local csr_file="$FRONTEND_CERT_DIR/planet-dev-server.csr"
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || {
|
||||
log_error "--allow-lan-https 需要 openssl 生成本地开发证书"
|
||||
exit 1
|
||||
}
|
||||
|
||||
mkdir -p "$FRONTEND_CERT_DIR"
|
||||
chmod 700 "$FRONTEND_CERT_DIR" 2>/dev/null || true
|
||||
|
||||
if [ ! -f "$FRONTEND_CA_KEY_FILE" ] || [ ! -f "$FRONTEND_CA_CERT_FILE" ]; then
|
||||
set_wait_detail "生成 Planet 本地开发 CA"
|
||||
openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
|
||||
-keyout "$FRONTEND_CA_KEY_FILE" \
|
||||
-out "$FRONTEND_CA_CERT_FILE" \
|
||||
-subj "/CN=Planet Dev Local CA" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
common_name="$(get_recommended_lan_ipv4 || printf "localhost")"
|
||||
alt_names="$(get_frontend_https_alt_names)"
|
||||
set_wait_detail "生成局域网 HTTPS 证书: ${common_name}"
|
||||
openssl req -newkey rsa:2048 -nodes \
|
||||
-keyout "$FRONTEND_HTTPS_KEY_FILE" \
|
||||
-out "$csr_file" \
|
||||
-subj "/CN=${common_name}" \
|
||||
-addext "subjectAltName=${alt_names}" >/dev/null 2>&1
|
||||
openssl x509 -req -in "$csr_file" \
|
||||
-CA "$FRONTEND_CA_CERT_FILE" \
|
||||
-CAkey "$FRONTEND_CA_KEY_FILE" \
|
||||
-CAcreateserial \
|
||||
-out "$FRONTEND_HTTPS_CERT_FILE" \
|
||||
-days 825 -sha256 -copy_extensions copy >/dev/null 2>&1
|
||||
chmod 600 "$FRONTEND_CA_KEY_FILE" "$FRONTEND_HTTPS_KEY_FILE" 2>/dev/null || true
|
||||
chmod 644 "$FRONTEND_CA_CERT_FILE" "$FRONTEND_HTTPS_CERT_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
start_frontend_ca_server() {
|
||||
[ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ] || return 0
|
||||
|
||||
cleanup_frontend_ca_server
|
||||
: > "$FRONTEND_CA_SERVER_LOG_FILE"
|
||||
local ca_server_pid=""
|
||||
ca_server_pid="$(start_detached_command "$FRONTEND_CA_SERVER_LOG_FILE" \
|
||||
python3 -m http.server "$FRONTEND_CA_DOWNLOAD_PORT" \
|
||||
--bind 0.0.0.0 \
|
||||
--directory "$FRONTEND_CERT_DIR")"
|
||||
write_pid_file "$FRONTEND_CA_SERVER_PID_FILE" "$ca_server_pid"
|
||||
}
|
||||
|
||||
cleanup_frontend_processes() {
|
||||
local frontend_port="${1:-$DEFAULT_FRONTEND_PORT}"
|
||||
local tracked_pid=""
|
||||
@@ -2543,6 +2825,7 @@ cleanup_frontend_processes() {
|
||||
pkill -f "${FRONTEND_VITE_ENTRY} --port ${frontend_port} --strictPort" 2>/dev/null || true
|
||||
pkill -f "${FRONTEND_VITE_ENTRY} --host 127.0.0.1 --port ${frontend_port} --strictPort" 2>/dev/null || true
|
||||
pkill -f "${FRONTEND_VITE_ENTRY} --host 0.0.0.0 --port ${frontend_port} --strictPort" 2>/dev/null || true
|
||||
cleanup_frontend_ca_server
|
||||
}
|
||||
|
||||
start_frontend_with_retry() {
|
||||
@@ -2566,8 +2849,18 @@ start_frontend_with_retry() {
|
||||
frontend_args+=(--host 0.0.0.0)
|
||||
fi
|
||||
frontend_args+=(--port "$frontend_port" --strictPort)
|
||||
PLANET_BACKEND_PORT="$backend_port" nohup "$FRONTEND_RUNTIME_BIN" "${frontend_args[@]}" > "$FRONTEND_LOG_FILE" 2>&1 &
|
||||
FRONTEND_PID=$!
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
FRONTEND_PID="$(start_detached_command "$FRONTEND_LOG_FILE" \
|
||||
env PLANET_BACKEND_PORT="$backend_port" \
|
||||
PLANET_FRONTEND_HTTPS=1 \
|
||||
PLANET_FRONTEND_HTTPS_KEY="$FRONTEND_HTTPS_KEY_FILE" \
|
||||
PLANET_FRONTEND_HTTPS_CERT="$FRONTEND_HTTPS_CERT_FILE" \
|
||||
"$FRONTEND_RUNTIME_BIN" "${frontend_args[@]}")"
|
||||
else
|
||||
FRONTEND_PID="$(start_detached_command "$FRONTEND_LOG_FILE" \
|
||||
env PLANET_BACKEND_PORT="$backend_port" \
|
||||
"$FRONTEND_RUNTIME_BIN" "${frontend_args[@]}")"
|
||||
fi
|
||||
write_pid_file "$FRONTEND_PID_FILE" "$FRONTEND_PID"
|
||||
|
||||
if wait_for_frontend_ready "$frontend_port" "$FRONTEND_PID" "$FRONTEND_LOG_FILE" "$FRONTEND_HEALTH_CHECK_ATTEMPTS" "$FRONTEND_HEALTH_CHECK_INTERVAL"; then
|
||||
@@ -2601,12 +2894,19 @@ start_frontend_service() {
|
||||
|
||||
start_wait_session "检查前端依赖"
|
||||
ensure_frontend_deps
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
ensure_frontend_https_certs
|
||||
fi
|
||||
stop_wait_session
|
||||
log_success "前端依赖已就绪"
|
||||
|
||||
start_wait_session "启动前端服务"
|
||||
if [ "$frontend_lan_enabled" -eq 1 ]; then
|
||||
set_wait_detail "启动 Vite 开发服务器(局域网开放)"
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
set_wait_detail "启动 Vite HTTPS 开发服务器(局域网开放)"
|
||||
else
|
||||
set_wait_detail "启动 Vite 开发服务器(局域网开放)"
|
||||
fi
|
||||
else
|
||||
set_wait_detail "启动 Vite 开发服务器"
|
||||
fi
|
||||
@@ -2620,6 +2920,7 @@ start_frontend_service() {
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
start_frontend_ca_server
|
||||
stop_wait_session
|
||||
log_success "前端已就绪"
|
||||
}
|
||||
@@ -2639,6 +2940,7 @@ parse_service_args() {
|
||||
MOTION_AGENT_DRY_RUN=0
|
||||
DATABASE_REQUESTED=0
|
||||
FRONTEND_LAN_ENABLED=0
|
||||
FRONTEND_LAN_HTTPS_ENABLED=0
|
||||
|
||||
case "${PLANET_START_MOTION_AGENT:-0}" in
|
||||
1|true|yes|on)
|
||||
@@ -2727,6 +3029,11 @@ parse_service_args() {
|
||||
FRONTEND_LAN_ENABLED=1
|
||||
shift 1
|
||||
;;
|
||||
--allow-lan-https)
|
||||
FRONTEND_LAN_ENABLED=1
|
||||
FRONTEND_LAN_HTTPS_ENABLED=1
|
||||
shift 1
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=1
|
||||
shift 1
|
||||
@@ -2804,6 +3111,28 @@ print_http_health_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
print_frontend_health_status() {
|
||||
local frontend_port="$1"
|
||||
local frontend_scheme="${2:-$(read_port_state_text_value FRONTEND_SCHEME http)}"
|
||||
|
||||
if [ "$frontend_scheme" = "https" ]; then
|
||||
if https_ok_insecure "https://localhost:${frontend_port}"; then
|
||||
echo -e "${DIM} 前端:${NC} ${GREEN}online${NC} ${DIM}(https://localhost:${frontend_port})${NC}"
|
||||
else
|
||||
echo -e "${DIM} 前端:${NC} ${RED}offline${NC} ${DIM}(https://localhost:${frontend_port})${NC}"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
if http_ok "http://localhost:${frontend_port}"; then
|
||||
echo -e "${DIM} 前端:${NC} ${GREEN}online${NC} ${DIM}(http://localhost:${frontend_port})${NC}"
|
||||
elif https_ok_insecure "https://localhost:${frontend_port}"; then
|
||||
echo -e "${DIM} 前端:${NC} ${GREEN}online${NC} ${DIM}(https://localhost:${frontend_port})${NC}"
|
||||
else
|
||||
echo -e "${DIM} 前端:${NC} ${RED}offline${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
print_process_health_status() {
|
||||
local service_name="$1"
|
||||
local pattern="$2"
|
||||
@@ -2933,9 +3262,8 @@ start_motion_agent_service() {
|
||||
motion_args+=(--dry-run)
|
||||
fi
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
nohup "$SCRIPT_DIR/.venv/bin/python" "${motion_args[@]}" > "$MOTION_AGENT_LOG_FILE" 2>&1 &
|
||||
MOTION_AGENT_PID=$!
|
||||
MOTION_AGENT_PID="$(start_detached_command "$MOTION_AGENT_LOG_FILE" \
|
||||
"$SCRIPT_DIR/.venv/bin/python" "${motion_args[@]}")"
|
||||
write_pid_file "$MOTION_AGENT_PID_FILE" "$MOTION_AGENT_PID"
|
||||
|
||||
if wait_for_motion_agent_ready "$motion_agent_port" "$MOTION_AGENT_PID"; then
|
||||
@@ -3049,11 +3377,13 @@ PY
|
||||
planet_app_services_running() {
|
||||
local backend_port=""
|
||||
local frontend_port=""
|
||||
local frontend_scheme=""
|
||||
local ai_provider_port=""
|
||||
local motion_agent_port=""
|
||||
|
||||
backend_port="$(read_port_state_value BACKEND_PORT "$DEFAULT_BACKEND_PORT")"
|
||||
frontend_port="$(read_port_state_value FRONTEND_PORT "$DEFAULT_FRONTEND_PORT")"
|
||||
frontend_scheme="$(read_port_state_text_value FRONTEND_SCHEME http)"
|
||||
ai_provider_port="$(read_port_state_value AI_PROVIDER_PORT "$DEFAULT_AI_PROVIDER_PORT")"
|
||||
motion_agent_port="$(read_port_state_value MOTION_AGENT_PORT "$DEFAULT_MOTION_AGENT_PORT")"
|
||||
|
||||
@@ -3061,8 +3391,14 @@ planet_app_services_running() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if http_ok "http://localhost:${frontend_port}"; then
|
||||
return 0
|
||||
if [ "$frontend_scheme" = "https" ]; then
|
||||
if https_ok_insecure "https://localhost:${frontend_port}"; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
if http_ok "http://localhost:${frontend_port}" || https_ok_insecure "https://localhost:${frontend_port}"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if http_ok "http://localhost:${ai_provider_port}/health"; then
|
||||
@@ -3251,8 +3587,16 @@ prepare_allow_lan_public_ports() {
|
||||
"后端" "$BACKEND_PORT" \
|
||||
"前端" "$FRONTEND_PORT" \
|
||||
"AI Provider" "$AI_PROVIDER_PORT"
|
||||
request_windows_portproxy_cleanup_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" || true
|
||||
request_windows_firewall_rules_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" || true
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
release_requested_ports "前端 CA 证书下载" "$FRONTEND_CA_DOWNLOAD_PORT"
|
||||
fi
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
request_windows_portproxy_cleanup_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" "$FRONTEND_CA_DOWNLOAD_PORT" || true
|
||||
request_windows_firewall_rules_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" "$FRONTEND_CA_DOWNLOAD_PORT" || true
|
||||
else
|
||||
request_windows_portproxy_cleanup_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" || true
|
||||
request_windows_firewall_rules_if_needed "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" || true
|
||||
fi
|
||||
}
|
||||
|
||||
verify_http_endpoint() {
|
||||
@@ -3271,12 +3615,30 @@ verify_allow_lan_access() {
|
||||
|
||||
[ "$FRONTEND_LAN_ENABLED" -eq 1 ] || return 0
|
||||
|
||||
verify_http_endpoint "本机访问" "http://localhost:${FRONTEND_PORT}" || exit 1
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
log_note "检查本机访问: https://localhost:${FRONTEND_PORT}"
|
||||
https_ok_insecure "https://localhost:${FRONTEND_PORT}" || {
|
||||
log_error "本机访问不可访问: https://localhost:${FRONTEND_PORT}"
|
||||
exit 1
|
||||
}
|
||||
verify_http_endpoint "本机 CA 下载" "http://localhost:${FRONTEND_CA_DOWNLOAD_PORT}/planet-dev-ca.crt" || exit 1
|
||||
else
|
||||
verify_http_endpoint "本机访问" "http://localhost:${FRONTEND_PORT}" || exit 1
|
||||
fi
|
||||
verify_http_endpoint "本机访问" "http://localhost:${BACKEND_PORT}/health" || exit 1
|
||||
verify_http_endpoint "本机访问" "http://localhost:${AI_PROVIDER_PORT}/health" || exit 1
|
||||
|
||||
if lan_ip="$(get_recommended_lan_ipv4)"; then
|
||||
verify_http_endpoint "局域网访问" "http://${lan_ip}:${FRONTEND_PORT}" || exit 1
|
||||
if [ "${FRONTEND_LAN_HTTPS_ENABLED:-0}" -eq 1 ]; then
|
||||
log_note "检查局域网访问: https://${lan_ip}:${FRONTEND_PORT}"
|
||||
https_ok_insecure "https://${lan_ip}:${FRONTEND_PORT}" || {
|
||||
log_error "局域网访问不可访问: https://${lan_ip}:${FRONTEND_PORT}"
|
||||
exit 1
|
||||
}
|
||||
verify_http_endpoint "局域网 CA 下载" "http://${lan_ip}:${FRONTEND_CA_DOWNLOAD_PORT}/planet-dev-ca.crt" || exit 1
|
||||
else
|
||||
verify_http_endpoint "局域网访问" "http://${lan_ip}:${FRONTEND_PORT}" || exit 1
|
||||
fi
|
||||
verify_http_endpoint "局域网访问" "http://${lan_ip}:${BACKEND_PORT}/health" || exit 1
|
||||
verify_http_endpoint "局域网访问" "http://${lan_ip}:${AI_PROVIDER_PORT}/health" || exit 1
|
||||
else
|
||||
@@ -3290,38 +3652,40 @@ start() {
|
||||
cleanup_exit_containers
|
||||
prepare_allow_lan_public_ports
|
||||
|
||||
START_RUN_ACTIVE=1
|
||||
START_RUN_COMPLETED=0
|
||||
STARTED_BACKEND_THIS_RUN=0
|
||||
STARTED_FRONTEND_THIS_RUN=0
|
||||
STARTED_MOTION_AGENT_THIS_RUN=0
|
||||
typeset -g START_RUN_ACTIVE=1
|
||||
typeset -g START_RUN_COMPLETED=0
|
||||
typeset -g STARTED_BACKEND_THIS_RUN=0
|
||||
typeset -g STARTED_FRONTEND_THIS_RUN=0
|
||||
typeset -g STARTED_MOTION_AGENT_THIS_RUN=0
|
||||
|
||||
print_splash
|
||||
|
||||
start_backend_service "$BACKEND_PORT" "$BACKEND_PORT_REQUESTED" "$AI_PROVIDER_PORT"
|
||||
STARTED_BACKEND_THIS_RUN=1
|
||||
typeset -g STARTED_BACKEND_THIS_RUN=1
|
||||
start_frontend_service "$FRONTEND_PORT" "$FRONTEND_PORT_REQUESTED" "$FRONTEND_LAN_ENABLED" "$BACKEND_PORT"
|
||||
STARTED_FRONTEND_THIS_RUN=1
|
||||
typeset -g STARTED_FRONTEND_THIS_RUN=1
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
start_motion_agent_service "$MOTION_AGENT_PORT" "$MOTION_AGENT_DRY_RUN" "$FRONTEND_LAN_ENABLED"
|
||||
STARTED_MOTION_AGENT_THIS_RUN=1
|
||||
typeset -g STARTED_MOTION_AGENT_THIS_RUN=1
|
||||
fi
|
||||
|
||||
write_port_state "$BACKEND_PORT" "$FRONTEND_PORT" "$AI_PROVIDER_PORT" "$MOTION_AGENT_PORT"
|
||||
local frontend_scheme=""
|
||||
frontend_scheme="$(current_frontend_scheme)"
|
||||
write_port_state "$BACKEND_PORT" "$FRONTEND_PORT" "$AI_PROVIDER_PORT" "$MOTION_AGENT_PORT" "$frontend_scheme"
|
||||
verify_allow_lan_access
|
||||
START_RUN_COMPLETED=1
|
||||
START_RUN_ACTIVE=0
|
||||
typeset -g START_RUN_COMPLETED=1
|
||||
typeset -g START_RUN_ACTIVE=0
|
||||
|
||||
log_success "启动完成"
|
||||
log_note "智能星球计划: http://localhost:${FRONTEND_PORT}/earth"
|
||||
log_note "智能星球仪表盘: http://localhost:${FRONTEND_PORT}/admin"
|
||||
log_note "AI Playground: http://localhost:${FRONTEND_PORT}/playground"
|
||||
log_note "智能星球开发文档: http://localhost:${BACKEND_PORT}/docs"
|
||||
log_note "智能星球计划: ${frontend_scheme}://localhost:${FRONTEND_PORT}/earth"
|
||||
log_note "智能星球仪表盘: ${frontend_scheme}://localhost:${FRONTEND_PORT}/admin"
|
||||
log_note "AI Playground: ${frontend_scheme}://localhost:${FRONTEND_PORT}/playground"
|
||||
log_note "智能星球文档: ${frontend_scheme}://localhost:${FRONTEND_PORT}/docs"
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
log_motion_agent_access_notes "$MOTION_AGENT_PORT" "$FRONTEND_LAN_ENABLED"
|
||||
fi
|
||||
if [ "$FRONTEND_LAN_ENABLED" -eq 1 ]; then
|
||||
log_lan_access_notes "$FRONTEND_PORT" "$BACKEND_PORT"
|
||||
log_lan_access_notes "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" "$frontend_scheme"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -3469,6 +3833,7 @@ restart() {
|
||||
cleanup_exit_containers
|
||||
local state_backend_port=""
|
||||
local state_frontend_port=""
|
||||
local state_frontend_scheme=""
|
||||
local state_ai_provider_port=""
|
||||
local state_motion_agent_port=""
|
||||
|
||||
@@ -3514,6 +3879,7 @@ restart() {
|
||||
|
||||
state_backend_port="$(read_port_state_value BACKEND_PORT "$DEFAULT_BACKEND_PORT")"
|
||||
state_frontend_port="$(read_port_state_value FRONTEND_PORT "$DEFAULT_FRONTEND_PORT")"
|
||||
state_frontend_scheme="$(read_port_state_text_value FRONTEND_SCHEME "$(current_frontend_scheme)")"
|
||||
state_ai_provider_port="$(read_port_state_value AI_PROVIDER_PORT "$DEFAULT_AI_PROVIDER_PORT")"
|
||||
state_motion_agent_port="$(read_port_state_value MOTION_AGENT_PORT "$DEFAULT_MOTION_AGENT_PORT")"
|
||||
if [ "$BACKEND_PORT_REQUESTED" -eq 1 ]; then
|
||||
@@ -3521,6 +3887,7 @@ restart() {
|
||||
fi
|
||||
if [ "$FRONTEND_PORT_REQUESTED" -eq 1 ]; then
|
||||
state_frontend_port="$FRONTEND_PORT"
|
||||
state_frontend_scheme="$(current_frontend_scheme)"
|
||||
fi
|
||||
if [ "$AI_PROVIDER_REQUESTED" -eq 1 ]; then
|
||||
state_ai_provider_port="$AI_PROVIDER_PORT"
|
||||
@@ -3528,7 +3895,9 @@ restart() {
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
state_motion_agent_port="$MOTION_AGENT_PORT"
|
||||
fi
|
||||
write_port_state "$state_backend_port" "$state_frontend_port" "$state_ai_provider_port" "$state_motion_agent_port"
|
||||
local frontend_scheme=""
|
||||
frontend_scheme="$state_frontend_scheme"
|
||||
write_port_state "$state_backend_port" "$state_frontend_port" "$state_ai_provider_port" "$state_motion_agent_port" "$frontend_scheme"
|
||||
|
||||
echo ""
|
||||
log_success "重启完成"
|
||||
@@ -3542,9 +3911,9 @@ restart() {
|
||||
log_note "后端: http://localhost:${BACKEND_PORT}"
|
||||
fi
|
||||
if [ "$FRONTEND_PORT_REQUESTED" -eq 1 ]; then
|
||||
log_note "前端: http://localhost:${FRONTEND_PORT}"
|
||||
log_note "前端: ${frontend_scheme}://localhost:${FRONTEND_PORT}"
|
||||
if [ "$FRONTEND_LAN_ENABLED" -eq 1 ]; then
|
||||
log_lan_access_notes "$FRONTEND_PORT" "$BACKEND_PORT"
|
||||
log_lan_access_notes "$FRONTEND_PORT" "$BACKEND_PORT" "$AI_PROVIDER_PORT" "$frontend_scheme"
|
||||
fi
|
||||
fi
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
@@ -3568,7 +3937,7 @@ health() {
|
||||
echo -e "${DIM}·${NC} ${BLUE}view${NC} ${WHITE}服务状态${NC}"
|
||||
print_http_health_status "后端" "http://localhost:${backend_port}/health"
|
||||
print_http_health_status "AI Provider" "http://localhost:${ai_provider_port}/health"
|
||||
print_http_health_status "前端" "http://localhost:${frontend_port}"
|
||||
print_frontend_health_status "$frontend_port"
|
||||
print_process_health_status "Motion Agent" "python.*-m motion_agent" "$MOTION_AGENT_PID_FILE"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user