#!/usr/bin/env bash # 本地构建 + 上传 + 远程发布(前后端都走预构建,服务器不编译) # # SERVER=user@server ./scripts/push-build.sh # SERVER=user@server REMOTE_DIR=/opt/fashion WEB_DIR=../test ./scripts/push-build.sh # # 流程: # 1. 建 SSH 隧道(本地 18091 → 服务器回环 18091):前端构建期靠它读线上 SSG 数据 # 2. 本机 npm run build → 静态页内容与线上数据一致,且不占服务器资源 # 3. 本机交叉编译 linux/amd64 → 产出 server-linux-amd64 # 4. 上传 dist/ 与二进制 # 5. 远程 ./deploy.sh all --prebuilt → 服务器只打镜像 + 蓝绿切换 set -euo pipefail cd "$(dirname "$0")/.." # 后端仓根 SERVER="${SERVER:-}" if [ -z "$SERVER" ]; then echo "用法: SERVER=user@host ./scripts/push-build.sh" echo "" echo "可选环境变量:" echo " REMOTE_DIR 服务器上的项目根(默认 /opt/fashion)" echo " WEB_DIR 本地前端仓(默认 ../test)" echo " SSH_OPTS 额外 ssh 参数(默认 -o ConnectTimeout=10)" exit 1 fi REMOTE_DIR="${REMOTE_DIR:-/opt/fashion}" REMOTE_BACKEND="${REMOTE_BACKEND:-${REMOTE_DIR}/backend_v2}" REMOTE_FRONTEND="${REMOTE_FRONTEND:-${REMOTE_DIR}/frontend_v2}" WEB_DIR="${WEB_DIR:-../frontend_v2}" SSH_OPTS="${SSH_OPTS:--o ConnectTimeout=10}" TUNNEL_PORT="${TUNNEL_PORT:-18091}" # 本机也放一份 .env(复制服务器那份即可),用于取 SSG_TOKEN 等构建期密钥 SSG_TOKEN="${SSG_TOKEN:-}" if [ -f .env ]; then # shellcheck disable=SC1091 set -a; . ./.env; set +a fi # Git Bash 的 $HOME 常是 /home/,而 Windows 上生成的密钥在 C:\Users\\.ssh 下。 # 这里自动兜底,避免在 Git Bash 里 ssh 找不到私钥而报 publickey denied。 if [ -z "${SSH_KEY:-}" ]; then # Windows 用户目录优先:Git Bash 的 $HOME 下可能还躺着一把无关的旧钥匙 for cand in "/c/Users/${USERNAME:-}/.ssh/id_ed25519" "${USERPROFILE:-}/.ssh/id_ed25519" "$HOME/.ssh/id_ed25519"; do if [ -n "$cand" ] && [ -f "$cand" ]; then SSH_KEY="$cand" break fi done fi if [ -n "${SSH_KEY:-}" ]; then SSH_OPTS="$SSH_OPTS -i $SSH_KEY" echo " 使用私钥:$SSH_KEY" fi TUNNEL_PID="" cleanup() { [ -n "$TUNNEL_PID" ] && kill "$TUNNEL_PID" 2>/dev/null || true; } trap cleanup EXIT echo "==> 0/5 检查 SSH 免密登录" # 隧道是后台起的(ssh -N ... &),一旦需要交互输密码就会静默卡住,所以必须免密。 if ! ssh $SSH_OPTS -o BatchMode=yes "$SERVER" true 2>/dev/null; then echo "!! 无法免密登录 $SERVER" echo "" echo " Windows(PowerShell):" echo " ssh-keygen -t ed25519 -C deploy@local" echo " type \$env:USERPROFILE\\.ssh\\id_ed25519.pub | ssh $SERVER \"mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys\"" echo "" echo " Linux / macOS / WSL:" echo " ssh-keygen -t ed25519 -C deploy@local && ssh-copy-id $SERVER" echo "" echo " 配好后用这条确认:ssh $SERVER true && echo ok" exit 1 fi echo " 通过" # 服务器若开了 SSG_TOKEN,构建期必须带同一个值。自动从服务器 .env 取,避免本机再维护一份密钥。 if [ -z "${SSG_TOKEN:-}" ]; then SSG_TOKEN="$(ssh $SSH_OPTS -o BatchMode=yes "$SERVER" \ "grep -m1 '^SSG_TOKEN=' '${REMOTE_BACKEND}/.env' 2>/dev/null | cut -d= -f2-" 2>/dev/null || true)" if [ -n "$SSG_TOKEN" ]; then echo " 已从服务器读取 SSG_TOKEN(${#SSG_TOKEN} 字符)" else echo " 服务器未设置 SSG_TOKEN,构建期不带 token" fi fi echo "==> 1/5 建立 SSH 隧道(本地 ${TUNNEL_PORT} → ${SERVER}:127.0.0.1:18091)" ssh -N $SSH_OPTS -o ExitOnForwardFailure=yes \ -L "${TUNNEL_PORT}:127.0.0.1:18091" "$SERVER" & TUNNEL_PID=$! for i in $(seq 1 15); do if (echo > "/dev/tcp/127.0.0.1/${TUNNEL_PORT}") >/dev/null 2>&1; then break fi if [ "$i" -eq 15 ]; then echo "!! 隧道 15 秒内没建起来(检查 SSH 连通性、服务器上 proxy 是否在跑)" exit 1 fi sleep 1 done echo "==> 2/5 构建前端(构建期数据来自线上)" (cd "$WEB_DIR" && BASE_API_SSG="http://127.0.0.1:${TUNNEL_PORT}" SSG_TOKEN="$SSG_TOKEN" npm run build) [ -f "$WEB_DIR/dist/server/entry.mjs" ] || { echo "!! 前端产物缺失:$WEB_DIR/dist/server/entry.mjs"; exit 1; } echo "==> 3/5 交叉编译后端(linux/amd64)" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -trimpath -ldflags "-s -w" -o server-linux-amd64 ./cmd/server echo "==> 4/5 上传产物到 ${SERVER}:${REMOTE_DIR}" ssh $SSH_OPTS "$SERVER" "mkdir -p '${REMOTE_BACKEND}' '${REMOTE_FRONTEND}'" tar -C "$WEB_DIR" -czf - dist | ssh $SSH_OPTS "$SERVER" "tar -xzf - -C '${REMOTE_FRONTEND}'" scp $SSH_OPTS server-linux-amd64 "${SERVER}:${REMOTE_BACKEND}/server-linux-amd64" echo "==> 5/5 远程发布(预构建 + 蓝绿切换)" RELEASE="$(date +%Y%m%d-%H%M%S)-$(git rev-parse --short HEAD 2>/dev/null || echo nogit)" ssh $SSH_OPTS "$SERVER" "cd '${REMOTE_BACKEND}' && RELEASE='${RELEASE}' ./deploy.sh all --prebuilt" echo "完成:$RELEASE"