#!/usr/bin/env bash # Font2SVG 生产部署脚本:M4 本机构建 → myweb 资源同步 → 服务与 Nginx 验收。 set -euo pipefail DEPLOY_BRANCH="${DEPLOY_BRANCH:-main}" GITRUN_SCRIPT="${GITRUN_SCRIPT:-./gitrun.sh}" SERVER_WEB="${SERVER_WEB:-myweb}" REMOTE_WORKDIR="${REMOTE_WORKDIR:-/home/gavin/font2svg}" SKIP_PUSH="${SKIP_PUSH:-0}" SKIP_SUDO="${SKIP_SUDO:-0}" COLOR_RESET=$'\033[0m' COLOR_INFO=$'\033[1;36m' COLOR_OK=$'\033[1;32m' COLOR_WARN=$'\033[1;33m' COLOR_ERROR=$'\033[1;31m' COLOR_STEP=$'\033[1;35m' log_step() { printf '\n%s==> %s%s\n' "$COLOR_STEP" "$1" "$COLOR_RESET"; } log_info() { printf '%s[信息]%s %s\n' "$COLOR_INFO" "$COLOR_RESET" "$1"; } log_ok() { printf '%s[完成]%s %s\n' "$COLOR_OK" "$COLOR_RESET" "$1"; } log_warn() { printf '%s[注意]%s %s\n' "$COLOR_WARN" "$COLOR_RESET" "$1"; } log_error() { printf '%s[失败]%s %s\n' "$COLOR_ERROR" "$COLOR_RESET" "$1" >&2; } require_command() { command -v "$1" >/dev/null 2>&1 || { log_error "缺少本机命令:$1" exit 1 } } ssh_web() { ssh -o BatchMode=yes -o ConnectTimeout=12 "$SERVER_WEB" "$@" } check_local_environment() { log_step '1/8 检查 M4 本机环境' for tool in git pnpm node python3 rsync ssh curl; do require_command "$tool" done [[ -f frontend/package.json ]] || { log_error '未找到 frontend/package.json,请在仓库根目录执行。'; exit 1; } [[ -f frontend/vite.config.ts ]] || { log_error '缺少 frontend/vite.config.ts:该本机配置用于加载 UnoCSS,无法构建前端。' exit 1 } [[ -f miniprogram/assets/fonts.json ]] || { log_error '缺少小程序字体清单。'; exit 1; } [[ -f frontend/public/fonts.json ]] || { log_error '缺少 Web 字体清单。'; exit 1; } [[ -d fonts ]] || { log_error '缺少 fonts/ 字体目录。'; exit 1; } node scripts/check-font-assets.js --manifest miniprogram/assets/fonts.json --root . node scripts/check-font-assets.js --manifest frontend/public/fonts.json --root . log_info "本机字体文件:$(find fonts -type f | wc -l | tr -d ' ') 个" log_ok '本机构建与同步依赖齐全' } build_and_check() { log_step '2/8 构建并校验 Web 前端' # 单独执行应用类型检查,避免将 Vite 配置文件误作应用源码检查。 pnpm -C frontend exec vue-tsc --noEmit -p tsconfig.app.json pnpm -C frontend run build [[ -f frontend/dist/index.html ]] || { log_error '前端构建未生成 index.html。'; exit 1; } pnpm run mp:syntax pnpm run mp:lint pnpm run mp:test log_ok '前端构建、类型检查和小程序校验通过' } push_source() { log_step '3/8 推送源代码到 Git' if [[ "$SKIP_PUSH" == '1' ]]; then log_warn 'SKIP_PUSH=1,跳过 Git 推送' return fi [[ -x "$GITRUN_SCRIPT" ]] || { log_error "推送脚本不可执行:$GITRUN_SCRIPT"; exit 1; } "$GITRUN_SCRIPT" log_ok '源代码已推送' } prepare_remote_code() { log_step '4/8 检查 myweb 并同步代码' ssh_web "bash -lc 'set -e; command -v git python3 node npm curl >/dev/null; test -d \"$REMOTE_WORKDIR/.git\"; cd \"$REMOTE_WORKDIR\"; git fetch origin \"$DEPLOY_BRANCH\"; git checkout \"$DEPLOY_BRANCH\"; git pull --ff-only origin \"$DEPLOY_BRANCH\"; git log -1 --oneline'" log_ok 'myweb 代码已同步' } sync_assets() { log_step '5/8 增量同步字体、前端构建产物与图标' ssh_web "mkdir -p '$REMOTE_WORKDIR/fonts' '$REMOTE_WORKDIR/miniprogram/assets' '$REMOTE_WORKDIR/assets/icons' '$REMOTE_WORKDIR/frontend/dist'" rsync -az --partial fonts/ "$SERVER_WEB:$REMOTE_WORKDIR/fonts/" rsync -az miniprogram/assets/fonts.json miniprogram/assets/default.json miniprogram/assets/route-config.json "$SERVER_WEB:$REMOTE_WORKDIR/miniprogram/assets/" rsync -az frontend/public/fonts.json "$SERVER_WEB:$REMOTE_WORKDIR/fonts.json" rsync -az --delete frontend/dist/ "$SERVER_WEB:$REMOTE_WORKDIR/frontend/dist/" rsync -az miniprogram/assets/icons/ "$SERVER_WEB:$REMOTE_WORKDIR/assets/icons/" rsync -az miniprogram/assets/icons/webicon.png "$SERVER_WEB:$REMOTE_WORKDIR/assets/webicon.png" rsync -az fonts.conf "$SERVER_WEB:$REMOTE_WORKDIR/fonts.conf" log_ok '静态资源和 Nginx 配置已同步' } prepare_remote_runtime() { log_step '6/8 修复 myweb Python 运行环境' ssh_web "bash -lc 'set -e; cd \"$REMOTE_WORKDIR\"; python3 -m venv .venv; .venv/bin/python -m pip install -q -r requirements.txt; .venv/bin/python -c \"import cv2, fontTools, uharfbuzz, numpy; print(\\\"Python 依赖正常\\\")\"'" log_ok 'Python 虚拟环境与依赖正常' } apply_nginx_and_restart() { log_step '7/8 应用 Nginx 配置并重启 API' if [[ "$SKIP_SUDO" == '1' ]]; then log_warn 'SKIP_SUDO=1,跳过需要 sudo 的 Nginx/API 重启' return fi log_info '将要求输入 myweb 的 sudo 密码,用于安装配置、校验并重载 Nginx。' ssh -tt -o ConnectTimeout=12 "$SERVER_WEB" "sudo install -m 644 '$REMOTE_WORKDIR/fonts.conf' /etc/nginx/conf.d/fonts.conf && sudo nginx -t && sudo systemctl reload nginx && sudo systemctl restart font2svg-api" log_ok 'Nginx 已重载,Font2SVG API 已重启' } verify_deployment() { log_step '8/8 验收 myweb 与公网服务' local local_font_count remote_font_count local_font_count=$(find fonts -type f | wc -l | tr -d ' ') remote_font_count=$(ssh_web "find '$REMOTE_WORKDIR/fonts' -type f | wc -l | tr -d ' '") [[ "$remote_font_count" -ge "$local_font_count" ]] || { log_error "远端字体数量不足:$remote_font_count/$local_font_count" exit 1 } ssh_web "bash -lc 'cd \"$REMOTE_WORKDIR\"; node scripts/check-font-assets.js --manifest miniprogram/assets/fonts.json --root .; node scripts/check-font-assets.js --manifest fonts.json --root .; test -s frontend/dist/index.html; test -s assets/webicon.png'" ssh_web "bash -lc 'systemctl is-active --quiet font2svg-api; curl -fsS http://127.0.0.1:9300/healthz'" curl --noproxy '*' --connect-timeout 10 --max-time 30 -fsS https://fonts.biboer.cn/ >/dev/null curl --noproxy '*' --connect-timeout 10 --max-time 30 -fsS https://fonts.biboer.cn/healthz >/dev/null curl --noproxy '*' --connect-timeout 10 --max-time 30 -fsS https://fonts.biboer.cn/fonts.json >/dev/null log_ok "部署验收通过:字体 $remote_font_count/$local_font_count,首页、API 与清单均可访问" } main() { check_local_environment build_and_check push_source prepare_remote_code sync_assets prepare_remote_runtime apply_nginx_and_restart verify_deployment printf '\n%s部署完成:M4 → %s%s\n' "$COLOR_OK" "$SERVER_WEB" "$COLOR_RESET" } main "$@"