update at 2026-07-26 10:44:05
This commit is contained in:
198
deploy.sh
198
deploy.sh
@@ -1,86 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# deploy.sh
|
||||
#
|
||||
# 用途
|
||||
# - 一键执行本地推送 + 远端两台服务器拉取代码(A/B)。
|
||||
# - 默认部署分支:main。
|
||||
#
|
||||
# 执行流程
|
||||
# 1) 运行本地推送脚本(默认 ./gitrun.sh)
|
||||
# 2) 服务器 A 执行 git fetch + checkout + pull --ff-only
|
||||
# 3) 服务器 B 执行 git fetch + checkout + pull --ff-only
|
||||
#
|
||||
# 前置条件
|
||||
# - 本机已配置 SSH 免密登录到 A/B 两台服务器。
|
||||
# - 远端目录已存在 Git 仓库(默认 ~/font2svg)。
|
||||
# - 本地 GITRUN_SCRIPT 可执行。
|
||||
#
|
||||
# 可覆盖环境变量
|
||||
# - GITRUN_SCRIPT 本地推送脚本路径(默认 ./gitrun.sh)
|
||||
# - DEPLOY_BRANCH 部署分支(默认 main)
|
||||
# - REMOTE_WORKDIR 远端项目目录(默认 ~/font2svg)
|
||||
# - SERVER_A 服务器 A(默认 gavin@mac.biboer.cn)
|
||||
# - SERVER_A_PORT 服务器 A SSH 端口(默认 22)
|
||||
# - SERVER_B 服务器 B(默认 gavin@biboer.cn)
|
||||
# - SERVER_B_PORT 服务器 B SSH 端口(默认 21174)
|
||||
#
|
||||
# 示例
|
||||
# - 使用默认配置:
|
||||
# bash deploy.sh
|
||||
# - 指定分支:
|
||||
# DEPLOY_BRANCH=release bash deploy.sh
|
||||
# - 指定远端目录:
|
||||
# REMOTE_WORKDIR=~/apps/font2svg bash deploy.sh
|
||||
#
|
||||
# 失败策略
|
||||
# - set -euo pipefail:任一步失败立即退出,避免部分成功导致状态不一致。
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Font2SVG 生产部署脚本:M4 本机构建 → myweb 资源同步 → 服务与 Nginx 验收。
|
||||
set -euo pipefail
|
||||
|
||||
# 部署配置(可通过环境变量覆盖)
|
||||
GITRUN_SCRIPT="${GITRUN_SCRIPT:-./gitrun.sh}"
|
||||
DEPLOY_BRANCH="${DEPLOY_BRANCH:-main}"
|
||||
REMOTE_WORKDIR="${REMOTE_WORKDIR:-~/font2svg}"
|
||||
SERVER_A="${SERVER_A:-gavin@mac.biboer.cn}"
|
||||
SERVER_A_PORT="${SERVER_A_PORT:-22}"
|
||||
SERVER_B="${SERVER_B:-gavin@biboer.cn}"
|
||||
SERVER_B_PORT="${SERVER_B_PORT:-21174}"
|
||||
SSH_OPTS=(
|
||||
-o BatchMode=yes
|
||||
-o ConnectTimeout=8
|
||||
-o StrictHostKeyChecking=accept-new
|
||||
)
|
||||
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}"
|
||||
|
||||
log_info() {
|
||||
echo "[INFO] $1"
|
||||
}
|
||||
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'
|
||||
|
||||
run_local_push() {
|
||||
if [[ ! -x "$GITRUN_SCRIPT" ]]; then
|
||||
echo "[ERROR] git 推送脚本不可执行: $GITRUN_SCRIPT"
|
||||
echo "请先执行: chmod +x $GITRUN_SCRIPT"
|
||||
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
|
||||
fi
|
||||
log_info "执行本地推送脚本: $GITRUN_SCRIPT"
|
||||
"$GITRUN_SCRIPT"
|
||||
}
|
||||
}
|
||||
|
||||
run_remote_pull() {
|
||||
local host="$1"
|
||||
local port="$2"
|
||||
log_info "远程拉取: $host:$port ($DEPLOY_BRANCH)"
|
||||
ssh "${SSH_OPTS[@]}" -p "$port" "$host" \
|
||||
"cd $REMOTE_WORKDIR && git fetch origin && git checkout $DEPLOY_BRANCH && git pull --ff-only origin $DEPLOY_BRANCH"
|
||||
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() {
|
||||
run_local_push
|
||||
run_remote_pull "$SERVER_A" "$SERVER_A_PORT"
|
||||
run_remote_pull "$SERVER_B" "$SERVER_B_PORT"
|
||||
log_info "部署完成"
|
||||
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 "$@"
|
||||
|
||||
Reference in New Issue
Block a user