182 lines
7.8 KiB
Bash
Executable File
182 lines
7.8 KiB
Bash
Executable File
#!/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
|
||
case "$line" in
|
||
MYWEB_SUDO_PASSWORD=*|SUDO_PASSWORD=*) secret="${line#*=}" ;;
|
||
''|'#'*) ;;
|
||
*) plain_lines+=("$line") ;;
|
||
esac
|
||
done < .env
|
||
|
||
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 读取 sudo 密码,用于安装配置、校验并重载 Nginx。'
|
||
printf '%s\n' "$MYWEB_SUDO_PASSWORD" | ssh -tt -o ConnectTimeout=12 "$SERVER_WEB" "sudo -S -p '' install -m 644 '$REMOTE_WORKDIR/fonts.conf' /etc/nginx/conf.d/fonts.conf && sudo -S -p '' nginx -t && sudo -S -p '' systemctl reload nginx && sudo -S -p '' 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 "$@"
|