913 lines
30 KiB
Markdown
913 lines
30 KiB
Markdown
# 部署指南(物理机)
|
||
|
||
## 一、环境信息
|
||
|
||
| 项目 | 值 |
|
||
|------|-----|
|
||
| 机器 | Acer TravelMate P249-G2-MG(物理机) |
|
||
| OS | Ubuntu 22.04.5 LTS |
|
||
| 内核 | 6.8.0-124-generic |
|
||
| 磁盘 | 387GB(/dev/sda6),可用 356GB |
|
||
| 内存 | 16GB |
|
||
| 网络 | 192.168.1.4(物理网卡 enp4s0)+ 100.83.103.1(WireGuard VPN wt0) |
|
||
| SSH | pan@100.83.103.1 密码 pan |
|
||
|
||
> 与 WSL2 部署的关键区别:无需 portproxy、无需 WSL2 IP 修复、文件系统不会变只读、Ceph 需从头配置。
|
||
|
||
## 二、服务架构
|
||
|
||
```
|
||
浏览器
|
||
│
|
||
├── http://100.83.103.1:5174 → 前端 (Node.js server.cjs)
|
||
├── http://100.83.103.1:3000 → Go 后端 API
|
||
├── http://100.83.103.1:7480 → Ceph S3 RGW
|
||
├── http://100.83.103.1:18080 → WVP 管理界面
|
||
│
|
||
▼
|
||
物理机 (100.83.103.1)
|
||
├── sshd :22 SSH 服务
|
||
├── Docker
|
||
│ ├── polaris-wvp :18080/:18978/:8116 GB28181 信令(18080管理界面/18978HTTP API/8116 SIP)
|
||
│ ├── polaris-media :8081/:10000 ZLMediaKit 流媒体
|
||
│ ├── polaris-mysql :3306 WVP 数据库
|
||
│ ├── polaris-redis WVP 缓存(Docker 内部网络)
|
||
│ └── silk-redis :6379 Go 后端缓存(密码 pan)
|
||
├── Ceph
|
||
│ ├── Monitor (a)
|
||
│ ├── Mgr (a)
|
||
│ ├── OSD 0/1 (loop device, 各 50GB, 共 100GB)
|
||
│ └── RGW :7480 S3 兼容存储
|
||
├── PostgreSQL 16 :5432 业务数据库
|
||
├── IoTDB 2.0.8 :18081 时序数据库
|
||
├── VerneMQ :1883 MQTT broker
|
||
├── Go 后端 :3000 silk-server-go
|
||
├── Go 录制服务 :9090 recorder-go
|
||
├── AI 推理服务 :8000 ai-service(FastAPI + ONNX Runtime,mock/onnx)
|
||
└── 前端 :5174 server.cjs
|
||
```
|
||
|
||
## 三、全新部署步骤
|
||
|
||
### 1. 安装系统依赖
|
||
|
||
```bash
|
||
# SSH 到服务器
|
||
ssh pan@100.83.103.1
|
||
|
||
# 更新 apt
|
||
sudo apt update
|
||
|
||
# 基础工具
|
||
sudo apt install -y curl wget git
|
||
|
||
# ffmpeg(视频转码)
|
||
sudo apt install -y ffmpeg
|
||
|
||
# PostgreSQL
|
||
sudo apt install -y postgresql postgresql-contrib
|
||
|
||
# JDK 17(IoTDB 依赖)
|
||
sudo apt install -y openjdk-17-jdk-headless
|
||
|
||
# Docker(阿里云镜像源)
|
||
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
|
||
sudo mkdir -p /etc/apt/keyrings
|
||
sudo curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://mirrors.aliyun.com/docker-ce/linux/ubuntu jammy stable" | sudo tee /etc/apt/sources.list.d/docker.list
|
||
sudo apt update
|
||
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||
|
||
# Docker 镜像加速
|
||
sudo mkdir -p /etc/docker
|
||
cat << 'EOF' | sudo tee /etc/docker/daemon.json
|
||
{
|
||
"registry-mirrors": ["https://docker.1panel.live", "https://hub.rat.dev"]
|
||
}
|
||
EOF
|
||
sudo systemctl restart docker
|
||
|
||
# 将 pan 加入 docker 组
|
||
sudo usermod -aG docker pan
|
||
|
||
# Node.js 20
|
||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||
sudo apt install -y nodejs
|
||
|
||
# Go 1.23
|
||
cd /tmp
|
||
wget https://golang.google.cn/dl/go1.23.4.linux-amd64.tar.gz
|
||
sudo rm -rf /usr/local/go
|
||
sudo tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz
|
||
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
|
||
source ~/.bashrc
|
||
```
|
||
|
||
### 2. 拉取 Docker 镜像
|
||
|
||
```bash
|
||
sudo docker pull mysql:8
|
||
sudo docker pull redis:latest
|
||
sudo docker pull zlmediakit/zlmediakit:master
|
||
```
|
||
|
||
### 3. 启动 Docker 容器
|
||
|
||
```bash
|
||
# 创建网络
|
||
sudo docker network create media-net
|
||
|
||
# MySQL(WVP 数据库)
|
||
sudo docker run -d --name polaris-mysql --network media-net --restart unless-stopped \
|
||
-e MYSQL_DATABASE=wvp -e MYSQL_ROOT_PASSWORD=root \
|
||
-e MYSQL_USER=wvp_user -e MYSQL_PASSWORD=wvp_password -e TZ=Asia/Shanghai \
|
||
-v /home/pan/silk/wvp/volumes/mysql/data:/var/lib/mysql \
|
||
-p 3306:3306 \
|
||
mysql:8 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --default-time-zone=+8:00 --lower-case-table-names=1
|
||
|
||
# Redis(WVP 缓存,Docker 内部网络)
|
||
sudo docker run -d --name polaris-redis --network media-net --restart unless-stopped \
|
||
-e TZ=Asia/Shanghai \
|
||
redis:latest redis-server --appendonly yes --requirepass root
|
||
|
||
# ZLMediaKit
|
||
mkdir -p /home/pan/silk/wvp/media /home/pan/silk/wvp/volumes/video /home/pan/silk/wvp/logs/media
|
||
cat > /home/pan/silk/wvp/media/config.ini << 'EOF'
|
||
[api]
|
||
apiDebug=1
|
||
secret=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
|
||
[http]
|
||
port=80
|
||
charSet=utf-8
|
||
[rtsp]
|
||
port=5540
|
||
[rtmp]
|
||
port=10935
|
||
[rtp_proxy]
|
||
port=10000
|
||
[record]
|
||
appName=record
|
||
sampleMS=100
|
||
[hls]
|
||
segDur=2
|
||
segNum=3
|
||
[hook]
|
||
enable=0
|
||
EOF
|
||
|
||
sudo docker run -d --name polaris-media --network media-net --restart always \
|
||
-e TZ=Asia/Shanghai \
|
||
-p 8081:80 -p 5540:5540 -p 10935:10935 -p 10000:10000 -p 10000:10000/udp \
|
||
-v /home/pan/silk/wvp/volumes/video:/opt/media/bin/www/record/ \
|
||
-v /home/pan/silk/wvp/logs/media:/opt/media/log/ \
|
||
-v /home/pan/silk/wvp/media/config.ini:/conf/config.ini \
|
||
zlmediakit/zlmediakit:master MediaServer -c /conf/config.ini -l 0
|
||
|
||
# Redis(Go 后端缓存,端口 6379,密码 pan)
|
||
sudo docker run -d --name silk-redis --restart always \
|
||
-p 6379:6379 \
|
||
redis:latest redis-server --requirepass pan --appendonly yes
|
||
```
|
||
|
||
### 4. 配置 PostgreSQL
|
||
|
||
```bash
|
||
sudo service postgresql start
|
||
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'pan';"
|
||
sudo -u postgres psql -c "CREATE DATABASE silk;"
|
||
```
|
||
|
||
### 5. 安装 Ceph
|
||
参考[Ceph 安装文档](https://docs.ceph.net.cn/en/latest/install/get-packages/)
|
||
#### 添加APT密钥
|
||
要安装 release.asc 密钥,请执行以下操作
|
||
```bash
|
||
wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo tee /etc/apt/trusted.gpg.d/ceph.asc
|
||
|
||
```
|
||
#### 添加软件源
|
||
将 Ceph 软件包仓库添加到系统的 APT 源列表中。对于较新版本的 Debian/Ubuntu,在命令行上调用 lsb_release -sc 以获取短代号,并替换以下命令中的 {codename}
|
||
```bash
|
||
sudo apt-add-repository 'deb https://download.ceph.com/debian-tentacle/ {codename} main'
|
||
|
||
```
|
||
#### 安装
|
||
```bash
|
||
# 安装 Ceph(Ubuntu 默认源,Quincy 17.2)
|
||
sudo apt install -y ceph ceph-common radosgw ceph-volume
|
||
|
||
# 生成配置
|
||
FSID=$(uuidgen)
|
||
sudo bash -c "cat > /etc/ceph/ceph.conf << EOF
|
||
[global]
|
||
fsid = $FSID
|
||
mon initial members = a
|
||
mon host = 127.0.0.1:6789
|
||
osd pool default size = 1
|
||
osd pool default min size = 1
|
||
osd crush chooseleaf type = 0
|
||
mon allow insecure global id reclaim = true
|
||
auth_cluster_required = none
|
||
auth_service_required = none
|
||
auth_client_required = none
|
||
|
||
[mon.a]
|
||
host = localhost
|
||
mon addr = 127.0.0.1:6789
|
||
|
||
[mgr.a]
|
||
host = localhost
|
||
|
||
[client.rgw.a]
|
||
host = localhost
|
||
rgw frontends = beast port=7480
|
||
EOF"
|
||
|
||
# 创建 Monitor
|
||
sudo ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon "allow *"
|
||
sudo ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon "allow *" --cap osd "allow *" --cap mds "allow *" --cap mgr "allow *"
|
||
sudo ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring
|
||
sudo monmaptool --create --add a 127.0.0.1:6789 --fsid $FSID /tmp/monmap
|
||
sudo mkdir -p /var/lib/ceph/mon/ceph-a
|
||
sudo chown -R ceph:ceph /var/lib/ceph/mon/ceph-a
|
||
sudo ceph-mon --mkfs -i a --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring
|
||
sudo ceph-mon -i a
|
||
sleep 10
|
||
|
||
# 创建 Manager
|
||
sudo mkdir -p /var/lib/ceph/mgr/ceph-a
|
||
sudo chown -R ceph:ceph /var/lib/ceph/mgr/ceph-a
|
||
sudo bash -c 'ceph auth get-or-create mgr.a mon "allow profile mgr" osd "allow *" mds "allow *" > /var/lib/ceph/mgr/ceph-a/keyring'
|
||
sudo ceph-mgr -i a
|
||
sleep 3
|
||
|
||
# 创建 OSD(loop device 方式)
|
||
for i in 0 1; do
|
||
sudo fallocate -l 50G /var/lib/ceph/osd-data-$i
|
||
LOOP_DEV=$(sudo losetup -f --show /var/lib/ceph/osd-data-$i)
|
||
sudo pvcreate -f $LOOPfd _DEV
|
||
sudo vgcreate ceph-osd-vg-$i $LOOP_DEV
|
||
sudo lvcreate -l 100%FREE -n osd-$i ceph-osd-vg-$i
|
||
sudo ceph-volume lvm prepare --bluestore --data ceph-osd-vg-$i/osd-$i
|
||
done
|
||
sudo ceph-volume lvm activate --all
|
||
sleep 15
|
||
|
||
# 启动 RGW
|
||
sudo mkdir -p /var/lib/ceph/radosgw/ceph-rgw.a
|
||
sudo chown -R ceph:ceph /var/lib/ceph/radosgw/ceph-rgw.a
|
||
sudo bash -c 'ceph auth get-or-create client.rgw.a osd "allow rwx" mon "allow rw" -o /var/lib/ceph/radosgw/ceph-rgw.a/keyring'
|
||
sudo bash -c 'nohup radosgw -n client.rgw.a -k /var/lib/ceph/radosgw/ceph-rgw.a/keyring --rgw-frontends "beast port=7480" > /var/log/ceph/radosgw.log 2>&1 &'
|
||
sleep 5
|
||
|
||
# 创建 S3 用户
|
||
sudo radosgw-admin user create --uid=silk-app --display-name="Silk App" \
|
||
--access-key=silk-app --secret-key=Silk-App-Secret-2026!
|
||
|
||
# 验证
|
||
sudo ceph -s
|
||
curl -s -o /dev/null -w "%{http_code}" http://localhost:7480/ # 应返回 200
|
||
```
|
||
|
||
> **Ceph 关键点**:
|
||
> - BlueStore 需要块设备,不能直接用目录
|
||
> - 用 `fallocate` + `losetup` 创建 loop device,再用 `ceph-volume lvm prepare` 创建 OSD
|
||
> - `auth_*_required = none` 简化认证(单节点测试环境)
|
||
> - `osd pool default size = 1` 适配单节点(默认 3 副本会导致 PG 一直 inactive)
|
||
|
||
### 6. 安装 VerneMQ
|
||
|
||
```bash
|
||
# 上传 deb 包
|
||
# pscp -pw pan e:\silk\tools\vernemq-1.13.0.jammy.x86_64.deb pan@100.83.103.1:/home/pan/
|
||
sudo dpkg -i /home/pan/vernemq-1.13.0.jammy.x86_64.deb
|
||
|
||
# 接受 EULA
|
||
sudo sed -i 's/^accept_eula.*/accept_eula = yes/' /etc/vernemq/vernemq.conf
|
||
sudo bash -c 'echo "accept_eula = yes" >> /etc/vernemq/vernemq.conf'
|
||
|
||
# 启动
|
||
sudo vernemq start
|
||
```
|
||
|
||
### 7. 安装 IoTDB
|
||
|
||
```bash
|
||
# 上传 IoTDB(含已有数据)
|
||
# pscp -r -pw pan e:\silk\tools\iotdb\apache-iotdb-2.0.8-all-bin pan@100.83.103.1:/home/pan/iotdb/
|
||
|
||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||
|
||
# 清理旧锁文件(从 Windows 上传的文件可能需要)
|
||
find /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/data -name "*.lock" -delete
|
||
|
||
# 设置 REST 端口为 18081(避免与 WVP 的 18080 冲突)
|
||
IOTDB_CONF=/home/pan/iotdb/apache-iotdb-2.0.8-all-bin/conf/iotdb-system.properties
|
||
grep -q "rest_service_port" $IOTDB_CONF && sed -i 's/^rest_service_port.*/rest_service_port=18081/' $IOTDB_CONF || echo "rest_service_port=18081" >> $IOTDB_CONF
|
||
|
||
# 启动
|
||
nohup /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/sbin/start-standalone.sh > /home/pan/iotdb.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
> **Valkey 不可用**:`valkey-8.1.2-noble-x86_64.tar.gz` 编译于 Ubuntu 24.04(GLIBC 2.38),Ubuntu 22.04 只有 GLIBC 2.35。用 Docker Redis 替代。
|
||
|
||
### 8. 构建 WVP Docker 镜像
|
||
|
||
```bash
|
||
# 克隆 WVP 源码
|
||
cd /home/pan/silk/wvp
|
||
git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git wvp-src
|
||
cd wvp-src
|
||
|
||
# 使用自定义 Dockerfile(带 Maven 阿里云镜像加速)
|
||
# 从 Windows 上传: pscp -pw pan e:\silk\wvp\Dockerfile pan@100.83.103.1:/home/pan/silk/wvp/wvp-src/Dockerfile
|
||
sudo docker build -t docker-polaris-wvp:latest .
|
||
|
||
# 导入 WVP 数据库初始化 SQL
|
||
sudo docker exec -i polaris-mysql mysql -uroot -proot wvp < "数据库/2.7.4/初始化-mysql-2.7.4.sql"
|
||
|
||
# 授予 MySQL 权限
|
||
sudo docker exec polaris-mysql mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'wvp_user'@'%'; FLUSH PRIVILEGES;"
|
||
```
|
||
|
||
### 9. 配置并启动 WVP
|
||
|
||
```bash
|
||
# 创建 WVP 配置
|
||
mkdir -p /home/pan/silk/wvp/wvp/wvp /home/pan/silk/wvp/logs/wvp
|
||
|
||
cat > /home/pan/silk/wvp/wvp/wvp/application.yml << 'EOF'
|
||
spring:
|
||
application:
|
||
name: wvp
|
||
servlet:
|
||
multipart:
|
||
max-file-size: 10GB
|
||
max-request-size: 10GB
|
||
cache:
|
||
type: redis
|
||
data:
|
||
redis:
|
||
host: polaris-redis
|
||
port: 6379
|
||
password: root
|
||
timeout: 10000
|
||
database: 6
|
||
datasource:
|
||
type: com.zaxxer.hikari.HikariDataSource
|
||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||
url: jdbc:mysql://polaris-mysql:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
|
||
username: wvp_user
|
||
password: wvp_password
|
||
hikari:
|
||
connection-timeout: 20000
|
||
maximum-pool-size: 30
|
||
minimum-idle: 5
|
||
|
||
server:
|
||
port: 18080
|
||
|
||
sip:
|
||
ip: 0.0.0.0
|
||
port: 8116
|
||
domain: 3402000000
|
||
id: 34020000002000000001
|
||
password: 12345678
|
||
|
||
media:
|
||
id:
|
||
ip: polaris-media
|
||
http-port: 80
|
||
secret: su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
|
||
rtp:
|
||
enable: true
|
||
port-range: 30000,30500
|
||
send-port-range: 30000,30500
|
||
record-assist-port: 18081
|
||
sdp-ip: 100.83.103.1
|
||
stream-ip: 100.83.103.1
|
||
|
||
user-settings:
|
||
record-push-live: true
|
||
auto-apply-play: true
|
||
push-authority: true
|
||
use-push-url: false
|
||
use-wvp-host: true
|
||
enable-fmp4-fallback: true
|
||
record-sip: true
|
||
enable-audio: true
|
||
|
||
logging:
|
||
level:
|
||
root: INFO
|
||
EOF
|
||
|
||
# 启动 WVP 容器
|
||
sudo docker run -d --name polaris-wvp --network media-net --restart always \
|
||
-p 18080:18080 -p 8116:8116/tcp -p 8116:8116/udp \
|
||
-v /home/pan/silk/wvp/wvp/wvp:/opt/ylcx/wvp/ \
|
||
-v /home/pan/silk/wvp/logs/wvp:/opt/wvp/logs/ \
|
||
-e TZ=Asia/Shanghai \
|
||
-e Stream_IP=100.83.103.1 \
|
||
-e SDP_IP=100.83.103.1 \
|
||
-e ZLM_HOST=polaris-media \
|
||
-e ZLM_SERCERT=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf \
|
||
-e REDIS_HOST=polaris-redis \
|
||
-e DATABASE_HOST=polaris-mysql \
|
||
-e SIP_ShowIP=100.83.103.1 \
|
||
docker-polaris-wvp:latest
|
||
```
|
||
|
||
> **WVP 端口变更**:WVP 2.7.4 的 `server.port` 是 18080(不是之前的 18978)。Go 后端 `.env` 中 `WVP_API_BASE` 需改为 `http://localhost:18080`。
|
||
|
||
### 10. 部署 Go 后端
|
||
|
||
```bash
|
||
# 在 Windows 上交叉编译
|
||
# cd e:\silk\server-go
|
||
# $env:GOOS="linux"; $env:GOARCH="amd64"; go build -o silk-server-go-linux ./cmd/server/
|
||
# pscp -pw pan silk-server-go-linux pan@100.83.103.1:/home/pan/
|
||
|
||
chmod +x /home/pan/silk-server-go-linux
|
||
|
||
# 创建 .env
|
||
cat > /home/pan/silk/.env << 'EOF'
|
||
PORT=3000
|
||
PG=postgresql://postgres:pan@localhost:5432/silk
|
||
REDIS=redis://:pan@localhost:6379
|
||
MQTT=mqtt://pan:pan@localhost:1883
|
||
IOTDB_URL=http://127.0.0.1:18081
|
||
S3_ENDPOINT=http://100.83.103.1:7480
|
||
S3_ACCESS_KEY=silk-app
|
||
S3_SECRET_KEY=Silk-App-Secret-2026!
|
||
S3_BUCKET_EVENTS=silk-video-events
|
||
S3_BUCKET_ARCHIVE=silk-video-archive
|
||
WVP_API_BASE=http://localhost:18080
|
||
WVP_USERNAME=admin
|
||
WVP_PASSWORD=admin
|
||
ZLM_API_BASE=http://localhost:8081
|
||
ZLM_SECRET=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
|
||
INTERNAL_API_KEY=silk-internal-2026
|
||
RECORDER_API_BASE=http://localhost:9090
|
||
EOF
|
||
|
||
# 启动
|
||
cd /home/pan/silk
|
||
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
### 11. 部署 Go 录制服务
|
||
|
||
```bash
|
||
# 在 Windows 上交叉编译
|
||
# cd e:\silk\wvp\recorder-go
|
||
# $env:GOOS="linux"; $env:GOARCH="amd64"; go build -o recorder-go-linux .
|
||
# pscp -pw pan recorder-go-linux pan@100.83.103.1:/home/pan/
|
||
|
||
chmod +x /home/pan/recorder-go-linux
|
||
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
### 12. 部署前端
|
||
|
||
```bash
|
||
# 在 Windows 上构建
|
||
# cd e:\silk\web; npm run build
|
||
# tar -czf dist.tar.gz dist
|
||
# pscp -pw pan dist.tar.gz pan@100.83.103.1:/home/pan/
|
||
# pscp -pw pan e:\silk\wvp\recorder-go\server.js pan@100.83.103.1:/home/pan/silk/web/server.cjs
|
||
|
||
mkdir -p /home/pan/silk/web
|
||
cd /home/pan/silk/web
|
||
tar xzf /home/pan/dist.tar.gz
|
||
|
||
# 启动
|
||
nohup node /home/pan/silk/web/server.cjs > /home/pan/silk/web/web.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
> **server.cjs 命名**:`package.json` 中 `"type": "module"` 会导致 `.js` 文件被当作 ES Module 解析。`server.js` 使用 `require()`(CommonJS),需重命名为 `server.cjs`。
|
||
|
||
### 13. 部署 ai-service(2026-08 新增)
|
||
|
||
```bash
|
||
# 上传源码 tar 包(本机 scripts/devssh.py --upload ai-service.tar.gz /home/pan/)
|
||
mkdir -p /home/pan/ai-service
|
||
tar xzf /home/pan/ai-service.tar.gz -C /home/pan
|
||
cd /home/pan/ai-service
|
||
|
||
# 前置:python3-venv(如缺失)
|
||
sudo apt install -y python3.10-venv
|
||
|
||
python3 -m venv .venv
|
||
.venv/bin/pip install -r requirements.txt # 服务器 pip 已配置清华镜像
|
||
|
||
cat > start.sh << 'EOF'
|
||
#!/bin/bash
|
||
cd /home/pan/ai-service
|
||
export MODEL_MODE=mock
|
||
export MOCK_CLASS=healthy
|
||
nohup .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 > ai-service.log 2>&1 < /dev/null &
|
||
echo STARTED
|
||
EOF
|
||
chmod +x start.sh && bash start.sh
|
||
sleep 4
|
||
|
||
curl -s http://localhost:8000/health # {"status":"ok","model":"mock"}
|
||
curl -s http://localhost:8000/metrics # 含请求量/延迟/GPU(nvidia-smi)
|
||
```
|
||
|
||
> 训练恢复后:放置 `models/best.onnx` 并把 `MODEL_MODE` 改为 `onnx` 重启即可(接口不变)。Go 后端通过 `AI_SERVICE_BASE`(默认 `http://localhost:8000`)对接。
|
||
|
||
## 四、验证
|
||
|
||
```bash
|
||
# 检查所有端口
|
||
ss -tlnp | grep -E '22|3000|5174|6379|1883|7480|8081|9090|18080|18081|8116'
|
||
|
||
# Go 后端
|
||
curl -s http://localhost:3000/api/v1/health
|
||
# 应返回 {"status":"ok"}
|
||
|
||
# 录制服务
|
||
curl -s http://localhost:9090/health
|
||
# 应返回 {"status":"ok"}
|
||
|
||
# 前端
|
||
curl -s -o /dev/null -w '%{http_code}' http://localhost:5174/
|
||
# 应返回 200
|
||
|
||
# ZLMediaKit
|
||
curl -s -o /dev/null -w '%{http_code}' http://localhost:8081/
|
||
# 应返回 200
|
||
|
||
# WVP
|
||
curl -s -o /dev/null -w '%{http_code}' http://localhost:18080/
|
||
# 应返回 404(正常,API 在具体路径下)
|
||
|
||
# Ceph S3
|
||
curl -s -o /dev/null -w '%{http_code}' http://localhost:7480/
|
||
# 应返回 200
|
||
|
||
# Docker 容器
|
||
sudo docker ps --format "{{.Names}}: {{.Status}}"
|
||
# 应显示 5 个容器全部 Up
|
||
|
||
# Ceph 集群
|
||
sudo ceph -s
|
||
# 应显示 2 osds: 2 up, 2 in
|
||
```
|
||
|
||
## 五、关键配置文件
|
||
|
||
### Go 后端 .env (`/home/pan/silk/.env`)
|
||
|
||
```
|
||
PORT=3000
|
||
PG=postgresql://postgres:pan@localhost:5432/silk
|
||
REDIS=redis://:pan@localhost:6379
|
||
MQTT=mqtt://pan:pan@localhost:1883
|
||
IOTDB_URL=http://127.0.0.1:18081
|
||
S3_ENDPOINT=http://100.83.103.1:7480
|
||
S3_ACCESS_KEY=silk-app
|
||
S3_SECRET_KEY=Silk-App-Secret-2026!
|
||
WVP_API_BASE=http://localhost:18080
|
||
ZLM_API_BASE=http://localhost:8081
|
||
ZLM_SECRET=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf
|
||
RECORDER_API_BASE=http://localhost:9090
|
||
```
|
||
|
||
### WVP application.yml (`/home/pan/silk/wvp/wvp/wvp/application.yml`)
|
||
|
||
关键配置:
|
||
- `sip.port`: 8116
|
||
- `sip.domain`: 3402000000
|
||
- `sip.id`: 34020000002000000001
|
||
- `media.ip`: polaris-media(Docker 内部网络)
|
||
- `media.sdp-ip` / `media.stream-ip`: 100.83.103.1(VPN IP,摄像头需要访问)
|
||
- `server.port`: 18080
|
||
|
||
### Ceph (`/etc/ceph/ceph.conf`)
|
||
|
||
- `auth_*_required = none`(单节点简化认证)
|
||
- `osd pool default size = 1`(单副本)
|
||
|
||
## 六、与 WSL2 部署的差异
|
||
|
||
| 项目 | WSL2 | 物理机 |
|
||
|------|------|--------|
|
||
| 文件系统 | ext4 on VHDX(可能变只读) | ext4 on /dev/sda6(稳定) |
|
||
| 网络 | 需要 portproxy 转发 | 直连,无需 portproxy |
|
||
| IP | 172.18.127.247(可能变化) | 100.83.103.1(WireGuard 固定) |
|
||
| WVP 端口 | 18978 | 18080 |
|
||
| 缓存 | Valkey(裸机安装) | Docker Redis(Valkey GLIBC 不兼容) |
|
||
| Ceph | 已配置(可直接启动) | 需从头配置(loop device OSD) |
|
||
| WVP 镜像 | 已构建 | 需从 Gitee 克隆源码构建 |
|
||
|
||
## 七、重启后恢复
|
||
|
||
物理机重启后,systemd 管理的服务(Docker、PostgreSQL、VerneMQ)会自动启动。需要手动启动的服务:
|
||
|
||
```bash
|
||
# 1. Ceph(systemd 管理,但 loop device 需要手动激活)
|
||
sudo ceph-mon -i a
|
||
sudo ceph-mgr -i a
|
||
# loop device 需要重新挂载
|
||
for i in 0 1; do
|
||
sudo losetup /dev/loop$((i+8)) /var/lib/ceph/osd-data-$i
|
||
sudo ceph-volume lvm activate --all
|
||
done
|
||
# RGW
|
||
sudo bash -c 'nohup radosgw -n client.rgw.a -k /var/lib/ceph/radosgw/ceph-rgw.a/keyring --rgw-frontends "beast port=7480" > /var/log/ceph/radosgw.log 2>&1 &'
|
||
|
||
# 2. IoTDB
|
||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||
nohup /home/pan/iotdb/apache-iotdb-2.0.8-all-bin/sbin/start-standalone.sh > /home/pan/iotdb.log 2>&1 < /dev/null &
|
||
|
||
# 3. Go 后端
|
||
cd /home/pan/silk
|
||
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &
|
||
|
||
# 4. Go 录制服务
|
||
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &
|
||
|
||
# 5. 前端
|
||
nohup node /home/pan/silk/web/server.cjs > /home/pan/silk/web/web.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
## 摄像头注册信息
|
||
|
||
摄像头 GB28181 注册参数:
|
||
|
||
| 参数 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| SIP 服务器 IP | `100.83.103.1` | WireGuard VPN IP |
|
||
| SIP 服务器端口 | `8116` | TCP/UDP,Docker 映射 `-p 8116:8116/tcp -p 8116:8116/udp` |
|
||
| SIP 域 | `3402000000` | |
|
||
| SIP 服务器 ID | `34020000002000000001` | |
|
||
| SIP 密码 | `12345678` | |
|
||
|
||
> 摄像头需能访问 `100.83.103.1`(WireGuard VPN)。若摄像头不在 VPN 网络,改用物理网卡 IP `192.168.1.4`,同时修改 WVP 配置中的 `sip.ip`、`media.sdp-ip`、`media.stream-ip`。
|
||
|
||
## 八、常见问题
|
||
|
||
### 1. Valkey GLIBC 不兼容
|
||
**现象**:`valkey-cli: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.38' not found`
|
||
**原因**:`valkey-8.1.2-noble-x86_64.tar.gz` 编译于 Ubuntu 24.04(Noble),需要 GLIBC 2.38,Ubuntu 22.04 只有 2.35。
|
||
**解决**:用 Docker Redis 替代(`docker run -d --name silk-redis -p 6379:6379 redis:latest redis-server --requirepass pan`)。
|
||
|
||
### 2. Ceph OSD 创建失败
|
||
**现象**:`bluestore _read_bdev_label failed to open /var/lib/ceph/osd/ceph-0/block: (2) No such file or directory`
|
||
**原因**:BlueStore 需要块设备,不能直接用目录。
|
||
**解决**:用 `fallocate` 创建文件 + `losetup` 创建 loop device + `ceph-volume lvm prepare --bluestore --data <vg/lv>`。
|
||
|
||
### 3. WVP 启动报 `Could not resolve placeholder 'media.id'`
|
||
**原因**:`application.yml` 中 `media` 和 `sip` 应在顶层,不是 `user-settings` 下。
|
||
**解决**:参考步骤 9 的 `application.yml` 模板。
|
||
|
||
### 4. WVP 报 `Failed to obtain JDBC Connection`
|
||
**原因**:MySQL 未导入 WVP 初始化 SQL,或 `wvp_user` 权限不足。
|
||
**解决**:
|
||
```bash
|
||
docker exec -i polaris-mysql mysql -uroot -proot wvp < "数据库/2.7.4/初始化-mysql-2.7.4.sql"
|
||
docker exec polaris-mysql mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'wvp_user'@'%'; FLUSH PRIVILEGES;"
|
||
```
|
||
|
||
### 5. server.js 报 `Cannot use import statement outside a module`
|
||
**原因**:`package.json` 中 `"type": "module"` 导致 `.js` 文件按 ES Module 解析,但 `server.js` 使用 CommonJS (`require()`)。
|
||
**解决**:将 `server.js` 重命名为 `server.cjs`。
|
||
|
||
### 6. Docker 镜像拉取失败
|
||
**现象**:`could not fetch content descriptor ... not found`
|
||
**解决**:配置 Docker 镜像加速(`/etc/docker/daemon.json` 中设置 `registry-mirrors`)。
|
||
|
||
### 7. Ceph OSD 重启后不自动激活
|
||
**原因**:loop device 重启后需要重新挂载。
|
||
**解决**:参考"七、重启后恢复"中的 Ceph 部分。
|
||
|
||
## 九、前端测试
|
||
|
||
- 地址:`http://100.83.103.1:5174/`
|
||
- 用户名:`admin`
|
||
- 密码:`admin123`
|
||
|
||
- WVP 管理: http://100.83.103.1:18080/
|
||
|
||
## 十、实时视频"等待视频流"问题排查与修复
|
||
|
||
### 现象
|
||
|
||
前端 /videos 页面点击查看实时视频,一直显示"等待视频流...",控制台报 `POST /api/v1/video/cameras/1/live 503`。点击"开始录制"同样报 503。
|
||
|
||
### 根因(共 4 个问题,需全部修复)
|
||
|
||
| # | 问题 | 原因 |
|
||
|---|------|------|
|
||
| 1 | Go 后端调用 WVP 端口错误 | `config.go` 中 `envDefault:"http://localhost:18978"`,Go 后端用 `caarlos0/env` 读 **OS 环境变量**而非 `.env` 文件,`.env` 未被加载 |
|
||
| 2 | ZLMediaKit hooks 指向 `127.0.0.1:18080` | WVP 每次启动时会重置 ZLMediaKit hooks,用 `127.0.0.1`(容器自身)而非 `polaris-wvp`(Docker 网络主机名) |
|
||
| 3 | ZLMediaKit `mediaServerId` 为空 | WVP 配置 `media.id:` 为空,推流时 `on_publish` hook 报"找不到对应的mediaServer" |
|
||
| 4 | 流 URL 含 Docker 内部主机名 | WVP 返回的播放地址用 `polaris-media:80`,`fixZlmPort` 只替换端口不替换主机名,浏览器无法解析 `polaris-media` |
|
||
|
||
### 修复方法
|
||
|
||
#### 1. Go 后端加载 .env 环境变量
|
||
|
||
Go 后端用 `caarlos0/env` 库读取 OS 环境变量,不自动读 `.env` 文件。启动时必须先 `source .env`:
|
||
|
||
```bash
|
||
cd /home/pan/silk
|
||
set -a
|
||
source .env
|
||
set +a
|
||
nohup /home/pan/silk-server-go-linux > server-go.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
确保 `.env` 中 `WVP_API_BASE=http://localhost:18080`(不是 18978)。
|
||
|
||
#### 2. 修复 ZLMediaKit hooks(每次 WVP 重启后需重新执行)
|
||
|
||
WVP 启动时会将 ZLMediaKit hooks 重置为 `127.0.0.1:18080`,需要手动改为 `polaris-wvp:18080`:
|
||
|
||
```bash
|
||
SECRET="su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf"
|
||
HOOK_BASE="http://polaris-wvp:18080"
|
||
curl -s "http://localhost:8081/index/api/setServerConfig?secret=$SECRET" \
|
||
--data-urlencode "hook.on_play=$HOOK_BASE/index/hook/on_play" \
|
||
--data-urlencode "hook.on_publish=$HOOK_BASE/index/hook/on_publish" \
|
||
--data-urlencode "hook.on_record_mp4=$HOOK_BASE/index/hook/on_record_mp4" \
|
||
--data-urlencode "hook.on_rtp_server_timeout=$HOOK_BASE/index/hook/on_rtp_server_timeout" \
|
||
--data-urlencode "hook.on_send_rtp_stopped=$HOOK_BASE/index/hook/on_send_rtp_stopped" \
|
||
--data-urlencode "hook.on_server_keepalive=$HOOK_BASE/index/hook/on_server_keepalive" \
|
||
--data-urlencode "hook.on_server_started=$HOOK_BASE/index/hook/on_server_started" \
|
||
--data-urlencode "hook.on_stream_changed=$HOOK_BASE/index/hook/on_stream_changed" \
|
||
--data-urlencode "hook.on_stream_none_reader=$HOOK_BASE/index/hook/on_stream_none_reader" \
|
||
--data-urlencode "hook.on_stream_not_found=$HOOK_BASE/index/hook/on_stream_not_found"
|
||
```
|
||
|
||
> **注意**:每次 WVP 容器重启后都需重新执行此命令。可将此命令加入启动脚本。
|
||
|
||
#### 3. 设置固定的 mediaServerId
|
||
|
||
在 WVP `application.yml` 中设置 `media.id`:
|
||
|
||
```yaml
|
||
media:
|
||
id: silk-media-001 # 不能为空
|
||
```
|
||
|
||
同时通过 ZLMediaKit API 设置:
|
||
|
||
```bash
|
||
curl -s "http://localhost:8081/index/api/setServerConfig?secret=su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf" \
|
||
--data-urlencode "general.mediaServerId=silk-media-001"
|
||
```
|
||
|
||
#### 4. 修复流 URL 中的 Docker 内部主机名
|
||
|
||
修改 `server-go/internal/service/media.go` 中的 `fixZlmPort` 函数,增加主机名替换:
|
||
|
||
```go
|
||
func fixZlmPort(u string) string {
|
||
if u == "" {
|
||
return u
|
||
}
|
||
// 替换 Docker 内部主机名为外部可访问 IP
|
||
u = strings.Replace(u, "polaris-media:", "100.83.103.1:", 1)
|
||
u = strings.Replace(u, ":80/", ":8081/", 1)
|
||
if strings.HasSuffix(u, ":80") {
|
||
u = u[:len(u)-2] + ":8081"
|
||
}
|
||
return u
|
||
}
|
||
```
|
||
|
||
重新交叉编译并部署:
|
||
|
||
```powershell
|
||
cd e:\silk\server-go
|
||
$env:GOOS="linux"; $env:GOARCH="amd64"
|
||
go build -o silk-server-go-linux ./cmd/server/
|
||
$env:GOOS=""; $env:GOARCH=""
|
||
pscp -pw pan silk-server-go-linux pan@100.83.103.1:/home/pan/silk-server-go-linux
|
||
```
|
||
|
||
### 验证
|
||
|
||
修复后 WVP 日志应显示:
|
||
|
||
```
|
||
[ZLM-连接成功] ID:silk-media-001, 地址: polaris-media:80
|
||
[ZLM HOOK]推流鉴权-允许-响应:silk-media-001
|
||
[ZLM HOOK] 流注册, silk-media-001->rtsp->rtp/...
|
||
[点播成功] 设备编号: 34020000002000000001
|
||
```
|
||
|
||
Go 后端日志应显示:
|
||
|
||
```
|
||
INFO 调用 WVP play/start url=http://localhost:18080/api/play/start/...
|
||
INFO 请求 method=POST path=/api/v1/video/cameras/1/live status=200 latency=1.7s
|
||
```
|
||
|
||
前端播放器收到的流地址格式:
|
||
|
||
```
|
||
http://100.83.103.1:8081/rtp/34020000002000000001_34020000001310000001.live.flv
|
||
```
|
||
|
||
### WVP 重启后的完整修复脚本
|
||
|
||
将以下脚本保存为 `/home/pan/fix-hooks.sh`,每次 WVP 重启后执行:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
SECRET="su6TiedN2rVAmBbIDX0aa0QTiBJLBdcf"
|
||
HOOK_BASE="http://polaris-wvp:18080"
|
||
curl -s "http://localhost:8081/index/api/setServerConfig?secret=$SECRET" \
|
||
--data-urlencode "hook.on_play=$HOOK_BASE/index/hook/on_play" \
|
||
--data-urlencode "hook.on_publish=$HOOK_BASE/index/hook/on_publish" \
|
||
--data-urlencode "hook.on_record_mp4=$HOOK_BASE/index/hook/on_record_mp4" \
|
||
--data-urlencode "hook.on_rtp_server_timeout=$HOOK_BASE/index/hook/on_rtp_server_timeout" \
|
||
--data-urlencode "hook.on_send_rtp_stopped=$HOOK_BASE/index/hook/on_send_rtp_stopped" \
|
||
--data-urlencode "hook.on_server_keepalive=$HOOK_BASE/index/hook/on_server_keepalive" \
|
||
--data-urlencode "hook.on_server_started=$HOOK_BASE/index/hook/on_server_started" \
|
||
--data-urlencode "hook.on_stream_changed=$HOOK_BASE/index/hook/on_stream_changed" \
|
||
--data-urlencode "hook.on_stream_none_reader=$HOOK_BASE/index/hook/on_stream_none_reader" \
|
||
--data-urlencode "hook.on_stream_not_found=$HOOK_BASE/index/hook/on_stream_not_found"
|
||
echo "Hooks fixed."
|
||
```
|
||
|
||
## 十一、录像片段查不到问题
|
||
|
||
### 现象
|
||
|
||
前端 /videos "历史回看"点击"查询录像"看不到任何录像片段。点击"开始录制"后录制服务立即退出,录制 0 段。
|
||
|
||
### 原因
|
||
|
||
Ceph RGW 只创建了 S3 用户(`silk-app`),但**没有创建 S3 bucket**。录制服务上传视频片段时 S3 返回 `404 NoSuchBucket`,录制线程立即退出。
|
||
|
||
录制服务日志:
|
||
|
||
```
|
||
CreateMultipartUpload 失败: operation error S3: CreateMultipartUpload, https response error StatusCode: 404, api error NoSuchBucket: UnknownError
|
||
录制线程退出(共完成 0 段)
|
||
```
|
||
|
||
### 修复方法
|
||
|
||
使用 Python S3 API 创建 bucket(`radosgw-admin` 没有 `bucket create` 子命令):
|
||
|
||
```bash
|
||
# 上传 create-buckets.py 到服务器后执行
|
||
python3 /home/pan/create-buckets.py
|
||
```
|
||
|
||
`create-buckets.py` 内容:
|
||
|
||
```python
|
||
#!/usr/bin/env python3
|
||
import http.client, hashlib, hmac, datetime, base64
|
||
|
||
host = "localhost:7480"
|
||
access_key = "silk-app"
|
||
secret_key = "Silk-App-Secret-2026!"
|
||
buckets = ["silk-video-archive", "silk-video-events"]
|
||
|
||
for bucket in buckets:
|
||
conn = http.client.HTTPConnection(host)
|
||
date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||
string_to_sign = f"PUT\n\n\n{date}\n/{bucket}"
|
||
signature = base64.b64encode(
|
||
hmac.new(secret_key.encode(), string_to_sign.encode(), hashlib.sha1).digest()
|
||
).decode()
|
||
conn.request("PUT", f"/{bucket}", headers={
|
||
"Date": date,
|
||
"Authorization": f"AWS {access_key}:{signature}"
|
||
})
|
||
resp = conn.getresponse()
|
||
print(f"Bucket {bucket}: {resp.status} {resp.reason}")
|
||
conn.close()
|
||
```
|
||
|
||
创建后重启录制服务:
|
||
|
||
```bash
|
||
sudo fuser -k 9090/tcp
|
||
sleep 2
|
||
nohup /home/pan/recorder-go-linux > /home/pan/recorder.log 2>&1 < /dev/null &
|
||
```
|
||
|
||
### 验证
|
||
|
||
```bash
|
||
# 检查 bucket 列表
|
||
python3 /home/pan/create-buckets.py # 会列出所有 bucket
|
||
|
||
# 录制服务日志应显示正常录制
|
||
tail -5 /home/pan/recorder.log
|
||
# 不应有 NoSuchBucket 错误
|
||
```
|
||
|
||
> **注意**:Ceph RGW 的 `radosgw-admin` 不支持 `bucket create` 命令,bucket 必须通过 S3 API(PUT /bucket-name)创建。
|