Initial commit: GovAI 政务AI平台
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user