ci: add CI/CD deployment scripts with PM2, Nginx, and auto-setup
Deploy EternalAI / deploy (push) Has been cancelled
Details
Deploy EternalAI / deploy (push) Has been cancelled
Details
- Add ecosystem.config.js for PM2 process management - Add deploy/setup-server.sh for one-shot server initialization (auto-detects OS, installs Node.js 20/PostgreSQL 15/PM2/Nginx) - Add deploy/deploy.sh for repeatable deployments (pull -> install -> migrate -> reload -> health check) - Add deploy/nginx.conf reverse proxy template with security headers - Rewrite .gitea/workflows/deploy.yml with full CI/CD pipeline (checkout -> build -> migrate -> deploy -> health check) - Add .env.example template with DATABASE_URL/JWT_SECRET/PORT/ALLOWED_ORIGINS - Add docs/deployment.md (full deployment guide) and docs/business-processes.md - Update package.json scripts (db:generate, test:e2e, deploy) - Add logs/ to .gitignore
This commit is contained in:
parent
dad034a833
commit
fc53fa2e58
|
|
@ -0,0 +1,18 @@
|
||||||
|
# ===== EternalAI 环境变量配置 =====
|
||||||
|
# 复制此文件为 .env 并填写真实值
|
||||||
|
# cp .env.example .env
|
||||||
|
|
||||||
|
# 数据库连接字符串
|
||||||
|
# 格式: postgresql://用户名:密码@主机:端口/数据库名
|
||||||
|
DATABASE_URL="postgresql://eternalai:YOUR_PASSWORD@localhost:5432/eternalai"
|
||||||
|
|
||||||
|
# JWT 密钥 — 生产环境必须修改为随机长字符串(至少 32 字节)
|
||||||
|
# 生成方法: openssl rand -base64 48
|
||||||
|
JWT_SECRET="PLEASE_CHANGE_THIS_TO_A_RANDOM_SECRET"
|
||||||
|
|
||||||
|
# 服务端口
|
||||||
|
PORT=3001
|
||||||
|
|
||||||
|
# CORS 允许的来源(逗号分隔,生产环境应限制为实际域名)
|
||||||
|
# 留空则允许所有来源(不推荐)
|
||||||
|
ALLOWED_ORIGINS=""
|
||||||
|
|
@ -1,23 +1,72 @@
|
||||||
name: Deploy EternalAI
|
name: Deploy EternalAI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '20'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Start server
|
- name: Generate Prisma Client
|
||||||
run: npm start
|
run: npx prisma generate
|
||||||
|
|
||||||
|
- name: Push database schema
|
||||||
|
run: npx prisma db push
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
|
||||||
|
- name: Create logs directory
|
||||||
|
run: mkdir -p logs
|
||||||
|
|
||||||
|
- name: Restart PM2 process
|
||||||
|
run: |
|
||||||
|
if pm2 describe eternalai &>/dev/null; then
|
||||||
|
pm2 reload ecosystem.config.js --update-env
|
||||||
|
echo "PM2 process reloaded"
|
||||||
|
else
|
||||||
|
pm2 start ecosystem.config.js
|
||||||
|
echo "PM2 process started"
|
||||||
|
fi
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
- name: Health check
|
||||||
|
run: |
|
||||||
|
sleep 3
|
||||||
|
for i in $(seq 1 15); do
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3001 || echo "000")
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "Health check passed (HTTP $HTTP_CODE)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Waiting for server... (attempt $i, HTTP $HTTP_CODE)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "Health check failed"
|
||||||
|
pm2 logs eternalai --lines 30 --nostream
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Reload Nginx
|
||||||
|
run: sudo nginx -t && sudo nginx -s reload || echo "Nginx reload skipped"
|
||||||
|
|
||||||
|
- name: Deploy summary
|
||||||
|
run: |
|
||||||
|
echo "============================================"
|
||||||
|
echo " Deploy completed: $(git rev-parse --short HEAD)"
|
||||||
|
echo " $(git log -1 --format='%s')"
|
||||||
|
echo "============================================"
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@ node_modules/
|
||||||
.env
|
.env
|
||||||
test-results/
|
test-results/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
|
logs/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ===== EternalAI 部署脚本 =====
|
||||||
|
# 每次部署时运行,拉取最新代码、安装依赖、迁移数据库、重启服务
|
||||||
|
# 用法: bash deploy/deploy.sh
|
||||||
|
|
||||||
|
# ---- 颜色输出 ----
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
# ---- 配置 ----
|
||||||
|
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
HEALTH_URL="http://localhost:3001"
|
||||||
|
HEALTH_TIMEOUT=30
|
||||||
|
|
||||||
|
cd "$APP_DIR"
|
||||||
|
info "应用目录: $APP_DIR"
|
||||||
|
|
||||||
|
# ---- 前置检查 ----
|
||||||
|
if [[ ! -f .env ]]; then
|
||||||
|
error ".env 文件不存在,请先运行 deploy/setup-server.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v pm2 &>/dev/null; then
|
||||||
|
error "PM2 未安装,请先运行 deploy/setup-server.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 拉取最新代码 ----
|
||||||
|
info "拉取最新代码..."
|
||||||
|
git fetch --all
|
||||||
|
git reset --hard origin/master
|
||||||
|
info "当前版本: $(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
# ---- 安装依赖 ----
|
||||||
|
info "安装 npm 依赖..."
|
||||||
|
npm install --production=false
|
||||||
|
|
||||||
|
# ---- 生成 Prisma Client ----
|
||||||
|
info "生成 Prisma Client..."
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
# ---- 数据库迁移 ----
|
||||||
|
info "推送数据库 Schema..."
|
||||||
|
npx prisma db push
|
||||||
|
|
||||||
|
# ---- 创建日志目录 ----
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# ---- 重启 PM2 进程 ----
|
||||||
|
info "重启 PM2 进程..."
|
||||||
|
if pm2 describe eternalai &>/dev/null; then
|
||||||
|
pm2 reload ecosystem.config.js --update-env
|
||||||
|
info "PM2 进程已 reload"
|
||||||
|
else
|
||||||
|
pm2 start ecosystem.config.js
|
||||||
|
info "PM2 进程已启动"
|
||||||
|
fi
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
# ---- 健康检查 ----
|
||||||
|
info "健康检查 (${HEALTH_TIMEOUT}s 超时)..."
|
||||||
|
ELAPSED=0
|
||||||
|
while [[ $ELAPSED -lt $HEALTH_TIMEOUT ]]; do
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null || echo "000")
|
||||||
|
if [[ "$HTTP_CODE" == "200" ]]; then
|
||||||
|
info "健康检查通过 (HTTP $HTTP_CODE)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
ELAPSED=$((ELAPSED + 2))
|
||||||
|
echo -n "."
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$HTTP_CODE" != "200" ]]; then
|
||||||
|
error "健康检查失败 (HTTP $HTTP_CODE),请检查日志: pm2 logs eternalai"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 完成 ----
|
||||||
|
echo ""
|
||||||
|
info "============================================"
|
||||||
|
info " 部署完成!"
|
||||||
|
info "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "版本: $(git rev-parse --short HEAD)"
|
||||||
|
echo "分支: $(git branch --show-current)"
|
||||||
|
echo "状态: pm2 status"
|
||||||
|
echo "日志: pm2 logs eternalai"
|
||||||
|
echo ""
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
# ===== EternalAI Nginx 反向代理配置 =====
|
||||||
|
#
|
||||||
|
# 使用方法:
|
||||||
|
# 1. 复制到 Nginx 配置目录:
|
||||||
|
# sudo cp deploy/nginx.conf /etc/nginx/sites-available/eternalai
|
||||||
|
# sudo ln -s /etc/nginx/sites-available/eternalai /etc/nginx/sites-enabled/
|
||||||
|
# 2. 替换 YOUR_DOMAIN 为实际域名
|
||||||
|
# 3. 测试配置: sudo nginx -t
|
||||||
|
# 4. 重载: sudo nginx -s reload
|
||||||
|
#
|
||||||
|
# HTTPS 配置(推荐):
|
||||||
|
# sudo certbot --nginx -d YOUR_DOMAIN
|
||||||
|
|
||||||
|
# 替换 YOUR_DOMAIN 为你的实际域名
|
||||||
|
upstream eternalai_backend {
|
||||||
|
server 127.0.0.1:3001;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name YOUR_DOMAIN;
|
||||||
|
|
||||||
|
# 安全头
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# 请求体大小限制
|
||||||
|
client_max_body_size 10m;
|
||||||
|
|
||||||
|
# API 请求代理到 Node.js
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://eternalai_backend;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态文件缓存
|
||||||
|
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
proxy_pass http://eternalai_backend;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# 主页和其他路由
|
||||||
|
location / {
|
||||||
|
proxy_pass http://eternalai_backend;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 禁止访问敏感文件
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
location ~ /\.(env|git) {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
location ~ /node_modules/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
location ~ /prisma/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
location ~ /e2e/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
location ~ /deploy/ {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
access_log /var/log/nginx/eternalai_access.log;
|
||||||
|
error_log /var/log/nginx/eternalai_error.log;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ===== EternalAI 服务器初始化脚本 =====
|
||||||
|
# 首次部署时在服务器上运行,自动检测并安装所有依赖
|
||||||
|
# 用法: bash deploy/setup-server.sh
|
||||||
|
#
|
||||||
|
# 可通过环境变量自定义:
|
||||||
|
# DB_NAME=eternalai 数据库名
|
||||||
|
# DB_USER=eternalai 数据库用户
|
||||||
|
# DB_PASS=xxx 数据库密码(必填,否则自动生成)
|
||||||
|
|
||||||
|
# ---- 颜色输出 ----
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||||
|
|
||||||
|
# ---- 配置 ----
|
||||||
|
DB_NAME="${DB_NAME:-eternalai}"
|
||||||
|
DB_USER="${DB_USER:-eternalai}"
|
||||||
|
DB_PASS="${DB_PASS:-$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)}"
|
||||||
|
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
NODE_VERSION="20"
|
||||||
|
|
||||||
|
info "应用目录: $APP_DIR"
|
||||||
|
info "数据库: $DB_NAME (用户: $DB_USER)"
|
||||||
|
|
||||||
|
# ---- 检测 OS ----
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
OS_ID="$ID"
|
||||||
|
info "检测到操作系统: $PRETTY_NAME"
|
||||||
|
else
|
||||||
|
error "无法检测操作系统,仅支持 Ubuntu/Debian/CentOS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 安装 Node.js ----
|
||||||
|
if ! command -v node &>/dev/null; then
|
||||||
|
info "安装 Node.js $NODE_VERSION LTS..."
|
||||||
|
if [[ "$OS_ID" == "ubuntu" || "$OS_ID" == "debian" ]]; then
|
||||||
|
curl -fsSL "https://deb.nodesource.com/setup_${NODE_VERSION}.x" | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
elif [[ "$OS_ID" == "centos" || "$OS_ID" == "rhel" || "$OS_ID" == "rocky" ]]; then
|
||||||
|
curl -fsSL "https://rpm.nodesource.com/setup_${NODE_VERSION}.x" | sudo -E bash -
|
||||||
|
sudo yum install -y nodejs
|
||||||
|
else
|
||||||
|
error "不支持的操作系统: $OS_ID,请手动安装 Node.js $NODE_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "Node.js 安装完成: $(node -v)"
|
||||||
|
else
|
||||||
|
info "Node.js 已安装: $(node -v)"
|
||||||
|
NODE_MAJOR=$(node -v | sed 's/v\([0-9]*\).*/\1/')
|
||||||
|
if [[ "$NODE_MAJOR" -lt 18 ]]; then
|
||||||
|
warn "Node.js 版本过低 (当前 $(node -v)),建议升级到 $NODE_VERSION LTS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 安装 PM2 ----
|
||||||
|
if ! command -v pm2 &>/dev/null; then
|
||||||
|
info "安装 PM2..."
|
||||||
|
sudo npm install -g pm2
|
||||||
|
info "PM2 安装完成: $(pm2 -v)"
|
||||||
|
else
|
||||||
|
info "PM2 已安装: $(pm2 -v)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 安装 PostgreSQL ----
|
||||||
|
if ! command -v psql &>/dev/null; then
|
||||||
|
info "安装 PostgreSQL 15..."
|
||||||
|
if [[ "$OS_ID" == "ubuntu" || "$OS_ID" == "debian" ]]; then
|
||||||
|
sudo apt-get install -y ca-certificates curl gnupg lsb-release
|
||||||
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/keyrings/postgresql.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y postgresql-15
|
||||||
|
elif [[ "$OS_ID" == "centos" || "$OS_ID" == "rhel" || "$OS_ID" == "rocky" ]]; then
|
||||||
|
sudo yum install -y "https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
|
||||||
|
sudo yum install -y postgresql15-server postgresql15
|
||||||
|
sudo /usr/pgsql-15/bin/postgresql-15-setup initdb
|
||||||
|
else
|
||||||
|
error "不支持的操作系统: $OS_ID,请手动安装 PostgreSQL 15"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "PostgreSQL 安装完成"
|
||||||
|
else
|
||||||
|
info "PostgreSQL 已安装: $(psql --version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 启动 PostgreSQL ----
|
||||||
|
if ! sudo systemctl is-active --quiet postgresql; then
|
||||||
|
info "启动 PostgreSQL 服务..."
|
||||||
|
sudo systemctl enable postgresql
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 创建数据库和用户 ----
|
||||||
|
info "创建数据库 $DB_NAME 和用户 $DB_USER..."
|
||||||
|
sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || {
|
||||||
|
sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASS';"
|
||||||
|
info "数据库用户 $DB_USER 已创建"
|
||||||
|
}
|
||||||
|
sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 || {
|
||||||
|
sudo -u postgres psql -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;"
|
||||||
|
info "数据库 $DB_NAME 已创建"
|
||||||
|
}
|
||||||
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;" >/dev/null
|
||||||
|
|
||||||
|
# 确保 PostgreSQL 对 pg_hba.conf 允许密码认证
|
||||||
|
PG_HBA=$(sudo -u postgres psql -t -c "SHOW hba_file" | xargs)
|
||||||
|
if [[ -f "$PG_HBA" ]]; then
|
||||||
|
if ! sudo grep -q "$DB_USER" "$PG_HBA" 2>/dev/null; then
|
||||||
|
warn "如果连接数据库失败,请检查 $PG_HBA 是否允许 md5/scram-sha-256 认证"
|
||||||
|
warn "添加: host $DB_NAME $DB_USER 127.0.0.1/32 scram-sha-256"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 安装 Nginx ----
|
||||||
|
if ! command -v nginx &>/dev/null; then
|
||||||
|
info "安装 Nginx..."
|
||||||
|
if [[ "$OS_ID" == "ubuntu" || "$OS_ID" == "debian" ]]; then
|
||||||
|
sudo apt-get install -y nginx
|
||||||
|
elif [[ "$OS_ID" == "centos" || "$OS_ID" == "rhel" || "$OS_ID" == "rocky" ]]; then
|
||||||
|
sudo yum install -y nginx
|
||||||
|
fi
|
||||||
|
info "Nginx 安装完成: $(nginx -v 2>&1)"
|
||||||
|
else
|
||||||
|
info "Nginx 已安装: $(nginx -v 2>&1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! sudo systemctl is-active --quiet nginx; then
|
||||||
|
sudo systemctl enable nginx
|
||||||
|
sudo systemctl start nginx
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 创建 .env 文件 ----
|
||||||
|
ENV_FILE="$APP_DIR/.env"
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
info "创建 .env 文件..."
|
||||||
|
JWT_SECRET=$(openssl rand -base64 48)
|
||||||
|
cat > "$ENV_FILE" <<EOF
|
||||||
|
DATABASE_URL="postgresql://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME"
|
||||||
|
JWT_SECRET="$JWT_SECRET"
|
||||||
|
PORT=3001
|
||||||
|
EOF
|
||||||
|
info ".env 文件已创建(请妥善保管数据库密码和 JWT 密钥)"
|
||||||
|
echo ""
|
||||||
|
echo "=============================="
|
||||||
|
echo " 数据库密码: $DB_PASS"
|
||||||
|
echo " JWT 密钥已自动生成"
|
||||||
|
echo "=============================="
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
info ".env 文件已存在,跳过创建"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 创建日志目录 ----
|
||||||
|
mkdir -p "$APP_DIR/logs"
|
||||||
|
|
||||||
|
# ---- 安装项目依赖 ----
|
||||||
|
info "安装项目依赖..."
|
||||||
|
cd "$APP_DIR"
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# ---- 生成 Prisma Client ----
|
||||||
|
info "生成 Prisma Client..."
|
||||||
|
npx prisma generate
|
||||||
|
|
||||||
|
# ---- 推送数据库 Schema ----
|
||||||
|
info "推送数据库 Schema..."
|
||||||
|
npx prisma db push
|
||||||
|
|
||||||
|
# ---- 配置 PM2 开机自启 ----
|
||||||
|
info "配置 PM2 开机自启..."
|
||||||
|
pm2 start ecosystem.config.js || true
|
||||||
|
pm2 save
|
||||||
|
pm2 startup systemd -u "$(whoami)" --hp "$HOME" 2>/dev/null | grep "sudo" | bash || true
|
||||||
|
|
||||||
|
# ---- 完成 ----
|
||||||
|
echo ""
|
||||||
|
info "============================================"
|
||||||
|
info " 服务器初始化完成!"
|
||||||
|
info "============================================"
|
||||||
|
echo ""
|
||||||
|
echo "下一步:"
|
||||||
|
echo " 1. 配置 Nginx 反向代理(参考 deploy/nginx.conf)"
|
||||||
|
echo " 2. 检查应用状态: pm2 status"
|
||||||
|
echo " 3. 查看日志: pm2 logs eternalai"
|
||||||
|
echo " 4. 访问应用: http://localhost:3001"
|
||||||
|
echo ""
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,332 @@
|
||||||
|
# EternalAI 部署指南
|
||||||
|
|
||||||
|
## 架构概览
|
||||||
|
|
||||||
|
```
|
||||||
|
用户浏览器 → Nginx (80/443) → Node.js Express (3001) → PostgreSQL (5432)
|
||||||
|
↓
|
||||||
|
PM2 进程管理
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件清单
|
||||||
|
|
||||||
|
| 文件 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `ecosystem.config.js` | PM2 进程配置 |
|
||||||
|
| `.env.example` | 环境变量模板 |
|
||||||
|
| `deploy/setup-server.sh` | 首次服务器初始化脚本 |
|
||||||
|
| `deploy/deploy.sh` | 每次部署脚本 |
|
||||||
|
| `deploy/nginx.conf` | Nginx 反向代理配置模板 |
|
||||||
|
| `.gitea/workflows/deploy.yml` | Gitea Actions CI/CD 工作流 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、首次部署(全量部署)
|
||||||
|
|
||||||
|
### 1.1 准备服务器
|
||||||
|
|
||||||
|
在目标服务器上执行以下操作。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆代码仓库
|
||||||
|
cd /opt # 或你选择的部署目录
|
||||||
|
git clone http://gitea.fischerai.cn/chigulong/eternalai.git
|
||||||
|
cd eternalai
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 运行服务器初始化脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash deploy/setup-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
此脚本会自动完成:
|
||||||
|
- 检测并安装 Node.js 20 LTS(如缺失)
|
||||||
|
- 检测并安装 PostgreSQL 15(如缺失)
|
||||||
|
- 检测并安装 PM2(如缺失)
|
||||||
|
- 检测并安装 Nginx(如缺失)
|
||||||
|
- 创建数据库 `eternalai` 和用户
|
||||||
|
- 自动生成 `.env` 文件(含随机 JWT 密钥和数据库密码)
|
||||||
|
- 安装 npm 依赖
|
||||||
|
- 生成 Prisma Client
|
||||||
|
- 推送数据库 Schema
|
||||||
|
- 启动 PM2 进程并配置开机自启
|
||||||
|
|
||||||
|
**自定义数据库配置**(可选):
|
||||||
|
```bash
|
||||||
|
DB_NAME=mydb DB_USER=myuser DB_PASS=mypassword bash deploy/setup-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本完成后会输出数据库密码,请妥善保存。
|
||||||
|
|
||||||
|
### 1.3 配置 Nginx 反向代理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 复制 Nginx 配置
|
||||||
|
sudo cp deploy/nginx.conf /etc/nginx/sites-available/eternalai
|
||||||
|
sudo ln -s /etc/nginx/sites-available/eternalai /etc/nginx/sites-enabled/
|
||||||
|
|
||||||
|
# 编辑配置,替换 YOUR_DOMAIN 为实际域名
|
||||||
|
sudo nano /etc/nginx/sites-enabled/eternalai
|
||||||
|
# 将所有 YOUR_DOMAIN 替换为你的域名,如 eternalai.example.com
|
||||||
|
|
||||||
|
# 测试配置
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# 重载 Nginx
|
||||||
|
sudo nginx -s reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 配置 HTTPS(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 Certbot
|
||||||
|
sudo apt-get install -y certbot python3-certbot-nginx # Ubuntu/Debian
|
||||||
|
# 或
|
||||||
|
sudo yum install -y certbot python3-certbot-nginx # CentOS/RHEL
|
||||||
|
|
||||||
|
# 自动获取并配置 SSL 证书
|
||||||
|
sudo certbot --nginx -d YOUR_DOMAIN
|
||||||
|
|
||||||
|
# 证书自动续期(Certbot 会自动配置 cron)
|
||||||
|
sudo certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 验证部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 PM2 进程状态
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# 查看应用日志
|
||||||
|
pm2 logs eternalai
|
||||||
|
|
||||||
|
# 测试本地访问
|
||||||
|
curl http://localhost:3001
|
||||||
|
|
||||||
|
# 测试 Nginx 代理访问
|
||||||
|
curl http://YOUR_DOMAIN
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、CI/CD 自动部署(推送即部署)
|
||||||
|
|
||||||
|
### 2.1 配置 Gitea Secrets
|
||||||
|
|
||||||
|
在 Gitea 仓库设置中添加 Secret:
|
||||||
|
|
||||||
|
1. 进入仓库 → Settings → Actions → Secrets
|
||||||
|
2. 添加 `DATABASE_URL`,值为 `.env` 文件中的 `DATABASE_URL`
|
||||||
|
|
||||||
|
### 2.2 配置 Gitea Runner
|
||||||
|
|
||||||
|
确保已注册 self-hosted runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在服务器上注册 runner(如尚未注册)
|
||||||
|
# 参考 Gitea 官方文档: https://docs.gitea.com/usage/actions/quickstart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 自动部署流程
|
||||||
|
|
||||||
|
每次推送代码到 `master` 分支时,Gitea Actions 会自动:
|
||||||
|
|
||||||
|
1. 拉取最新代码
|
||||||
|
2. 安装 npm 依赖
|
||||||
|
3. 生成 Prisma Client
|
||||||
|
4. 推送数据库 Schema(`prisma db push`)
|
||||||
|
5. 重启 PM2 进程(`pm2 reload`)
|
||||||
|
6. 健康检查(等待 HTTP 200)
|
||||||
|
7. 重载 Nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推送代码触发自动部署
|
||||||
|
git push origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 手动触发部署
|
||||||
|
|
||||||
|
在 Gitea 仓库 → Actions 页面,可手动触发 `Deploy EternalAI` 工作流。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、手动部署(不使用 CI/CD)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 登录服务器
|
||||||
|
ssh user@your-server
|
||||||
|
|
||||||
|
# 进入应用目录
|
||||||
|
cd /opt/eternalai
|
||||||
|
|
||||||
|
# 运行部署脚本
|
||||||
|
bash deploy/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
部署脚本会自动完成:拉取代码 → 安装依赖 → 数据库迁移 → 重启 PM2 → 健康检查。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、常用运维命令
|
||||||
|
|
||||||
|
### PM2 进程管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pm2 status # 查看进程状态
|
||||||
|
pm2 logs eternalai # 实时查看日志
|
||||||
|
pm2 logs eternalai --lines 100 # 查看最近 100 行日志
|
||||||
|
pm2 restart eternalai # 重启进程
|
||||||
|
pm2 reload eternalai # 零停机重载
|
||||||
|
pm2 stop eternalai # 停止进程
|
||||||
|
pm2 delete eternalai # 删除进程
|
||||||
|
pm2 monit # 监控面板
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库操作
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推送 Schema 变更
|
||||||
|
npx prisma db push
|
||||||
|
|
||||||
|
# 打开 Prisma Studio(数据库可视化管理)
|
||||||
|
npx prisma studio
|
||||||
|
|
||||||
|
# 连接 PostgreSQL
|
||||||
|
psql -U eternalai -d eternalai -h localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx 操作
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t # 测试配置
|
||||||
|
sudo nginx -s reload # 重载配置
|
||||||
|
sudo systemctl status nginx # 查看状态
|
||||||
|
sudo tail -f /var/log/nginx/eternalai_error.log # 查看错误日志
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日志查看
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 应用日志
|
||||||
|
tail -f logs/out.log # 标准输出
|
||||||
|
tail -f logs/err.log # 错误输出
|
||||||
|
|
||||||
|
# Nginx 日志
|
||||||
|
sudo tail -f /var/log/nginx/eternalai_access.log
|
||||||
|
sudo tail -f /var/log/nginx/eternalai_error.log
|
||||||
|
|
||||||
|
# PostgreSQL 日志
|
||||||
|
sudo tail -f /var/log/postgresql/postgresql-15-main.log
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、更新部署
|
||||||
|
|
||||||
|
### 5.1 日常更新(代码变更)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法一:推送代码触发 CI/CD(推荐)
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
# 方法二:手动部署
|
||||||
|
bash deploy/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 数据库 Schema 变更
|
||||||
|
|
||||||
|
修改 `prisma/schema.prisma` 后:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推送到 master,CI/CD 会自动执行 prisma db push
|
||||||
|
git add prisma/schema.prisma
|
||||||
|
git commit -m "feat: update schema"
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
# 或手动执行
|
||||||
|
npx prisma db push
|
||||||
|
pm2 reload eternalai
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 环境变量变更
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑 .env 文件
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# 重启应用使配置生效
|
||||||
|
pm2 reload eternalai --update-env
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、回滚
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看部署历史
|
||||||
|
git log --oneline -10
|
||||||
|
|
||||||
|
# 回滚到指定版本
|
||||||
|
git checkout <commit-hash>
|
||||||
|
npm install
|
||||||
|
npx prisma db push
|
||||||
|
pm2 reload eternalai
|
||||||
|
|
||||||
|
# 或回滚到上一个版本
|
||||||
|
git checkout HEAD~1
|
||||||
|
npm install
|
||||||
|
npx prisma db push
|
||||||
|
pm2 reload eternalai
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、故障排查
|
||||||
|
|
||||||
|
### 应用无法启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看错误日志
|
||||||
|
pm2 logs eternalai --err --lines 50
|
||||||
|
|
||||||
|
# 常见原因:
|
||||||
|
# 1. .env 文件缺失 → 运行 deploy/setup-server.sh
|
||||||
|
# 2. 数据库连接失败 → 检查 DATABASE_URL 和 PostgreSQL 服务
|
||||||
|
# 3. 端口被占用 → 检查 PORT 环境变量
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库连接失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 PostgreSQL 服务
|
||||||
|
sudo systemctl status postgresql
|
||||||
|
|
||||||
|
# 测试连接
|
||||||
|
psql -U eternalai -d eternalai -h localhost -W
|
||||||
|
|
||||||
|
# 检查 pg_hba.conf 认证配置
|
||||||
|
sudo cat /etc/postgresql/15/main/pg_hba.conf | grep -v '^#'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx 502 Bad Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 Node.js 进程是否运行
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# 检查端口
|
||||||
|
curl http://localhost:3001
|
||||||
|
|
||||||
|
# 检查 Nginx 错误日志
|
||||||
|
sudo tail -f /var/log/nginx/eternalai_error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### PM2 进程未开机自启
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 重新配置开机自启
|
||||||
|
pm2 startup systemd
|
||||||
|
# 执行输出的 sudo 命令
|
||||||
|
pm2 save
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'eternalai',
|
||||||
|
script: 'server.js',
|
||||||
|
cwd: __dirname,
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
autorestart: true,
|
||||||
|
max_restarts: 10,
|
||||||
|
restart_delay: 3000,
|
||||||
|
max_memory_restart: '512M',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
},
|
||||||
|
error_file: './logs/err.log',
|
||||||
|
out_file: './logs/out.log',
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||||
|
merge_logs: true,
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -7,8 +7,11 @@
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "node --watch server.js",
|
"dev": "node --watch server.js",
|
||||||
"db:push": "prisma db push",
|
"db:push": "prisma db push",
|
||||||
|
"db:generate": "prisma generate",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"test": "jest"
|
"test": "playwright test",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"deploy": "bash deploy/deploy.sh"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue