Files
font2pic/deploy.sh
2026-07-26 10:52:38 +08:00

189 lines
8.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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}"
MYWEB_SUDO_PASSWORD="${MYWEB_SUDO_PASSWORD:-}"
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" "$@"
}
load_sudo_password() {
if [[ -n "$MYWEB_SUDO_PASSWORD" ]]; then
return
fi
[[ -r .env ]] || {
log_error '未找到 .env请设置 MYWEB_SUDO_PASSWORD 或在 .env 中配置密码。'
exit 1
}
local line secret='' plain_lines=()
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*(MYWEB_SUDO_PASSWORD|SUDO_PASSWORD|MYWEB_PASS)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
secret="${BASH_REMATCH[2]}"
elif [[ -z "${line//[[:space:]]/}" || "$line" == \#* ]]; then
continue
else
plain_lines+=("$line")
fi
done < .env
# 去除键值分隔符附近的空白,避免 `KEY = value` 将空格传给 sudo。
secret="${secret#"${secret%%[![:space:]]*}"}"
secret="${secret%"${secret##*[![:space:]]}"}"
if [[ -z "$secret" && ${#plain_lines[@]} -eq 1 ]]; then
secret="${plain_lines[0]}"
fi
# 兼容 .env 中常见的单引号或双引号包裹形式,不执行其中任何内容。
if [[ ${#secret} -ge 2 ]] && { [[ ${secret:0:1} == '"' && ${secret: -1} == '"' ]] || [[ ${secret:0:1} == "'" && ${secret: -1} == "'" ]]; }; then
secret="${secret:1:${#secret}-2}"
fi
[[ -n "$secret" ]] || {
log_error '无法从 .env 读取 sudo 密码;请使用 MYWEB_SUDO_PASSWORD=密码。'
exit 1
}
MYWEB_SUDO_PASSWORD="$secret"
}
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\"; if ! git diff --quiet || ! git diff --cached --quiet; then git stash push --include-untracked -m \"deploy auto-backup $(date +%Y%m%d-%H%M%S)\"; echo \"已将远端本地改动备份至 git stash\"; fi; 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
load_sudo_password
log_info '使用 .env 中的凭据执行 Nginx 配置与服务重启。'
# 禁用伪终端,避免标准输入中的敏感内容被远端终端回显。
printf '%s\n' "$MYWEB_SUDO_PASSWORD" | ssh -T -o ConnectTimeout=12 "$SERVER_WEB" "sudo -S -p '' bash -lc 'install -m 644 \"$REMOTE_WORKDIR/fonts.conf\" /etc/nginx/conf.d/fonts.conf && nginx -t && systemctl reload nginx && 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 "$@"