428 lines
13 KiB
Bash
Executable File
428 lines
13 KiB
Bash
Executable File
#!/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
|