Files
2026-06-15 23:48:37 +08:00

428 lines
13 KiB
Bash
Executable File
Raw Permalink 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
# ============================================================
# GovAI 政务AI平台 - 一键部署脚本(Git + 无Docker
# 目标服务器: agents (154.8.162.18)
# 域名: gov.opc8ai.com
# 用法: bash deploy.sh [init|update|restart|migrate|status|logs]
# init - 首次部署(安装依赖+初始化数据库+部署应用)
# update - 更新部署(git提交+构建+上传+迁移+重启)
# restart - 仅重启服务
# migrate - 仅执行数据库迁移
# status - 查看远程服务状态
# logs - 查看远程服务日志
# ============================================================
set -euo pipefail
SERVER="h2agent"
REMOTE_DIR="/opt/govai"
DOMAIN="gov.opc8ai.com"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
GIT_REMOTE="${GIT_REMOTE:-origin}"
GIT_BRANCH="${GIT_BRANCH:-main}"
DB_USER="govai"
DB_PASS="GovAI@2024Secure"
DB_NAME="govai_portal"
JWT_SECRET="govai-production-jwt-secret-x7k9m2p4"
OPENAI_API_KEY="${OPENAI_API_KEY:-sk-placeholder}"
OPENAI_BASE_URL="${OPENAI_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}"
OPENAI_MODEL="${OPENAI_MODEL:-qwen-plus}"
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; }
# ---- Git 操作 ----
git_commit_and_push() {
step "Git 提交与推送"
cd "$PROJECT_DIR"
if [ ! -d .git ]; then
log "初始化 Git 仓库..."
git init
git add .
git commit -m "feat: 初始化 GovAI 政务AI平台"
log "Git 仓库已初始化"
fi
if ! git remote get-url "$GIT_REMOTE" &>/dev/null; then
warn "未配置 Git 远程仓库 ($GIT_REMOTE),跳过推送"
warn "请手动执行: git remote add origin <your-repo-url>"
return 0
fi
local changes
changes=$(git status --porcelain)
if [ -n "$changes" ]; then
log "检测到未提交的更改:"
echo "$changes" | head -20
[ "$(echo "$changes" | wc -l)" -gt 20 ] && echo " ... 还有更多文件"
echo ""
git add .
local msg="deploy: $(date '+%Y-%m-%d %H:%M') 更新部署"
git commit -m "$msg"
log "已提交: $msg"
else
log "工作区干净,无需提交"
fi
log "推送到远程 ($GIT_REMOTE/$GIT_BRANCH)..."
git push -u "$GIT_REMOTE" "$GIT_BRANCH" 2>&1 || warn "推送失败,继续部署..."
}
# ---- 本地构建 ----
build_local() {
step "本地构建"
mkdir -p "$PROJECT_DIR/dist"
log "编译 Go 后端 (linux/amd64)..."
cd "$PROJECT_DIR/server"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$PROJECT_DIR/dist/server" ./cmd/server/
log "后端: dist/server ($(du -h "$PROJECT_DIR/dist/server" | cut -f1))"
log "编译 Next.js 前端..."
cd "$PROJECT_DIR/apps/web"
NEXT_PUBLIC_API_URL="http://localhost:8080" npm run build
log "前端编译完成"
cd "$PROJECT_DIR"
}
# ---- 上传文件 ----
upload() {
step "上传文件到服务器"
ssh $SERVER "mkdir -p $REMOTE_DIR/{server,web,migrations}"
log "停止服务以释放文件锁..."
ssh $SERVER "systemctl stop govai-api govai-web 2>/dev/null || true"
log "上传后端二进制..."
scp "$PROJECT_DIR/dist/server" $SERVER:$REMOTE_DIR/server/server
log "上传迁移文件..."
rsync -az --delete "$PROJECT_DIR/server/migrations/" $SERVER:$REMOTE_DIR/migrations/
log "上传前端构建产物..."
rsync -az --delete "$PROJECT_DIR/apps/web/.next/" $SERVER:$REMOTE_DIR/web/.next/
rsync -az "$PROJECT_DIR/apps/web/public/" $SERVER:$REMOTE_DIR/web/public/ 2>/dev/null || true
scp "$PROJECT_DIR/apps/web/package.json" $SERVER:$REMOTE_DIR/web/
scp "$PROJECT_DIR/apps/web/next.config.ts" $SERVER:$REMOTE_DIR/web/
log "上传完成"
}
# ---- 首次初始化服务器 ----
server_init() {
step "服务器环境初始化"
ssh $SERVER bash << 'REMOTE_INIT'
set -e
echo "[1/5] 安装软件..."
sed -i 's/exclude=.*nginx.*/exclude=cloud-init/' /etc/dnf/dnf.conf 2>/dev/null || true
dnf install -y postgresql-server postgresql postgresql-contrib redis nginx-core curl tar git 2>/dev/null
if ! command -v node &>/dev/null; then
echo "[*] 安装 Node.js 20 (二进制)..."
cd /tmp
curl -sL https://nodejs.org/dist/v20.18.0/node-v20.18.0-linux-x64.tar.xz -o node.tar.xz
tar -xf node.tar.xz
cp -r node-v20.18.0-linux-x64/{bin,lib,include,share} /usr/local/
rm -rf node.tar.xz node-v20.18.0-linux-x64
fi
echo " Node: $(node -v)"
echo "[2/5] 初始化 PostgreSQL..."
if [ ! -f /var/lib/pgsql/data/PG_VERSION ]; then
postgresql-setup --initdb
fi
cat > /var/lib/pgsql/data/pg_hba.conf << 'PGHBA'
local all postgres peer
local all all md5
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
PGHBA
echo "[3/5] 启动基础服务..."
systemctl enable --now postgresql redis
systemctl restart postgresql
echo "[4/5] 创建数据库..."
cd /tmp
sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='govai'" | grep -q 1 || \
sudo -u postgres psql -c "CREATE USER govai WITH PASSWORD 'GovAI@2024Secure';"
sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='govai_portal'" | grep -q 1 || \
sudo -u postgres psql -c "CREATE DATABASE govai_portal OWNER govai;"
sudo -u postgres psql -d govai_portal -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
sudo -u postgres psql -d govai_portal -c "CREATE EXTENSION IF NOT EXISTS vector;" 2>/dev/null || true
echo "[5/5] 安装 migrate..."
if ! command -v migrate &>/dev/null; then
curl -sL https://gh.idayer.com/https://github.com/golang-migrate/migrate/releases/download/v4.17.0/migrate.linux-amd64.tar.gz -o /tmp/migrate.tar.gz
cd /tmp && tar xzf migrate.tar.gz && mv migrate /usr/local/bin/ && rm -f migrate.tar.gz
fi
echo "✅ 初始化完成"
REMOTE_INIT
}
# ---- 安装前端依赖 ----
install_web_deps() {
step "安装前端依赖"
ssh $SERVER "cd $REMOTE_DIR/web && npm install --omit=dev --ignore-scripts 2>&1 | tail -3"
log "前端依赖安装完成"
}
# ---- 数据库迁移 ----
migrate_db() {
step "数据库迁移"
ssh $SERVER bash << REMOTE_MIGRATE
set -e
export PGPASSWORD="$DB_PASS"
echo "[*] 执行 schema 迁移..."
migrate -database "postgres://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME?sslmode=disable" \
-path $REMOTE_DIR/migrations up 2>&1 || true
echo "[*] 导入种子数据..."
for f in $REMOTE_DIR/migrations/seed*.sql; do
[ -f "\$f" ] || continue
echo " -> \$(basename \$f)"
psql -h localhost -U $DB_USER -d $DB_NAME -f "\$f" 2>&1 | tail -1
done
echo "✅ 迁移完成"
REMOTE_MIGRATE
}
# ---- 配置服务 ----
setup_services() {
step "配置系统服务"
ssh $SERVER bash << REMOTE_SVC
set -e
# .env
cat > $REMOTE_DIR/.env << 'DOTENV'
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
DATABASE_URL=postgres://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME?sslmode=disable
REDIS_URL=redis://localhost:6379
JWT_SECRET=$JWT_SECRET
JWT_EXPIRY=24h
LLM_PROVIDER=openai
OPENAI_API_KEY=$OPENAI_API_KEY
OPENAI_BASE_URL=$OPENAI_BASE_URL
OPENAI_MODEL=$OPENAI_MODEL
DOTENV
# govai-api.service
cat > /etc/systemd/system/govai-api.service << 'EOF'
[Unit]
Description=GovAI API Server
After=network.target postgresql.service redis.service
[Service]
Type=simple
WorkingDirectory=/opt/govai/server
EnvironmentFile=/opt/govai/.env
ExecStart=/opt/govai/server/server
Restart=always
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
# govai-web.service
cat > /etc/systemd/system/govai-web.service << 'EOF'
[Unit]
Description=GovAI Web Frontend
After=network.target govai-api.service
[Service]
Type=simple
WorkingDirectory=/opt/govai/web
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=NEXT_PUBLIC_API_URL=http://localhost:8080
ExecStart=/usr/bin/node node_modules/.bin/next start -p 3000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# nginx
cat > /etc/nginx/conf.d/govai.conf << 'NGINXCONF'
server {
listen 80;
server_name $DOMAIN;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 300s;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 300s;
}
}
NGINXCONF
# 移除 nginx 默认 server 块
if grep -q 'listen.*80.*default_server' /etc/nginx/nginx.conf; then
sed -i '/server {/,/^ }/d' /etc/nginx/nginx.conf 2>/dev/null || true
fi
chmod +x /opt/govai/server/server
systemctl daemon-reload
systemctl enable govai-api govai-web nginx
REMOTE_SVC
log "服务配置完成"
}
# ---- 启动/重启 ----
start_services() {
step "启动服务"
ssh $SERVER bash << 'REMOTE_START'
systemctl restart govai-api
sleep 2
systemctl restart govai-web
sleep 2
systemctl restart nginx
echo "服务状态:"
for svc in govai-api govai-web nginx postgresql redis; do
printf " %-15s %s\n" "$svc" "$(systemctl is-active $svc)"
done
sleep 2
if curl -sf http://localhost:8080/api/v1/store/featured -o /dev/null; then
echo -e "\n✅ API 正常"
else
echo -e "\n⚠️ API 启动中,请稍后检查: journalctl -u govai-api -n 20"
fi
REMOTE_START
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN} 部署完成!${NC}"
echo -e "${GREEN} http://$DOMAIN${NC}"
echo -e "${GREEN} http://154.8.162.18${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
}
# ---- 查看远程状态 ----
remote_status() {
step "远程服务状态"
ssh $SERVER bash << 'REMOTE_STATUS'
echo "── 服务状态 ──"
for svc in govai-api govai-web nginx postgresql redis; do
status=$(systemctl is-active $svc 2>/dev/null || echo "inactive")
uptime=""
if [ "$status" = "active" ]; then
uptime="($(systemctl show $svc --property=ActiveEnterTimestamp --value 2>/dev/null))"
fi
printf " %-15s %-10s %s\n" "$svc" "$status" "$uptime"
done
echo ""
echo "── 磁盘使用 ──"
df -h / | tail -1 | awk '{printf " 总: %s 已用: %s (%s) 可用: %s\n", $2, $3, $5, $4}'
echo ""
echo "── 内存使用 ──"
free -h | awk '/^Mem:/{printf " 总: %s 已用: %s 可用: %s\n", $2, $3, $7}'
echo ""
echo "── API 健康检查 ──"
if curl -sf http://localhost:8080/api/v1/store/featured -o /dev/null; then
echo " ✅ API 正常响应"
else
echo " ❌ API 无法访问"
fi
if curl -sf http://localhost:3000 -o /dev/null; then
echo " ✅ 前端正常响应"
else
echo " ❌ 前端无法访问"
fi
REMOTE_STATUS
}
# ---- 查看远程日志 ----
remote_logs() {
local service="${2:-govai-api}"
local lines="${3:-50}"
step "查看 $service 日志 (最近 $lines 行)"
ssh $SERVER "journalctl -u $service -n $lines --no-pager"
}
# ---- 主流程 ----
ACTION="${1:-update}"
case "$ACTION" in
init)
echo -e "${BLUE}>>> GovAI 首次部署 -> $DOMAIN${NC}"
git_commit_and_push
build_local
upload
server_init
install_web_deps
migrate_db
setup_services
start_services
;;
update)
echo -e "${BLUE}>>> GovAI 更新部署 -> $DOMAIN${NC}"
git_commit_and_push
build_local
upload
migrate_db
install_web_deps
start_services
;;
restart)
echo -e "${BLUE}>>> GovAI 重启服务${NC}"
start_services
;;
migrate)
echo -e "${BLUE}>>> GovAI 数据库迁移${NC}"
upload
migrate_db
;;
status)
remote_status
;;
logs)
remote_logs "$@"
;;
*)
echo "用法: bash deploy.sh [init|update|restart|migrate|status|logs]"
echo ""
echo " init - 首次部署(安装依赖+初始化数据库+部署应用)"
echo " update - 更新部署(git提交+构建+上传+迁移+重启)"
echo " restart - 仅重启服务"
echo " migrate - 仅上传迁移文件并执行数据库迁移"
echo " status - 查看远程服务状态和健康检查"
echo " logs - 查看日志 (可选: logs govai-web 100)"
exit 1
;;
esac