release: bump version to 0.50.0
This commit is contained in:
450
planet.sh
450
planet.sh
@@ -104,10 +104,13 @@ PORT_PRESTART_RETRY_INTERVAL="${PORT_PRESTART_RETRY_INTERVAL:-2}"
|
||||
DEFAULT_BACKEND_PORT="${DEFAULT_BACKEND_PORT:-8000}"
|
||||
DEFAULT_FRONTEND_PORT="${DEFAULT_FRONTEND_PORT:-3000}"
|
||||
DEFAULT_AI_PROVIDER_PORT="${DEFAULT_AI_PROVIDER_PORT:-8010}"
|
||||
DEFAULT_MOTION_AGENT_PORT="${DEFAULT_MOTION_AGENT_PORT:-8765}"
|
||||
FRONTEND_RUNTIME_BIN="${FRONTEND_RUNTIME_BIN:-}"
|
||||
FRONTEND_RUNTIME_SOURCE="${FRONTEND_RUNTIME_SOURCE:-}"
|
||||
FRONTEND_PID_FILE="/tmp/planet_frontend.pid"
|
||||
FRONTEND_VITE_ENTRY="$SCRIPT_DIR/frontend/node_modules/vite/bin/vite.js"
|
||||
MOTION_AGENT_PID_FILE="/tmp/planet_motion_agent.pid"
|
||||
MOTION_AGENT_LOG_FILE="/tmp/planet_motion_agent.log"
|
||||
AI_PROVIDER_BUILD_STAMP_FILE="$HOME/.cache/planet/aiprovider_build.sha256"
|
||||
AI_PROVIDER_BUILD_LOG_FILE="/tmp/planet_aiprovider_build.log"
|
||||
AI_PROVIDER_IMAGE_NAME="${AI_PROVIDER_IMAGE_NAME:-planet_aiprovider:latest}"
|
||||
@@ -533,6 +536,22 @@ log_lan_access_notes() {
|
||||
fi
|
||||
}
|
||||
|
||||
log_motion_agent_access_notes() {
|
||||
local motion_agent_port="$1"
|
||||
local lan_enabled="${2:-0}"
|
||||
local recommended_lan_ip=""
|
||||
|
||||
log_note "Motion Agent 本机: ws://127.0.0.1:${motion_agent_port}/ws/gestures"
|
||||
if [ "$lan_enabled" -eq 1 ]; then
|
||||
if recommended_lan_ip="$(get_recommended_lan_ipv4)"; then
|
||||
log_note "Motion Agent 局域网: ws://${recommended_lan_ip}:${motion_agent_port}/ws/gestures"
|
||||
log_note "Earth 局域网页面可追加: ?motion=1&motionAgent=ws://${recommended_lan_ip}:${motion_agent_port}/ws/gestures"
|
||||
else
|
||||
log_note "Motion Agent 已对局域网开放,请使用本机局域网 IP 访问 :${motion_agent_port}/ws/gestures"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
print_splash() {
|
||||
clear_wait_spinner
|
||||
printf "%b" "$CYAN"
|
||||
@@ -882,6 +901,68 @@ ensure_uv_backend_deps() {
|
||||
fi
|
||||
}
|
||||
|
||||
motion_agent_live_deps_ready() {
|
||||
"$SCRIPT_DIR/.venv/bin/python" - <<'PY' >/dev/null 2>&1
|
||||
import cv2 # noqa: F401
|
||||
import mediapipe # noqa: F401
|
||||
PY
|
||||
}
|
||||
|
||||
detect_motion_agent_camera_indexes() {
|
||||
local devices=""
|
||||
local device=""
|
||||
local index=""
|
||||
|
||||
if command -v v4l2-ctl >/dev/null 2>&1; then
|
||||
devices="$(v4l2-ctl --list-devices 2>/dev/null | sed -nE 's/^[[:space:]]*\\/dev\\/video([0-9]+).*/\\1/p' || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$devices" ]; then
|
||||
for device in /dev/video*(N); do
|
||||
[ -e "$device" ] || continue
|
||||
index="${device#/dev/video}"
|
||||
[[ "$index" =~ ^[0-9]+$ ]] || continue
|
||||
printf "%s\n" "$index"
|
||||
done | sort -n | awk '!seen[$0]++' | head -n 2 | paste -sd, -
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf "%s\n" "$devices" | sort -n | awk '!seen[$0]++' | head -n 2 | paste -sd, -
|
||||
}
|
||||
|
||||
is_wsl_environment() {
|
||||
grep -qiE "(microsoft|wsl)" /proc/version 2>/dev/null
|
||||
}
|
||||
|
||||
ensure_motion_agent_live_deps() {
|
||||
local auto_install="${PLANET_MOTION_AGENT_AUTO_INSTALL:-1}"
|
||||
|
||||
motion_agent_live_deps_ready && return 0
|
||||
|
||||
case "$auto_install" in
|
||||
0|false|no|off)
|
||||
log_error "Motion Agent live 模式缺少 mediapipe/opencv-python,且 PLANET_MOTION_AGENT_AUTO_INSTALL=0"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
set_wait_detail "Motion Agent 缺少 CV 依赖,准备用 uv 自动安装"
|
||||
if ! run_with_retry \
|
||||
"$DEPENDENCY_INSTALL_MAX_RETRIES" \
|
||||
"$DEPENDENCY_INSTALL_RETRY_INTERVAL" \
|
||||
"Motion Agent CV 依赖安装失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次" \
|
||||
"uv add mediapipe opencv-python" \
|
||||
uv add mediapipe opencv-python; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! motion_agent_live_deps_ready; then
|
||||
log_error "Motion Agent CV 依赖安装后仍无法导入 mediapipe/opencv-python"
|
||||
log_note "可先用 --motion-agent-dry-run 验证 Web 端连接;真实摄像头识别需要当前 Python 版本可用的 mediapipe/opencv-python。"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Frontend runtime resolution
|
||||
resolve_frontend_runtime() {
|
||||
local shell_bin
|
||||
@@ -1156,6 +1237,7 @@ fail_unreleased_port() {
|
||||
if [ -z "$(collect_port_pids "$port" || true)" ]; then
|
||||
log_error "端口 ${port} 当前环境内未发现占用进程,但端口仍不可用,请检查宿主机或外部环境占用"
|
||||
print_port_listener_details "$port"
|
||||
log_note "如果显示 Windows listener(如 svchost.exe / iphlpsvc),请优先用管理员 PowerShell 检查 portproxy:netsh interface portproxy show all;删除冲突转发或改用其他端口。"
|
||||
else
|
||||
log_error "端口 ${port} 清理失败,请检查占用进程"
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
@@ -1164,6 +1246,7 @@ fail_unreleased_port() {
|
||||
ss -ltnp "( sport = :${port} )" 2>/dev/null || true
|
||||
fi
|
||||
print_windows_port_listener_details "$port" || true
|
||||
log_note "请手动停止占用进程,或改用其他端口。"
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@@ -1198,6 +1281,101 @@ retry_wait_for_port_release() {
|
||||
return 1
|
||||
}
|
||||
|
||||
force_cleanup_external_port_listener() {
|
||||
local port="$1"
|
||||
local service_name="$2"
|
||||
local output=""
|
||||
local saw_windows_listener=0
|
||||
local saw_no_windows_listener=0
|
||||
local saw_cleanup_failure=0
|
||||
|
||||
is_wsl_environment || return 1
|
||||
command -v powershell.exe >/dev/null 2>&1 || return 1
|
||||
|
||||
log_warn "检测到 WSL 环境,正在检查 ${service_name} 端口 ${port} 的 Windows 侧监听"
|
||||
output="$(
|
||||
powershell.exe -NoProfile -Command "
|
||||
\$ErrorActionPreference = 'Continue'
|
||||
\$port = [int]${port}
|
||||
\$connections = @(Get-NetTCPConnection -LocalPort \$port -State Listen -ErrorAction SilentlyContinue)
|
||||
if (-not \$connections -or \$connections.Count -eq 0) {
|
||||
'no-windows-listener'
|
||||
exit 0
|
||||
}
|
||||
\$seen = @{}
|
||||
foreach (\$connection in \$connections) {
|
||||
\$pidValue = [int]\$connection.OwningProcess
|
||||
if (\$seen.ContainsKey(\$pidValue)) { continue }
|
||||
\$seen[\$pidValue] = \$true
|
||||
\$process = Get-CimInstance Win32_Process -Filter \"ProcessId=\$pidValue\" -ErrorAction SilentlyContinue
|
||||
\$processName = if (\$process.Name) { \$process.Name } else { 'unknown' }
|
||||
\$services = @(Get-CimInstance Win32_Service -ErrorAction SilentlyContinue | Where-Object { \$_.ProcessId -eq \$pidValue })
|
||||
\$serviceNames = @(\$services | Select-Object -ExpandProperty Name)
|
||||
'attempt-windows-cleanup pid={0} process={1} services={2}' -f \$pidValue, \$processName, (\$serviceNames -join ',')
|
||||
|
||||
foreach (\$service in \$services) {
|
||||
try {
|
||||
Stop-Service -Name \$service.Name -Force -ErrorAction Stop
|
||||
'stopped-service {0}' -f \$service.Name
|
||||
} catch {
|
||||
'failed-stop-service {0}: {1}' -f \$service.Name, \$_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Stop-Process -Id \$pidValue -Force -ErrorAction Stop
|
||||
'stopped-process {0}' -f \$pidValue
|
||||
} catch {
|
||||
'failed-stop-process {0}: {1}' -f \$pidValue, \$_.Exception.Message
|
||||
}
|
||||
}
|
||||
" 2>&1 | tr -d '\r'
|
||||
)"
|
||||
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
case "$line" in
|
||||
no-windows-listener)
|
||||
saw_no_windows_listener=1
|
||||
log_note "Windows 侧未发现端口 ${port} 监听,继续启动"
|
||||
;;
|
||||
stopped-service*|stopped-process*)
|
||||
saw_windows_listener=1
|
||||
printf "${GREEN} %s${NC}\n" "$line"
|
||||
;;
|
||||
failed-*)
|
||||
saw_windows_listener=1
|
||||
saw_cleanup_failure=1
|
||||
printf "${DIM} %s${NC}\n" "$line"
|
||||
;;
|
||||
attempt-windows-cleanup*)
|
||||
saw_windows_listener=1
|
||||
printf "${DIM} %s${NC}\n" "$line"
|
||||
;;
|
||||
*)
|
||||
printf "${DIM} %s${NC}\n" "$line"
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
$output
|
||||
EOF
|
||||
|
||||
if wait_for_port_release "$port" 10 0.3; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$saw_no_windows_listener" -eq 1 ] && [ "$saw_windows_listener" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$saw_windows_listener" -eq 0 ]; then
|
||||
log_warn "Windows 侧未发现端口 ${port} 监听,但端口仍不可绑定"
|
||||
elif [ "$saw_cleanup_failure" -eq 1 ]; then
|
||||
log_warn "Windows 侧监听清理失败,端口 ${port} 仍不可绑定"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Service startup helpers
|
||||
wait_for_database_health() {
|
||||
wait_for_container_health "planet_postgres" "$AI_PROVIDER_HEALTH_CHECK_ATTEMPTS" "$DATABASE_RETRY_INTERVAL" "PostgreSQL" &&
|
||||
@@ -1227,8 +1405,14 @@ cleanup_backend_processes() {
|
||||
|
||||
if ! wait_for_port_release "$backend_port" 15 0.2; then
|
||||
terminate_backend_processes KILL "$backend_port"
|
||||
wait_for_port_release "$backend_port" 15 0.2 || true
|
||||
if ! wait_for_port_release "$backend_port" 15 0.2; then
|
||||
if force_cleanup_external_port_listener "$backend_port" "后端"; then
|
||||
return 0
|
||||
fi
|
||||
wait_for_port_release "$backend_port" 15 0.2 || return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
start_backend_with_retry() {
|
||||
@@ -1236,7 +1420,9 @@ start_backend_with_retry() {
|
||||
local retry=1
|
||||
|
||||
while [ "$retry" -le "$BACKEND_MAX_RETRIES" ]; do
|
||||
cleanup_backend_processes "$backend_port"
|
||||
if ! cleanup_backend_processes "$backend_port"; then
|
||||
fail_unreleased_port "$backend_port"
|
||||
fi
|
||||
cd "$SCRIPT_DIR/backend"
|
||||
: > /tmp/planet_backend.log
|
||||
PYTHONPATH="$SCRIPT_DIR/backend" nohup uv run --project "$SCRIPT_DIR" python -m uvicorn app.main:app --host 0.0.0.0 --port "$backend_port" --reload > /tmp/planet_backend.log 2>&1 &
|
||||
@@ -1251,6 +1437,12 @@ start_backend_with_retry() {
|
||||
|
||||
kill "$BACKEND_PID" 2>/dev/null || true
|
||||
if backend_log_indicates_port_conflict "/tmp/planet_backend.log"; then
|
||||
cleanup_backend_processes "$backend_port" || fail_unreleased_port "$backend_port"
|
||||
if wait_for_port_release "$backend_port" 5 0.2; then
|
||||
log_warn "后端端口冲突已清理,立即重试启动"
|
||||
retry=$((retry + 1))
|
||||
continue
|
||||
fi
|
||||
report_backend_port_conflict_if_needed "$backend_port" "/tmp/planet_backend.log"
|
||||
return 1
|
||||
fi
|
||||
@@ -1579,9 +1771,11 @@ kill_port_if_requested() {
|
||||
fi
|
||||
|
||||
if [ -z "$pids" ]; then
|
||||
log_warn "端口 ${port} 当前环境未发现监听进程,跳过预清理并交给${service_name}启动流程确认"
|
||||
print_port_listener_details "$port"
|
||||
return 0
|
||||
if force_cleanup_external_port_listener "$port" "$service_name"; then
|
||||
log_success "端口 ${port} 已释放"
|
||||
return 0
|
||||
fi
|
||||
fail_unreleased_port "$port"
|
||||
fi
|
||||
|
||||
log_step "发现端口 ${port} 占用,正在终止"
|
||||
@@ -1595,8 +1789,11 @@ kill_port_if_requested() {
|
||||
fi
|
||||
|
||||
if [ -z "$(collect_port_pids "$port" || true)" ]; then
|
||||
log_warn "端口 ${port} 监听进程已清理,继续交给${service_name}启动流程确认"
|
||||
return 0
|
||||
if force_cleanup_external_port_listener "$port" "$service_name"; then
|
||||
log_success "端口 ${port} 已释放"
|
||||
return 0
|
||||
fi
|
||||
fail_unreleased_port "$port"
|
||||
fi
|
||||
|
||||
log_warn "端口 ${port} 仍被占用,正在强制终止"
|
||||
@@ -1611,8 +1808,11 @@ kill_port_if_requested() {
|
||||
fi
|
||||
|
||||
if [ -z "$(collect_port_pids "$port" || true)" ]; then
|
||||
log_warn "端口 ${port} 强制清理后未发现监听进程,继续交给${service_name}启动流程确认"
|
||||
return 0
|
||||
if force_cleanup_external_port_listener "$port" "$service_name"; then
|
||||
log_success "端口 ${port} 已释放"
|
||||
return 0
|
||||
fi
|
||||
fail_unreleased_port "$port"
|
||||
fi
|
||||
|
||||
fail_unreleased_port "$port"
|
||||
@@ -1819,12 +2019,28 @@ parse_service_args() {
|
||||
BACKEND_PORT="$DEFAULT_BACKEND_PORT"
|
||||
FRONTEND_PORT="$DEFAULT_FRONTEND_PORT"
|
||||
AI_PROVIDER_PORT="$DEFAULT_AI_PROVIDER_PORT"
|
||||
MOTION_AGENT_PORT="$DEFAULT_MOTION_AGENT_PORT"
|
||||
MOTION_AGENT_CAMERA_INDEXES="${MOTION_AGENT_CAMERA_INDEXES:-}"
|
||||
MOTION_AGENT_CAMERA_URLS="${MOTION_AGENT_CAMERA_URLS:-}"
|
||||
BACKEND_PORT_REQUESTED=0
|
||||
FRONTEND_PORT_REQUESTED=0
|
||||
AI_PROVIDER_REQUESTED=0
|
||||
MOTION_AGENT_REQUESTED=0
|
||||
MOTION_AGENT_DRY_RUN=0
|
||||
DATABASE_REQUESTED=0
|
||||
FRONTEND_LAN_ENABLED=0
|
||||
|
||||
case "${PLANET_START_MOTION_AGENT:-0}" in
|
||||
1|true|yes|on)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
;;
|
||||
esac
|
||||
case "${MOTION_AGENT_DRY_RUN:-0}" in
|
||||
1|true|yes|on)
|
||||
MOTION_AGENT_DRY_RUN=1
|
||||
;;
|
||||
esac
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-b|--backend-port)
|
||||
@@ -1854,6 +2070,45 @@ parse_service_args() {
|
||||
shift 1
|
||||
fi
|
||||
;;
|
||||
-m|--motion-agent)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
shift 1
|
||||
;;
|
||||
--motion-agent-port)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then
|
||||
MOTION_AGENT_PORT="$2"
|
||||
shift 2
|
||||
else
|
||||
log_error "--motion-agent-port 需要端口号"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--motion-agent-dry-run)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
MOTION_AGENT_DRY_RUN=1
|
||||
shift 1
|
||||
;;
|
||||
--motion-agent-camera-indexes)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
if [ -n "$2" ]; then
|
||||
MOTION_AGENT_CAMERA_INDEXES="$2"
|
||||
shift 2
|
||||
else
|
||||
log_error "--motion-agent-camera-indexes 需要逗号分隔的摄像头 index,例如 0,1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--motion-agent-camera-urls)
|
||||
MOTION_AGENT_REQUESTED=1
|
||||
if [ -n "$2" ]; then
|
||||
MOTION_AGENT_CAMERA_URLS="$2"
|
||||
shift 2
|
||||
else
|
||||
log_error "--motion-agent-camera-urls 需要逗号分隔的 RTSP/HTTP 摄像头 URL"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-d|--database)
|
||||
DATABASE_REQUESTED=1
|
||||
shift 1
|
||||
@@ -1876,6 +2131,7 @@ parse_service_args() {
|
||||
validate_port "$BACKEND_PORT"
|
||||
validate_port "$FRONTEND_PORT"
|
||||
validate_port "$AI_PROVIDER_PORT"
|
||||
validate_port "$MOTION_AGENT_PORT"
|
||||
}
|
||||
|
||||
cleanup_exit_containers() {
|
||||
@@ -1927,6 +2183,146 @@ stop_frontend_service() {
|
||||
fi
|
||||
}
|
||||
|
||||
motion_agent_pids() {
|
||||
local pids=""
|
||||
|
||||
if [ -f "$MOTION_AGENT_PID_FILE" ]; then
|
||||
pids="$(cat "$MOTION_AGENT_PID_FILE" 2>/dev/null || true)"
|
||||
if [ -n "$pids" ] && kill -0 "$pids" 2>/dev/null; then
|
||||
printf "%s\n" "$pids"
|
||||
fi
|
||||
fi
|
||||
|
||||
pgrep -f "python.*-m motion_agent" 2>/dev/null || true
|
||||
}
|
||||
|
||||
cleanup_motion_agent_processes() {
|
||||
local signal="${1:-TERM}"
|
||||
local pid
|
||||
|
||||
while IFS= read -r pid; do
|
||||
[ -n "$pid" ] || continue
|
||||
terminate_process_group "$signal" "$pid"
|
||||
terminate_process_tree "$signal" "$pid"
|
||||
done < <(motion_agent_pids | awk '!seen[$0]++')
|
||||
|
||||
rm -f "$MOTION_AGENT_PID_FILE"
|
||||
}
|
||||
|
||||
wait_for_motion_agent_ready() {
|
||||
local port="$1"
|
||||
local pid="$2"
|
||||
local attempt=1
|
||||
|
||||
while [ "$attempt" -le 20 ]; do
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
if ! can_bind_port "$port"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
start_motion_agent_service() {
|
||||
local motion_agent_port="$1"
|
||||
local dry_run="$2"
|
||||
local lan_enabled="${3:-0}"
|
||||
local bind_host="127.0.0.1"
|
||||
local -a motion_args
|
||||
|
||||
ensure_uv_backend_deps
|
||||
if [ "$dry_run" -eq 0 ] && [ -z "$MOTION_AGENT_CAMERA_URLS" ] && [ -z "$MOTION_AGENT_CAMERA_INDEXES" ]; then
|
||||
MOTION_AGENT_CAMERA_INDEXES="$(detect_motion_agent_camera_indexes)"
|
||||
if [ -n "$MOTION_AGENT_CAMERA_INDEXES" ]; then
|
||||
log_note "Motion Agent 自动发现摄像头 index: ${MOTION_AGENT_CAMERA_INDEXES}"
|
||||
else
|
||||
if is_wsl_environment; then
|
||||
log_warn "Motion Agent 未自动发现 /dev/video* 摄像头"
|
||||
log_note "当前看起来是 WSL;Windows 摄像头通常不会自动出现在 /dev/video*。"
|
||||
log_note "真实识别请选择一种方式:"
|
||||
log_note " 1. 用 --motion-agent-camera-urls 接手机/RTSP/HTTP 摄像头流"
|
||||
log_note " 2. 用 usbipd-win 把 USB 摄像头透传到 WSL,再重试"
|
||||
log_note " 3. 仅验证 WebSocket/调试面板时,显式加 --motion-agent-dry-run"
|
||||
case "${PLANET_MOTION_AGENT_WSL_ALLOW_DRY_RUN_FALLBACK:-0}" in
|
||||
1|true|yes|on)
|
||||
log_warn "已设置 PLANET_MOTION_AGENT_WSL_ALLOW_DRY_RUN_FALLBACK=1,降级为 dry-run"
|
||||
dry_run=1
|
||||
;;
|
||||
*)
|
||||
MOTION_AGENT_CAMERA_MISSING=1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_warn "Motion Agent 未自动发现 /dev/video* 摄像头,默认尝试 index 0"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "${MOTION_AGENT_CAMERA_MISSING:-0}" -eq 1 ]; then
|
||||
log_error "Motion Agent live 模式缺少可用摄像头"
|
||||
log_note "WSL 示例:./planet.sh restart -m --motion-agent-camera-urls http://<手机IP>:8080/video"
|
||||
log_note "或仅调试协议:./planet.sh restart -m --motion-agent-dry-run"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$dry_run" -eq 0 ]; then
|
||||
ensure_motion_agent_live_deps
|
||||
fi
|
||||
cleanup_motion_agent_processes TERM
|
||||
wait_for_port_release "$motion_agent_port" 10 0.2 || true
|
||||
|
||||
if ! can_bind_port "$motion_agent_port"; then
|
||||
log_error "Motion Agent 端口已被占用: ${motion_agent_port}"
|
||||
print_port_listener_details "$motion_agent_port"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: > "$MOTION_AGENT_LOG_FILE"
|
||||
if [ "$lan_enabled" -eq 1 ]; then
|
||||
bind_host="0.0.0.0"
|
||||
fi
|
||||
|
||||
motion_args=(-m motion_agent --host "$bind_host" --port "$motion_agent_port")
|
||||
if [ -n "$MOTION_AGENT_CAMERA_URLS" ]; then
|
||||
motion_args+=(--camera-urls "$MOTION_AGENT_CAMERA_URLS")
|
||||
elif [ -n "$MOTION_AGENT_CAMERA_INDEXES" ]; then
|
||||
motion_args+=(--camera-indexes "$MOTION_AGENT_CAMERA_INDEXES")
|
||||
fi
|
||||
if [ "$dry_run" -eq 1 ]; then
|
||||
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=$!
|
||||
printf "%s" "$MOTION_AGENT_PID" > "$MOTION_AGENT_PID_FILE"
|
||||
|
||||
if wait_for_motion_agent_ready "$motion_agent_port" "$MOTION_AGENT_PID"; then
|
||||
log_success "Motion Agent 已就绪"
|
||||
log_motion_agent_access_notes "$motion_agent_port" "$lan_enabled"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Motion Agent 启动失败"
|
||||
tail -20 "$MOTION_AGENT_LOG_FILE" 2>/dev/null || true
|
||||
log_note "如只需验证 Web 端连接,可加 --motion-agent-dry-run。"
|
||||
log_note "如需真实摄像头识别,请确认系统存在 /dev/video*;脚本会自动发现,也可用 --motion-agent-camera-indexes 1,2 覆盖。"
|
||||
log_note "WSL 下也可用 --motion-agent-camera-urls rtsp://... 或 http://... 接入手机/网络摄像头。"
|
||||
log_note "依赖缺失时脚本会自动执行 uv add mediapipe opencv-python。"
|
||||
cleanup_motion_agent_processes TERM
|
||||
exit 1
|
||||
}
|
||||
|
||||
stop_motion_agent_service() {
|
||||
if [ -f "$MOTION_AGENT_PID_FILE" ] || pgrep -f "python.*-m motion_agent" >/dev/null 2>&1; then
|
||||
cleanup_motion_agent_processes TERM
|
||||
log_halt "Motion Agent 已停止"
|
||||
fi
|
||||
}
|
||||
|
||||
create_user() {
|
||||
local username
|
||||
local password
|
||||
@@ -2041,12 +2437,18 @@ start() {
|
||||
|
||||
start_backend_service "$BACKEND_PORT" "$BACKEND_PORT_REQUESTED" "$AI_PROVIDER_PORT"
|
||||
start_frontend_service "$FRONTEND_PORT" "$FRONTEND_PORT_REQUESTED" "$FRONTEND_LAN_ENABLED"
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
start_motion_agent_service "$MOTION_AGENT_PORT" "$MOTION_AGENT_DRY_RUN" "$FRONTEND_LAN_ENABLED"
|
||||
fi
|
||||
|
||||
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"
|
||||
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"
|
||||
fi
|
||||
@@ -2057,6 +2459,7 @@ stop() {
|
||||
stop_backend_service
|
||||
stop_ai_provider_service
|
||||
stop_frontend_service
|
||||
stop_motion_agent_service
|
||||
stop_container_if_running "planet_postgres" "PostgreSQL"
|
||||
stop_container_if_running "planet_redis" "Redis"
|
||||
log_success "已停止"
|
||||
@@ -2066,7 +2469,7 @@ restart() {
|
||||
parse_service_args "$@"
|
||||
cleanup_exit_containers
|
||||
|
||||
if [ "$BACKEND_PORT_REQUESTED" -eq 0 ] && [ "$FRONTEND_PORT_REQUESTED" -eq 0 ] && [ "$AI_PROVIDER_REQUESTED" -eq 0 ] && [ "$DATABASE_REQUESTED" -eq 0 ]; then
|
||||
if [ "$BACKEND_PORT_REQUESTED" -eq 0 ] && [ "$FRONTEND_PORT_REQUESTED" -eq 0 ] && [ "$AI_PROVIDER_REQUESTED" -eq 0 ] && [ "$MOTION_AGENT_REQUESTED" -eq 0 ] && [ "$DATABASE_REQUESTED" -eq 0 ]; then
|
||||
stop
|
||||
sleep 1
|
||||
start "$@"
|
||||
@@ -2097,6 +2500,12 @@ restart() {
|
||||
start_frontend_service "$FRONTEND_PORT" 1 "$FRONTEND_LAN_ENABLED"
|
||||
fi
|
||||
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
stop_motion_agent_service
|
||||
sleep 1
|
||||
start_motion_agent_service "$MOTION_AGENT_PORT" "$MOTION_AGENT_DRY_RUN" "$FRONTEND_LAN_ENABLED"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "重启完成"
|
||||
if [ "$DATABASE_REQUESTED" -eq 1 ]; then
|
||||
@@ -2114,6 +2523,9 @@ restart() {
|
||||
log_lan_access_notes "$FRONTEND_PORT" "$BACKEND_PORT"
|
||||
fi
|
||||
fi
|
||||
if [ "$MOTION_AGENT_REQUESTED" -eq 1 ]; then
|
||||
log_motion_agent_access_notes "$MOTION_AGENT_PORT" "$FRONTEND_LAN_ENABLED"
|
||||
fi
|
||||
}
|
||||
|
||||
health() {
|
||||
@@ -2139,6 +2551,12 @@ health() {
|
||||
else
|
||||
echo -e "${DIM} 前端:${NC} ${RED}offline${NC}"
|
||||
fi
|
||||
|
||||
if pgrep -f "python.*-m motion_agent" >/dev/null 2>&1 || [ -f "$MOTION_AGENT_PID_FILE" ]; then
|
||||
echo -e "${DIM} Motion Agent:${NC} ${GREEN}online${NC}"
|
||||
else
|
||||
echo -e "${DIM} Motion Agent:${NC} ${RED}offline${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
log() {
|
||||
@@ -2158,6 +2576,11 @@ log() {
|
||||
log_note "按 Ctrl+C 退出"
|
||||
docker logs -f planet_aiprovider
|
||||
;;
|
||||
-m|--motion-agent)
|
||||
log_step "查看 Motion Agent 日志"
|
||||
log_note "按 Ctrl+C 退出"
|
||||
tail -f "$MOTION_AGENT_LOG_FILE"
|
||||
;;
|
||||
*)
|
||||
log_step "查看最近日志"
|
||||
log_note "后端"
|
||||
@@ -2166,6 +2589,8 @@ log() {
|
||||
docker logs --tail 20 planet_aiprovider 2>/dev/null || log_note "无日志"
|
||||
log_note "前端"
|
||||
tail -20 /tmp/planet_frontend.log 2>/dev/null || log_note "无日志"
|
||||
log_note "Motion Agent"
|
||||
tail -20 "$MOTION_AGENT_LOG_FILE" 2>/dev/null || log_note "无日志"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -2213,14 +2638,15 @@ case "$1" in
|
||||
*)
|
||||
log_error "用法: ./planet.sh {start|stop|restart|createuser|health|log}"
|
||||
log_note "全局参数: -v, --verbose 在当前执行行下方滚动显示最多 5 行命令输出"
|
||||
log_note "start 启动服务,可选: -b <后端端口> -f <前端端口> -a <AI Provider 端口> --allow-lan --verbose"
|
||||
log_note "start 启动服务,可选: -b <后端端口> -f <前端端口> -a <AI Provider 端口> -m/--motion-agent --motion-agent-port <端口> --motion-agent-camera-indexes 0,1 --motion-agent-camera-urls rtsp://... --motion-agent-dry-run --allow-lan --verbose"
|
||||
log_note "stop 停止服务"
|
||||
log_note "restart 重启服务,可选: -b [后端端口] -f [前端端口] -a [AI Provider 端口] -d --allow-lan --verbose"
|
||||
log_note "restart 重启服务,可选: -b [后端端口] -f [前端端口] -a [AI Provider 端口] -m [Motion Agent] -d --allow-lan --verbose"
|
||||
log_note "createuser 交互创建用户"
|
||||
log_note "health 检查健康状态"
|
||||
log_note "log 查看日志"
|
||||
log_note "log -f 查看前端日志"
|
||||
log_note "log -b 查看后端日志"
|
||||
log_note "log -a 查看 AI Provider 日志"
|
||||
log_note "log -m 查看 Motion Agent 日志"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user