feat(edge): 断电保护(overlayroot+/data)与产品镜像批量生产流程
- 新增 setup-overlayroot.sh: 一键断电保护 - overlayroot(tmpfs) 保护根分区, 断电不再损坏文件系统 - 自动建 /data 独立分区 + fstab bind 挂载运行数据 - 迁移存量数据, journald 内存日志限容 - disable/enable 一键切换维护模式(适配香橙派 U-Boot/extlinux, 无 GRUB) - 新增 prepare-image.sh: 母卡出厂化清理 - 清 machine-id/SSH 密钥/device_id/hostname/日志 - 防误: 检测 overlayroot 启用状态, 保证镜像为可写出厂状态 - 新增 init-device.sh: 每台初始化 - 主机名/静态IP(netplan)/身份验证 - /data 自动 growpart 扩容到整卡剩余空间(64G 产品卡) - 统一启用断电保护 - deploy.sh/ops.sh/package-offline.sh: 集成新脚本 - ops.sh 新增 overlay-status/disable/enable/verify - docs/deployment.md: 重写为主流程文档 - 产品镜像批量生产(16G 母卡->小镜像->Etcher 批量烧录->每台初始化) - 移除废弃的克隆卡流程
This commit is contained in:
parent
e931e4d95b
commit
227dc04004
@ -1,95 +1,185 @@
|
||||
# Edge 设备部署指南
|
||||
|
||||
## 首次部署(从 SD 卡克隆)
|
||||
|
||||
### 克隆前准备
|
||||
|
||||
在源设备上清理不需要的文件后关机,取出 SD 卡,使用 `dd` 或烧录工具克隆到新卡。
|
||||
|
||||
### 克隆后配置
|
||||
|
||||
将新 SD 卡插入新设备,开机,执行以下步骤:
|
||||
|
||||
#### 1. 生成新的设备 ID
|
||||
|
||||
管理端通过设备 ID 区分不同设备,克隆后必须更换:
|
||||
|
||||
```bash
|
||||
sudo rm -f /etc/machine-id /var/lib/safesight-edge-agent/device_id
|
||||
sudo systemd-machine-id-setup
|
||||
```
|
||||
|
||||
#### 2. 重新生成 SSH 主机密钥
|
||||
|
||||
```bash
|
||||
sudo rm -f /etc/ssh/ssh_host_*_key*
|
||||
sudo ssh-keygen -A
|
||||
```
|
||||
|
||||
#### 3. 设置主机名
|
||||
|
||||
```bash
|
||||
sudo hostnamectl set-hostname <新主机名>
|
||||
```
|
||||
|
||||
#### 4. 配置静态 IP(生产环境)
|
||||
|
||||
编辑 netplan 配置:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/netplan/01-network-manager-all.yaml
|
||||
```
|
||||
|
||||
写入:
|
||||
|
||||
```yaml
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
enP4p65s0: # 网卡名,用 ip addr 确认
|
||||
dhcp4: no
|
||||
addresses:
|
||||
- 10.0.0.82/24 # 目标 IP
|
||||
routes:
|
||||
- to: default
|
||||
via: 10.0.0.1 # 网关
|
||||
nameservers:
|
||||
addresses:
|
||||
- 10.0.0.1 # DNS
|
||||
```
|
||||
|
||||
应用:
|
||||
|
||||
```bash
|
||||
sudo netplan apply
|
||||
```
|
||||
|
||||
#### 5. 重启
|
||||
|
||||
```bash
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
### 验证
|
||||
|
||||
```bash
|
||||
# 确认服务状态
|
||||
systemctl status safesight-edge-server safesight-edge-agent
|
||||
|
||||
# 确认新 IP
|
||||
ip addr show | grep inet
|
||||
|
||||
# 确认新设备 ID
|
||||
cat /var/lib/safesight-edge-agent/device_id
|
||||
|
||||
# 管理端发现新设备后,在设备详情页设置别名
|
||||
```
|
||||
> **本文件是 Edge 设备的唯一操作手册。** 现场部署时配合 `docs/客户现场部署检查表.md` 逐项打勾。
|
||||
>
|
||||
> **推荐流程(新项目)**:`产品镜像批量生产`(第 2 章)——16G 母卡制作产品镜像 → Etcher 批量烧录 → 每台脚本初始化。
|
||||
> 旧版"克隆卡"方式不再使用。
|
||||
>
|
||||
> **仅部署 1~2 台**:可跳过镜像,直接在设备上执行 `deploy.sh install` 后用第 3 章启用断电保护。
|
||||
|
||||
---
|
||||
|
||||
## 更新部署
|
||||
## 1. 涉及的工具与脚本
|
||||
|
||||
### 首次安装(新设备/离线部署)
|
||||
| 脚本(安装于 `/opt/safesight-edge-server/scripts/`) | 作用 | 在哪个阶段使用 |
|
||||
|---|---|---|
|
||||
| `install_deps.sh` | 安装运行环境(MPP / RKNN / ffmpeg-rockchip) | 母卡制作 |
|
||||
| `package-offline.sh` | 编译并打包离线部署包 | 母卡制作 |
|
||||
| `deploy.sh` | 安装/升级 safesight 程序与服务 | 母卡制作 |
|
||||
| `setup-overlayroot.sh` | 断电保护:建 /data 分区 + overlayroot | 母卡制作 / 每台初始化 |
|
||||
| `prepare-image.sh` | 出厂化清理(打镜像前必跑) | 母卡制作 |
|
||||
| `init-device.sh` | 每台初始化(IP/主机名/扩 /data/启用保护) | 批量烧录后每台 |
|
||||
| `ops.sh` | 运维(状态/日志/断电保护切换) | 日常 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 产品镜像批量生产(推荐主流程)
|
||||
|
||||
> **核心思路:用一张 16G 小卡做母卡,镜像体积 = 官方镜像级别**(gzip 后约 1~2GB)。
|
||||
> 镜像结构上仍是标准整卡镜像(官方也是):分区表 + U-Boot + 分区内容。它小的原因是**母卡容量小**,
|
||||
> 而不是特殊打包。烧到 64G 产品卡后,`/data` 分区由 init-device.sh 首次启动时自动 growpart 扩满整卡,
|
||||
> 存储不浪费。未使用空间是零块,gzip 压缩后几乎不占体积,Etcher 直接支持 `.img.gz`。
|
||||
|
||||
### 阶段一:母卡制作(16G 开发卡,插在 Orange Pi 上)
|
||||
|
||||
1. Etcher 烧录官方镜像(如 `Orangepi5_1.2.2_ubuntu_jammy_server_linux5.10.160.img`)→ **16G SD 卡**,启动。
|
||||
2. 安装运行环境:`sudo bash scripts/install_deps.sh`(MPP / RKNN / ffmpeg-rockchip)。
|
||||
3. 编译并打离线包:`bash scripts/build.sh -m -j4 && bash scripts/package-offline.sh`。
|
||||
4. 部署并验证:`sudo AGENT_TOKEN=<主钥匙> ./deploy.sh install`;导入模型/人脸库/RTSP,验证推流与算法。
|
||||
5. 创建 /data 分区与 fstab(出厂带断电保护能力,但**不启用保护**):
|
||||
|
||||
```bash
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh install --yes # 建 /data 分区 + fstab + 装 overlayroot
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable # 保持可写!镜像出厂不启用保护
|
||||
```
|
||||
|
||||
> `install` 会创建 /data 分区、写入 fstab、安装 overlayroot 但**不重启**(当前会话仍可写);
|
||||
> 随后 `disable` 把 overlayroot 置空,保证出厂镜像首次启动处于可写状态,
|
||||
> 机器 ID 才能固化、init-device.sh 才能改配置。
|
||||
|
||||
6. **出厂化清理**(关键,否则每台设备身份相同):
|
||||
|
||||
```bash
|
||||
sudo /opt/safesight-edge-server/scripts/prepare-image.sh
|
||||
sudo poweroff # 确保文件系统一致后摘卡
|
||||
```
|
||||
|
||||
> `prepare-image.sh` 清除 machine-id / SSH 密钥 / device_id / hostname / 全部日志与告警数据,保留程序、依赖、`/data` 分区与 fstab。
|
||||
> 若脚本提示"overlayroot 已启用",说明第 5 步的 `disable` 未生效,先补跑再执行。
|
||||
|
||||
### 阶段二:生成镜像文件(Windows / Linux 主机)
|
||||
|
||||
```bash
|
||||
# Linux(推荐,边读边压缩,16G 卡约 2~4 分钟):
|
||||
sudo dd if=/dev/sdX bs=4M status=progress conv=fsync | gzip > safesight-product-$(date +%Y%m%d).img.gz
|
||||
# Windows: 用 Win32DiskImager "读取"生成 .img(16G 卡很快),再用 7-Zip 转 .gz
|
||||
```
|
||||
|
||||
> 为什么还是整卡 dd?香橙派 U-Boot 引导写在 SD 卡非文件系统区域,分区级打包会丢引导、无法启动;
|
||||
> 官方镜像同样如此。只是**源卡用 16G**,镜像就是官方那种体积,不再有 64G 整卡 dump 的问题。
|
||||
> (若坚持从 64G 卡做小镜像:Linux 上用 `bmaptool` 只复制已使用块,效果相同且更慢,不推荐。)
|
||||
|
||||
### 阶段三:批量烧录
|
||||
|
||||
- 用 Balena Etcher 批量烧录 `.img.gz` 到 **64G 产品卡**(写入 + 自动校验)。
|
||||
- 每台烧录后**首次启动会自动生成唯一** machine-id / SSH 主机密钥 / device_id,互不冲突。
|
||||
|
||||
### 阶段四:每台初始化
|
||||
|
||||
```bash
|
||||
# 设备上电后(首次为 DHCP,用 IP 扫描找到设备后 SSH 登录)
|
||||
sudo /opt/safesight-edge-server/scripts/init-device.sh \
|
||||
--hostname safesight-01 --ip 10.0.0.83/24 --gw 10.0.0.1 --dns 10.0.0.1
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
脚本自动完成:主机名 → 静态 IP(netplan) → 身份验证 → **/data 自动扩容到整卡剩余空间**
|
||||
(growpart + resize2fs,64G 卡 /data ≈ 55G)→ 服务验证 → **启用断电保护** → 提示重启。
|
||||
可选参数:`--hls-ram`(HLS 放内存)、`--dhcp`(保持 DHCP)、`--no-overlay`、`--token <主钥匙>`(轮换 token)。
|
||||
|
||||
### 阶段五:验收
|
||||
|
||||
```bash
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh verify # 断电保护验证(全部 ✓)
|
||||
df -h /data # 应为 ~55G
|
||||
systemctl status safesight-edge-server safesight-edge-agent # 服务正常
|
||||
# 管理端:发现设备 → 设置别名 → 导入摄像头/模型 → 下发配置 → 验证推流与算法
|
||||
```
|
||||
|
||||
### 为什么出厂镜像不启用 overlayroot?
|
||||
|
||||
- overlayroot 启用后根分区只读,`/etc/machine-id` 无法在首次启动时固化(tmpfs 重启即丢)→ 每次重启设备 ID 漂移,管理端会不断看到"新设备"。
|
||||
- 每台改 IP/主机名也需先进维护模式,批量初始化多一次重启。
|
||||
- 因此:**镜像保持可写出厂状态,overlayroot 由 init-device.sh 在每台初始化最后统一启用**,每台只重启一次即进入保护。
|
||||
|
||||
---
|
||||
|
||||
## 3. 断电保护(overlayroot + /data 分区)参考
|
||||
|
||||
> 解决 SD 卡断电重启导致根文件系统损坏、无法启动的问题。
|
||||
> **启用后根分区只读,修改系统配置需先进入维护模式。**
|
||||
|
||||
### 原理
|
||||
|
||||
- 用 Ubuntu 官方 `overlayroot` 把根文件系统置于 tmpfs overlay 之上:根分区永久只读,一切写入进入内存,重启即丢,**断电不再损坏根分区**。
|
||||
- 在 SD 卡剩余空间划分独立读写分区 `/data`(ext4),保存日志、告警图片/视频、设备 ID 等必须持久化的数据。
|
||||
- `safesight` 的运行目录(`/var/lib/safesight-edge-server/*`、`/var/lib/safesight-edge-agent`)通过 fstab bind 挂载到 `/data`,**路径不变**,现有部署脚本零改动。
|
||||
|
||||
### 单台启用(未走镜像流程时)
|
||||
|
||||
```bash
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh install
|
||||
sudo reboot
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh verify # 全部 ✓ 即为生效
|
||||
```
|
||||
|
||||
脚本自动完成:检测空闲空间建 /data(ext4)→ 写 fstab(/data + bind,均 `nofail`)→ 迁移存量数据
|
||||
→ journald 内存日志(64M 上限)→ 安装并配置 overlayroot → 重建 initramfs。
|
||||
|
||||
### 可选参数
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `--yes` | 跳过分区确认(无人值守) |
|
||||
| `--hls-ram` | HLS 分片放内存 tmpfs(512M),减小 SD 卡写入;重启后直播画面需重新拉流 |
|
||||
| `--journal-data` | 系统日志持久化到 /data(默认放内存,重启即丢) |
|
||||
|
||||
### 日常维护(临时进入可写模式)
|
||||
|
||||
> 香橙派使用 U-Boot + extlinux 引导,**没有 GRUB**,不要在引导菜单改内核参数。用脚本一键切换:
|
||||
|
||||
```bash
|
||||
# 进入维护模式(装软件/改系统配置)
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable
|
||||
sudo reboot
|
||||
|
||||
# 维护完成,恢复保护
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh enable
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
或使用运维脚本:`sudo /opt/safesight-edge-server/scripts/ops.sh overlay-disable` / `overlay-enable`。
|
||||
|
||||
### 查看状态
|
||||
|
||||
```bash
|
||||
sudo /opt/safesight-edge-server/scripts/ops.sh overlay-status
|
||||
# 或
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh status
|
||||
```
|
||||
|
||||
### 重要注意事项
|
||||
|
||||
- **启用前**确认:静态 IP、主机名、SSH 密钥、设备 ID 均已配置完成(启用后根分区只读,改动需维护模式)。
|
||||
- **维护模式期间**断电仍有损坏风险,维护完成后务必 `enable` 恢复保护。
|
||||
- HLS 分片写入量较大(与码率、路数、保留天数相关),默认落 `/data`;若 SD 写入寿命紧张可加 `--hls-ram`。
|
||||
- 若 SD 卡无剩余空间(根分区已占满),需重新烧录含 `/data` 分区的母卡镜像后再执行本方案。
|
||||
|
||||
---
|
||||
|
||||
## 4. 更新部署(软件升级)
|
||||
|
||||
### 4.1 单台升级(目标设备可访问 git)
|
||||
|
||||
```bash
|
||||
cd ~/apps/safesight-edge
|
||||
git pull
|
||||
bash scripts/build.sh -m -j4
|
||||
cd agent && go build -o safesight-edge-agent ./cmd/safesight-agent && cd ..
|
||||
sudo ./scripts/deploy.sh upgrade
|
||||
```
|
||||
|
||||
### 4.2 离线升级包(无 git 环境)
|
||||
|
||||
在开发机(已安装 safesight-edge 源码和编译环境)上:
|
||||
|
||||
@ -104,31 +194,23 @@ git pull && bash scripts/package-offline.sh
|
||||
scp safesight-upgrade-*.tar.gz orangepi@<目标IP>:/tmp/
|
||||
```
|
||||
|
||||
在目标设备上:
|
||||
在目标设备上(**已启用断电保护的设备需先进入维护模式**):
|
||||
|
||||
```bash
|
||||
# 已启用断电保护的设备:
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable && sudo reboot
|
||||
|
||||
# 升级:
|
||||
cd /tmp && tar -xzf safesight-upgrade-*.tar.gz
|
||||
cd safesight-upgrade-*
|
||||
sudo AGENT_TOKEN=<管理端token> ./deploy.sh install
|
||||
sudo AGENT_TOKEN=<管理端token> ./deploy.sh install # 或 ./deploy.sh upgrade 保留配置
|
||||
|
||||
# 恢复保护:
|
||||
sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh enable && sudo reboot
|
||||
```
|
||||
|
||||
> 安装后配置为空模板,通过管理端部署向导下发实际配置。
|
||||
### 4.3 升级产品镜像(批量设备升级)
|
||||
|
||||
### 在线更新(可访问 git)
|
||||
重新走第 2 章:母卡 `git pull` → 编译 → `deploy.sh upgrade` → `prepare-image.sh` → 打镜像 → 批量烧录 → 每台初始化。
|
||||
|
||||
```bash
|
||||
cd ~/apps/safesight-edge
|
||||
git pull
|
||||
bash scripts/build.sh -m -j4
|
||||
cd agent && go build -o safesight-edge-agent ./cmd/safesight-agent && cd ..
|
||||
sudo ./scripts/deploy.sh upgrade
|
||||
```
|
||||
|
||||
### 旧设备首次升级
|
||||
|
||||
```bash
|
||||
cd ~/apps/safesight-edge && git pull
|
||||
bash scripts/build.sh -m -j4
|
||||
cd agent && go build -o safesight-edge-agent ./cmd/safesight-agent
|
||||
sudo ./scripts/upgrade-from-old.sh
|
||||
```
|
||||
> 提示:程序升级通常不动 `/data`(设备 ID 不丢);若升级脚本需要改写系统配置,母卡流程已天然覆盖。
|
||||
|
||||
@ -257,6 +257,21 @@ cmd_install_media_server() {
|
||||
chmod +x "$INSTALL_DIR/scripts/monitor_hw.sh"
|
||||
echo -e "${GREEN}✓${NC} 已复制运维脚本: monitor_hw.sh"
|
||||
fi
|
||||
if [ -f "$PROJECT_DIR/scripts/setup-overlayroot.sh" ]; then
|
||||
cp "$PROJECT_DIR/scripts/setup-overlayroot.sh" "$INSTALL_DIR/scripts/"
|
||||
chmod +x "$INSTALL_DIR/scripts/setup-overlayroot.sh"
|
||||
echo -e "${GREEN}✓${NC} 已复制断电保护脚本: setup-overlayroot.sh"
|
||||
fi
|
||||
if [ -f "$PROJECT_DIR/scripts/init-device.sh" ]; then
|
||||
cp "$PROJECT_DIR/scripts/init-device.sh" "$INSTALL_DIR/scripts/"
|
||||
chmod +x "$INSTALL_DIR/scripts/init-device.sh"
|
||||
echo -e "${GREEN}✓${NC} 已复制设备初始化脚本: init-device.sh"
|
||||
fi
|
||||
if [ -f "$PROJECT_DIR/scripts/prepare-image.sh" ]; then
|
||||
cp "$PROJECT_DIR/scripts/prepare-image.sh" "$INSTALL_DIR/scripts/"
|
||||
chmod +x "$INSTALL_DIR/scripts/prepare-image.sh"
|
||||
echo -e "${GREEN}✓${NC} 已复制镜像出厂化脚本: prepare-image.sh"
|
||||
fi
|
||||
|
||||
# 复制插件
|
||||
if [ -d "$BUILD_DIR/plugins" ]; then
|
||||
@ -723,6 +738,10 @@ cmd_install() {
|
||||
echo ""
|
||||
echo "HLS播放:"
|
||||
echo " http://$(hostname -I | awk '{print $1}'):9000/hls_player.html"
|
||||
echo ""
|
||||
echo "断电保护(推荐):"
|
||||
echo " sudo $INSTALL_DIR/scripts/setup-overlayroot.sh install"
|
||||
echo " 说明: 防止 SD 卡断电损坏,详见 docs/deployment.md 断电保护章节"
|
||||
}
|
||||
|
||||
# 升级
|
||||
|
||||
243
scripts/init-device.sh
Normal file
243
scripts/init-device.sh
Normal file
@ -0,0 +1,243 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# init-device.sh — 批量烧录后的单台设备初始化
|
||||
# -----------------------------------------------------------------------------
|
||||
# 在每台烧录好产品镜像的盒子上运行(root)。完成:
|
||||
# 1. 设置主机名
|
||||
# 2. 设置静态 IP(netplan)
|
||||
# 3. 验证设备身份唯一(machine-id / SSH 密钥 / device_id)
|
||||
# 4. 验证 safesight 服务正常
|
||||
# 5. 启用断电保护 overlayroot(出厂镜像故意未启用,统一在此启用)
|
||||
# 6. 重启
|
||||
#
|
||||
# 用法:
|
||||
# sudo ./init-device.sh --hostname safesight-01 \
|
||||
# --ip 10.0.0.83/24 --gw 10.0.0.1 --dns 10.0.0.1
|
||||
# 可选:
|
||||
# --token <后台管理统一主钥匙> 轮换 agent token(默认沿用镜像内主钥匙)
|
||||
# --hls-ram HLS 分片放内存(减小 SD 写入)
|
||||
# --dhcp 保持 DHCP(默认会写静态 IP)
|
||||
# --no-overlay 不启用断电保护(仅初始化)
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
||||
die() { echo -e "${RED}✗${NC} $*" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 参数
|
||||
# ---------------------------------------------------------------------------
|
||||
HOSTNAME="" IP_CIDR="" GW="" DNS="" TOKEN="" HLS_RAM=0 USE_DHCP=0 NO_OVERLAY=0
|
||||
|
||||
usage() {
|
||||
echo "用法: sudo $0 --hostname <名> --ip <IP/掩码> --gw <网关> --dns <DNS> [选项]"
|
||||
echo ""
|
||||
echo "必选:"
|
||||
echo " --hostname <名称> 如 safesight-01"
|
||||
echo " --ip <IP/掩码> 如 10.0.0.83/24"
|
||||
echo " --gw <网关> 如 10.0.0.1"
|
||||
echo " --dns <DNS> 如 10.0.0.1"
|
||||
echo "可选:"
|
||||
echo " --token <主钥匙> 轮换后台管理统一主钥匙"
|
||||
echo " --hls-ram HLS 分片放内存"
|
||||
echo " --dhcp 保持 DHCP(不写静态 IP)"
|
||||
echo " --no-overlay 不启用断电保护"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--hostname) HOSTNAME="$2"; shift 2 ;;
|
||||
--ip) IP_CIDR="$2"; shift 2 ;;
|
||||
--gw) GW="$2"; shift 2 ;;
|
||||
--dns) DNS="$2"; shift 2 ;;
|
||||
--token) TOKEN="$2"; shift 2 ;;
|
||||
--hls-ram) HLS_RAM=1; shift ;;
|
||||
--dhcp) USE_DHCP=1; shift ;;
|
||||
--no-overlay) NO_OVERLAY=1; shift ;;
|
||||
*) warn "未知参数: $1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "请使用 sudo 运行"
|
||||
|
||||
if [ "$USE_DHCP" = "0" ]; then
|
||||
[ -n "$HOSTNAME" ] || die "缺少 --hostname"
|
||||
[ -n "$IP_CIDR" ] || die "缺少 --ip"
|
||||
[ -n "$GW" ] || die "缺少 --gw"
|
||||
[ -n "$DNS" ] || die "缺少 --dns"
|
||||
fi
|
||||
|
||||
OVERLAY_SCRIPT="/opt/safesight-edge-server/scripts/setup-overlayroot.sh"
|
||||
[ -x "$OVERLAY_SCRIPT" ] || OVERLAY_SCRIPT="$(cd "$(dirname "$0")" && pwd)/setup-overlayroot.sh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. 主机名
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -n "$HOSTNAME" ]; then
|
||||
info "设置主机名: $HOSTNAME"
|
||||
hostnamectl set-hostname "$HOSTNAME"
|
||||
sed -i "/^127.0.1.1/d" /etc/hosts
|
||||
echo "127.0.1.1 $HOSTNAME" >> /etc/hosts
|
||||
ok "主机名已设置"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. 静态 IP(netplan)
|
||||
# ---------------------------------------------------------------------------
|
||||
NETPLAN_DIR="/etc/netplan"
|
||||
if [ "$USE_DHCP" = "0" ]; then
|
||||
# 检测默认网卡(优先有默认路由的网卡,其次第一个非 lo 网卡)
|
||||
IFACE="$(ip route show default 2>/dev/null | awk '/default/{print $5; exit}')"
|
||||
[ -n "$IFACE" ] || IFACE="$(ls /sys/class/net | grep -v '^lo$' | head -1)"
|
||||
[ -n "$IFACE" ] || die "未检测到网卡"
|
||||
|
||||
info "配置静态 IP: $IFACE $IP_CIDR (gw=$GW dns=$DNS)"
|
||||
mkdir -p "$NETPLAN_DIR"
|
||||
# 备份现有配置
|
||||
cp -a "$NETPLAN_DIR"/01-*.yaml "$NETPLAN_DIR/backup-"$(date +%s)".yaml" 2>/dev/null || true
|
||||
rm -f "$NETPLAN_DIR"/01-*.yaml 2>/dev/null || true
|
||||
|
||||
cat > "$NETPLAN_DIR/01-safesight-static.yaml" << EOF
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
$IFACE:
|
||||
dhcp4: no
|
||||
addresses:
|
||||
- $IP_CIDR
|
||||
routes:
|
||||
- to: default
|
||||
via: $GW
|
||||
nameservers:
|
||||
addresses:
|
||||
- $DNS
|
||||
EOF
|
||||
netplan apply 2>/dev/null || warn "netplan apply 失败(重启后生效)"
|
||||
ok "静态 IP 已配置: $IP_CIDR"
|
||||
else
|
||||
info "保持 DHCP"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. 设备身份验证(出厂镜像已清空,首次启动应已自动重建)
|
||||
# ---------------------------------------------------------------------------
|
||||
info "验证设备身份 ..."
|
||||
[ -s /etc/machine-id ] || { systemd-machine-id-setup 2>/dev/null || warn "machine-id 缺失(重启后自动生成)"; }
|
||||
[ -s /etc/machine-id ] && ok "machine-id: $(cat /etc/machine-id)"
|
||||
if ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1; then
|
||||
ok "SSH 主机密钥已生成"
|
||||
else
|
||||
ssh-keygen -A 2>/dev/null || true
|
||||
ok "SSH 主机密钥已生成(ssh-keygen -A)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. agent token(可选轮换)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -n "$TOKEN" ]; then
|
||||
info "轮换 agent 主钥匙 ..."
|
||||
printf '%s\n' "$TOKEN" > /opt/safesight-edge-agent/agent.token
|
||||
chmod 600 /opt/safesight-edge-agent/agent.token
|
||||
# 更新 agent 配置中的 token
|
||||
if [ -f /opt/safesight-edge-agent/agent.config.json ]; then
|
||||
sed -i "s/\"token\": \"[^\"]*\"/\"token\": \"$TOKEN\"/" /opt/safesight-edge-agent/agent.config.json
|
||||
fi
|
||||
ok "agent token 已更新"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4.5 /data 分区自动扩容(小镜像烧到 64G 产品卡后,把 /data 扩到整卡剩余)
|
||||
# ---------------------------------------------------------------------------
|
||||
grow_data_partition() {
|
||||
info "扩容 /data 分区到 SD 卡剩余空间 ..."
|
||||
DATA_PART="$(findmnt -no SOURCE /data 2>/dev/null || true)"
|
||||
[ -n "$DATA_PART" ] || { warn "/data 未挂载,跳过扩容"; return; }
|
||||
|
||||
DISK=""; PARTNUM=""
|
||||
case "$DATA_PART" in
|
||||
/dev/mmcblk*) DISK="${DATA_PART%p*}"; PARTNUM="${DATA_PART##*p}" ;;
|
||||
/dev/nvme*) DISK="${DATA_PART%p*}"; PARTNUM="${DATA_PART##*p}" ;;
|
||||
/dev/sd*) DISK="${DATA_PART%[0-9]*}"; PARTNUM="${DATA_PART##*[a-z]}" ;;
|
||||
esac
|
||||
[ -n "$DISK" ] || { warn "无法识别 /data 所在磁盘,跳过扩容"; return; }
|
||||
|
||||
# 需要 growpart(cloud-guest-utils 提供)
|
||||
if ! command -v growpart >/dev/null 2>&1; then
|
||||
info "安装 growpart ..."
|
||||
apt-get install -y cloud-guest-utils 2>/dev/null || warn "growpart 安装失败,跳过扩容"
|
||||
fi
|
||||
command -v growpart >/dev/null 2>&1 || return
|
||||
|
||||
# 停服务并卸载 /data 后扩容(避免分区忙)
|
||||
systemctl stop safesight-edge-server safesight-edge-agent 2>/dev/null || true
|
||||
umount /data 2>/dev/null || true
|
||||
growpart "$DISK" "$PARTNUM" 2>/dev/null || warn "growpart 无变化(可能已扩满)"
|
||||
resize2fs "$DATA_PART" 2>/dev/null || warn "resize2fs 失败"
|
||||
mount /data 2>/dev/null || mount -a 2>/dev/null || true
|
||||
systemctl start safesight-edge-server safesight-edge-agent 2>/dev/null || true
|
||||
ok "/data 扩容完成: $(df -h /data 2>/dev/null | awk 'NR==2{print "总"$2" 可用"$4}')(64G 产品卡自动占满剩余空间)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. 验证 safesight 服务
|
||||
# ---------------------------------------------------------------------------
|
||||
info "验证服务 ..."
|
||||
systemctl start safesight-edge-server safesight-edge-agent 2>/dev/null || true
|
||||
sleep 3
|
||||
for s in safesight-edge-server safesight-edge-agent; do
|
||||
if systemctl is-active --quiet "$s" 2>/dev/null; then
|
||||
ok "$s 运行中"
|
||||
else
|
||||
warn "$s 未运行(可稍后 systemctl status $s 排查)"
|
||||
fi
|
||||
done
|
||||
if [ -f /var/lib/safesight-edge-agent/device_id ]; then
|
||||
ok "device_id: $(cat /var/lib/safesight-edge-agent/device_id)"
|
||||
else
|
||||
info "device_id 由 agent 首次运行时生成(读取 machine-id)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4.5 /data 分区自动扩容(小镜像烧到 64G 产品卡后,把 /data 扩到整卡剩余)
|
||||
# ---------------------------------------------------------------------------
|
||||
grow_data_partition
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. 启用断电保护
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$NO_OVERLAY" = "1" ]; then
|
||||
warn "跳过断电保护启用(--no-overlay)"
|
||||
else
|
||||
info "启用断电保护(overlayroot + /data)..."
|
||||
EXTRA=""
|
||||
[ "$HLS_RAM" = "1" ] && EXTRA="--hls-ram"
|
||||
if [ -x "$OVERLAY_SCRIPT" ]; then
|
||||
bash "$OVERLAY_SCRIPT" install --yes $EXTRA || die "断电保护启用失败"
|
||||
else
|
||||
die "未找到 setup-overlayroot.sh($OVERLAY_SCRIPT),请先完成部署"
|
||||
fi
|
||||
ok "断电保护已配置,重启后生效"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. 完成
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} 初始化完成!${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo " IP: ${IP_CIDR:-DHCP}"
|
||||
echo " 主机名: ${HOSTNAME:-未设置}"
|
||||
echo " 管理界面: http://${IP_CIDR%/*}:9100"
|
||||
echo ""
|
||||
echo "请重启使全部配置生效:"
|
||||
echo " sudo reboot"
|
||||
echo ""
|
||||
echo "重启后建议:"
|
||||
echo " sudo $OVERLAY_SCRIPT verify # 断电保护验证"
|
||||
echo " 在管理端发现该设备 → 设置别名 → 下发配置"
|
||||
@ -2,7 +2,7 @@
|
||||
# RK3588 Edge Server 运维脚本
|
||||
# 安装后位于 /opt/safesight-edge-server/script/ops.sh
|
||||
#
|
||||
# 用法: sudo ./ops.sh [status|start|stop|restart|logs|hw|ddr-status|ddr-performance|ddr-ondemand|ddr-restore]
|
||||
# 用法: sudo ./ops.sh [status|start|stop|restart|logs|hw|ddr-status|ddr-performance|ddr-ondemand|ddr-restore|overlay-status|overlay-disable|overlay-enable|overlay-verify]
|
||||
|
||||
set -e
|
||||
|
||||
@ -134,6 +134,22 @@ cmd_ddr_restore() {
|
||||
bash /opt/safesight-edge-server/scripts/ddr_mode.sh restore
|
||||
}
|
||||
|
||||
cmd_overlay_status() {
|
||||
bash /opt/safesight-edge-server/scripts/setup-overlayroot.sh status
|
||||
}
|
||||
|
||||
cmd_overlay_disable() {
|
||||
bash /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable
|
||||
}
|
||||
|
||||
cmd_overlay_enable() {
|
||||
bash /opt/safesight-edge-server/scripts/setup-overlayroot.sh enable
|
||||
}
|
||||
|
||||
cmd_overlay_verify() {
|
||||
bash /opt/safesight-edge-server/scripts/setup-overlayroot.sh verify
|
||||
}
|
||||
|
||||
# 主入口
|
||||
case "${1:-status}" in
|
||||
status)
|
||||
@ -166,6 +182,18 @@ case "${1:-status}" in
|
||||
ddr-restore)
|
||||
cmd_ddr_restore
|
||||
;;
|
||||
overlay-status)
|
||||
cmd_overlay_status
|
||||
;;
|
||||
overlay-disable)
|
||||
cmd_overlay_disable
|
||||
;;
|
||||
overlay-enable)
|
||||
cmd_overlay_enable
|
||||
;;
|
||||
overlay-verify)
|
||||
cmd_overlay_verify
|
||||
;;
|
||||
*)
|
||||
echo "RK3588 Edge Server 运维脚本"
|
||||
echo ""
|
||||
@ -182,6 +210,10 @@ case "${1:-status}" in
|
||||
echo " ddr-performance DDR 切到高性能模式"
|
||||
echo " ddr-ondemand DDR 切回 dmc_ondemand"
|
||||
echo " ddr-restore 恢复上次保存的 DDR 配置"
|
||||
echo " overlay-status 断电保护状态 (overlayroot)"
|
||||
echo " overlay-disable 断电保护-临时禁用(维护模式,可写)"
|
||||
echo " overlay-enable 断电保护-重新启用(只读保护)"
|
||||
echo " overlay-verify 断电保护-重启后验证"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " sudo $(basename $0) status"
|
||||
|
||||
@ -65,6 +65,9 @@ if [ -f scripts/ops.sh ]; then cp scripts/ops.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/monitor_hw.sh ]; then cp scripts/monitor_hw.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/ddr_mode.sh ]; then cp scripts/ddr_mode.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/install_deps.sh ]; then cp scripts/install_deps.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/setup-overlayroot.sh ]; then cp scripts/setup-overlayroot.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/prepare-image.sh ]; then cp scripts/prepare-image.sh "$BUILD_DIR/"; fi
|
||||
if [ -f scripts/init-device.sh ]; then cp scripts/init-device.sh "$BUILD_DIR/"; fi
|
||||
|
||||
# 系统硬件加速库(从当前设备复制,确保兼容)
|
||||
mkdir -p "$BUILD_DIR/lib_deps"
|
||||
|
||||
138
scripts/prepare-image.sh
Normal file
138
scripts/prepare-image.sh
Normal file
@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# prepare-image.sh — 产品镜像出厂化清理(母卡制作镜像前运行)
|
||||
# -----------------------------------------------------------------------------
|
||||
# 用法(母卡上,root):
|
||||
# sudo ./prepare-image.sh 出厂化清理
|
||||
# sudo ./prepare-image.sh --zero 额外清零空闲空间(压缩镜像体积,较慢)
|
||||
#
|
||||
# 作用:把"部署完成、验证通过"的母卡变成干净的出厂状态:
|
||||
# - 清除设备个体身份(machine-id / SSH 密钥 / device_id / hostname)
|
||||
# - 清空 /data 与运行时数据(日志/告警/HLS),保留分区与 fstab
|
||||
# - 保留:已部署的 safesight、已装的依赖、overlayroot 配置能力(出厂即带断电保护)
|
||||
#
|
||||
# 重要:本脚本后【不要】启用 overlayroot(setup-overlayroot.sh enable),
|
||||
# 保持"可写出厂状态",overlayroot 留到每台设备初始化最后一步启用。
|
||||
# 否则 /etc/machine-id 无法固化,每台重启后设备 ID 漂移。
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
||||
die() { echo -e "${RED}✗${NC} $*" >&2; exit 1; }
|
||||
|
||||
ZERO=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--zero) ZERO=1 ;;
|
||||
*) warn "未知参数: $arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "请使用 sudo 运行"
|
||||
|
||||
RUNTIME_ROOT="/var/lib/safesight-edge-server"
|
||||
AGENT_LIB="/var/lib/safesight-edge-agent"
|
||||
DATA_MOUNT="/data"
|
||||
OVERLAY_CONF="/etc/overlayroot.conf"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 安全检查
|
||||
# ---------------------------------------------------------------------------
|
||||
if grep -qs 'overlay' /proc/mounts; then
|
||||
die "当前处于 overlayroot 保护模式!请先进入维护模式(setup-overlayroot.sh disable + 重启)再运行。"
|
||||
fi
|
||||
if grep -qE '^overlayroot="[^"]+"' "$OVERLAY_CONF" 2>/dev/null; then
|
||||
die "检测到 $OVERLAY_CONF 已启用 overlayroot(保护模式配置)。出厂镜像必须保持可写状态,\n 请先运行 setup-overlayroot.sh disable(设置 overlayroot=\"\")后再执行出厂化。"
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${YELLOW} 即将把母卡重置为出厂状态,此操作不可恢复!${NC}"
|
||||
echo -e "${YELLOW}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo "将清除:"
|
||||
echo " - /etc/machine-id、SSH 主机密钥、hostname、device_id(首次启动自动重建)"
|
||||
echo " - /data 与 /var/lib/safesight-edge-server 下所有日志/告警/HLS 数据"
|
||||
echo ""
|
||||
echo "将保留:"
|
||||
echo " - safesight 程序与配置模板、全部依赖、/data 分区与 fstab(断电保护能力)"
|
||||
echo ""
|
||||
read -r -p "继续? [y/N]: " yn
|
||||
[ "${yn:-n}" = "y" ] || [ "${yn:-n}" = "Y" ] || die "已取消"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. 停止服务
|
||||
# ---------------------------------------------------------------------------
|
||||
info "停止服务 ..."
|
||||
systemctl stop safesight-edge-server safesight-edge-agent 2>/dev/null || true
|
||||
ok "服务已停止"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. 清空数据(保留目录结构)
|
||||
# ---------------------------------------------------------------------------
|
||||
info "清空运行数据 ..."
|
||||
for d in hls logs alarms clips; do
|
||||
rm -rf "$RUNTIME_ROOT/$d"/*
|
||||
rm -rf "$DATA_MOUNT/safesight/$d"/* 2>/dev/null || true
|
||||
done
|
||||
rm -rf "$AGENT_LIB"/* /data/safesight/agent/* 2>/dev/null || true
|
||||
[ -f "$AGENT_LIB/device_id" ] && rm -f "$AGENT_LIB/device_id"
|
||||
ok "运行数据已清空(目录保留)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. 设备身份重置(首次启动自动重建)
|
||||
# ---------------------------------------------------------------------------
|
||||
info "重置设备身份 ..."
|
||||
rm -f /etc/machine-id /var/lib/dbus/machine-id
|
||||
rm -f /etc/ssh/ssh_host_*_key* /etc/ssh/ssh_host_*_key*.pub
|
||||
rm -f "$AGENT_LIB/device_id"
|
||||
hostnamectl set-hostname orangepi 2>/dev/null || true
|
||||
ok "machine-id / SSH 密钥 / hostname / device_id 已重置(首次启动自动生成唯一值)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. 日志与缓存清理
|
||||
# ---------------------------------------------------------------------------
|
||||
info "清理日志与缓存 ..."
|
||||
journalctl --vacuum-size=1M >/dev/null 2>&1 || true
|
||||
rm -rf /var/log/journal/* 2>/dev/null || true
|
||||
find /var/log -type f -name "*.log" -delete 2>/dev/null || true
|
||||
apt-get clean 2>/dev/null || true
|
||||
rm -f /root/.bash_history /home/orangepi/.bash_history 2>/dev/null || true
|
||||
ok "日志与缓存已清理"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. 清零空闲空间(可选,压缩镜像体积)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$ZERO" = "1" ]; then
|
||||
info "清零空闲空间(SD 卡写入较慢,请耐心等待)..."
|
||||
for target in /data / ; do
|
||||
fill="$target/zero.fill"
|
||||
echo " $target ..."
|
||||
dd if=/dev/zero of="$fill" bs=4M 2>/dev/null || true
|
||||
rm -f "$fill"
|
||||
done
|
||||
ok "空闲空间已清零"
|
||||
else
|
||||
warn "未清零空闲空间。如需缩小镜像体积,可加 --zero 重新执行。"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. 完成
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} 出厂化完成!可以关机、摘卡、制作产品镜像${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1) sudo poweroff # 关机(确保文件系统一致)"
|
||||
echo " 2) 摘卡,在主机上制作镜像:"
|
||||
echo " Windows: Win32DiskImager 读取 → img"
|
||||
echo " Linux: sudo dd if=/dev/sdX bs=4M status=progress | gzip > safesight-product.img.gz"
|
||||
echo " 3) 每台烧录后首次启动会自动生成唯一 machine-id / SSH 密钥 / device_id"
|
||||
echo " 4) 每台初始化: sudo ./init-device.sh --hostname ... --ip ...(见脚本说明)"
|
||||
echo ""
|
||||
echo "注意: 此状态【尚未启用 overlayroot】——出厂镜像故意如此。"
|
||||
echo " overlayroot 由 init-device.sh 在每台初始化最后一步统一启用。"
|
||||
554
scripts/setup-overlayroot.sh
Normal file
554
scripts/setup-overlayroot.sh
Normal file
@ -0,0 +1,554 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# setup-overlayroot.sh — RK3588 Edge 断电保护(方案一:overlayroot + /data 分区)
|
||||
# -----------------------------------------------------------------------------
|
||||
# 解决场景:SD 卡断电重启导致根文件系统损坏、无法启动。
|
||||
#
|
||||
# 原理:
|
||||
# 1) 用 Ubuntu 官方 overlayroot 把根文件系统置于 tmpfs overlay 之上,
|
||||
# 系统对根分区的一切写入进入内存,重启即丢,根分区永久只读,
|
||||
# 断电不再损坏根文件系统。
|
||||
# 2) 在 SD 卡剩余空间划分独立读写分区 /data(ext4),存放日志、
|
||||
# 告警图片/视频、设备 ID 等需要持久化的数据,不受 overlay 影响。
|
||||
#
|
||||
# 用法(在设备上执行):
|
||||
# sudo ./setup-overlayroot.sh install 一键启用(推荐)
|
||||
# sudo ./setup-overlayroot.sh install --yes 跳过确认(无人值守)
|
||||
# sudo ./setup-overlayroot.sh install --hls-ram HLS 分片放内存(tmpfs),
|
||||
# 减小 SD 写入(按需)
|
||||
# sudo ./setup-overlayroot.sh status 查看当前状态
|
||||
# sudo ./setup-overlayroot.sh verify 重启后验证是否生效
|
||||
# sudo ./setup-overlayroot.sh disable 临时禁用(维护模式,可写)
|
||||
# sudo ./setup-overlayroot.sh enable 重新启用(退出维护模式)
|
||||
#
|
||||
# 可选环境变量:
|
||||
# HLS_MODE=ram|data HLS 分片存放位置(默认 data,--hls-ram 等价 ram)
|
||||
# JOURNAL_MODE=ram|data 系统 journal 日志(默认 ram)
|
||||
# SKIP_PARTITION=1 跳过分区创建(镜像已含 /data 分区时使用)
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 颜色与输出
|
||||
# ---------------------------------------------------------------------------
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
|
||||
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
||||
die() { echo -e "${RED}✗${NC} $*" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 全局配置
|
||||
# ---------------------------------------------------------------------------
|
||||
DATA_MOUNT="/data"
|
||||
DATA_LABEL="safesight-data"
|
||||
DATA_SUBDIRS="hls logs alarms clips agent"
|
||||
RUNTIME_ROOT="/var/lib/safesight-edge-server"
|
||||
AGENT_LIB="/var/lib/safesight-edge-agent"
|
||||
FSTAB="/etc/fstab"
|
||||
OVERLAY_CONF="/etc/overlayroot.conf"
|
||||
MARKER_BEGIN="# safesight-overlayroot (自动生成,勿手动修改)"
|
||||
MARKER_END="# end safesight-overlayroot"
|
||||
|
||||
HLS_MODE="${HLS_MODE:-data}" # data | ram
|
||||
JOURNAL_MODE="${JOURNAL_MODE:-ram}" # ram | data
|
||||
SKIP_PARTITION="${SKIP_PARTITION:-0}"
|
||||
ASSUME_YES=0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 参数解析
|
||||
# ---------------------------------------------------------------------------
|
||||
CMD="${1:-install}"
|
||||
shift || true
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--yes) ASSUME_YES=1 ;;
|
||||
--hls-ram) HLS_MODE=ram ;;
|
||||
--journal-data) JOURNAL_MODE=data ;;
|
||||
*) warn "未知参数: $arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 基础检查
|
||||
# ---------------------------------------------------------------------------
|
||||
require_root() {
|
||||
[ "$(id -u)" -eq 0 ] || die "请使用 sudo 运行此脚本"
|
||||
}
|
||||
|
||||
detect_distro() {
|
||||
if [ -r /etc/os-release ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
case "${ID:-}" in
|
||||
ubuntu|debian) return 0 ;;
|
||||
esac
|
||||
fi
|
||||
warn "非 Ubuntu/Debian 系统,overlayroot 可能不可用,继续尝试..."
|
||||
}
|
||||
|
||||
overlay_active() {
|
||||
grep -qs 'overlay' /proc/mounts
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 磁盘/分区检测
|
||||
# ---------------------------------------------------------------------------
|
||||
detect_root_disk() {
|
||||
ROOT_DEV="$(findmnt -no SOURCE / 2>/dev/null || true)"
|
||||
[ -n "${ROOT_DEV:-}" ] || die "无法定位根分区"
|
||||
case "$ROOT_DEV" in
|
||||
/dev/mmcblk*) ROOT_DISK="${ROOT_DEV%p*}"; ROOT_PART="${ROOT_DEV##*p}" ;;
|
||||
/dev/nvme*) ROOT_DISK="${ROOT_DEV%p*}"; ROOT_PART="${ROOT_DEV##*p}" ;;
|
||||
/dev/sd*) ROOT_DISK="${ROOT_DEV%[0-9]*}"; ROOT_PART="${ROOT_DEV##*[a-z]}" ;;
|
||||
*) die "无法识别的根设备: $ROOT_DEV(本脚本仅支持 SD 卡/eMMC/NVMe 直连分区)" ;;
|
||||
esac
|
||||
info "根分区: $ROOT_DEV → 磁盘: $ROOT_DISK"
|
||||
}
|
||||
|
||||
# /data 是否已存在(已挂载,或磁盘上有数据分区)
|
||||
data_partition_exists() {
|
||||
# 已挂载
|
||||
if mountpoint -q "$DATA_MOUNT" 2>/dev/null; then
|
||||
DATA_PART="$(findmnt -no SOURCE "$DATA_MOUNT" 2>/dev/null || true)"
|
||||
return 0
|
||||
fi
|
||||
# 磁盘上已有标签 safesight-data 的分区(未挂载)
|
||||
for p in $(lsblk -ln -o NAME,TYPE "$ROOT_DISK" 2>/dev/null | awk '$2=="part"{print $1}'); do
|
||||
d="/dev/$p"
|
||||
if [ "$(blkid -s LABEL -o value "$d" 2>/dev/null)" = "$DATA_LABEL" ]; then
|
||||
DATA_PART="$d"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
find_free_space() {
|
||||
# 返回 "起始MiB 大小MiB"(取最后一个 >=2048MiB 的 Free Space),否则返回空
|
||||
LANG=C parted -s "$ROOT_DISK" unit MiB print free 2>/dev/null | \
|
||||
awk '$4=="Free" && $3>=2048 {start=$1; size=$3} END {if (size>=2048) print start, size}'
|
||||
}
|
||||
|
||||
create_data_partition() {
|
||||
info "在 $ROOT_DISK 空闲空间中创建 /data 分区($DATA_LABEL)..."
|
||||
FREE_INFO="$(find_free_space || true)"
|
||||
[ -n "$FREE_INFO" ] || die "未找到足够空闲空间(至少 2GiB)。请先扩容 SD 卡镜像或参考文档手动划分 /data 分区。"
|
||||
FREE_START="$(echo "$FREE_INFO" | awk '{print $1}' | tr -d 'MiB')"
|
||||
FREE_SIZE="$(echo "$FREE_INFO" | awk '{print $2}')"
|
||||
|
||||
echo ""
|
||||
echo "即将执行:"
|
||||
echo " 在 $ROOT_DISK 偏移 ${FREE_START}MiB 处创建 ext4 分区(约 ${FREE_SIZE})"
|
||||
echo " 分区标签: $DATA_LABEL"
|
||||
echo " 该分区将挂载到 $DATA_MOUNT,保存日志/告警/设备ID等持久数据"
|
||||
echo ""
|
||||
if [ "$ASSUME_YES" -ne 1 ]; then
|
||||
read -r -p "继续? [y/N]: " yn
|
||||
[ "${yn:-n}" = "y" ] || [ "${yn:-n}" = "Y" ] || die "已取消"
|
||||
fi
|
||||
|
||||
# 分区表类型:gpt 用名称参数,msdos 用 primary
|
||||
TABLE_TYPE="$(parted -s "$ROOT_DISK" print 2>/dev/null | awk '/Partition Table/{print $3}')"
|
||||
if [ "$TABLE_TYPE" = "gpt" ]; then
|
||||
parted -s "$ROOT_DISK" mkpart "$DATA_LABEL" ext4 "${FREE_START}MiB" 100%
|
||||
else
|
||||
parted -s "$ROOT_DISK" mkpart primary ext4 "${FREE_START}MiB" 100%
|
||||
fi
|
||||
|
||||
# 刷新分区表
|
||||
partprobe "$ROOT_DISK" 2>/dev/null || partx -u "$ROOT_DISK" 2>/dev/null || true
|
||||
udevadm settle 2>/dev/null || sleep 3
|
||||
|
||||
# 新分区 = 磁盘上最后一个 part
|
||||
DATA_PART="$(lsblk -ln -o NAME,TYPE "$ROOT_DISK" | awk '$2=="part"{p=$1} END{print "/dev/"p}')"
|
||||
[ -n "${DATA_PART:-}" ] || die "分区创建失败"
|
||||
|
||||
mkfs.ext4 -F -L "$DATA_LABEL" "$DATA_PART"
|
||||
ok "数据分区已创建: $DATA_PART (标签 $DATA_LABEL)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# fstab 写入(先于挂载执行)
|
||||
# ---------------------------------------------------------------------------
|
||||
write_fstab() {
|
||||
[ -n "${DATA_UUID:-}" ] || die "未获取到数据分区 UUID"
|
||||
local bak="$FSTAB.bak-$(date +%Y%m%d%H%M%S)"
|
||||
cp -a "$FSTAB" "$bak"
|
||||
info "fstab 已备份: $bak"
|
||||
|
||||
# 移除旧的管理块(幂等)
|
||||
sed -i "/^${MARKER_BEGIN}/,/^${MARKER_END}/d" "$FSTAB"
|
||||
|
||||
{
|
||||
echo "$MARKER_BEGIN"
|
||||
echo "# 独立读写数据分区(断电保护核心,rootfs 只读后唯一持久化区域)"
|
||||
echo "UUID=$DATA_UUID $DATA_MOUNT ext4 defaults,noatime,nofail,x-systemd.device-timeout=10s,commit=60 0 2"
|
||||
|
||||
# safesight 运行数据 bind 到 /data(路径保持 /var/lib/... 不变,兼容现有部署)
|
||||
echo "# safesight 运行数据 → /data(日志/告警/视频/设备ID 断电不丢)"
|
||||
if [ "$HLS_MODE" = "ram" ]; then
|
||||
echo "# HLS 分片 → 内存 tmpfs(重启即丢,减小 SD 写入;容量按需调整)"
|
||||
echo "tmpfs $RUNTIME_ROOT/hls tmpfs defaults,noatime,size=512m,mode=755 0 0"
|
||||
else
|
||||
echo "$DATA_MOUNT/safesight/hls $RUNTIME_ROOT/hls none bind,nofail 0 0"
|
||||
fi
|
||||
echo "$DATA_MOUNT/safesight/logs $RUNTIME_ROOT/logs none bind,nofail 0 0"
|
||||
echo "$DATA_MOUNT/safesight/alarms $RUNTIME_ROOT/alarms none bind,nofail 0 0"
|
||||
echo "$DATA_MOUNT/safesight/clips $RUNTIME_ROOT/clips none bind,nofail 0 0"
|
||||
echo "$DATA_MOUNT/safesight/agent $AGENT_LIB none bind,nofail 0 0"
|
||||
|
||||
if [ "$JOURNAL_MODE" = "data" ]; then
|
||||
echo "# 系统 journal → /data(断电不丢,便于事后排查)"
|
||||
echo "$DATA_MOUNT/safesight/journal /var/log/journal none bind,nofail 0 0"
|
||||
fi
|
||||
echo "$MARKER_END"
|
||||
} >> "$FSTAB"
|
||||
|
||||
ok "fstab 已更新"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /data 目录初始化 + 存量数据迁移 + 挂载
|
||||
# ---------------------------------------------------------------------------
|
||||
setup_data_dirs() {
|
||||
local sub
|
||||
for sub in $DATA_SUBDIRS; do
|
||||
mkdir -p "$DATA_MOUNT/safesight/$sub"
|
||||
done
|
||||
[ "$JOURNAL_MODE" = "data" ] && mkdir -p "$DATA_MOUNT/safesight/journal"
|
||||
|
||||
# 迁移已有数据(bind 挂载前执行,避免覆盖)
|
||||
migrate_dir() {
|
||||
local src="$1" dst="$2" name="$3"
|
||||
# bind 已挂载时 src 与 dst 是同一目录,跳过
|
||||
[ "$(readlink -f "$src")" != "$(readlink -f "$dst")" ] || return 0
|
||||
if [ -d "$src" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ]; then
|
||||
cp -a "$src"/. "$dst"/ && ok "已迁移 $name 存量数据 → $dst"
|
||||
fi
|
||||
}
|
||||
for sub in hls logs alarms clips; do
|
||||
migrate_dir "$RUNTIME_ROOT/$sub" "$DATA_MOUNT/safesight/$sub" "$sub"
|
||||
done
|
||||
migrate_dir "$AGENT_LIB" "$DATA_MOUNT/safesight/agent" "agent"
|
||||
|
||||
# 确保 bind 目标目录存在
|
||||
for sub in hls logs alarms clips; do
|
||||
mkdir -p "$RUNTIME_ROOT/$sub"
|
||||
done
|
||||
mkdir -p "$AGENT_LIB"
|
||||
mkdir -p /var/log/journal
|
||||
}
|
||||
|
||||
mount_all() {
|
||||
# 挂载 /data 与全部 bind(依赖上一步 fstab/目录已就绪)
|
||||
mount -a 2>/dev/null || true
|
||||
if ! mountpoint -q "$DATA_MOUNT" 2>/dev/null; then
|
||||
die "$DATA_MOUNT 挂载失败,请检查 /etc/fstab"
|
||||
fi
|
||||
ok "$DATA_MOUNT 已挂载 rw (UUID=$DATA_UUID)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# journald 配置(控制系统日志内存占用 / 持久化)
|
||||
# ---------------------------------------------------------------------------
|
||||
setup_journald() {
|
||||
mkdir -p /etc/systemd/journald.conf.d
|
||||
if [ "$JOURNAL_MODE" = "data" ]; then
|
||||
cat > /etc/systemd/journald.conf.d/safesight.conf << 'EOF'
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
SystemMaxUse=256M
|
||||
SystemMaxFile=32M
|
||||
MaxRetentionSec=14d
|
||||
SyncIntervalSec=1h
|
||||
EOF
|
||||
ok "journald → /data(持久化,上限 256M / 保留14天)"
|
||||
else
|
||||
cat > /etc/systemd/journald.conf.d/safesight.conf << 'EOF'
|
||||
[Journal]
|
||||
Storage=volatile
|
||||
RuntimeMaxUse=64M
|
||||
EOF
|
||||
ok "journald → 内存(volatile,上限 64M,重启即丢)"
|
||||
fi
|
||||
systemctl restart systemd-journald 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# overlayroot 安装与配置
|
||||
# ---------------------------------------------------------------------------
|
||||
install_overlayroot() {
|
||||
if ! dpkg -s overlayroot >/dev/null 2>&1; then
|
||||
info "安装 overlayroot ..."
|
||||
apt-get update -qq 2>/dev/null || warn "apt update 失败(设备可能离线),尝试直接安装..."
|
||||
apt-get install -y overlayroot || die "overlayroot 安装失败。若设备离线,请将 overlayroot 预装进母卡镜像(sudo apt install overlayroot)后重新打包。"
|
||||
else
|
||||
ok "overlayroot 已安装"
|
||||
fi
|
||||
|
||||
# 备份旧配置
|
||||
if [ -f "$OVERLAY_CONF" ]; then
|
||||
cp -a "$OVERLAY_CONF" "$OVERLAY_CONF.bak-$(date +%Y%m%d%H%M%S)"
|
||||
fi
|
||||
|
||||
cat > "$OVERLAY_CONF" << 'EOF'
|
||||
# /etc/overlayroot.conf — 由 setup-overlayroot.sh 生成
|
||||
# 启用后根文件系统只读,一切写入进入内存(tmpfs),重启即丢,断电不损坏根分区。
|
||||
# 维护模式: 运行 sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable
|
||||
overlayroot="tmpfs"
|
||||
overlayroot_tmpfs_size="50%"
|
||||
overlayroot_quiet="n"
|
||||
# /data 是独立分区(fstab 挂载,不经过 overlay),保持可写,无需排除。
|
||||
# 若个别镜像出现其它分区被意外只读,可在此追加排除,例如:
|
||||
# overlayroot_exclude_partitions="/var/lib/docker"
|
||||
EOF
|
||||
ok "/etc/overlayroot.conf 已配置 (overlayroot=\"tmpfs\")"
|
||||
|
||||
info "重建 initramfs ..."
|
||||
update-initramfs -u || die "update-initramfs 失败"
|
||||
|
||||
if [ -d /boot/extlinux ]; then
|
||||
warn "检测到 extlinux 引导(U-Boot/香橙派)。overlayroot 由 initramfs 加载,无需改 extlinux.conf。"
|
||||
fi
|
||||
ok "initramfs 已更新"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 命令实现
|
||||
# ---------------------------------------------------------------------------
|
||||
cmd_install() {
|
||||
require_root
|
||||
detect_distro
|
||||
detect_root_disk
|
||||
|
||||
if overlay_active; then
|
||||
warn "当前系统已处于 overlayroot 保护模式。如需调整配置,请先运行: $0 disable"
|
||||
fi
|
||||
|
||||
# 1) 数据分区
|
||||
if data_partition_exists; then
|
||||
ok "检测到数据分区: ${DATA_PART:-(/data 已挂载)}"
|
||||
else
|
||||
if [ "${SKIP_PARTITION:-0}" = "1" ]; then
|
||||
die "SKIP_PARTITION=1 但未检测到 $DATA_LABEL 分区,请确认镜像包含数据分区"
|
||||
fi
|
||||
create_data_partition
|
||||
fi
|
||||
DATA_UUID="$(blkid -s UUID -o value "$DATA_PART" 2>/dev/null || true)"
|
||||
[ -n "$DATA_UUID" ] || die "未获取到数据分区 UUID ($DATA_PART)"
|
||||
|
||||
# 2) fstab + 目录 + 挂载(fstab 先写,再按新 fstab 挂载)
|
||||
write_fstab
|
||||
setup_data_dirs
|
||||
mount_all
|
||||
|
||||
# 3) journald
|
||||
setup_journald
|
||||
|
||||
# 4) overlayroot
|
||||
install_overlayroot
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} 配置完成!重启后根文件系统将进入只读保护模式${NC}"
|
||||
echo -e "${GREEN}════════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " sudo reboot # 重启生效"
|
||||
echo ""
|
||||
echo "重启后验证:"
|
||||
echo " sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh verify"
|
||||
echo ""
|
||||
echo "数据分区(断电不丢):"
|
||||
echo " $DATA_MOUNT/safesight/{logs,alarms,clips,agent}"
|
||||
echo " $DATA_MOUNT/safesight/hls # HLS 分片 (${HLS_MODE})"
|
||||
echo ""
|
||||
echo "日常维护(装软件/改系统配置,需临时可写):"
|
||||
echo " sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh disable"
|
||||
echo " sudo reboot # 进入可写维护模式"
|
||||
echo " sudo /opt/safesight-edge-server/scripts/setup-overlayroot.sh enable"
|
||||
echo " sudo reboot # 恢复只读保护"
|
||||
echo ""
|
||||
echo "注意:overlayroot 启用后,apt 安装、系统配置修改都会在重启后丢失,"
|
||||
echo " 必须进入维护模式操作!"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
require_root
|
||||
echo -e "${BLUE}══════════ overlayroot 状态 ══════════${NC}"
|
||||
|
||||
if overlay_active; then
|
||||
echo -e " 根文件系统: ${GREEN}● 保护模式(overlay, 只读+tmpfs)${NC}"
|
||||
mount | grep ' / ' | sed 's/^/ /'
|
||||
echo -e " 上层(tmpfs)容量: $(df -h / | awk 'NR==2{print $2}') 已用: $(df -h / | awk 'NR==2{print $3}')"
|
||||
else
|
||||
echo -e " 根文件系统: ${YELLOW}○ 可写模式(overlay 未启用)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if mountpoint -q "$DATA_MOUNT" 2>/dev/null; then
|
||||
echo -e " $DATA_MOUNT: ${GREEN}已挂载 rw${NC} $(findmnt -no SOURCE,FSTYPE "$DATA_MOUNT")"
|
||||
echo " 磁盘占用: $(df -h "$DATA_MOUNT" | awk 'NR==2{print "总"$2" 已用"$3" 可用"$4}')"
|
||||
else
|
||||
echo -e " $DATA_MOUNT: ${RED}未挂载${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " bind 挂载:"
|
||||
for b in logs alarms clips hls agent; do
|
||||
m="$RUNTIME_ROOT/$b"
|
||||
[ "$b" = "agent" ] && m="$AGENT_LIB"
|
||||
if mountpoint -q "$m" 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${NC} $m → $(findmnt -no SOURCE "$m")"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $m 未挂载"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [ -f "$OVERLAY_CONF" ]; then
|
||||
echo " /etc/overlayroot.conf:"
|
||||
grep -E '^overlayroot' "$OVERLAY_CONF" | sed 's/^/ /'
|
||||
else
|
||||
echo -e " /etc/overlayroot.conf: ${RED}不存在${NC}"
|
||||
fi
|
||||
|
||||
if [ -f /etc/systemd/journald.conf.d/safesight.conf ]; then
|
||||
echo ""
|
||||
echo " journald: $(grep -E '^\s*(Storage|RuntimeMaxUse|SystemMaxUse)=' /etc/systemd/journald.conf.d/safesight.conf | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ -f "$AGENT_LIB/device_id" ]; then
|
||||
echo " 设备 ID: $(cat "$AGENT_LIB/device_id")(持久化于 /data)"
|
||||
else
|
||||
echo -e " 设备 ID: ${YELLOW}尚未生成${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
require_root
|
||||
echo -e "${BLUE}══════════ 断电保护验证 ══════════${NC}"
|
||||
FAIL=0
|
||||
|
||||
if overlay_active; then
|
||||
ok "overlay 已启用,根分区写入受保护"
|
||||
else
|
||||
warn "overlay 未启用(仍处于可写模式)"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# /data 可写测试
|
||||
if mountpoint -q "$DATA_MOUNT"; then
|
||||
TEST_FILE="$DATA_MOUNT/.write_test_$(date +%s)"
|
||||
if echo ok > "$TEST_FILE" 2>/dev/null && rm -f "$TEST_FILE"; then
|
||||
ok "$DATA_MOUNT 可读写"
|
||||
else
|
||||
warn "$DATA_MOUNT 不可写!"
|
||||
FAIL=1
|
||||
fi
|
||||
else
|
||||
warn "$DATA_MOUNT 未挂载"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# bind 挂载
|
||||
for b in logs alarms clips agent; do
|
||||
m="$RUNTIME_ROOT/$b"
|
||||
[ "$b" = "agent" ] && m="$AGENT_LIB"
|
||||
if mountpoint -q "$m" 2>/dev/null; then
|
||||
ok "$m → $(findmnt -no SOURCE "$m")"
|
||||
else
|
||||
warn "$m 未挂载(若为首次启用,服务运行后会自动创建)"
|
||||
fi
|
||||
done
|
||||
|
||||
# 根分区是否真的只读(写测试)
|
||||
ROTEST="/.write_test_root_$$"
|
||||
if touch "$ROTEST" 2>/dev/null; then
|
||||
rm -f "$ROTEST"
|
||||
warn "警告: 根分区仍可写!请检查 overlayroot 配置"
|
||||
FAIL=1
|
||||
else
|
||||
ok "根分区只读"
|
||||
fi
|
||||
|
||||
# 服务状态
|
||||
for s in safesight-edge-server safesight-edge-agent; do
|
||||
if systemctl is-active --quiet "$s" 2>/dev/null; then
|
||||
ok "$s 运行中"
|
||||
else
|
||||
warn "$s 未运行"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ 断电保护配置正常,可放心断电${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}存在未通过项,请按上方提示处理${NC}"
|
||||
fi
|
||||
exit "$FAIL"
|
||||
}
|
||||
|
||||
# 切换 overlayroot 开关(disable=可写维护 / enable=恢复保护)
|
||||
toggle_overlay() {
|
||||
local mode="$1"
|
||||
require_root
|
||||
[ -f "$OVERLAY_CONF" ] || cp /dev/null "$OVERLAY_CONF"
|
||||
cp -a "$OVERLAY_CONF" "$OVERLAY_CONF.bak-$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
if [ "$mode" = "disable" ]; then
|
||||
sed -i 's/^overlayroot=.*/overlayroot=""/' "$OVERLAY_CONF"
|
||||
# 若文件里没有可识别的行则追加
|
||||
grep -q '^overlayroot=' "$OVERLAY_CONF" || echo 'overlayroot=""' >> "$OVERLAY_CONF"
|
||||
echo ""
|
||||
echo -e "${YELLOW}overlayroot 已设为空(可写模式)。重启后根分区恢复可写,用于安装软件/修改配置。${NC}"
|
||||
else
|
||||
sed -i 's/^overlayroot=.*/overlayroot="tmpfs"/' "$OVERLAY_CONF"
|
||||
grep -q '^overlayroot=' "$OVERLAY_CONF" || echo 'overlayroot="tmpfs"' >> "$OVERLAY_CONF"
|
||||
echo ""
|
||||
echo -e "${GREEN}overlayroot 已重新启用(保护模式)。重启后根分区只读。${NC}"
|
||||
fi
|
||||
|
||||
info "重建 initramfs ..."
|
||||
update-initramfs -u
|
||||
echo ""
|
||||
echo "请执行: sudo reboot"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 主入口
|
||||
# ---------------------------------------------------------------------------
|
||||
case "$CMD" in
|
||||
install) cmd_install ;;
|
||||
status) cmd_status ;;
|
||||
verify) cmd_verify ;;
|
||||
disable) toggle_overlay disable ;;
|
||||
enable) toggle_overlay enable ;;
|
||||
*)
|
||||
echo "RK3588 Edge 断电保护脚本(overlayroot + /data)"
|
||||
echo ""
|
||||
echo "用法: sudo $(basename "$0") [命令] [选项]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " install 一键启用断电保护(创建/挂载 /data + 启用 overlayroot)"
|
||||
echo " status 查看当前保护状态"
|
||||
echo " verify 重启后验证保护是否生效"
|
||||
echo " disable 临时禁用(维护模式:装软件/改系统配置)"
|
||||
echo " enable 重新启用(恢复保护)"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --yes 跳过确认"
|
||||
echo " --hls-ram HLS 分片放内存(减小 SD 写入,重启即丢)"
|
||||
echo " --journal-data 系统日志持久化到 /data(默认内存)"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " sudo $(basename "$0") install"
|
||||
echo " sudo $(basename "$0") install --hls-ram"
|
||||
echo " sudo $(basename "$0") status"
|
||||
;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user