OrangePi3588Media/scripts/deploy.sh

891 lines
29 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# RK3588 Media Server + Agent 完善部署脚本
# 部署到 /opt包含完整的运行时目录、权限、日志轮转等
#
# 用法: sudo ./deploy.sh [install|upgrade|status|logs|clean-hls|uninstall]
# 首次安装 Agent 时传入后台管理统一主钥匙;后续部署默认沿用设备保存的主钥匙:
# sudo AGENT_TOKEN=<managerd.json 中的 agent_token> ./deploy.sh install
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# 目录定义
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BUILD_DIR="$PROJECT_DIR/build"
# 安装目录
INSTALL_DIR="/opt/rk3588-media-server"
AGENT_INSTALL_DIR="/opt/rk3588-agent"
SERVICE_DIR="/etc/systemd/system"
LOGROTATE_DIR="/etc/logrotate.d"
# 运行时数据目录(持久化数据)
RUNTIME_DIR="/var/lib/rk3588-media-server"
HLS_DIR="$RUNTIME_DIR/hls"
LOGS_DIR="$RUNTIME_DIR/logs"
ALARMS_DIR="$RUNTIME_DIR/alarms"
CLIPS_DIR="$RUNTIME_DIR/clips"
# 默认使用模板/profile/overlay 渲染部署配置。可通过环境变量覆盖:
# DEPLOY_CONFIG_TEMPLATE=configs/templates/workshop_face_shoe_alarm.json
# DEPLOY_CONFIG_PROFILE=configs/profiles/local_3588_test.json
# DEPLOY_CONFIG_OVERLAYS="configs/overlays/face_debug.json configs/overlays/production_quiet.json"
# DEPLOY_CONFIG_ID=local_3588_face_debug
# DEPLOY_CONFIG_VERSION=20260419.001
DEPLOY_CONFIG_TEMPLATE="${DEPLOY_CONFIG_TEMPLATE:-configs/templates/workshop_face_shoe_alarm.json}"
DEPLOY_CONFIG_PROFILE="${DEPLOY_CONFIG_PROFILE:-configs/profiles/local_3588_test.json}"
DEPLOY_CONFIG_OVERLAYS="${DEPLOY_CONFIG_OVERLAYS:-configs/overlays/face_debug.json}"
DEPLOY_CONFIG_ID="${DEPLOY_CONFIG_ID:-local_3588_face_debug}"
DEPLOY_CONFIG_VERSION="${DEPLOY_CONFIG_VERSION:-$(date +%Y%m%d.%H%M%S)}"
# 后台管理系统访问所有设备 agent 的统一主钥匙。传入 AGENT_TOKEN 表示写入/轮换主钥匙;
# 不传时沿用设备上保存的主钥匙。禁止设备本地随机生成,避免后台和设备 token 漂移。
# 首次安装示例:
# sudo AGENT_TOKEN=4fe2d69fda23d0d5d04a1486d4920e68 ./scripts/deploy.sh install
# 后续升级示例:
# sudo ./scripts/deploy.sh upgrade
AGENT_TOKEN="${AGENT_TOKEN:-}"
AGENT_TOKEN_FILE="$AGENT_INSTALL_DIR/agent.token"
resolve_agent_token() {
if [ -z "$AGENT_TOKEN" ]; then
if [ -f "$AGENT_TOKEN_FILE" ]; then
AGENT_TOKEN="$(tr -d '\r\n[:space:]' < "$AGENT_TOKEN_FILE")"
echo -e "${GREEN}${NC} 使用设备已保存的后台管理统一主钥匙"
else
echo -e "${RED}错误: 未提供 AGENT_TOKEN且未找到已保存的主钥匙 $AGENT_TOKEN_FILE${NC}"
echo "首次安装必须使用sudo AGENT_TOKEN=<managerd.json 中的 agent_token> ./scripts/deploy.sh install"
echo "部署脚本不会本地生成 token。"
exit 1
fi
else
echo -e "${GREEN}${NC} 使用 AGENT_TOKEN 写入/轮换后台管理统一主钥匙"
fi
if ! printf '%s' "$AGENT_TOKEN" | grep -Eq '^[A-Za-z0-9._:-]+$'; then
echo -e "${RED}错误: AGENT_TOKEN 包含不支持的字符${NC}"
echo "请使用字母、数字、点、下划线、冒号或短横线组成的 token。"
exit 1
fi
}
# 创建必要的运行时目录
create_runtime_dirs() {
echo -e "${CYAN}[创建运行时目录]${NC}"
# 主运行时目录
mkdir -p "$RUNTIME_DIR"
mkdir -p "$HLS_DIR"
mkdir -p "$LOGS_DIR"
mkdir -p "$ALARMS_DIR"
mkdir -p "$CLIPS_DIR"
# 创建 HLS 符号链接,使 HTTP server 能访问 HLS 文件
# HLS 输出到 /var/lib/...,但 HTTP server 从 web_root/hls/ 提供文件
if [ ! -L "$INSTALL_DIR/web/hls" ]; then
rm -rf "$INSTALL_DIR/web/hls"
ln -s "$HLS_DIR" "$INSTALL_DIR/web/hls"
echo -e "${GREEN}${NC} 创建 HLS 符号链接: web/hls -> $HLS_DIR"
fi
# 设置权限(日志目录使用 orangepi 用户,方便查看和管理)
chown -R root:root "$RUNTIME_DIR"
# HLS 输出目录 - root 所有,但 orangepi 可读取
chmod 755 "$HLS_DIR"
# 日志目录 - orangepi 用户完全控制
chown orangepi:orangepi "$LOGS_DIR"
chmod 755 "$LOGS_DIR"
# 告警目录 - orangepi 用户完全控制
chown orangepi:orangepi "$ALARMS_DIR"
chmod 755 "$ALARMS_DIR"
# 视频片段目录 - orangepi 用户完全控制
chown orangepi:orangepi "$CLIPS_DIR"
chmod 755 "$CLIPS_DIR"
echo -e "${GREEN}${NC} 运行时目录创建完成:"
echo " HLS输出: $HLS_DIR (root:orangepi, 755)"
echo " 日志: $LOGS_DIR (orangepi:orangepi, 755)"
echo " 告警图片: $ALARMS_DIR (orangepi:orangepi, 755)"
echo " 告警视频: $CLIPS_DIR (orangepi:orangepi, 755)"
}
# 安装日志轮转配置
install_logrotate() {
echo -e "${CYAN}[配置日志轮转]${NC}"
# Media Server 日志轮转
cat > "$LOGROTATE_DIR/rk3588-media-server" << 'EOF'
/var/lib/rk3588-media-server/logs/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 644 orangepi orangepi
sharedscripts
postrotate
/bin/kill -HUP $(cat /var/run/syslogd.pid 2>/dev/null) 2>/dev/null || true
endscript
}
EOF
# HLS 分片清理保留最近7天
cat > "$LOGROTATE_DIR/rk3588-hls" << 'EOF'
/var/lib/rk3588-media-server/hls/*/index*.ts {
daily
rotate 1
maxage 7
missingok
notifempty
nocompress
}
/var/lib/rk3588-media-server/hls/*/index*.m3u8 {
daily
rotate 1
maxage 7
missingok
notifempty
nocompress
}
EOF
chmod 644 "$LOGROTATE_DIR/rk3588-media-server"
chmod 644 "$LOGROTATE_DIR/rk3588-hls"
echo -e "${GREEN}${NC} 日志轮转配置完成"
}
# 安装 HLS 清理脚本
install_hls_cleanup() {
echo -e "${CYAN}[安装 HLS 清理脚本]${NC}"
cat > "$INSTALL_DIR/bin/cleanup-hls.sh" << 'EOF'
#!/bin/bash
# HLS 分片清理脚本 - 保留最近 N 天的分片
KEEP_DAYS=${1:-3}
HLS_DIR="/var/lib/rk3588-media-server/hls"
if [ ! -d "$HLS_DIR" ]; then
echo "HLS 目录不存在: $HLS_DIR"
exit 1
fi
# 清理旧的分片文件
find "$HLS_DIR" -name "*.ts" -type f -mtime +$KEEP_DAYS -delete 2>/dev/null
# 清理空的摄像头目录
find "$HLS_DIR" -type d -empty -delete 2>/dev/null
echo "$(date): HLS 清理完成 (保留最近 $KEEP_DAYS 天)"
EOF
chmod +x "$INSTALL_DIR/bin/cleanup-hls.sh"
# 添加到 crontab每天凌晨3点执行
CRON_FILE="/etc/cron.d/rk3588-media-server"
cat > "$CRON_FILE" << 'EOF'
# RK3588 Media Server 定时任务
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# 每天凌晨3点清理HLS旧分片保留3天
0 3 * * * root /opt/rk3588-media-server/bin/cleanup-hls.sh 3 >> /var/lib/rk3588-media-server/logs/cleanup.log 2>&1
# 每周日凌晨4点清理告警视频保留30天
0 4 * * 0 root find /var/lib/rk3588-media-server/clips -name "*.mp4" -type f -mtime +30 -delete 2>/dev/null
# 每周日凌晨4点清理告警图片保留90天
30 4 * * 0 root find /var/lib/rk3588-media-server/alarms -name "*.jpg" -type f -mtime +90 -delete 2>/dev/null
EOF
chmod 644 "$CRON_FILE"
echo -e "${GREEN}${NC} HLS 清理脚本安装完成"
echo " 清理策略:"
echo " - HLS分片: 保留3天"
echo " - 告警视频: 保留30天"
echo " - 告警图片: 保留90天"
}
# 安装 Media Server
cmd_install_media_server() {
echo -e "${BLUE}[1/5] 安装 Media Server...${NC}"
# 检查编译产物
if [ ! -f "$BUILD_DIR/media-server" ]; then
echo -e "${RED}错误: 未找到编译好的 media-server${NC}"
echo "请先编译: cd $PROJECT_DIR && cmake --build build"
exit 1
fi
# 创建目录结构
mkdir -p "$INSTALL_DIR/bin/plugins"
mkdir -p "$INSTALL_DIR/lib"
mkdir -p "$INSTALL_DIR/etc"
mkdir -p "$INSTALL_DIR/web"
mkdir -p "$INSTALL_DIR/models"
# 复制二进制文件
cp "$BUILD_DIR/media-server" "$INSTALL_DIR/bin/"
chmod +x "$INSTALL_DIR/bin/media-server"
# 复制运维脚本
mkdir -p "$INSTALL_DIR/scripts"
if [ -f "$PROJECT_DIR/scripts/ops.sh" ]; then
cp "$PROJECT_DIR/scripts/ops.sh" "$INSTALL_DIR/scripts/"
chmod +x "$INSTALL_DIR/scripts/ops.sh"
echo -e "${GREEN}${NC} 已复制运维脚本: ops.sh"
fi
if [ -f "$PROJECT_DIR/scripts/monitor_hw.sh" ]; then
cp "$PROJECT_DIR/scripts/monitor_hw.sh" "$INSTALL_DIR/scripts/"
chmod +x "$INSTALL_DIR/scripts/monitor_hw.sh"
echo -e "${GREEN}${NC} 已复制运维脚本: monitor_hw.sh"
fi
# 复制插件
if [ -d "$BUILD_DIR/plugins" ]; then
cp "$BUILD_DIR"/plugins/*.so "$INSTALL_DIR/bin/plugins/" 2>/dev/null || true
PLUGIN_COUNT=$(ls "$INSTALL_DIR/bin/plugins/"/*.so 2>/dev/null | wc -l)
echo -e "${GREEN}${NC} 已复制插件 ($PLUGIN_COUNT 个)"
fi
# 复制依赖库
if ls "$BUILD_DIR"/*.so 1> /dev/null 2>&1; then
cp "$BUILD_DIR"/*.so "$INSTALL_DIR/lib/" 2>/dev/null || true
fi
# 复制项目 lib 目录中的 RKNN 等系统库
if [ -d "$PROJECT_DIR/lib" ]; then
for lib_file in "$PROJECT_DIR"/lib/*.so*; do
[ -f "$lib_file" ] || continue
cp "$lib_file" "$INSTALL_DIR/lib/"
echo -e "${GREEN}${NC} 已复制 $(basename "$lib_file")"
done
fi
# 复制共享状态库rk_shared_state
if [ -f "$BUILD_DIR/librk_shared_state.so" ]; then
cp "$BUILD_DIR/librk_shared_state.so" "$INSTALL_DIR/lib/"
echo -e "${GREEN}${NC} 已复制 librk_shared_state.so"
fi
# 复制 web 静态文件
if [ -d "$PROJECT_DIR/web" ]; then
cp -r "$PROJECT_DIR/web"/* "$INSTALL_DIR/web/" 2>/dev/null || true
WEB_FILES=$(find "$INSTALL_DIR/web" -type f | wc -l)
echo -e "${GREEN}${NC} 已复制 web 静态文件 ($WEB_FILES 个文件)"
fi
# 复制模型文件(包括 .rknn 模型和 .db 人脸库)
if [ -d "$PROJECT_DIR/models" ]; then
cp -r "$PROJECT_DIR/models"/* "$INSTALL_DIR/models/" 2>/dev/null || true
MODEL_COUNT=$(find "$INSTALL_DIR/models" -type f \( -name "*.rknn" -o -name "*.db" \) | wc -l)
echo -e "${GREEN}${NC} 已复制模型文件 ($MODEL_COUNT 个模型/人脸库)"
fi
# 选择并复制配置文件
select_config
# 修复配置文件路径
fix_config_paths
echo -e "${GREEN}${NC} Media Server 安装完成"
}
# 渲染模板化部署配置
render_template_config() {
local template_path="$PROJECT_DIR/$DEPLOY_CONFIG_TEMPLATE"
local profile_path="$PROJECT_DIR/$DEPLOY_CONFIG_PROFILE"
local render_tool="$PROJECT_DIR/tools/render_config.py"
local rendered_at
local out_file
local overlay_args=()
local overlay_path
if [ ! -f "$render_tool" ] || [ ! -f "$template_path" ] || [ ! -f "$profile_path" ]; then
return 1
fi
rendered_at="$(date -Iseconds)"
out_file="/tmp/rk3588-media-server-${DEPLOY_CONFIG_ID}-${DEPLOY_CONFIG_VERSION}.json"
for overlay in $DEPLOY_CONFIG_OVERLAYS; do
overlay_path="$PROJECT_DIR/$overlay"
if [ ! -f "$overlay_path" ]; then
echo -e "${YELLOW}警告: overlay 不存在,跳过: $overlay${NC}"
continue
fi
overlay_args+=(--overlay "$overlay_path")
done
echo -e "${CYAN}[渲染模板配置]${NC}"
echo " template: $DEPLOY_CONFIG_TEMPLATE"
echo " profile: $DEPLOY_CONFIG_PROFILE"
echo " overlays: ${DEPLOY_CONFIG_OVERLAYS:-<none>}"
echo " config_id: $DEPLOY_CONFIG_ID"
echo " config_version: $DEPLOY_CONFIG_VERSION"
python3 "$render_tool" \
--template "$template_path" \
--profile "$profile_path" \
"${overlay_args[@]}" \
--config-id "$DEPLOY_CONFIG_ID" \
--config-version "$DEPLOY_CONFIG_VERSION" \
--rendered-at "$rendered_at" \
--out "$out_file"
cp "$out_file" "$INSTALL_DIR/etc/media-server.json"
echo -e "${GREEN}${NC} 已渲染部署配置: $out_file"
return 0
}
# 选择配置文件
select_config() {
echo -e "${CYAN}[选择配置文件]${NC}"
if render_template_config; then
return
fi
echo -e "${YELLOW}模板渲染不可用,回退到旧配置文件选择流程${NC}"
# 查找所有可用的配置文件
CONFIGS=()
for f in "$PROJECT_DIR/configs"/*.json; do
if [ -f "$f" ]; then
CONFIGS+=("$(basename "$f")")
fi
done
echo "可用的配置文件:"
for i in "${!CONFIGS[@]}"; do
printf " %2d) %s\n" $((i+1)) "${CONFIGS[$i]}"
done
echo ""
printf "请选择 [1-${#CONFIGS[@]}默认1]: "
read choice
choice=${choice:-1}
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#CONFIGS[@]}" ]; then
choice=1
fi
SELECTED_CONFIG="$PROJECT_DIR/configs/${CONFIGS[$((choice-1))]}"
cp "$SELECTED_CONFIG" "$INSTALL_DIR/etc/media-server.json"
echo -e "${GREEN}${NC} 使用配置: ${CONFIGS[$((choice-1))]}"
}
# 修复配置文件中的路径
fix_config_paths() {
echo -e "${CYAN}[修复配置路径]${NC}"
python3 << 'EOF'
import json
import os
import re
config_file = "/opt/rk3588-media-server/etc/media-server.json"
install_dir = "/opt/rk3588-media-server"
runtime_dir = "/var/lib/rk3588-media-server"
project_dir = os.path.expanduser("~/apps/OrangePi3588Media")
def fix_path(path):
"""修复配置文件中的路径"""
if not path or not isinstance(path, str):
return path
# 模型路径 -> 安装目录的 models
if 'third_party/rknpu2' in path or '.rknn' in path:
return os.path.join(install_dir, 'models', os.path.basename(path))
# HLS 输出路径 -> 运行时目录的 hls
if 'web/hls' in path or path.startswith('./web/hls'):
return os.path.join(runtime_dir, 'hls', os.path.basename(os.path.dirname(path)), 'index.m3u8')
# web 根目录 -> 安装目录的 web
if path.startswith('./web/'):
return os.path.join(install_dir, path[2:])
# models 目录 -> 安装目录的 models
if path.startswith('./models/'):
return os.path.join(install_dir, path[2:])
# 其他相对路径 -> 尝试解析
if path.startswith('./'):
abs_path = os.path.join(project_dir, path)
if os.path.exists(abs_path):
return abs_path
return os.path.join(install_dir, path[2:])
return path
def process_node(node):
"""处理节点配置"""
# 修复模型路径
if 'model_path' in node:
node['model_path'] = fix_path(node['model_path'])
# 修复图库路径
if 'gallery' in node and isinstance(node['gallery'], dict) and 'path' in node['gallery']:
node['gallery']['path'] = fix_path(node['gallery']['path'])
# 修复输出路径HLS、报警图片等
if 'outputs' in node and isinstance(node['outputs'], list):
for output in node['outputs']:
if 'path' in output:
output['path'] = fix_path(output['path'])
# 修复 MinIO 端点(如果是本地)
if 'minio_endpoint' in node:
endpoint = node['minio_endpoint']
if '127.0.0.1' in endpoint or 'localhost' in endpoint:
# 保持本地端点不变
pass
return node
try:
with open(config_file, 'r') as f:
config = json.load(f)
# 处理 graphs 格式
if 'graphs' in config:
for graph in config['graphs']:
if 'nodes' in graph:
for node in graph['nodes']:
process_node(node)
# 处理 templates + instances 格式
if 'templates' in config and 'instances' in config:
for template_name, template in config['templates'].items():
if 'nodes' in template:
for node in template['nodes']:
process_node(node)
# 添加全局配置(如果不存在)
if 'global' not in config:
config['global'] = {}
# 确保 web_root 指向正确位置
config['global']['web_root'] = os.path.join(install_dir, 'web')
config['global']['metrics_port'] = config['global'].get('metrics_port', 9000)
with open(config_file, 'w') as f:
json.dump(config, f, indent=2)
print(f"配置路径修复完成")
except Exception as e:
print(f"配置处理出错: {e}")
import traceback
traceback.print_exc()
EOF
}
# 安装 Agent
cmd_install_agent() {
echo -e "${BLUE}[2/5] 安装 Agent...${NC}"
resolve_agent_token
# 查找 Agent 编译产物(优先使用预编译的 arm64 二进制)
AGENT_SOURCE=""
if [ -f "$PROJECT_DIR/agent/rk3588-agent_linux_arm64" ]; then
AGENT_SOURCE="$PROJECT_DIR/agent/rk3588-agent_linux_arm64"
echo -e "${GREEN}${NC} 找到预编译 Agent: rk3588-agent_linux_arm64"
elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" ]; then
AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent"
echo -e "${GREEN}${NC} 找到源码编译 Agent: rk3588-agent"
fi
if [ -z "$AGENT_SOURCE" ]; then
echo -e "${YELLOW}警告: 未找到编译好的 Agent跳过安装${NC}"
return
fi
mkdir -p "$AGENT_INSTALL_DIR"
mkdir -p "/var/lib/rk3588-agent"
cp "$AGENT_SOURCE" "$AGENT_INSTALL_DIR/rk3588-agent"
chmod +x "$AGENT_INSTALL_DIR/rk3588-agent"
printf '%s\n' "$AGENT_TOKEN" > "$AGENT_TOKEN_FILE"
chmod 600 "$AGENT_TOKEN_FILE"
# Agent 配置
cat > "$AGENT_INSTALL_DIR/agent.config.json" << EOF
{
"agent": {
"listen": "0.0.0.0:9100",
"token": "$AGENT_TOKEN",
"require_token_for_read": false,
"discovery_enable": true,
"discovery_port": 35688,
"device_name": "rk3588_$(hostname)",
"device_id_path": "/var/lib/rk3588-agent/device_id",
"models_dir": "$INSTALL_DIR/models",
"max_upload_mb": 200,
"config_path": "$INSTALL_DIR/etc/media-server.json",
"media_server_process": {
"enable": false,
"configs_dir": "$INSTALL_DIR/etc"
},
"media_server_base_url": "http://127.0.0.1:9000",
"media_server_timeout_ms": 3000,
"media_server_retry": { "max_attempts": 3, "backoff_ms": [200, 500] }
}
}
EOF
chown -R orangepi:orangepi "/var/lib/rk3588-agent"
echo -e "${GREEN}${NC} Agent 安装完成,已写入后台管理统一主钥匙"
}
# 安装 systemd 服务
cmd_install_services() {
echo -e "${BLUE}[3/5] 安装 Systemd 服务...${NC}"
# Media Server 服务
cat > "$SERVICE_DIR/media-server.service" << EOF
[Unit]
Description=RK3588 Media Server
After=network.target
Wants=network.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=$INSTALL_DIR
Environment="LD_LIBRARY_PATH=$INSTALL_DIR/lib:/usr/local/lib"
Environment="RKNPU_LOG_LEVEL=2"
ExecStartPre=/bin/bash -c 'mkdir -p $HLS_DIR $LOGS_DIR $ALARMS_DIR $CLIPS_DIR && chown orangepi:orangepi $LOGS_DIR $ALARMS_DIR $CLIPS_DIR && chmod 755 $LOGS_DIR $ALARMS_DIR $CLIPS_DIR'
ExecStart=$INSTALL_DIR/bin/media-server --config $INSTALL_DIR/etc/media-server.json
Restart=always
RestartSec=5
StartLimitInterval=60s
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
SyslogIdentifier=media-server
# 资源限制
LimitNOFILE=65536
LimitNPROC=4096
# 优雅关闭
TimeoutStopSec=30
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target
EOF
# Agent 服务
cat > "$SERVICE_DIR/rk3588-agent.service" << EOF
[Unit]
Description=RK3588 Agent Service
After=network.target media-server.service
Wants=media-server.service
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=$AGENT_INSTALL_DIR
ExecStart=$AGENT_INSTALL_DIR/rk3588-agent --config $AGENT_INSTALL_DIR/agent.config.json
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rk3588-agent
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
echo -e "${GREEN}${NC} Systemd 服务安装完成"
}
# 主安装流程
cmd_install() {
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ RK3588 Media Server 完善部署脚本 ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
# 检查 root 权限
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误: 请使用 sudo 运行此脚本${NC}"
exit 1
fi
echo "项目目录: $PROJECT_DIR"
echo "安装目录: $INSTALL_DIR"
echo "运行时目录: $RUNTIME_DIR"
echo ""
# 执行安装步骤
cmd_install_media_server
cmd_install_agent
create_runtime_dirs
cmd_install_services
install_logrotate
install_hls_cleanup
# 启动服务
echo -e "${BLUE}[启动服务]${NC}"
systemctl enable media-server
systemctl start media-server
systemctl enable rk3588-agent
systemctl start rk3588-agent
echo ""
echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ 部署完成! ║${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "目录结构:"
echo " 程序: $INSTALL_DIR/"
echo " 数据: $RUNTIME_DIR/"
echo " 配置: $INSTALL_DIR/etc/media-server.json"
echo ""
echo "服务管理:"
echo " 状态: sudo systemctl status media-server"
echo " 日志: sudo journalctl -u media-server -f"
echo " 界面: http://$(hostname -I | awk '{print $1}'):9100"
echo ""
echo "HLS播放:"
echo " http://$(hostname -I | awk '{print $1}'):9000/hls_player.html"
}
# 升级
cmd_upgrade() {
echo -e "${YELLOW}========== 升级 Media Server ==========${NC}"
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误: 请使用 sudo 运行${NC}"
exit 1
fi
# 备份配置
BACKUP_DIR="/root/rk3588-backup-$(date +%Y%m%d%H%M%S)"
mkdir -p "$BACKUP_DIR"
[ -f "$INSTALL_DIR/etc/media-server.json" ] && cp "$INSTALL_DIR/etc/media-server.json" "$BACKUP_DIR/"
echo "配置已备份到: $BACKUP_DIR"
# 停止服务
echo "停止服务..."
systemctl stop media-server rk3588-agent
# 重新安装
cmd_install
echo -e "${GREEN}升级完成${NC}"
}
# 查看状态
cmd_status() {
echo -e "${BLUE}========== RK3588 Media Server 状态 ==========${NC}"
echo ""
# 系统信息
echo -e "${CYAN}[系统信息]${NC}"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo " 运行时间: $(uptime -p 2>/dev/null || uptime | awk -F',' '{print $1}')"
echo ""
# NPU
echo -e "${CYAN}[NPU 负载]${NC}"
if [ -f /proc/rknpu/load ]; then
cat /proc/rknpu/load | sed 's/^/ /'
else
echo " NPU 信息不可用"
fi
echo ""
# Media Server
echo -e "${CYAN}[Media Server]${NC}"
if systemctl is-active --quiet media-server 2>/dev/null; then
echo -e " 状态: ${GREEN}● 运行中${NC}"
PID=$(systemctl show --property=MainPID --value media-server 2>/dev/null)
echo " PID: $PID"
if [ -n "$PID" ] && [ "$PID" != "0" ]; then
CPU_MEM=$(ps -p $PID -o %cpu,%mem --no-headers 2>/dev/null || echo "N/A")
echo " CPU/MEM: $CPU_MEM"
fi
echo " 端口:"
ss -tlnp 2>/dev/null | grep -E "(9000|8555)" | head -3 | sed 's/^/ /'
else
echo -e " 状态: ${RED}○ 停止${NC}"
fi
echo ""
# HLS 输出
echo -e "${CYAN}[HLS 输出]${NC}"
if [ -d "$HLS_DIR" ]; then
CHANNELS=$(find "$HLS_DIR" -maxdepth 1 -type d | wc -l)
CHANNELS=$((CHANNELS - 1))
echo " 通道数: $CHANNELS"
if [ $CHANNELS -gt 0 ]; then
echo " 分片统计:"
for d in "$HLS_DIR"/*/; do
[ -d "$d" ] || continue
name=$(basename "$d")
count=$(find "$d" -name "*.ts" | wc -l)
size=$(du -sh "$d" 2>/dev/null | cut -f1)
echo " $name: ${count}个分片, ${size}"
done
fi
else
echo " HLS 目录不存在"
fi
echo ""
# Agent
echo -e "${CYAN}[RK3588 Agent]${NC}"
if systemctl is-active --quiet rk3588-agent 2>/dev/null; then
echo -e " 状态: ${GREEN}● 运行中${NC}"
echo " 管理界面: http://$(hostname -I | awk '{print $1}'):9100"
else
echo -e " 状态: ${RED}○ 停止${NC}"
fi
echo ""
# 存储使用
echo -e "${CYAN}[存储使用]${NC}"
echo " HLS 输出: $(du -sh "$HLS_DIR" 2>/dev/null | cut -f1)"
echo " 日志: $(du -sh "$LOGS_DIR" 2>/dev/null | cut -f1)"
echo " 告警图片: $(du -sh "$ALARMS_DIR" 2>/dev/null | cut -f1)"
echo " 告警视频: $(du -sh "$CLIPS_DIR" 2>/dev/null | cut -f1)"
echo ""
echo "常用命令:"
echo " 查看日志: sudo journalctl -u media-server -f"
echo " HLS清理: sudo $INSTALL_DIR/bin/cleanup-hls.sh"
echo " 实时监控: watch -n 1 cat /proc/rknpu/load"
}
# 查看日志
cmd_logs() {
SERVICE="${2:-media-server}"
echo -e "${BLUE}查看 $SERVICE 日志 (按 Ctrl+C 退出)...${NC}"
journalctl -u "$SERVICE" -f
}
# 清理 HLS
cmd_clean_hls() {
echo -e "${YELLOW}清理 HLS 旧分片...${NC}"
KEEP_DAYS="${2:-3}"
if [ -f "$INSTALL_DIR/bin/cleanup-hls.sh" ]; then
"$INSTALL_DIR/bin/cleanup-hls.sh" "$KEEP_DAYS"
else
echo "清理脚本不存在"
exit 1
fi
}
# 卸载
cmd_uninstall() {
echo -e "${YELLOW}========== 卸载 RK3588 Media Server ==========${NC}"
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误: 请使用 sudo 运行${NC}"
exit 1
fi
read -p "确定要卸载? (y/N): " -r
[ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && echo "已取消" && exit 0
echo "[1/4] 停止服务..."
systemctl stop media-server rk3588-agent 2>/dev/null || true
systemctl disable media-server rk3588-agent 2>/dev/null || true
echo "[2/4] 删除服务文件..."
rm -f "$SERVICE_DIR/media-server.service"
rm -f "$SERVICE_DIR/rk3588-agent.service"
rm -f "$LOGROTATE_DIR/rk3588-media-server"
rm -f "$LOGROTATE_DIR/rk3588-hls"
rm -f "/etc/cron.d/rk3588-media-server"
systemctl daemon-reload
echo "[3/4] 备份数据..."
BACKUP_DIR="/root/rk3588-backup-$(date +%Y%m%d%H%M%S)"
mkdir -p "$BACKUP_DIR"
[ -f "$INSTALL_DIR/etc/media-server.json" ] && cp "$INSTALL_DIR/etc/media-server.json" "$BACKUP_DIR/"
[ -d "$HLS_DIR" ] && cp -r "$HLS_DIR" "$BACKUP_DIR/" 2>/dev/null || true
echo "数据已备份到: $BACKUP_DIR"
echo "[4/4] 删除文件..."
read -p "是否删除所有数据 (包括 HLS/日志/告警)? (y/N): " -r
if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then
rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR" "$RUNTIME_DIR"
echo "所有数据已删除"
else
rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR"
echo "程序已删除,数据保留在: $RUNTIME_DIR"
fi
echo -e "${GREEN}卸载完成${NC}"
}
# 主入口
case "${1:-install}" in
install)
cmd_install
;;
upgrade)
cmd_upgrade
;;
status)
cmd_status
;;
logs)
cmd_logs "$@"
;;
clean-hls)
cmd_clean_hls "$@"
;;
uninstall)
cmd_uninstall
;;
*)
echo "RK3588 Media Server 部署脚本"
echo ""
echo "用法: sudo ./deploy.sh [命令]"
echo ""
echo "命令:"
echo " install 安装/部署服务 (默认)"
echo " upgrade 升级服务 (保留配置和数据)"
echo " status 查看运行状态"
echo " logs 查看日志"
echo " clean-hls 清理 HLS 旧分片"
echo " uninstall 卸载服务"
echo ""
echo "示例:"
echo " sudo AGENT_TOKEN=<managerd.json 中的 agent_token> ./deploy.sh install"
echo " sudo ./deploy.sh upgrade # 沿用设备保存的后台管理统一主钥匙"
echo " sudo ./deploy.sh status"
echo " sudo ./deploy.sh clean-hls 7 # 保留最近7天"
exit 1
;;
esac