#!/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 --gw <网关> --dns [选项]" echo "" echo "必选:" echo " --hostname <名称> 如 safesight-01" echo " --ip 如 10.0.0.83/24" echo " --gw <网关> 如 10.0.0.1" echo " --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 " 在管理端发现该设备 → 设置别名 → 下发配置"