release: bump version to 0.50.0

This commit is contained in:
rayd1o
2026-05-10 22:06:01 +08:00
parent e1984c7a35
commit 455b8360d0
80 changed files with 10936 additions and 298 deletions

450
planet.sh
View File

@@ -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 检查 portproxynetsh 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 "当前看起来是 WSLWindows 摄像头通常不会自动出现在 /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