| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- #!/bin/bash
- ###############################################################################
- # PlateRecApp 一键部署启动脚本 (fix24 v41)
- #
- # 功能:
- # deploy 编译 + 部署 + 重启服务(默认动作)
- # build 仅编译
- # restart 重启 systemd 服务
- # start 启动服务
- # stop 停止服务
- # status 查看服务状态 + 端口 + 进程 + 磁盘
- # logs 跟踪运行日志 (PlateRecApp.log)
- # journal 跟踪 systemd journal
- # backup 手动备份当前部署
- # rollback 回滚到上一次备份
- # deps 检查/安装编译与运行依赖
- # sudoers 配置 frpc 管理所需 sudoers 权限
- #
- # 用法:
- # bash deploy.sh # 默认 = deploy(编译+部署+重启)
- # bash deploy.sh build
- # bash deploy.sh restart
- # bash deploy.sh logs
- # sudo bash deploy.sh deps # 安装系统依赖需要 root
- #
- # 可用环境变量覆盖默认值:
- # APP_DIR 部署目录 (默认 /opt/openAI/project/003.PlateRecAPP)
- # RUN_USER 运行用户 (默认 stevenroc)
- # SVC_NAME systemd 服务名 (默认 PlateRecApp)
- ###############################################################################
- set -euo pipefail
- # ============================ 配置区(可环境变量覆盖) ============================
- APP_DIR="${APP_DIR:-/opt/openAI/project/003.PlateRecAPP}"
- RUN_USER="${RUN_USER:-stevenroc}"
- SVC_NAME="${SVC_NAME:-PlateRecApp}"
- SERVICE_FILE="/etc/systemd/system/${SVC_NAME}.service"
- # 源码目录 = 本脚本所在目录的上一级(脚本位于 <src>/scripts/deploy.sh)
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- SRC_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
- BUILD_DIR="${SRC_DIR}/build"
- BIN_NAME="PlateRecApp"
- LOG_FILE="${APP_DIR}/PlateRecApp.log"
- BACKUP_ROOT="${APP_DIR}/backup"
- HEALTH_PORT="8080" # HTTPS 服务端口(与 config.ini web_server_url 一致)
- HTTP_REDIRECT_PORT="8079" # HTTP→HTTPS 重定向端口
- # 颜色输出
- 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 "${GREEN}[✓]${NC} $*"; }
- warn() { echo -e "${YELLOW}[!]${NC} $*"; }
- err() { echo -e "${RED}[✗]${NC} $*" >&2; }
- step() { echo -e "${CYAN}▶ $*${NC}"; }
- die() { err "$*"; exit 1; }
- # root 检测
- is_root() { [ "$(id -u)" -eq 0 ]; }
- # 以 RUN_USER 身份执行命令(root 时降权,非 root 且用户匹配则直接执行)
- as_run_user() {
- if is_root; then
- if id "$RUN_USER" &>/dev/null; then
- sudo -u "$RUN_USER" "$@"
- else
- warn "用户 $RUN_USER 不存在,以当前用户执行: $*"
- "$@"
- fi
- else
- "$@"
- fi
- }
- # ============================ 依赖检查 ============================
- deps_check() {
- step "检查编译/运行依赖..."
- local missing=()
- # 编译工具
- for cmd in cmake make g++ pkg-config; do
- command -v "$cmd" &>/dev/null || missing+=("$cmd")
- done
- # 运行工具
- for cmd in curl ss; do
- command -v "$cmd" &>/dev/null || missing+=("$cmd")
- done
- # 开发库(pkg-config / 头文件探测)
- pkg-config --exists opencv4 2>/dev/null || missing+=("libopencv-dev")
- pkg-config --exists openssl 2>/dev/null || missing+=("libssl-dev")
- pkg-config --exists sqlite3 2>/dev/null || missing+=("libsqlite3-dev")
- pkg-config --exists freetype2 2>/dev/null || missing+=("libfreetype-dev")
- pkg-config --exists libcurl 2>/dev/null || missing+=("libcurl4-openssl-dev")
- [ -f /usr/include/cjson/cJSON.h ] || [ -f /usr/local/include/cjson/cJSON.h ] || missing+=("libcjson-dev")
- # HyperLPR3 SDK(源码树内)
- [ -d "${SRC_DIR}/hyperlpr3/lib" ] || warn "未找到 ${SRC_DIR}/hyperlpr3/lib —— 编译需要 HyperLPR3 SDK,请确认 SDK 已部署到源码目录"
- if [ ${#missing[@]} -gt 0 ]; then
- warn "缺少依赖: ${missing[*]}"
- echo " 安装命令(Debian/Ubuntu/RPi OS):"
- echo " sudo apt update && sudo apt install -y cmake build-essential pkg-config \\"
- echo " libopencv-dev libssl-dev libsqlite3-dev libfreetype-dev \\"
- echo " libcurl4-openssl-dev libcjson-dev curl iproute2"
- return 1
- fi
- info "依赖检查通过"
- return 0
- }
- deps_install() {
- is_root || die "安装依赖需要 root,请用: sudo bash $0 deps"
- step "安装编译与运行依赖..."
- apt update
- apt install -y cmake build-essential pkg-config \
- libopencv-dev libssl-dev libsqlite3-dev libfreetype-dev \
- libcurl4-openssl-dev libcjson-dev curl iproute2
- info "依赖安装完成"
- # nginx stream 模块(v2 架构需要,不强制)
- if ! nginx -V 2>&1 | grep -q stream; then
- warn "nginx stream 模块未安装(v2 公网架构需要): sudo apt install -y libnginx-mod-stream"
- fi
- }
- # ============================ 编译 ============================
- do_build() {
- step "编译 PlateRecApp (源码: ${SRC_DIR})"
- deps_check || die "依赖缺失,请先运行: sudo bash $0 deps"
- mkdir -p "${BUILD_DIR}"
- cd "${BUILD_DIR}"
- cmake -DCMAKE_BUILD_TYPE=Release "${SRC_DIR}"
- make -j"$(nproc)"
- [ -x "${BUILD_DIR}/${BIN_NAME}" ] || die "编译失败:未生成 ${BUILD_DIR}/${BIN_NAME}"
- info "编译成功: ${BUILD_DIR}/${BIN_NAME} ($(du -h "${BUILD_DIR}/${BIN_NAME}" | cut -f1))"
- }
- # ============================ 部署 ============================
- ensure_dirs() {
- step "创建部署目录结构: ${APP_DIR}"
- mkdir -p "${APP_DIR}"/{assets/web,hyperlpr3,PlateJPG,data,scripts,ssl,backup}
- }
- backup_current() {
- if [ ! -f "${APP_DIR}/${BIN_NAME}" ]; then
- info "首次部署(${APP_DIR}/${BIN_NAME} 不存在),跳过备份"
- return 0
- fi
- local ts; ts="$(date +%Y%m%d_%H%M%S)"
- local bk="${BACKUP_ROOT}/${ts}"
- step "备份当前版本 -> ${bk}"
- mkdir -p "${bk}"
- cp -a "${APP_DIR}/${BIN_NAME}" "${bk}/" 2>/dev/null || true
- cp -a "${APP_DIR}/config.ini" "${bk}/" 2>/dev/null || true
- cp -a "${APP_DIR}/config.txt" "${bk}/" 2>/dev/null || true
- [ -d "${APP_DIR}/assets" ] && cp -a "${APP_DIR}/assets" "${bk}/" 2>/dev/null || true
- [ -d "${APP_DIR}/hyperlpr3" ] && cp -a "${APP_DIR}/hyperlpr3" "${bk}/" 2>/dev/null || true
- # 记录最新备份路径
- echo "${bk}" > "${BACKUP_ROOT}/.latest"
- # 只保留最近 5 个备份
- ls -1dt "${BACKUP_ROOT}"/20* 2>/dev/null | tail -n +6 | xargs -r rm -rf
- info "备份完成(保留最近 5 个版本)"
- }
- do_deploy() {
- do_build
- is_root || die "部署需要 root(写入 ${APP_DIR} / 安装 systemd 服务),请用: sudo bash $0 deploy"
- ensure_dirs
- backup_current
- step "复制程序文件..."
- install -m 0755 "${BUILD_DIR}/${BIN_NAME}" "${APP_DIR}/${BIN_NAME}"
- # Web 前端资源(全量同步,保证 help.html 等更新生效)
- if [ -d "${SRC_DIR}/assets/web" ]; then
- cp -a "${SRC_DIR}/assets/web/." "${APP_DIR}/assets/web/"
- info "Web 资源已更新 ($(ls -1 "${SRC_DIR}/assets/web" | wc -l) 个文件)"
- fi
- # HyperLPR3 SDK(模型 + 动态库),缺失时给出警告但不阻断
- if [ -d "${SRC_DIR}/hyperlpr3" ]; then
- cp -an "${SRC_DIR}/hyperlpr3/." "${APP_DIR}/hyperlpr3/" 2>/dev/null || true
- info "HyperLPR3 SDK 已同步(-n 不覆盖已有模型文件)"
- else
- warn "源码树无 hyperlpr3/ 目录,沿用部署目录现有 SDK"
- fi
- # 运维脚本
- cp -a "${SCRIPT_DIR}/." "${APP_DIR}/scripts/" 2>/dev/null || true
- # config.ini:首次部署从模板生成;已存在则永不覆盖(保留现场配置)
- if [ ! -f "${APP_DIR}/config.ini" ]; then
- if [ -f "${SRC_DIR}/config.ini" ]; then
- cp -a "${SRC_DIR}/config.ini" "${APP_DIR}/config.ini"
- warn "已生成初始 config.ini,请按现场环境修改(RTSP/上报地址/飞书等)后重启"
- else
- warn "源码包中无 config.ini 模板,请手动放置到 ${APP_DIR}/config.ini"
- fi
- else
- info "config.ini 已存在,保留现场配置不覆盖"
- fi
- # 动态库搜索路径:hyperlpr3/lib 加入 rpath 链接缓存(部署目录内)
- if [ -d "${APP_DIR}/hyperlpr3/lib" ]; then
- echo "${APP_DIR}/hyperlpr3/lib" > /etc/ld.so.conf.d/platerecapp.conf
- ldconfig
- info "已注册动态库路径: ${APP_DIR}/hyperlpr3/lib"
- fi
- # 目录属主
- if id "$RUN_USER" &>/dev/null; then
- chown -R "${RUN_USER}:${RUN_USER}" "${APP_DIR}"
- info "目录属主已设置为 ${RUN_USER}"
- else
- warn "用户 ${RUN_USER} 不存在,请先创建: sudo useradd -r -s /bin/bash ${RUN_USER}"
- fi
- install_service
- do_restart
- sleep 3
- do_status
- info "部署完成 ✅ 访问: https://<设备IP>:${HEALTH_PORT}/"
- }
- # ============================ systemd 服务 ============================
- install_service() {
- step "安装 systemd 服务: ${SERVICE_FILE}"
- cat > "${SERVICE_FILE}" <<EOF
- [Unit]
- Description=PlateRecApp License Plate Recognition System
- After=network-online.target frpc.service
- Wants=network-online.target
- [Service]
- Type=simple
- User=${RUN_USER}
- Group=${RUN_USER}
- WorkingDirectory=${APP_DIR}
- ExecStartPre=/bin/sleep 10
- ExecStart=${APP_DIR}/${BIN_NAME}
- TimeoutStartSec=60
- TimeoutStopSec=15
- Restart=on-failure
- RestartSec=5
- StandardOutput=append:${LOG_FILE}
- StandardError=append:${LOG_FILE}
- LimitNOFILE=1048576
- [Install]
- WantedBy=multi-user.target
- EOF
- systemctl daemon-reload
- systemctl enable "${SVC_NAME}" &>/dev/null || true
- info "服务已安装并设置开机自启"
- }
- do_start() { step "启动 ${SVC_NAME}"; systemctl start "${SVC_NAME}"; sleep 2; do_status; }
- do_stop() { step "停止 ${SVC_NAME}"; systemctl stop "${SVC_NAME}"; info "已停止"; }
- do_restart() { step "重启 ${SVC_NAME}"; systemctl restart "${SVC_NAME}"; sleep 2; info "已重启"; }
- do_status() {
- step "服务状态"
- systemctl status "${SVC_NAME}" --no-pager -l 2>/dev/null || warn "服务未运行或未安装"
- echo
- step "端口监听"
- ss -tlnp 2>/dev/null | grep -E ":${HEALTH_PORT}|:${HTTP_REDIRECT_PORT}" || warn "未检测到 ${HEALTH_PORT}/${HTTP_REDIRECT_PORT} 端口监听(服务可能仍在初始化,启动有 10s sleep)"
- echo
- step "进程"
- pgrep -af "${BIN_NAME}" | grep -v grep || warn "无 ${BIN_NAME} 进程"
- echo
- step "磁盘占用 (${APP_DIR})"
- if [ -d "${APP_DIR}" ]; then
- du -sh "${APP_DIR}/PlateJPG" 2>/dev/null || true
- du -sh "${APP_DIR}" 2>/dev/null || true
- fi
- }
- do_logs() { step "跟踪日志: ${LOG_FILE} (Ctrl+C 退出)"; touch "${LOG_FILE}" 2>/dev/null; tail -n 100 -f "${LOG_FILE}"; }
- do_journal() { step "journalctl -u ${SVC_NAME} (Ctrl+C 退出)"; journalctl -u "${SVC_NAME}" -f --no-pager; }
- # ============================ 备份/回滚 ============================
- do_backup() {
- is_root || die "备份需要 root"
- ensure_dirs
- backup_current
- }
- do_rollback() {
- is_root || die "回滚需要 root"
- local bk
- bk="$(cat "${BACKUP_ROOT}/.latest" 2>/dev/null || true)"
- [ -n "$bk" ] && [ -d "$bk" ] || die "没有可用备份(${BACKUP_ROOT})"
- step "回滚到: ${bk}"
- systemctl stop "${SVC_NAME}" 2>/dev/null || true
- [ -f "${bk}/${BIN_NAME}" ] && cp -a "${bk}/${BIN_NAME}" "${APP_DIR}/${BIN_NAME}"
- [ -f "${bk}/config.ini" ] && cp -a "${bk}/config.ini" "${APP_DIR}/config.ini"
- [ -f "${bk}/config.txt" ] && cp -a "${bk}/config.txt" "${APP_DIR}/config.txt"
- [ -d "${bk}/assets" ] && rm -rf "${APP_DIR}/assets" && cp -a "${bk}/assets" "${APP_DIR}/assets"
- [ -d "${bk}/hyperlpr3" ] && rm -rf "${APP_DIR}/hyperlpr3" && cp -a "${bk}/hyperlpr3" "${APP_DIR}/hyperlpr3"
- chown -R "${RUN_USER}:${RUN_USER}" "${APP_DIR}" 2>/dev/null || true
- # 回滚后移除 .latest 指向,防止重复回滚同版本
- rm -f "${BACKUP_ROOT}/.latest"
- systemctl start "${SVC_NAME}"
- sleep 3
- do_status
- info "回滚完成"
- }
- # ============================ sudoers ============================
- do_sudoers() {
- is_root || die "配置 sudoers 需要 root: sudo bash $0 sudoers"
- step "配置 frpc 管理 sudoers 权限"
- bash "${SCRIPT_DIR}/setup_frpc_sudoers.sh" "${RUN_USER}"
- }
- # ============================ 入口 ============================
- usage() {
- sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
- exit 0
- }
- case "${1:-deploy}" in
- deploy) do_deploy ;;
- build) do_build ;;
- start) do_start ;;
- stop) do_stop ;;
- restart) do_restart; do_status ;;
- status) do_status ;;
- logs) do_logs ;;
- journal) do_journal ;;
- backup) do_backup ;;
- rollback) do_rollback ;;
- deps) deps_install ;;
- sudoers) do_sudoers ;;
- -h|--help|help) usage ;;
- *) die "未知命令: $1(运行 bash $0 help 查看用法)" ;;
- esac
|