#!/usr/bin/env bash # ============================================================ # GovAI - Go 补丁版本升级脚本 # 功能:检查 Go 最新补丁版 -> 本地编译 -> 上传部署 -> 重启服务 # 监控:建议通过 cron 定期执行(如每周一次) # 用法: bash go-upgrade.sh [check|upgrade|status] # check - 仅检查版本(默认) # upgrade - 执行完整升级流程 # status - 查看当前 Go 版本和服务状态 # ============================================================ set -euo pipefail SERVER="${SERVER:-govai-root}" REMOTE_DIR="/opt/govai" REMOTE_SERVER_BIN="$REMOTE_DIR/server/server" PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" SERVER_GO_VERSION="${SERVER_GO_VERSION:-1.25.0}" GREEN='\033[0;32m'; BLUE='\033[0;34m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; NC='\033[0m' log() { echo -e "${GREEN}[✓]${NC} $1"; } step() { echo -e "\n${BLUE}==== $1 ====${NC}"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } err() { echo -e "${RED}[✗]${NC} $1"; exit 1; } # ---- 当前版本信息 ---- CURRENT_GO_VERSION=$(go version 2>/dev/null | sed 's/go version //' | awk '{print $1}') MAJOR_MINOR=$(echo "$CURRENT_GO_VERSION" | sed 's/\.[0-9]*$//') echo "当前本地 Go 版本: $CURRENT_GO_VERSION" echo "服务器 Go 版本: $SERVER_GO_VERSION" # ---- 获取 Go 官方最新补丁版本 ---- fetch_latest_patch() { local major_minor="$1" # 从 Go 官方 dl 页面解析最新补丁版本 # 例如: go1.25.11, go1.24.5 等 local url="https://go.dev/dl/?mode=json&include=all" local latest latest=$(curl -sL "$url" 2>/dev/null | \ python3 -c " import sys, json data = json.load(sys.stdin) current = '$major_minor' for p in data: v = p.get('version','') if v.startswith('go' + current + '.'): print(v) break " 2>/dev/null) || true echo "$latest" } # ---- 版本比较 ---- # 返回 0 表示有更新,返回 1 表示已是最新 is_update_available() { local current="$1" local latest="$2" [ -n "$latest" ] && [ "$latest" != "$current" ] } # ---- 检查模式 ---- do_check() { step "检查 Go 版本更新" echo "" local latest latest=$(fetch_latest_patch "$MAJOR_MINOR") echo " 本地当前版本: $CURRENT_GO_VERSION" echo " $MAJOR_MINOR 系列最新: $latest" echo "" if is_update_available "$CURRENT_GO_VERSION" "$latest"; then echo " 发现新版本: $CURRENT_GO_VERSION → $latest" echo " 建议执行: bash go-upgrade.sh upgrade" return 0 else echo " ✅ 当前版本已是最新" return 1 fi } # ---- 升级模式 ---- do_upgrade() { local latest latest=$(fetch_latest_patch "$MAJOR_MINOR") if ! is_update_available "$CURRENT_GO_VERSION" "$latest"; then echo "已是最新版本,无需升级" return 0 fi local new_patch="${latest#go}" step "Go 升级: $CURRENT_GO_VERSION → go$new_patch" # 1. 安装新版本 Go echo "" log "下载 Go $new_patch..." cd /tmp local go_archive="go$new_patch.darwin-arm64.tar.gz" local download_url="https://go.dev/dl/$go_archive" # 检测架构 local arch case "$(uname -m)" in x86_64) arch="darwin-amd64" ;; arm64|aarch64) arch="darwin-arm64" ;; *) err "不支持的架构: $(uname -m)" ;; esac go_archive="go$new_patch.$arch.tar.gz" download_url="https://go.dev/dl/$go_archive" if [ -f "/usr/local/go/bin/go" ]; then if /usr/local/go/bin/go version 2>/dev/null | grep -q "$new_patch"; then echo "目标版本已安装" else curl -sL "$download_url" -o "go.tar.gz" rm -rf /usr/local/go tar -C /usr/local -xzf go.tar.gz rm -f go.tar.gz fi else curl -sL "$download_url" -o "go.tar.gz" tar -C /usr/local -xzf go.tar.gz rm -f go.tar.gz fi local new_go="/usr/local/go/bin/go" echo " 新版本: $($new_go version)" # 2. 备份当前服务器二进制 step "备份服务器二进制" local backup_file="server.bak.$(date '+%Y%m%d%H%M%S')" ssh "$SERVER" "cp $REMOTE_SERVER_BIN $REMOTE_DIR/$backup_file" # 3. 本地编译 step "编译新版本二进制" local dist_dir="$PROJECT_DIR/dist" mkdir -p "$dist_dir" log "编译 Go 后端 (linux/amd64)..." export PATH="/usr/local/go/bin:$PATH" export GOROOT="/usr/local/go" cd "$PROJECT_DIR/server" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOROOT="/usr/local/go" go build \ -ldflags="-s -w" \ -o "$dist_dir/server" ./cmd/server/ local binary_size binary_size=$(ls -lh "$dist_dir/server" 2>/dev/null | awk '{print $5}') log "编译完成: dist/server ($binary_size)" # 4. 上传 step "上传到服务器" ssh $SERVER "systemctl stop govai-api 2>/dev/null || true" ssh $SERVER "cp $REMOTE_SERVER_BIN $REMOTE_DIR/server.bak.pre-upgrade" cat "$dist_dir/server" | ssh $SERVER "cat > $REMOTE_SERVER_BIN.tmp && mv $REMOTE_SERVER_BIN.tmp $REMOTE_SERVER_BIN && chmod +x $REMOTE_SERVER_BIN" log "上传完成" # 5. 重启服务 step "重启服务" ssh $SERVER "systemctl start govai-api && sleep 2 && systemctl status govai-api --no-pager | head -5" # 6. 验证 step "验证服务" if ssh $SERVER "curl -sf http://localhost:8080/api/v1/store/featured -o /dev/null"; then log "API 服务正常" else err "API 服务异常,请检查: ssh $SERVER 'journalctl -u govai-api -n 30'" fi # 7. 记录 local upgrade_log="$PROJECT_DIR/.go-upgrade-log" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $CURRENT_GO_VERSION → go$new_patch" >> "$upgrade_log" echo "" echo -e "${GREEN}════════════════════════════════════════${NC}" echo -e "${GREEN} 升级完成!${NC}" echo -e "${GREEN} Go: $CURRENT_GO_VERSION → go$new_patch${NC}" echo -e "${GREEN}════════════════════════════════════════${NC}" } # ---- 状态模式 ---- do_status() { step "版本状态" echo " 本地 Go 版本: $CURRENT_GO_VERSION" ssh $SERVER "echo ' 服务器 Go 版本: $SERVER_GO_VERSION'; echo ' GovAI 二进制:'; ssh $SERVER 'ls -lh $REMOTE_SERVER_BIN 2>/dev/null || echo 不存在'" echo "" local latest latest=$(fetch_latest_patch "$MAJOR_MINOR" 2>/dev/null) echo " $MAJOR_MINOR 系列最新: ${latest:-无法获取}" echo "" # 检查 govulncheck if command -v ~/go/bin/govulncheck &>/dev/null; then echo " govulncheck 已知漏洞: $(cd "$PROJECT_DIR/server" && ~/go/bin/govulncheck ./... 2>&1 | grep -c '^Vulnerability #' || echo 0) 个" fi } # ---- 主流程 ---- ACTION="${1:-check}" case "$ACTION" in check) do_check ;; upgrade) do_upgrade ;; status) do_status ;; *) echo "用法: bash go-upgrade.sh [check|upgrade|status]" echo "" echo " check - 检查 Go 版本更新(默认)" echo " upgrade - 执行完整升级:下载 -> 编译 -> 上传 -> 重启" echo " status - 查看当前 Go 版本和补丁状态" exit 1 ;; esac