修改部署脚本,media-server和agent独立部署

This commit is contained in:
haotian 2026-02-25 15:23:58 +08:00
parent c343b2bc91
commit 317a8b1097
14 changed files with 273 additions and 79 deletions

View File

@ -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 { if cfg.Agent.DiscoveryEnable {
wg.Add(1) wg.Add(1)
go func() { go func() {

View File

@ -32,7 +32,7 @@ type Server struct {
agentCfg config.AgentConfig agentCfg config.AgentConfig
ms *mediaserver.Client ms *mediaserver.Client
store *modelstore.Store store *modelstore.Store
proc *procctl.Controller proc procctl.ProcessController
audit *audit.Recorder audit *audit.Recorder
tasks *tasks.Registry tasks *tasks.Registry
baseDir string baseDir string
@ -46,6 +46,12 @@ type Server struct {
cpuMu sync.Mutex cpuMu sync.Mutex
lastCPU metrics.CPUStat lastCPU metrics.CPUStat
lastCPUTS time.Time 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 { type InfoResponse struct {
@ -62,10 +68,14 @@ type InfoResponse struct {
UptimeSec int64 `json:"uptime_sec"` 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 { 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.Controller var pc procctl.ProcessController
if agentCfg.MediaServerProcess.Enable { if agentCfg.MediaServerProcess.Enable {
// 使用传统的 fork/exec 模式
pc = procctl.New(agentCfg, baseDir) pc = procctl.New(agentCfg, baseDir)
} else {
// 使用 systemctl 模式管理 Media Server
pc = procctl.NewSystemCtlController("media-server")
} }
execPath, _ := os.Executable() execPath, _ := os.Executable()
if execPath != "" { 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/assets", s.handleAssets)
mux.HandleFunc("/v1/tasks/", s.handleTask) mux.HandleFunc("/v1/tasks/", s.handleTask)
return mux s.handler = mux
return s
} }
func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) { func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) {
@ -436,6 +447,15 @@ type mediaProcReq struct {
Config string `json:"config"` 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) { func (s *Server) handleMediaStart(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed") errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")

View File

@ -23,6 +23,19 @@ var ErrConflict = errors.New("conflict")
var ErrInvalidConfig = errors.New("invalid config") var ErrInvalidConfig = errors.New("invalid config")
var ErrConfigNotFound = errors.New("config not found") 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 { type Status struct {
Running bool `json:"running"` Running bool `json:"running"`
Pid int `json:"pid"` Pid int `json:"pid"`

View File

@ -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
}

BIN
agent/rk3588-agent_linux_arm64 Normal file → Executable file

Binary file not shown.

View File

@ -159,14 +159,14 @@
"template": "security_pipeline", "template": "security_pipeline",
"params": { "params": {
"name": "cam1", "name": "cam1",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "minioadmin", "minio_ak": "minioadmin",
"minio_sk": "minioadmin" "minio_sk": "minioadmin"

View File

@ -9,7 +9,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,
@ -194,7 +194,7 @@
"quality": 85, "quality": 85,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "vi", "bucket": "vi",
"region": "us-east-1", "region": "us-east-1",
"access_key": "admin", "access_key": "admin",
@ -209,7 +209,7 @@
"fps": 30, "fps": 30,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "vi", "bucket": "vi",
"region": "us-east-1", "region": "us-east-1",
"access_key": "admin", "access_key": "admin",
@ -257,7 +257,7 @@
"quality": 85, "quality": 85,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "vi", "bucket": "vi",
"region": "us-east-1", "region": "us-east-1",
"access_key": "admin", "access_key": "admin",

View File

@ -9,7 +9,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,
@ -130,7 +130,7 @@
"quality": 85, "quality": 85,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "test", "bucket": "test",
"region": "us-east-1", "region": "us-east-1",
"access_key": "your-access-key", "access_key": "your-access-key",
@ -145,7 +145,7 @@
"fps": 30, "fps": 30,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "test", "bucket": "test",
"region": "us-east-1", "region": "us-east-1",
"access_key": "your-access-key", "access_key": "your-access-key",
@ -180,7 +180,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,

View File

@ -171,14 +171,14 @@
"template": "strict_minio_alarm_pipeline", "template": "strict_minio_alarm_pipeline",
"params": { "params": {
"name": "cam1", "name": "cam1",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "your-access-key", "minio_ak": "your-access-key",
"minio_sk": "your-secret-key" "minio_sk": "your-secret-key"
@ -189,14 +189,14 @@
"template": "strict_minio_alarm_pipeline", "template": "strict_minio_alarm_pipeline",
"params": { "params": {
"name": "cam2", "name": "cam2",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "your-access-key", "minio_ak": "your-access-key",
"minio_sk": "your-secret-key" "minio_sk": "your-secret-key"
@ -207,14 +207,14 @@
"template": "strict_minio_alarm_pipeline", "template": "strict_minio_alarm_pipeline",
"params": { "params": {
"name": "cam3", "name": "cam3",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "your-access-key", "minio_ak": "your-access-key",
"minio_sk": "your-secret-key" "minio_sk": "your-secret-key"
@ -225,14 +225,14 @@
"template": "strict_minio_alarm_pipeline", "template": "strict_minio_alarm_pipeline",
"params": { "params": {
"name": "cam4", "name": "cam4",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "your-access-key", "minio_ak": "your-access-key",
"minio_sk": "your-secret-key" "minio_sk": "your-secret-key"
@ -243,14 +243,14 @@
"template": "strict_minio_alarm_pipeline", "template": "strict_minio_alarm_pipeline",
"params": { "params": {
"name": "cam5", "name": "cam5",
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"src_w": 1280, "src_w": 1280,
"src_h": 720, "src_h": 720,
"fps": 30, "fps": 30,
"gop": 60, "gop": 60,
"bitrate_kbps": 2000, "bitrate_kbps": 2000,
"model_path": "./third_party/rknpu2/examples/rknn_yolov5_demo/model/RK3588/yolov5s-640-640.rknn", "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_bucket": "test",
"minio_ak": "your-access-key", "minio_ak": "your-access-key",
"minio_sk": "your-secret-key" "minio_sk": "your-secret-key"

View File

@ -9,7 +9,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,

View File

@ -9,7 +9,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,

View File

@ -9,7 +9,7 @@
"type": "input_rtsp", "type": "input_rtsp",
"role": "source", "role": "source",
"enable": true, "enable": true,
"url": "rtsp://10.0.0.5:8554/cam", "url": "rtsp://10.0.0.49:8554/cam",
"fps": 30, "fps": 30,
"width": 1280, "width": 1280,
"height": 720, "height": 720,
@ -92,7 +92,7 @@
"quality": 85, "quality": 85,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "test", "bucket": "test",
"region": "us-east-1", "region": "us-east-1",
"access_key": "minioadmin", "access_key": "minioadmin",
@ -107,7 +107,7 @@
"fps": 25, "fps": 25,
"upload": { "upload": {
"type": "minio", "type": "minio",
"endpoint": "http://10.0.0.5:9000", "endpoint": "http://10.0.0.49:9000",
"bucket": "test", "bucket": "test",
"region": "us-east-1", "region": "us-east-1",
"access_key": "minioadmin", "access_key": "minioadmin",

View File

@ -19,3 +19,6 @@ ffplay rtsp://localhost:8555/live/cam1_face_det
- 在 Windows 上用 VLC 播放处理后的流: - 在 Windows 上用 VLC 播放处理后的流:
rtsp://3588的IP:8555/live/cam1_face_det rtsp://3588的IP:8555/live/cam1_face_det
- 编译agent
go build -o rk3588-agent_linux_arm64 ./cmd/rk3588-agent

View File

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
# RK3588 Media Server + Agent 统一管理脚本 # RK3588 Media Server + Agent 统一管理脚本
# Media Server 和 Agent 是独立的 systemd 服务
# 用法: sudo ./manage.sh [install|status|uninstall] # 用法: sudo ./manage.sh [install|status|uninstall]
set -e set -e
@ -25,7 +26,7 @@ cmd_install() {
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
echo "" echo ""
if [ "$EUID" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误: 请使用 sudo 运行此脚本${NC}" echo -e "${RED}错误: 请使用 sudo 运行此脚本${NC}"
exit 1 exit 1
fi fi
@ -48,24 +49,23 @@ cmd_install() {
# 检查插件 # 检查插件
if [ ! -d "$BUILD_DIR/plugins" ] || [ -z "$(ls -A $BUILD_DIR/plugins/*.so 2>/dev/null)" ]; then if [ ! -d "$BUILD_DIR/plugins" ] || [ -z "$(ls -A $BUILD_DIR/plugins/*.so 2>/dev/null)" ]; then
echo -e "${YELLOW}警告: 未找到插件目录或插件为空${NC}" echo -e "${YELLOW}警告: 未找到插件目录或插件为空${NC}"
read -p "是否继续? (y/N): " -n 1 -r read -p "是否继续? (y/N): " -r
echo [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && exit 1
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 1
fi fi
# 检查 Agent 编译产物(强制要求) # 检查 Agent 编译产物(强制要求)
AGENT_SOURCE="" AGENT_SOURCE=""
if [ -f "$PROJECT_DIR/agent/rk3588-agent_linux_arm64" ]; then if [ -f "$PROJECT_DIR/agent/rk3588-agent_linux_arm64" ]; then
AGENT_SOURCE="$PROJECT_DIR/agent/rk3588-agent_linux_arm64" AGENT_SOURCE="$PROJECT_DIR/agent/rk3588-agent_linux_arm64"
elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent" ]; then elif [ -f "$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent" ]; then
AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent" AGENT_SOURCE="$PROJECT_DIR/agent/cmd/rk3588-agent/rk3588-agent"
fi fi
if [ -z "$AGENT_SOURCE" ]; then if [ -z "$AGENT_SOURCE" ]; then
echo -e "${RED}错误: 未找到编译好的 rk3588-agent${NC}" echo -e "${RED}错误: 未找到编译好的 rk3588-agent${NC}"
echo "请先编译 Agent:" echo "请先编译 Agent:"
echo " cd $PROJECT_DIR/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 exit 1
fi fi
@ -79,7 +79,8 @@ cmd_install() {
echo " 3) test_cam1_face_det_recog_rtsp_server.json (人脸识别)" echo " 3) test_cam1_face_det_recog_rtsp_server.json (人脸识别)"
echo " 4) sample_cam1.json (完整功能,需手动修复)" echo " 4) sample_cam1.json (完整功能,需手动修复)"
echo "" echo ""
read -p "请输入选项 [1-4默认1]: " choice printf "请输入选项 [1-4默认1]: "
read choice
choice=${choice:-1} choice=${choice:-1}
case $choice in case $choice in
@ -95,7 +96,12 @@ cmd_install() {
# 安装 Media Server # 安装 Media Server
echo -e "${YELLOW}[1/3] 安装 Media Server...${NC}" echo -e "${YELLOW}[1/3] 安装 Media Server...${NC}"
rm -rf "$INSTALL_DIR" 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/" cp "$BUILD_DIR/media-server" "$INSTALL_DIR/bin/"
chmod +x "$INSTALL_DIR/bin/media-server" chmod +x "$INSTALL_DIR/bin/media-server"
@ -127,6 +133,8 @@ project_dir = os.path.expanduser("~/apps/OrangePi3588Media")
def fix_path(path): def fix_path(path):
if not path or not isinstance(path, str): if not path or not isinstance(path, str):
return path 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('./'):
if path.startswith('./web/'): if path.startswith('./web/'):
return os.path.join(install_dir, path[2:]) return os.path.join(install_dir, path[2:])
@ -179,6 +187,7 @@ EOF
cp "$AGENT_SOURCE" "$AGENT_INSTALL_DIR/rk3588-agent" cp "$AGENT_SOURCE" "$AGENT_INSTALL_DIR/rk3588-agent"
chmod +x "$AGENT_INSTALL_DIR/rk3588-agent" chmod +x "$AGENT_INSTALL_DIR/rk3588-agent"
# Agent 配置 - 禁用进程管理,改为监控模式
cat > "$AGENT_INSTALL_DIR/agent.config.json" << 'AGENT_CONFIG' cat > "$AGENT_INSTALL_DIR/agent.config.json" << 'AGENT_CONFIG'
{ {
"agent": { "agent": {
@ -193,11 +202,11 @@ EOF
"max_upload_mb": 200, "max_upload_mb": 200,
"config_path": "/opt/rk3588-media-server/etc/media-server.json", "config_path": "/opt/rk3588-media-server/etc/media-server.json",
"media_server_process": { "media_server_process": {
"enable": true, "enable": false,
"exec_path": "/opt/rk3588-media-server/bin/media-server", "exec_path": "",
"work_dir": "/opt/rk3588-media-server", "work_dir": "",
"configs_dir": "/opt/rk3588-media-server/etc", "configs_dir": "",
"pid_file": "/var/run/rk3588sys-media-server.pid", "pid_file": "",
"graceful_timeout_ms": 5000 "graceful_timeout_ms": 5000
}, },
"media_server_base_url": "http://127.0.0.1:9000", "media_server_base_url": "http://127.0.0.1:9000",
@ -213,10 +222,32 @@ AGENT_CONFIG
# 安装服务 # 安装服务
echo -e "${YELLOW}[3/3] 安装 Systemd 服务...${NC}" 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' cat > "$SERVICE_DIR/rk3588-agent.service" << 'EOF'
[Unit] [Unit]
Description=RK3588 Agent Service Description=RK3588 Agent Service
After=network.target After=network.target media-server.service
Wants=media-server.service
[Service] [Service]
Type=simple Type=simple
@ -233,10 +264,11 @@ WantedBy=multi-user.target
EOF EOF
systemctl daemon-reload systemctl daemon-reload
touch /var/run/rk3588sys-media-server.pid
chmod 666 /var/run/rk3588sys-media-server.pid
# 启动 AgentAgent 会自动管理 Media Server # 启动服务(独立运行)
systemctl enable media-server
systemctl start media-server
systemctl enable rk3588-agent systemctl enable rk3588-agent
systemctl start rk3588-agent systemctl start rk3588-agent
@ -248,9 +280,13 @@ EOF
echo -e "${GREEN}║ 安装完成! ║${NC}" echo -e "${GREEN}║ 安装完成! ║${NC}"
echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}"
echo "" echo ""
echo "架构说明:"
echo " Media Server: 独立 systemd 服务(高可用,崩溃自动重启)"
echo " Agent: 独立 systemd 服务监控状态Web 管理)"
echo ""
echo "管理命令:" echo "管理命令:"
echo " 状态: sudo ./manage.sh status" echo " 状态: sudo ./manage.sh status"
echo " 日志: sudo journalctl -u rk3588-agent -f" echo " 日志: sudo journalctl -u media-server -f"
echo " 界面: http://<设备IP>:9100" echo " 界面: http://<设备IP>:9100"
} }
@ -271,6 +307,24 @@ cmd_status() {
fi fi
echo "" 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}" echo -e "${YELLOW}[RK3588 Agent]${NC}"
if systemctl is-active --quiet rk3588-agent 2>/dev/null; then if systemctl is-active --quiet rk3588-agent 2>/dev/null; then
echo -e " 状态: ${GREEN}● 运行中${NC}" echo -e " 状态: ${GREEN}● 运行中${NC}"
@ -285,29 +339,14 @@ cmd_status() {
fi fi
echo "" echo ""
echo -e "${YELLOW}[Media Server]${NC}" echo -e "${YELLOW}[最近日志 - Media Server]${NC}"
# 检查 Media Server 是否由 Agent 启动 journalctl -u media-server --no-pager -n 5 2>/dev/null | tail -5 || echo " 无日志"
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 "" echo ""
echo "常用命令:" echo "常用命令:"
echo " 重启: sudo systemctl restart rk3588-agent" echo " 启动: sudo systemctl start media-server"
echo " 日志: sudo journalctl -u rk3588-agent -f" echo " 停止: sudo systemctl stop media-server"
echo " 重启: sudo systemctl restart media-server"
echo " 配置: sudo nano /opt/rk3588-media-server/etc/media-server.json" echo " 配置: sudo nano /opt/rk3588-media-server/etc/media-server.json"
} }
@ -315,21 +354,21 @@ cmd_uninstall() {
echo -e "${YELLOW}========== RK3588 Media Server 卸载 ==========${NC}" echo -e "${YELLOW}========== RK3588 Media Server 卸载 ==========${NC}"
echo "" echo ""
if [ "$EUID" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误: 请使用 sudo 运行${NC}" echo -e "${RED}错误: 请使用 sudo 运行${NC}"
exit 1 exit 1
fi fi
read -p "确定要卸载并停止所有服务? (y/N): " -n 1 -r read -p "确定要卸载并停止所有服务? (y/N): " -r
echo [ "$REPLY" != "y" ] && [ "$REPLY" != "Y" ] && echo "已取消" && exit 0
[[ ! $REPLY =~ ^[Yy]$ ]] && echo "已取消" && exit 0
echo "[1/3] 停止服务..." echo "[1/3] 停止服务..."
systemctl stop rk3588-agent 2>/dev/null || true systemctl stop media-server rk3588-agent 2>/dev/null || true
systemctl disable rk3588-agent 2>/dev/null || true systemctl disable media-server rk3588-agent 2>/dev/null || true
echo -e "${GREEN}${NC} 服务已停止" echo -e "${GREEN}${NC} 服务已停止"
echo "[2/3] 删除服务文件..." echo "[2/3] 删除服务文件..."
rm -f "$SERVICE_DIR/media-server.service"
rm -f "$SERVICE_DIR/rk3588-agent.service" rm -f "$SERVICE_DIR/rk3588-agent.service"
systemctl daemon-reload systemctl daemon-reload
echo -e "${GREEN}${NC} 服务文件已删除" 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 [ -d "$INSTALL_DIR/models" ] && cp -r "$INSTALL_DIR/models" "$BACKUP_DIR/" 2>/dev/null || true
echo -e "${GREEN}${NC} 配置已备份到: $BACKUP_DIR" echo -e "${GREEN}${NC} 配置已备份到: $BACKUP_DIR"
read -p "是否删除安装目录? (y/N): " -n 1 -r read -p "是否删除安装目录? (y/N): " -r
echo if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR" rm -rf "$INSTALL_DIR" "$AGENT_INSTALL_DIR"
echo -e "${GREEN}${NC} 目录已删除" echo -e "${GREEN}${NC} 目录已删除"
else else