# 知命阁（zhimingge）Ubuntu 部署文档（PM2 备选）

> 仓库：[https://git.bz121.com/dekun/zhimingge.git](https://git.bz121.com/dekun/zhimingge.git)

> **推荐改用 Docker 部署**，见 [DOCKER.md](./DOCKER.md)。本文档保留 PM2 手动部署说明。

本文档说明在 **Ubuntu 服务器**上使用 **root 用户**、**PM2** 将知命阁部署到 **`/opt/zhimingge`** 的完整流程。

---

## 1. 部署架构

```
                    ┌─────────────────┐
   用户浏览器  ──→  │  Nginx (可选)   │  :80 / :443
                    │  反向代理        │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  PM2            │
                    │  zhimingge      │  :3130
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  Next.js        │
                    │  standalone     │
                    │  /opt/zhimingge │
                    └─────────────────┘
                             │
                    ┌────────▼────────┐
                    │  op.bz121.com   │  AI API
                    │  /v1            │
                    └─────────────────┘
```

| 组件 | 说明 |
|------|------|
| 操作系统 | Ubuntu 20.04 / 22.04 / 24.04 LTS |
| 运行用户 | root（按需求） |
| 安装目录 | `/opt/zhimingge` |
| 进程管理 | PM2 |
| 构建模式 | Next.js `output: "standalone"` |
| 默认端口 | **3130** |

---

## 2. 服务器要求

| 项目 | 最低配置 | 推荐配置 |
|------|----------|----------|
| CPU | 1 核 | 2 核 |
| 内存 | 1 GB | 2 GB+ |
| 磁盘 | 2 GB | 10 GB |
| 网络 | 可访问 git.bz121.com、op.bz121.com | — |

---

## 3. 一次性环境准备

以 **root** 登录服务器后执行：

### 3.1 更新系统

```bash
apt update && apt upgrade -y
```

### 3.2 安装 Node.js 20（推荐 LTS）

```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
node -v   # 应 >= v20
npm -v
```

### 3.3 安装 PM2

```bash
npm install -g pm2
pm2 -v
```

> **说明**：项目使用 **npm** + `package-lock.json` 安装依赖（无需 pnpm）。若已安装 pnpm 也可改用 `npm install`，避免 `pnpm-lock.yaml` 不同步问题。

### 3.4 安装 Git

```bash
apt install -y git
```

### 3.5（可选）安装 Nginx 反向代理

若需域名 + HTTPS，建议安装 Nginx 与 Certbot：

```bash
apt install -y nginx certbot python3-certbot-nginx
```

---

## 4. 克隆与目录

```bash
mkdir -p /opt
cd /opt

# 首次克隆（私有仓库需配置凭据，见第 5 节）
git clone https://git.bz121.com/dekun/zhimingge.git zhimingge

cd /opt/zhimingge
```

最终目录：

```
/opt/zhimingge/
├── app/
├── content/
├── ecosystem.config.cjs
├── .env.local          ← 生产环境变量（勿提交 Git）
├── package.json
└── ...
```

---

## 5. Git 凭据（私有仓库）

仓库 [dekun/zhimingge](https://git.bz121.com/dekun/zhimingge.git) 为私有仓库，克隆前需配置认证。

### 方式 A：HTTPS + 访问令牌（推荐）

```bash
git config --global credential.helper store
git clone https://git.bz121.com/dekun/zhimingge.git /opt/zhimingge
# 按提示输入 Gitea 用户名与 Personal Access Token
```

在 Gitea 生成 Token：用户设置 → Applications → Generate New Token（勾选 `repo` 权限）。

### 方式 B：SSH

```bash
ssh-keygen -t ed25519 -C "zhimingge-deploy" -f /root/.ssh/zhimingge -N ""
cat /root/.ssh/zhimingge.pub
# 将公钥添加到 Gitea → Settings → SSH Keys

cat >> /root/.ssh/config << 'EOF'
Host git.bz121.com
  IdentityFile /root/.ssh/zhimingge
  StrictHostKeyChecking accept-new
EOF

git clone git@git.bz121.com:dekun/zhimingge.git /opt/zhimingge
```

---

## 6. 环境变量

```bash
cd /opt/zhimingge
cp .env.example .env.local
nano .env.local
```

**`.env.local` 示例：**

```env
# AI 接口（必填）
OPENAI_API_KEY=你的密钥

# AI 地址与模型（已有默认值，可按需覆盖）
OPENAI_BASE_URL=https://op.bz121.com/v1
OPENAI_MODEL=huihui_ai/gemma-4-abliterated:e4b

# 服务端口（与 PM2 ecosystem.config.cjs 一致）
PORT=3130
NODE_ENV=production
```

保存后限制权限：

```bash
chmod 600 .env.local
```

---

## 7. 构建与启动

### 7.1 安装依赖

```bash
cd /opt/zhimingge
npm ci || npm install
```

### 7.2 生产构建

```bash
npm run build
```

构建成功后生成 `.next/standalone/` 独立运行包。

### 7.3 复制静态资源（standalone 必需）

Next.js standalone 模式需手动链接静态文件：

```bash
cd /opt/zhimingge

# 复制 static 与 public 到 standalone 目录
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public

# 复制内容库（服务端读取卦辞 Markdown）
cp -r content .next/standalone/content
```

> **说明**：若应用从项目根目录读取 `content/`，PM2 的 `cwd` 应设为 `/opt/zhimingge` 而非 standalone 子目录。当前 `ecosystem.config.cjs` 使用项目根目录 + `npm start`，构建后也可改用 standalone 的 `server.js`（见 7.5 备选方案）。

### 7.4 使用 PM2 启动（推荐方式）

项目根目录已包含 `ecosystem.config.cjs`：

```bash
cd /opt/zhimingge
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup
# 按 PM2 提示执行生成的命令，确保开机自启
```

常用 PM2 命令：

```bash
pm2 status                  # 查看状态
pm2 logs zhimingge          # 查看日志
pm2 restart zhimingge       # 重启
pm2 stop zhimingge          # 停止
pm2 delete zhimingge        # 删除进程
```

### 7.5 备选：standalone 直接运行

若希望仅运行 standalone 产物（体积更小）：

```bash
cd /opt/zhimingge/.next/standalone
PORT=3130 node server.js
```

对应 PM2 配置片段：

```javascript
{
  name: "zhimingge",
  cwd: "/opt/zhimingge/.next/standalone",
  script: "server.js",
  env: {
    PORT: 3130,
    NODE_ENV: "production",
  },
}
```

此时需确保 `content/`、`public/` 路径在代码中相对于 `cwd` 可访问。

### 7.6 验证

```bash
curl -I http://127.0.0.1:3130
# 应返回 HTTP/1.1 200
```

浏览器访问：`http://服务器IP:3130`

---

## 8. Nginx 反向代理（可选）

假设域名为 `zhimingge.example.com`：

```bash
nano /etc/nginx/sites-available/zhimingge
```

```nginx
server {
    listen 80;
    server_name zhimingge.example.com;

    location / {
        proxy_pass http://127.0.0.1:3130;
        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;

        # AI 流式响应需禁用缓冲
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}
```

```bash
ln -s /etc/nginx/sites-available/zhimingge /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

# HTTPS（Let's Encrypt）
certbot --nginx -d zhimingge.example.com
```

---

## 9. 更新部署流程

代码推送至 Gitea 后，在服务器执行：

```bash
cd /opt/zhimingge

# 拉取最新代码
git pull origin main

# 安装新依赖（如有变更）
npm ci || npm install

# 重新构建
npm run build

# 若使用 standalone 复制方式，重新复制 static/public/content
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public
cp -r content .next/standalone/content

# 重启
pm2 restart zhimingge
```

可写入脚本 `/opt/zhimingge/scripts/deploy.sh` 一键执行（见项目内脚本）。

---

## 10. 防火墙

若使用 UFW：

```bash
# 仅 Nginx 对外（推荐）
ufw allow 22
ufw allow 80
ufw allow 443
ufw enable

# 若不用 Nginx，直接暴露 3130
ufw allow 3130
```

---

## 11. 日志与排错

### PM2 日志

```bash
pm2 logs zhimingge --lines 100
```

日志文件位置（默认）：

```
/root/.pm2/logs/zhimingge-out.log
/root/.pm2/logs/zhimingge-error.log
```

### 常见问题

| 现象 | 可能原因 | 处理 |
|------|----------|------|
| 502 Bad Gateway | PM2 进程未运行 | `pm2 restart zhimingge` |
| AI 解读失败 | API Key 错误或模型名不对 | 检查 `.env.local` |
| 卦辞读取失败 | `content/` 路径不存在 | 确认 `content/zhouyi/docs/` 存在 |
| 构建 OOM | 内存不足 | 增加 swap 或升级内存 |
| 流式输出中断 | Nginx 缓冲 | 设置 `proxy_buffering off` |
| Git pull 失败 | 凭据过期 | 重新配置 Token 或 SSH |
| `npm install` 长时间无输出 | 官方 npm 源慢 / 下载 `@next/swc` 等大包 | 见下方「npm 安装卡住」 |

### npm 安装卡住

国内服务器访问 `registry.npmjs.org` 常极慢，表现为 `npm install` 只有转圈、无进度。

**1. 先 Ctrl+C 中断，改用国内镜像：**

```bash
cd /opt/zhimingge
npm config set registry https://registry.npmmirror.com
npm config set fetch-timeout 600000
npm install --no-audit --no-fund --loglevel=info
```

项目根目录已包含 `.npmrc`（`git pull` 后自动生效），也可直接：

```bash
git pull origin main
npm install --loglevel=info
```

**2. 看卡在哪一步**（可选）：

```bash
npm install --loglevel=verbose 2>&1 | tee /tmp/npm-install.log
```

若长时间停在 `@next/swc-*` 或 `sharp`，属正常现象，多等几分钟；超 10 分钟仍无进展再换镜像重试。

**3. 内存不足时**（构建阶段 OOM）见下方 Swap 配置。

### 构建内存不足时增加 Swap

```bash
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
```

### 测试 AI 接口

```bash
curl https://op.bz121.com/v1/models \
  -H "Authorization: Bearer 你的OPENAI_API_KEY"
```

确认返回列表中包含 `huihui_ai/gemma-4-abliterated:e4b`。

---

## 12. 安全建议

虽然当前按 **root** 部署，仍建议：

1. **`.env.local` 权限** 设为 `600`，勿提交 Git
2. **API Key** 仅保存在服务器，不要写入前端代码
3. 生产环境使用 **Nginx + HTTPS**
4. 定期 `apt update` 与 `npm update`
5. 若后续改为非 root 运行，创建专用用户：

```bash
useradd -r -s /bin/false zhimingge
chown -R zhimingge:zhimingge /opt/zhimingge
# PM2 中以 zhimingge 用户启动
```

---

## 13. 快速命令参考

```bash
# 完整首次部署
cd /opt && git clone https://git.bz121.com/dekun/zhimingge.git zhimingge
cd /opt/zhimingge
cp .env.example .env.local && nano .env.local
npm ci || npm install
npm run build
pm2 start ecosystem.config.cjs
pm2 save && pm2 startup

# 日常更新
cd /opt/zhimingge && git pull && npm ci && npm run build && pm2 restart zhimingge

# 查看状态
pm2 status && curl -I http://127.0.0.1:3130
```

---

## 14. 相关文档

- 产品说明：[docs/SPEC.md](./SPEC.md)
- 环境变量示例：[../.env.example](../.env.example)
- PM2 配置：[../ecosystem.config.cjs](../ecosystem.config.cjs)
