# 部署指南(物理机) ## 一、环境信息 | 项目 | 值 | |------|-----| | 机器 | 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 APP_ENV=production ALLOW_DEV_AUTOMIGRATE=false 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 & ``` > **数据库迁移**:Go 后端启动时会自动执行 `server-go/migrations/` 下的 SQL 迁移并校验 `schema_migrations` 版本;生产环境不要开启 `ALLOW_DEV_AUTOMIGRATE=true`。升级前先按 AGENTS.md 完成数据库备份。 ### 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 APP_ENV=production ALLOW_DEV_AUTOMIGRATE=false 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 `。 ### 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)创建。 ## 可观测性与恢复验证 - 每个 HTTP 请求统一记录 `X-Request-ID`,日志不得包含 Authorization 原始值或敏感 query。 - 依赖指标通过 `GET /api/v1/ops/metrics` 查看;发布前按 `docs/operations/slo-and-alerts.md` 检查告警阈值。 - 发布前备份 PostgreSQL,并参考 `docs/operations/backup-restore-drill.md` 记录备份时间、恢复时间和 RPO/RTO。 - 负载和容量验证场景见 `docs/operations/load-test-scenarios.md`。