diff --git a/agent/cmd/rk3588-agent/main.go b/agent/cmd/rk3588-agent/main.go index 15e90d6..bc8cb77 100644 --- a/agent/cmd/rk3588-agent/main.go +++ b/agent/cmd/rk3588-agent/main.go @@ -94,6 +94,17 @@ func main() { } }() + // 自动启动 media-server(如果启用) + if cfg.Agent.MediaServerProcess.Enable { + time.Sleep(500 * time.Millisecond) // 等待 HTTP 服务启动 + log.Info("auto-starting media-server...") + if err := h.StartMediaServer(""); err != nil { + log.Error("auto-start media-server failed: " + err.Error()) + } else { + log.Info("media-server auto-started") + } + } + if cfg.Agent.DiscoveryEnable { wg.Add(1) go func() { diff --git a/agent/internal/httpapi/server.go b/agent/internal/httpapi/server.go index d96db8c..521ad57 100644 --- a/agent/internal/httpapi/server.go +++ b/agent/internal/httpapi/server.go @@ -32,7 +32,7 @@ type Server struct { agentCfg config.AgentConfig ms *mediaserver.Client store *modelstore.Store - proc *procctl.Controller + proc procctl.ProcessController audit *audit.Recorder tasks *tasks.Registry baseDir string @@ -46,6 +46,12 @@ type Server struct { cpuMu sync.Mutex lastCPU metrics.CPUStat lastCPUTS time.Time + handler http.Handler +} + +// ServeHTTP 实现 http.Handler 接口 +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.handler.ServeHTTP(w, r) } type InfoResponse struct { @@ -62,10 +68,14 @@ type InfoResponse struct { UptimeSec int64 `json:"uptime_sec"` } -func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, store *modelstore.Store, deviceID string, agentPort int, mediaPort int, version, gitSHA string) http.Handler { - var pc *procctl.Controller +func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, store *modelstore.Store, deviceID string, agentPort int, mediaPort int, version, gitSHA string) *Server { + var pc procctl.ProcessController if agentCfg.MediaServerProcess.Enable { + // 使用传统的 fork/exec 模式 pc = procctl.New(agentCfg, baseDir) + } else { + // 使用 systemctl 模式管理 Media Server + pc = procctl.NewSystemCtlController("media-server") } execPath, _ := os.Executable() if execPath != "" { @@ -119,7 +129,8 @@ func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, st mux.HandleFunc("/v1/assets", s.handleAssets) mux.HandleFunc("/v1/tasks/", s.handleTask) - return mux + s.handler = mux + return s } func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) { @@ -436,6 +447,15 @@ type mediaProcReq struct { Config string `json:"config"` } +// StartMediaServer 启动 media-server(供外部调用) +func (s *Server) StartMediaServer(configName string) error { + if s.proc == nil || !s.proc.Enabled() { + return errors.New("media server process management not enabled") + } + _, err := s.proc.Start(configName) + return err +} + func (s *Server) handleMediaStart(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") diff --git a/agent/internal/procctl/procctl.go b/agent/internal/procctl/procctl.go index 3b5bf9e..fc410d8 100644 --- a/agent/internal/procctl/procctl.go +++ b/agent/internal/procctl/procctl.go @@ -23,6 +23,19 @@ var ErrConflict = errors.New("conflict") var ErrInvalidConfig = errors.New("invalid config") var ErrConfigNotFound = errors.New("config not found") +// ProcessController 定义进程控制器接口 +type ProcessController interface { + Enabled() bool + Status() (Status, error) + Version() (string, error) + Start(configName string) (Status, error) + Stop() (Status, error) + Restart(configName string) (Status, error) + BinaryInfo() (BinaryUpdateResult, error) + UpdateBinary(r io.Reader, contentLength int64, expectedSha256 string) (BinaryUpdateResult, error) + RollbackBinary(backupPath string) (BinaryUpdateResult, error) +} + type Status struct { Running bool `json:"running"` Pid int `json:"pid"` diff --git a/agent/internal/procctl/systemctl.go b/agent/internal/procctl/systemctl.go new file mode 100644 index 0000000..7ac14e1 --- /dev/null +++ b/agent/internal/procctl/systemctl.go @@ -0,0 +1,109 @@ +package procctl + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "strconv" + "strings" + "time" +) + +// SystemCtlController 使用 systemctl 管理 Media Server +type SystemCtlController struct { + serviceName string +} + +// NewSystemCtlController 创建 systemctl 控制器 +func NewSystemCtlController(serviceName string) *SystemCtlController { + return &SystemCtlController{ + serviceName: serviceName, + } +} + +func (s *SystemCtlController) Enabled() bool { return s != nil } + +func (s *SystemCtlController) Status() (Status, error) { + // 检查服务是否运行 + cmd := exec.Command("systemctl", "is-active", "--quiet", s.serviceName) + err := cmd.Run() + + if err != nil { + // 服务未运行 + return Status{Running: false}, nil + } + + // 获取 PID + pidCmd := exec.Command("systemctl", "show", "--property=MainPID", "--value", s.serviceName) + out, err := pidCmd.Output() + if err != nil { + return Status{Running: true}, nil // 运行但无法获取 PID + } + + pid, _ := strconv.Atoi(strings.TrimSpace(string(out))) + if pid <= 0 { + return Status{Running: true}, nil + } + + return Status{ + Running: true, + Pid: pid, + ConfigPath: "/opt/rk3588-media-server/etc/media-server.json", + }, nil +} + +func (s *SystemCtlController) Version() (string, error) { + // 直接执行 media-server --version + cmd := exec.Command("/opt/rk3588-media-server/bin/media-server", "--version") + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("get version failed: %w", err) + } + return strings.TrimSpace(string(out)), nil +} + +func (s *SystemCtlController) Start(configName string) (Status, error) { + // 使用 systemctl start + cmd := exec.Command("systemctl", "start", s.serviceName) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return Status{}, fmt.Errorf("start failed: %w, stderr: %s", err, stderr.String()) + } + + // 等待服务启动 + time.Sleep(500 * time.Millisecond) + return s.Status() +} + +func (s *SystemCtlController) Stop() (Status, error) { + cmd := exec.Command("systemctl", "stop", s.serviceName) + if err := cmd.Run(); err != nil { + return Status{}, fmt.Errorf("stop failed: %w", err) + } + return Status{Running: false}, nil +} + +func (s *SystemCtlController) Restart(configName string) (Status, error) { + cmd := exec.Command("systemctl", "restart", s.serviceName) + if err := cmd.Run(); err != nil { + return Status{}, fmt.Errorf("restart failed: %w", err) + } + time.Sleep(500 * time.Millisecond) + return s.Status() +} + +// 二进制更新相关功能在 systemctl 模式下不支持 +func (s *SystemCtlController) BinaryInfo() (BinaryUpdateResult, error) { + return BinaryUpdateResult{}, ErrNotSupported +} + +func (s *SystemCtlController) UpdateBinary(r io.Reader, contentLength int64, expectedSha256 string) (BinaryUpdateResult, error) { + return BinaryUpdateResult{}, ErrNotSupported +} + +func (s *SystemCtlController) RollbackBinary(backupPath string) (BinaryUpdateResult, error) { + return BinaryUpdateResult{}, ErrNotSupported +} diff --git a/agent/rk3588-agent_linux_arm64 b/agent/rk3588-agent_linux_arm64 old mode 100644 new mode 100755 index e2a5301..9115d6f Binary files a/agent/rk3588-agent_linux_arm64 and b/agent/rk3588-agent_linux_arm64 differ diff --git a/configs/multi_security_instances.json b/configs/multi_security_instances.json index 9420a91..cf5be17 100644 --- a/configs/multi_security_instances.json +++ b/configs/multi_security_instances.json @@ -159,14 +159,14 @@ "template": "security_pipeline", "params": { "name": "cam1", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "minioadmin", "minio_sk": "minioadmin" diff --git a/configs/sample_cam1.json b/configs/sample_cam1.json index 4e570c0..2cb2c8a 100644 --- a/configs/sample_cam1.json +++ b/configs/sample_cam1.json @@ -9,7 +9,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, @@ -194,7 +194,7 @@ "quality": 85, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "vi", "region": "us-east-1", "access_key": "admin", @@ -209,7 +209,7 @@ "fps": 30, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "vi", "region": "us-east-1", "access_key": "admin", @@ -257,7 +257,7 @@ "quality": 85, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "vi", "region": "us-east-1", "access_key": "admin", diff --git a/configs/sample_cam1_native_rtsp_face.json b/configs/sample_cam1_native_rtsp_face.json index fceb1ec..bc05b0d 100644 --- a/configs/sample_cam1_native_rtsp_face.json +++ b/configs/sample_cam1_native_rtsp_face.json @@ -9,7 +9,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, @@ -130,7 +130,7 @@ "quality": 85, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "test", "region": "us-east-1", "access_key": "your-access-key", @@ -145,7 +145,7 @@ "fps": 30, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "test", "region": "us-east-1", "access_key": "your-access-key", @@ -180,7 +180,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, diff --git a/configs/stress_cam1_strict_minio_alarm_5ch_rtsp_hls.json b/configs/stress_cam1_strict_minio_alarm_5ch_rtsp_hls.json index a4a6cc9..dbba639 100644 --- a/configs/stress_cam1_strict_minio_alarm_5ch_rtsp_hls.json +++ b/configs/stress_cam1_strict_minio_alarm_5ch_rtsp_hls.json @@ -171,14 +171,14 @@ "template": "strict_minio_alarm_pipeline", "params": { "name": "cam1", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "your-access-key", "minio_sk": "your-secret-key" @@ -189,14 +189,14 @@ "template": "strict_minio_alarm_pipeline", "params": { "name": "cam2", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "your-access-key", "minio_sk": "your-secret-key" @@ -207,14 +207,14 @@ "template": "strict_minio_alarm_pipeline", "params": { "name": "cam3", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "your-access-key", "minio_sk": "your-secret-key" @@ -225,14 +225,14 @@ "template": "strict_minio_alarm_pipeline", "params": { "name": "cam4", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "your-access-key", "minio_sk": "your-secret-key" @@ -243,14 +243,14 @@ "template": "strict_minio_alarm_pipeline", "params": { "name": "cam5", - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "src_w": 1280, "src_h": 720, "fps": 30, "gop": 60, "bitrate_kbps": 2000, "model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", - "minio_endpoint": "http://10.0.0.5:9000", + "minio_endpoint": "http://10.0.0.49:9000", "minio_bucket": "test", "minio_ak": "your-access-key", "minio_sk": "your-secret-key" diff --git a/configs/test_cam1_face_det_recog_rtsp_server.json b/configs/test_cam1_face_det_recog_rtsp_server.json index 1228749..c465c1d 100644 --- a/configs/test_cam1_face_det_recog_rtsp_server.json +++ b/configs/test_cam1_face_det_recog_rtsp_server.json @@ -9,7 +9,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, diff --git a/configs/test_cam1_face_det_rtsp_server.json b/configs/test_cam1_face_det_rtsp_server.json index f8d0bb8..ba5c1ab 100644 --- a/configs/test_cam1_face_det_rtsp_server.json +++ b/configs/test_cam1_face_det_rtsp_server.json @@ -9,7 +9,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, diff --git a/configs/test_cam1_strict_minio_alarm_rtsp_server.json b/configs/test_cam1_strict_minio_alarm_rtsp_server.json index 4498d64..9221a8d 100644 --- a/configs/test_cam1_strict_minio_alarm_rtsp_server.json +++ b/configs/test_cam1_strict_minio_alarm_rtsp_server.json @@ -9,7 +9,7 @@ "type": "input_rtsp", "role": "source", "enable": true, - "url": "rtsp://10.0.0.5:8554/cam", + "url": "rtsp://10.0.0.49:8554/cam", "fps": 30, "width": 1280, "height": 720, @@ -92,7 +92,7 @@ "quality": 85, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "test", "region": "us-east-1", "access_key": "minioadmin", @@ -107,7 +107,7 @@ "fps": 25, "upload": { "type": "minio", - "endpoint": "http://10.0.0.5:9000", + "endpoint": "http://10.0.0.49:9000", "bucket": "test", "region": "us-east-1", "access_key": "minioadmin", diff --git a/docs/requirements/guide.md b/docs/requirements/guide.md index 0bd844a..8e70142 100644 --- a/docs/requirements/guide.md +++ b/docs/requirements/guide.md @@ -18,4 +18,7 @@ ffplay rtsp://localhost:8554/cam ffplay rtsp://localhost:8555/live/cam1_face_det - 在 Windows 上用 VLC 播放处理后的流: -rtsp://3588的IP:8555/live/cam1_face_det \ No newline at end of file +rtsp://3588的IP:8555/live/cam1_face_det + +- 编译agent +go build -o rk3588-agent_linux_arm64 ./cmd/rk3588-agent \ No newline at end of file diff --git a/scripts/manage.sh b/scripts/manage.sh index c2b68f1..ae5b0ac 100755 --- a/scripts/manage.sh +++ b/scripts/manage.sh @@ -1,5 +1,6 @@ #!/bin/bash # RK3588 Media Server + Agent 统一管理脚本 +# Media Server 和 Agent 是独立的 systemd 服务 # 用法: sudo ./manage.sh [install|status|uninstall] set -e @@ -25,7 +26,7 @@ cmd_install() { echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" echo "" - if [ "$EUID" -ne 0 ]; then + if [ "$(id -u)" -ne 0 ]; then echo -e "${RED}错误: 请使用 sudo 运行此脚本${NC}" exit 1 fi @@ -48,24 +49,23 @@ cmd_install() { # 检查插件 if [ ! -d "$BUILD_DIR/plugins" ] || [ -z "$(ls -A $BUILD_DIR/plugins/*.so 2>/dev/null)" ]; then echo -e "${YELLOW}警告: 未找到插件目录或插件为空${NC}" - read -p "是否继续? (y/N): " -n 1 -r - echo - [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 + read -p "是否继续? (y/N): " -r + [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && exit 1 fi # 检查 Agent 编译产物(强制要求) AGENT_SOURCE="" if [ -f "$PROJECT_DIR/agent/rk3588-agent_linux_arm64" ]; then AGENT_SOURCE="$PROJECT_DIR/agent/rk3588-agent_linux_arm64" - elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent" ]; then - AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent" + elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" ]; then + AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" fi if [ -z "$AGENT_SOURCE" ]; then echo -e "${RED}错误: 未找到编译好的 rk3588-agent${NC}" echo "请先编译 Agent:" echo " cd $PROJECT_DIR/agent" - echo " GOOS=linux GOARCH=arm64 go build -o rk3588-agent_linux_arm64 ./cmd" + echo " go build -o rk3588-agent_linux_arm64 ./cmd/rk3588-agent" exit 1 fi @@ -79,7 +79,8 @@ cmd_install() { echo " 3) test_cam1_face_det_recog_rtsp_server.json (人脸识别)" echo " 4) sample_cam1.json (完整功能,需手动修复)" echo "" - read -p "请输入选项 [1-4,默认1]: " choice + printf "请输入选项 [1-4,默认1]: " + read choice choice=${choice:-1} case $choice in @@ -95,7 +96,12 @@ cmd_install() { # 安装 Media Server echo -e "${YELLOW}[1/3] 安装 Media Server...${NC}" rm -rf "$INSTALL_DIR" - mkdir -p "$INSTALL_DIR"/{bin/plugins,lib,etc,logs,models,web/hls} + mkdir -p "$INSTALL_DIR/bin/plugins" + mkdir -p "$INSTALL_DIR/lib" + mkdir -p "$INSTALL_DIR/etc" + mkdir -p "$INSTALL_DIR/logs" + mkdir -p "$INSTALL_DIR/models" + mkdir -p "$INSTALL_DIR/web/hls" cp "$BUILD_DIR/media-server" "$INSTALL_DIR/bin/" chmod +x "$INSTALL_DIR/bin/media-server" @@ -127,6 +133,8 @@ project_dir = os.path.expanduser("~/apps/OrangePi3588Media") def fix_path(path): if not path or not isinstance(path, str): return path + if 'third_party/rknpu2' in path: + return os.path.join(install_dir, 'models', os.path.basename(path)) if path.startswith('./'): if path.startswith('./web/'): return os.path.join(install_dir, path[2:]) @@ -179,6 +187,7 @@ EOF cp "$AGENT_SOURCE" "$AGENT_INSTALL_DIR/rk3588-agent" chmod +x "$AGENT_INSTALL_DIR/rk3588-agent" + # Agent 配置 - 禁用进程管理,改为监控模式 cat > "$AGENT_INSTALL_DIR/agent.config.json" << 'AGENT_CONFIG' { "agent": { @@ -193,11 +202,11 @@ EOF "max_upload_mb": 200, "config_path": "/opt/rk3588-media-server/etc/media-server.json", "media_server_process": { - "enable": true, - "exec_path": "/opt/rk3588-media-server/bin/media-server", - "work_dir": "/opt/rk3588-media-server", - "configs_dir": "/opt/rk3588-media-server/etc", - "pid_file": "/var/run/rk3588sys-media-server.pid", + "enable": false, + "exec_path": "", + "work_dir": "", + "configs_dir": "", + "pid_file": "", "graceful_timeout_ms": 5000 }, "media_server_base_url": "http://127.0.0.1:9000", @@ -213,10 +222,32 @@ AGENT_CONFIG # 安装服务 echo -e "${YELLOW}[3/3] 安装 Systemd 服务...${NC}" + # Media Server 独立服务 + cat > "$SERVICE_DIR/media-server.service" << 'EOF' +[Unit] +Description=RK3588 Media Server +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/rk3588-media-server +ExecStart=/opt/rk3588-media-server/bin/media-server --config /opt/rk3588-media-server/etc/media-server.json +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + + # Agent 独立服务 cat > "$SERVICE_DIR/rk3588-agent.service" << 'EOF' [Unit] Description=RK3588 Agent Service -After=network.target +After=network.target media-server.service +Wants=media-server.service [Service] Type=simple @@ -233,10 +264,11 @@ WantedBy=multi-user.target EOF systemctl daemon-reload - touch /var/run/rk3588sys-media-server.pid - chmod 666 /var/run/rk3588sys-media-server.pid - # 启动 Agent(Agent 会自动管理 Media Server) + # 启动服务(独立运行) + systemctl enable media-server + systemctl start media-server + systemctl enable rk3588-agent systemctl start rk3588-agent @@ -248,9 +280,13 @@ EOF echo -e "${GREEN}║ 安装完成! ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" echo "" + echo "架构说明:" + echo " Media Server: 独立 systemd 服务(高可用,崩溃自动重启)" + echo " Agent: 独立 systemd 服务(监控状态,Web 管理)" + echo "" echo "管理命令:" echo " 状态: sudo ./manage.sh status" - echo " 日志: sudo journalctl -u rk3588-agent -f" + echo " 日志: sudo journalctl -u media-server -f" echo " 界面: http://<设备IP>:9100" } @@ -271,6 +307,24 @@ cmd_status() { fi echo "" + echo -e "${YELLOW}[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|8554|8555)" | head -3 | sed 's/^/ /' + else + echo -e " 状态: ${RED}○ 停止${NC}" + echo " 错误日志:" + journalctl -u media-server --no-pager -n 3 2>/dev/null | sed 's/^/ /' + fi + echo "" + echo -e "${YELLOW}[RK3588 Agent]${NC}" if systemctl is-active --quiet rk3588-agent 2>/dev/null; then echo -e " 状态: ${GREEN}● 运行中${NC}" @@ -285,51 +339,36 @@ cmd_status() { fi echo "" - echo -e "${YELLOW}[Media Server]${NC}" - # 检查 Media Server 是否由 Agent 启动 - AGENT_PID=$(systemctl show --property=MainPID --value rk3588-agent 2>/dev/null) - MS_PID=$(pgrep -f "media-server" | head -1) - if [ -n "$MS_PID" ]; then - echo -e " 状态: ${GREEN}● 运行中${NC}" - echo " PID: $MS_PID" - CPU_MEM=$(ps -p $MS_PID -o %cpu,%mem --no-headers 2>/dev/null || echo "N/A") - echo " CPU/MEM: $CPU_MEM" - echo " 端口:" - ss -tlnp 2>/dev/null | grep -E "(9000|8554|8555)" | head -3 | sed 's/^/ /' - else - echo -e " 状态: ${RED}○ 停止${NC}" - fi - echo "" - - echo -e "${YELLOW}[最近日志]${NC}" - journalctl -u rk3588-agent --no-pager -n 5 2>/dev/null | tail -5 || echo " 无日志" + echo -e "${YELLOW}[最近日志 - Media Server]${NC}" + journalctl -u media-server --no-pager -n 5 2>/dev/null | tail -5 || echo " 无日志" echo "" echo "常用命令:" - echo " 重启: sudo systemctl restart rk3588-agent" - echo " 日志: sudo journalctl -u rk3588-agent -f" - echo " 配置: sudo nano /opt/rk3588-media-server/etc/media-server.json" + echo " 启动: sudo systemctl start media-server" + echo " 停止: sudo systemctl stop media-server" + echo " 重启: sudo systemctl restart media-server" + echo " 配置: sudo nano /opt/rk3588-media-server/etc/media-server.json" } cmd_uninstall() { echo -e "${YELLOW}========== RK3588 Media Server 卸载 ==========${NC}" echo "" - if [ "$EUID" -ne 0 ]; then + if [ "$(id -u)" -ne 0 ]; then echo -e "${RED}错误: 请使用 sudo 运行${NC}" exit 1 fi - read -p "确定要卸载并停止所有服务? (y/N): " -n 1 -r - echo - [[ ! $REPLY =~ ^[Yy]$ ]] && echo "已取消" && exit 0 + read -p "确定要卸载并停止所有服务? (y/N): " -r + [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && echo "已取消" && exit 0 echo "[1/3] 停止服务..." - systemctl stop rk3588-agent 2>/dev/null || true - systemctl disable rk3588-agent 2>/dev/null || true + systemctl stop media-server rk3588-agent 2>/dev/null || true + systemctl disable media-server rk3588-agent 2>/dev/null || true echo -e "${GREEN}✓${NC} 服务已停止" echo "[2/3] 删除服务文件..." + rm -f "$SERVICE_DIR/media-server.service" rm -f "$SERVICE_DIR/rk3588-agent.service" systemctl daemon-reload echo -e "${GREEN}✓${NC} 服务文件已删除" @@ -341,9 +380,8 @@ cmd_uninstall() { [ -d "$INSTALL_DIR/models" ] && cp -r "$INSTALL_DIR/models" "$BACKUP_DIR/" 2>/dev/null || true echo -e "${GREEN}✓${NC} 配置已备份到: $BACKUP_DIR" - read -p "是否删除安装目录? (y/N): " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then + read -p "是否删除安装目录? (y/N): " -r + if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR" echo -e "${GREEN}✓${NC} 目录已删除" else