- 新增 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 批量烧录->每台初始化) - 移除废弃的克隆卡流程
555 lines
22 KiB
Bash
555 lines
22 KiB
Bash
#!/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
|