#!/bin/bash #======================================================================= # FILE: scripts/sync-board-time.sh # DESC: 校正 223 板卡(RK3588,默认 192.168.0.223)的系统时间:以 # 本机时间(或 --time 指定时间)为基准,经 SSH 设置板卡系统时 # 钟,并尽力写入硬件时钟 (RTC)。板卡网络隔离无 NTP,用本脚本对时。 # # 用法: # ./scripts/sync-board-time.sh # 用本机时间校正板卡 # ./scripts/sync-board-time.sh status # 只查看时间与偏差 # ./scripts/sync-board-time.sh --time "2026-09-01 12:00:00" # # 用指定时间校正(本地时区) # # 参数: # --host 目标主机(默认 192.168.0.223) # --user 登录用户(默认 root) # --time 手动指定基准时间(date 可解析的格式,按本地时区解释) # --keep-ntp 不执行 timedatectl set-ntp false # --no-rtc 不写硬件时钟(跳过 hwclock --systohc) # -h, --help 显示帮助 # # 说明: # - 需要 root 登录与免密 SSH(ssh-copy-id root@)。 # - 对时经 SSH 往返时延 (RTT) 折半补偿,精度约 ±0.1s(受网络抖动影响)。 # - 板卡若无可用 RTC,hwclock 失败仅告警;重启后时间可能回跳。 # - 对时会调整系统时钟,运行中的服务日志时间戳会跳变,属正常现象。 #======================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HOST="192.168.0.223" USER="root" SET_TIME="" KEEP_NTP=0 NO_RTC=0 info() { printf '\033[1;36m[time]\033[0m %s\n' "$*"; } ok() { printf '\033[1;32m[time]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[time]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[time]\033[0m %s\n' "$*"; } usage() { sed -n '2,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit 0 } #------------------------------------------------------------------- # 参数解析 #------------------------------------------------------------------- ACTION="" while [ $# -gt 0 ]; do case "$1" in --host) [ $# -ge 2 ] || { err "--host 需要参数"; exit 1; }; HOST="$2"; shift 2 ;; --user) [ $# -ge 2 ] || { err "--user 需要参数"; exit 1; }; USER="$2"; shift 2 ;; --time) [ $# -ge 2 ] || { err "--time 需要参数"; exit 1; }; SET_TIME="$2"; shift 2 ;; --keep-ntp) KEEP_NTP=1; shift ;; --no-rtc) NO_RTC=1; shift ;; status|help|-h|--help) ACTION="$1"; shift ;; "") shift ;; *) err "未知参数: $1(见 help)"; exit 1 ;; esac done [ -n "${ACTION}" ] || ACTION="sync" SSH_TARGET="${USER}@${HOST}" SSH="ssh -o BatchMode=yes -o ConnectTimeout=5" #------------------------------------------------------------------- # 读取板卡时间并测 RTT # 输出: "|<板卡可读时间> "(t0/t1 为本机采样时刻) #------------------------------------------------------------------- read_board_time() { local t0 t1 out t0="$(date +%s.%N)" out="$(${SSH} "${SSH_TARGET}" 'date "+%s.%N|%F %T %Z"' 2>/dev/null)" || { err "无法免密 SSH 到 ${SSH_TARGET}(先配置免密: ssh-copy-id ${SSH_TARGET})" >&2 return 1 } t1="$(date +%s.%N)" printf '%s %.3f %.3f\n' "${out}" "${t0}" "${t1}" } # 从 read_board_time 输出解析: set -- 后取 $1/$2/$3 parse_board_time() { B_EPOCH="${1%%|*}" B_DATE="${1#*|}" T0="$2" T1="$3" MID="$(awk -v t0="${T0}" -v t1="${T1}" 'BEGIN{printf "%.3f", t0 + (t1-t0)/2}')" DIFF="$(awk -v b="${B_EPOCH}" -v m="${MID}" 'BEGIN{printf "%+.3f", b - m}')" } #------------------------------------------------------------------- # 命令: status #------------------------------------------------------------------- cmd_status() { local out out="$(read_board_time)" || return 1 parse_board_time ${out} local rtt rtt="$(awk -v t0="${T0}" -v t1="${T1}" 'BEGIN{printf "%.3f", t1-t0}')" printf '本机时间: %s (RTT %.3fs)\n' "$(date -d "@${MID}" '+%F %T')" "${rtt}" printf '板卡时间: %s\n' "${B_DATE}" printf '偏差: 板卡比本机快 %s 秒\n' "${DIFF}" } #------------------------------------------------------------------- # 命令: sync(默认) #------------------------------------------------------------------- cmd_sync() { local out target board_reply # 前置检查:SSH 连通 + 板卡 root 权限 out="$(read_board_time)" || return 1 parse_board_time ${out} if ! ${SSH} "${SSH_TARGET}" '[ "$(id -u)" = 0 ]' 2>/dev/null; then err "板卡登录用户需要 root 权限(当前 --user ${USER})" return 1 fi ok "SSH 连接正常: ${SSH_TARGET}" info "当前偏差: 板卡比本机快 ${DIFF} 秒" # 关闭 NTP 自动同步(板卡网络隔离本就无 NTP 源,防止干扰手动对时) if [ "${KEEP_NTP}" = "0" ]; then info "关闭板卡 NTP 自动同步 (timedatectl set-ntp false) ..." ${SSH} "${SSH_TARGET}" "command -v timedatectl >/dev/null 2>&1 && timedatectl set-ntp false || true" fi # 计算目标时间:手动指定值直接用;否则取本机时间 + RTT/2 补偿 if [ -n "${SET_TIME}" ]; then target="$(date -d "${SET_TIME}" '+%s.%N' 2>/dev/null)" || { err "无法解析时间: ${SET_TIME}"; return 1; } info "基准时间(手动): ${SET_TIME}" else local t2 t2="$(date +%s.%N)" target="$(awk -v t2="${t2}" -v t0="${T0}" -v t1="${T1}" 'BEGIN{printf "%.3f", t2 + (t1-t0)/2}')" info "基准时间(本机 + RTT/2 补偿)" fi info "设置板卡系统时间 -> $(date -d "@${target}" '+%F %T') ..." board_reply="$(${SSH} "${SSH_TARGET}" "date -s @${target} 2>&1")" || { err "date -s 失败: ${board_reply}" return 1 } ok "板卡返回: ${board_reply}" # 写硬件时钟(无 RTC 时仅告警) if [ "${NO_RTC}" = "0" ]; then info "同步硬件时钟 (hwclock --systohc) ..." if ${SSH} "${SSH_TARGET}" "hwclock --systohc" 2>/dev/null; then ok "RTC 已同步" else warn "hwclock 失败(板卡可能无 RTC 或驱动未加载),重启后时间可能回跳" fi fi # 校验 out="$(read_board_time)" || return 1 parse_board_time ${out} info "校正后偏差: 板卡比本机快 ${DIFF} 秒" ok "板卡时间: ${B_DATE}" } #------------------------------------------------------------------- # 命令入口 #------------------------------------------------------------------- case "${ACTION}" in sync) cmd_sync ;; status) cmd_status ;; help|-h|--help) usage ;; *) usage ;; esac