deploy.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/bin/bash
  2. ###############################################################################
  3. # PlateRecApp 一键部署启动脚本 (fix24 v41)
  4. #
  5. # 功能:
  6. # deploy 编译 + 部署 + 重启服务(默认动作)
  7. # build 仅编译
  8. # restart 重启 systemd 服务
  9. # start 启动服务
  10. # stop 停止服务
  11. # status 查看服务状态 + 端口 + 进程 + 磁盘
  12. # logs 跟踪运行日志 (PlateRecApp.log)
  13. # journal 跟踪 systemd journal
  14. # backup 手动备份当前部署
  15. # rollback 回滚到上一次备份
  16. # deps 检查/安装编译与运行依赖
  17. # sudoers 配置 frpc 管理所需 sudoers 权限
  18. #
  19. # 用法:
  20. # bash deploy.sh # 默认 = deploy(编译+部署+重启)
  21. # bash deploy.sh build
  22. # bash deploy.sh restart
  23. # bash deploy.sh logs
  24. # sudo bash deploy.sh deps # 安装系统依赖需要 root
  25. #
  26. # 可用环境变量覆盖默认值:
  27. # APP_DIR 部署目录 (默认 /opt/openAI/project/003.PlateRecAPP)
  28. # RUN_USER 运行用户 (默认 stevenroc)
  29. # SVC_NAME systemd 服务名 (默认 PlateRecApp)
  30. ###############################################################################
  31. set -euo pipefail
  32. # ============================ 配置区(可环境变量覆盖) ============================
  33. APP_DIR="${APP_DIR:-/opt/openAI/project/003.PlateRecAPP}"
  34. RUN_USER="${RUN_USER:-stevenroc}"
  35. SVC_NAME="${SVC_NAME:-PlateRecApp}"
  36. SERVICE_FILE="/etc/systemd/system/${SVC_NAME}.service"
  37. # 源码目录 = 本脚本所在目录的上一级(脚本位于 <src>/scripts/deploy.sh)
  38. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  39. SRC_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
  40. BUILD_DIR="${SRC_DIR}/build"
  41. BIN_NAME="PlateRecApp"
  42. LOG_FILE="${APP_DIR}/PlateRecApp.log"
  43. BACKUP_ROOT="${APP_DIR}/backup"
  44. HEALTH_PORT="8080" # HTTPS 服务端口(与 config.ini web_server_url 一致)
  45. HTTP_REDIRECT_PORT="8079" # HTTP→HTTPS 重定向端口
  46. # 颜色输出
  47. RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
  48. BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
  49. info() { echo -e "${GREEN}[✓]${NC} $*"; }
  50. warn() { echo -e "${YELLOW}[!]${NC} $*"; }
  51. err() { echo -e "${RED}[✗]${NC} $*" >&2; }
  52. step() { echo -e "${CYAN}▶ $*${NC}"; }
  53. die() { err "$*"; exit 1; }
  54. # root 检测
  55. is_root() { [ "$(id -u)" -eq 0 ]; }
  56. # 以 RUN_USER 身份执行命令(root 时降权,非 root 且用户匹配则直接执行)
  57. as_run_user() {
  58. if is_root; then
  59. if id "$RUN_USER" &>/dev/null; then
  60. sudo -u "$RUN_USER" "$@"
  61. else
  62. warn "用户 $RUN_USER 不存在,以当前用户执行: $*"
  63. "$@"
  64. fi
  65. else
  66. "$@"
  67. fi
  68. }
  69. # ============================ 依赖检查 ============================
  70. deps_check() {
  71. step "检查编译/运行依赖..."
  72. local missing=()
  73. # 编译工具
  74. for cmd in cmake make g++ pkg-config; do
  75. command -v "$cmd" &>/dev/null || missing+=("$cmd")
  76. done
  77. # 运行工具
  78. for cmd in curl ss; do
  79. command -v "$cmd" &>/dev/null || missing+=("$cmd")
  80. done
  81. # 开发库(pkg-config / 头文件探测)
  82. pkg-config --exists opencv4 2>/dev/null || missing+=("libopencv-dev")
  83. pkg-config --exists openssl 2>/dev/null || missing+=("libssl-dev")
  84. pkg-config --exists sqlite3 2>/dev/null || missing+=("libsqlite3-dev")
  85. pkg-config --exists freetype2 2>/dev/null || missing+=("libfreetype-dev")
  86. pkg-config --exists libcurl 2>/dev/null || missing+=("libcurl4-openssl-dev")
  87. [ -f /usr/include/cjson/cJSON.h ] || [ -f /usr/local/include/cjson/cJSON.h ] || missing+=("libcjson-dev")
  88. # HyperLPR3 SDK(源码树内)
  89. [ -d "${SRC_DIR}/hyperlpr3/lib" ] || warn "未找到 ${SRC_DIR}/hyperlpr3/lib —— 编译需要 HyperLPR3 SDK,请确认 SDK 已部署到源码目录"
  90. if [ ${#missing[@]} -gt 0 ]; then
  91. warn "缺少依赖: ${missing[*]}"
  92. echo " 安装命令(Debian/Ubuntu/RPi OS):"
  93. echo " sudo apt update && sudo apt install -y cmake build-essential pkg-config \\"
  94. echo " libopencv-dev libssl-dev libsqlite3-dev libfreetype-dev \\"
  95. echo " libcurl4-openssl-dev libcjson-dev curl iproute2"
  96. return 1
  97. fi
  98. info "依赖检查通过"
  99. return 0
  100. }
  101. deps_install() {
  102. is_root || die "安装依赖需要 root,请用: sudo bash $0 deps"
  103. step "安装编译与运行依赖..."
  104. apt update
  105. apt install -y cmake build-essential pkg-config \
  106. libopencv-dev libssl-dev libsqlite3-dev libfreetype-dev \
  107. libcurl4-openssl-dev libcjson-dev curl iproute2
  108. info "依赖安装完成"
  109. # nginx stream 模块(v2 架构需要,不强制)
  110. if ! nginx -V 2>&1 | grep -q stream; then
  111. warn "nginx stream 模块未安装(v2 公网架构需要): sudo apt install -y libnginx-mod-stream"
  112. fi
  113. }
  114. # ============================ 编译 ============================
  115. do_build() {
  116. step "编译 PlateRecApp (源码: ${SRC_DIR})"
  117. deps_check || die "依赖缺失,请先运行: sudo bash $0 deps"
  118. mkdir -p "${BUILD_DIR}"
  119. cd "${BUILD_DIR}"
  120. cmake -DCMAKE_BUILD_TYPE=Release "${SRC_DIR}"
  121. make -j"$(nproc)"
  122. [ -x "${BUILD_DIR}/${BIN_NAME}" ] || die "编译失败:未生成 ${BUILD_DIR}/${BIN_NAME}"
  123. info "编译成功: ${BUILD_DIR}/${BIN_NAME} ($(du -h "${BUILD_DIR}/${BIN_NAME}" | cut -f1))"
  124. }
  125. # ============================ 部署 ============================
  126. ensure_dirs() {
  127. step "创建部署目录结构: ${APP_DIR}"
  128. mkdir -p "${APP_DIR}"/{assets/web,hyperlpr3,PlateJPG,data,scripts,ssl,backup}
  129. }
  130. backup_current() {
  131. if [ ! -f "${APP_DIR}/${BIN_NAME}" ]; then
  132. info "首次部署(${APP_DIR}/${BIN_NAME} 不存在),跳过备份"
  133. return 0
  134. fi
  135. local ts; ts="$(date +%Y%m%d_%H%M%S)"
  136. local bk="${BACKUP_ROOT}/${ts}"
  137. step "备份当前版本 -> ${bk}"
  138. mkdir -p "${bk}"
  139. cp -a "${APP_DIR}/${BIN_NAME}" "${bk}/" 2>/dev/null || true
  140. cp -a "${APP_DIR}/config.ini" "${bk}/" 2>/dev/null || true
  141. cp -a "${APP_DIR}/config.txt" "${bk}/" 2>/dev/null || true
  142. [ -d "${APP_DIR}/assets" ] && cp -a "${APP_DIR}/assets" "${bk}/" 2>/dev/null || true
  143. [ -d "${APP_DIR}/hyperlpr3" ] && cp -a "${APP_DIR}/hyperlpr3" "${bk}/" 2>/dev/null || true
  144. # 记录最新备份路径
  145. echo "${bk}" > "${BACKUP_ROOT}/.latest"
  146. # 只保留最近 5 个备份
  147. ls -1dt "${BACKUP_ROOT}"/20* 2>/dev/null | tail -n +6 | xargs -r rm -rf
  148. info "备份完成(保留最近 5 个版本)"
  149. }
  150. do_deploy() {
  151. do_build
  152. is_root || die "部署需要 root(写入 ${APP_DIR} / 安装 systemd 服务),请用: sudo bash $0 deploy"
  153. ensure_dirs
  154. backup_current
  155. step "复制程序文件..."
  156. install -m 0755 "${BUILD_DIR}/${BIN_NAME}" "${APP_DIR}/${BIN_NAME}"
  157. # Web 前端资源(全量同步,保证 help.html 等更新生效)
  158. if [ -d "${SRC_DIR}/assets/web" ]; then
  159. cp -a "${SRC_DIR}/assets/web/." "${APP_DIR}/assets/web/"
  160. info "Web 资源已更新 ($(ls -1 "${SRC_DIR}/assets/web" | wc -l) 个文件)"
  161. fi
  162. # HyperLPR3 SDK(模型 + 动态库),缺失时给出警告但不阻断
  163. if [ -d "${SRC_DIR}/hyperlpr3" ]; then
  164. cp -an "${SRC_DIR}/hyperlpr3/." "${APP_DIR}/hyperlpr3/" 2>/dev/null || true
  165. info "HyperLPR3 SDK 已同步(-n 不覆盖已有模型文件)"
  166. else
  167. warn "源码树无 hyperlpr3/ 目录,沿用部署目录现有 SDK"
  168. fi
  169. # 运维脚本
  170. cp -a "${SCRIPT_DIR}/." "${APP_DIR}/scripts/" 2>/dev/null || true
  171. # config.ini:首次部署从模板生成;已存在则永不覆盖(保留现场配置)
  172. if [ ! -f "${APP_DIR}/config.ini" ]; then
  173. if [ -f "${SRC_DIR}/config.ini" ]; then
  174. cp -a "${SRC_DIR}/config.ini" "${APP_DIR}/config.ini"
  175. warn "已生成初始 config.ini,请按现场环境修改(RTSP/上报地址/飞书等)后重启"
  176. else
  177. warn "源码包中无 config.ini 模板,请手动放置到 ${APP_DIR}/config.ini"
  178. fi
  179. else
  180. info "config.ini 已存在,保留现场配置不覆盖"
  181. fi
  182. # 动态库搜索路径:hyperlpr3/lib 加入 rpath 链接缓存(部署目录内)
  183. if [ -d "${APP_DIR}/hyperlpr3/lib" ]; then
  184. echo "${APP_DIR}/hyperlpr3/lib" > /etc/ld.so.conf.d/platerecapp.conf
  185. ldconfig
  186. info "已注册动态库路径: ${APP_DIR}/hyperlpr3/lib"
  187. fi
  188. # 目录属主
  189. if id "$RUN_USER" &>/dev/null; then
  190. chown -R "${RUN_USER}:${RUN_USER}" "${APP_DIR}"
  191. info "目录属主已设置为 ${RUN_USER}"
  192. else
  193. warn "用户 ${RUN_USER} 不存在,请先创建: sudo useradd -r -s /bin/bash ${RUN_USER}"
  194. fi
  195. install_service
  196. do_restart
  197. sleep 3
  198. do_status
  199. info "部署完成 ✅ 访问: https://<设备IP>:${HEALTH_PORT}/"
  200. }
  201. # ============================ systemd 服务 ============================
  202. install_service() {
  203. step "安装 systemd 服务: ${SERVICE_FILE}"
  204. cat > "${SERVICE_FILE}" <<EOF
  205. [Unit]
  206. Description=PlateRecApp License Plate Recognition System
  207. After=network-online.target frpc.service
  208. Wants=network-online.target
  209. [Service]
  210. Type=simple
  211. User=${RUN_USER}
  212. Group=${RUN_USER}
  213. WorkingDirectory=${APP_DIR}
  214. ExecStartPre=/bin/sleep 10
  215. ExecStart=${APP_DIR}/${BIN_NAME}
  216. TimeoutStartSec=60
  217. TimeoutStopSec=15
  218. Restart=on-failure
  219. RestartSec=5
  220. StandardOutput=append:${LOG_FILE}
  221. StandardError=append:${LOG_FILE}
  222. LimitNOFILE=1048576
  223. [Install]
  224. WantedBy=multi-user.target
  225. EOF
  226. systemctl daemon-reload
  227. systemctl enable "${SVC_NAME}" &>/dev/null || true
  228. info "服务已安装并设置开机自启"
  229. }
  230. do_start() { step "启动 ${SVC_NAME}"; systemctl start "${SVC_NAME}"; sleep 2; do_status; }
  231. do_stop() { step "停止 ${SVC_NAME}"; systemctl stop "${SVC_NAME}"; info "已停止"; }
  232. do_restart() { step "重启 ${SVC_NAME}"; systemctl restart "${SVC_NAME}"; sleep 2; info "已重启"; }
  233. do_status() {
  234. step "服务状态"
  235. systemctl status "${SVC_NAME}" --no-pager -l 2>/dev/null || warn "服务未运行或未安装"
  236. echo
  237. step "端口监听"
  238. ss -tlnp 2>/dev/null | grep -E ":${HEALTH_PORT}|:${HTTP_REDIRECT_PORT}" || warn "未检测到 ${HEALTH_PORT}/${HTTP_REDIRECT_PORT} 端口监听(服务可能仍在初始化,启动有 10s sleep)"
  239. echo
  240. step "进程"
  241. pgrep -af "${BIN_NAME}" | grep -v grep || warn "无 ${BIN_NAME} 进程"
  242. echo
  243. step "磁盘占用 (${APP_DIR})"
  244. if [ -d "${APP_DIR}" ]; then
  245. du -sh "${APP_DIR}/PlateJPG" 2>/dev/null || true
  246. du -sh "${APP_DIR}" 2>/dev/null || true
  247. fi
  248. }
  249. do_logs() { step "跟踪日志: ${LOG_FILE} (Ctrl+C 退出)"; touch "${LOG_FILE}" 2>/dev/null; tail -n 100 -f "${LOG_FILE}"; }
  250. do_journal() { step "journalctl -u ${SVC_NAME} (Ctrl+C 退出)"; journalctl -u "${SVC_NAME}" -f --no-pager; }
  251. # ============================ 备份/回滚 ============================
  252. do_backup() {
  253. is_root || die "备份需要 root"
  254. ensure_dirs
  255. backup_current
  256. }
  257. do_rollback() {
  258. is_root || die "回滚需要 root"
  259. local bk
  260. bk="$(cat "${BACKUP_ROOT}/.latest" 2>/dev/null || true)"
  261. [ -n "$bk" ] && [ -d "$bk" ] || die "没有可用备份(${BACKUP_ROOT})"
  262. step "回滚到: ${bk}"
  263. systemctl stop "${SVC_NAME}" 2>/dev/null || true
  264. [ -f "${bk}/${BIN_NAME}" ] && cp -a "${bk}/${BIN_NAME}" "${APP_DIR}/${BIN_NAME}"
  265. [ -f "${bk}/config.ini" ] && cp -a "${bk}/config.ini" "${APP_DIR}/config.ini"
  266. [ -f "${bk}/config.txt" ] && cp -a "${bk}/config.txt" "${APP_DIR}/config.txt"
  267. [ -d "${bk}/assets" ] && rm -rf "${APP_DIR}/assets" && cp -a "${bk}/assets" "${APP_DIR}/assets"
  268. [ -d "${bk}/hyperlpr3" ] && rm -rf "${APP_DIR}/hyperlpr3" && cp -a "${bk}/hyperlpr3" "${APP_DIR}/hyperlpr3"
  269. chown -R "${RUN_USER}:${RUN_USER}" "${APP_DIR}" 2>/dev/null || true
  270. # 回滚后移除 .latest 指向,防止重复回滚同版本
  271. rm -f "${BACKUP_ROOT}/.latest"
  272. systemctl start "${SVC_NAME}"
  273. sleep 3
  274. do_status
  275. info "回滚完成"
  276. }
  277. # ============================ sudoers ============================
  278. do_sudoers() {
  279. is_root || die "配置 sudoers 需要 root: sudo bash $0 sudoers"
  280. step "配置 frpc 管理 sudoers 权限"
  281. bash "${SCRIPT_DIR}/setup_frpc_sudoers.sh" "${RUN_USER}"
  282. }
  283. # ============================ 入口 ============================
  284. usage() {
  285. sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
  286. exit 0
  287. }
  288. case "${1:-deploy}" in
  289. deploy) do_deploy ;;
  290. build) do_build ;;
  291. start) do_start ;;
  292. stop) do_stop ;;
  293. restart) do_restart; do_status ;;
  294. status) do_status ;;
  295. logs) do_logs ;;
  296. journal) do_journal ;;
  297. backup) do_backup ;;
  298. rollback) do_rollback ;;
  299. deps) deps_install ;;
  300. sudoers) do_sudoers ;;
  301. -h|--help|help) usage ;;
  302. *) die "未知命令: $1(运行 bash $0 help 查看用法)" ;;
  303. esac