# Ubuntu 零 Node 部署文档

> **中学成绩档案系统** · 版权所有 © 马建军 · 微信 **dekun03** · 手机 **18364911125**  
> 仓库：[https://git.bz121.com/dekun/secondary-school-grade-archive.git](https://git.bz121.com/dekun/secondary-school-grade-archive.git)

---

## 1. 部署方式

| 项目 | 说明 |
|------|------|
| 方式 | **systemd + FastAPI 单进程**（服务器**无需** Node.js / npm） |
| 系统 | Ubuntu 20.04 / 22.04 / 24.04 |
| 用户 | **root** |
| 目录 | `/opt/secondary-school-grade-archive` |
| 端口 | **23566**（API + 前端静态资源同一端口） |

### 架构

```
浏览器 → :23566 (systemd: grade-archive, Uvicorn)
              ├── /api/*     → FastAPI 接口
              └── /*         → frontend/dist 静态文件
                        └──→ PostgreSQL (本机)
                        └──→ uploads/
                        └──→ Ollama (本机可选, :11434)
```

### 为何不在服务器构建前端？

前端 `npm ci && npm run build` 会占用大量磁盘与内存，且需在服务器安装 Node.js。  
因此采用 **开发机构建 → 推送 `frontend/dist` → 服务器只拉取** 的方式，服务器仅需 Python + PostgreSQL。

---

## 2. 代码修改与发布流程（重要）

每次修改代码后，按改动范围在**开发机**操作，再推送到远端仓库，最后在服务器执行 `update.sh`。

> **服务器需代理时**：在 `update.sh` 前设置与 [§3 一键部署](#3-一键部署新服务器) 相同的 `export http_proxy=...` 命令。

### 流程总览

```
┌─────────────┐    ┌──────────────────┐    ┌─────────────┐    ┌─────────────────┐
│ 开发机改代码 │ → │ 本地构建（如需） │ → │ git push 远端 │ → │ 服务器 update.sh │
└─────────────┘    └──────────────────┘    └─────────────┘    └─────────────────┘
```

### 2.1 仅修改后端（`backend/`）

不涉及前端页面时，**无需** `npm run build`：

```bash
git add backend/
git commit -m "fix: 说明本次改动"
git push
```

服务器：

```bash
bash /opt/secondary-school-grade-archive/deploy/update.sh
```

`update.sh` 会：`git pull` → `pip install` → `systemctl restart grade-archive`。

### 2.2 修改前端（`frontend/src` 等）或同时改前后端

**必须先在开发机构建**，并将构建产物 `frontend/dist/` 提交到仓库：

```powershell
# Windows
.\deploy\build-frontend.ps1
```

```bash
# Linux / macOS
bash deploy/build-frontend.sh
```

上述脚本等价于 `cd frontend && npm ci && npm run build`，并检查 `dist/index.html` 是否生成。

然后提交并推送（**务必包含 `frontend/dist`**）：

```bash
git add frontend/ frontend/dist
git commit -m "feat: 说明本次改动"
git push
```

服务器：

```bash
bash /opt/secondary-school-grade-archive/deploy/update.sh
```

### 2.3 常见错误

| 现象 | 原因 | 处理 |
|------|------|------|
| 服务器页面没变化 | 只 push 了源码，未 push `frontend/dist` | 本地 `npm run build` 后重新提交 dist |
| `install.sh` 报错找不到 dist | 仓库里没有预构建的 dist | 开发机构建并 push 后再部署 |
| API 正常但页面 404 | `FRONTEND_DIST` 路径不对 | 检查 `.env` 中 `FRONTEND_DIST` |

---

## 3. 一键部署（新服务器）

若服务器访问 Git / apt / PyPI 需走代理，**在 `bash deploy/install.sh` 之前**设置：

```bash
export http_proxy=http://192.168.8.246:10810
export https_proxy=http://192.168.8.246:10810
export HTTP_PROXY="$http_proxy"
export HTTPS_PROXY="$https_proxy"
```

完整安装命令：

```bash
git clone https://git.bz121.com/dekun/secondary-school-grade-archive.git /opt/secondary-school-grade-archive
cd /opt/secondary-school-grade-archive
chmod +x deploy/*.sh
bash deploy/install.sh
```

> 脚本会读取上述环境变量，并自动配置 `apt` 代理。`git pull`、`pip install` 同样生效。内网直连时可不设代理。

脚本自动完成：

1. 安装 PostgreSQL、Python 依赖  
2. 检查 `frontend/dist/index.html` 是否存在  
3. 生成 `.env`、创建数据库  
4. 注册并启动 systemd 服务 `grade-archive`  

**前提：** 仓库中已包含 `frontend/dist/`（开发机构建后推送）。

部署成功后访问：**`http://<服务器IP>:23566`**

默认超级管理员：**admin / admin123**（登录后请在「系统设置」中修改）

---

## 4. 环境变量（`.env`）

| 变量 | 默认 | 说明 |
|------|------|------|
| `WEB_PORT` | 23566 | 对外端口 |
| `FRONTEND_DIST` | `.../frontend/dist` | 前端静态目录（绝对路径） |
| `DATABASE_URL` | 自动生成 | PostgreSQL 连接 |
| `SECRET_KEY` | 自动生成 | JWT 密钥 |
| `UPLOAD_DIR` | `.../uploads` | 错题图片目录 |
| `ADMIN_DEFAULT_USERNAME` | admin | 首次安装默认管理员用户名 |
| `ADMIN_DEFAULT_PASSWORD` | admin123 | 首次安装默认管理员密码 |

示例见仓库根目录 [.env.example](../.env.example)。

---

## 5. 常用命令

```bash
# 服务状态
systemctl status grade-archive

# 实时日志
journalctl -u grade-archive -f

# 拉代码并重启（日常更新；需代理时先 export，见 §3）
export http_proxy=http://192.168.8.246:10810
export https_proxy=http://192.168.8.246:10810
export HTTP_PROXY="$http_proxy"
export HTTPS_PROXY="$https_proxy"
bash /opt/secondary-school-grade-archive/deploy/update.sh

# 备份数据库与 uploads
bash /opt/secondary-school-grade-archive/deploy/backup.sh
```

---

## 5.1 停止与启动服务

仅暂停服务、不卸载时使用：

```bash
sudo systemctl stop grade-archive      # 停止
sudo systemctl start grade-archive     # 启动
sudo systemctl restart grade-archive   # 重启
systemctl status grade-archive         # 查看状态
```

若曾安装 OCR Worker：

```bash
sudo systemctl stop ocr-worker
sudo systemctl start ocr-worker
```

停止后浏览器访问 `http://<服务器IP>:23566` 会无法连接；启动后即可恢复。

---

## 5.2 卸载

卸载脚本会停止并禁用 systemd 服务、清理旧版 PM2 进程，以及可选删除安装目录 / 数据库。

```bash
# 停止服务并禁用，保留代码、数据库、uploads（可再次 install / update）
sudo bash /opt/secondary-school-grade-archive/deploy/uninstall.sh

# 额外删除安装目录 /opt/secondary-school-grade-archive
sudo bash /opt/secondary-school-grade-archive/deploy/uninstall.sh --purge

# 额外删除 PostgreSQL 库与用户（默认库 student_archive、用户 gradeapp）
sudo bash /opt/secondary-school-grade-archive/deploy/uninstall.sh --purge-db

# 完全清除（目录 + 数据库），便于干净重装
sudo bash /opt/secondary-school-grade-archive/deploy/uninstall.sh --purge --purge-db
```

说明：

| 选项 | 效果 |
|------|------|
| （无选项） | 停止并删除 `grade-archive` / `ocr-worker` 服务单元；保留 `/opt/...`、数据库、上传文件 |
| `--purge` | 删除安装目录（含代码、`.env`、`uploads`） |
| `--purge-db` | 删除 PostgreSQL 数据库与应用用户 |

脚本需 **root** 执行（`sudo bash ...`）。交互式终端下会询问确认；取消则退出且不做修改。

卸载后重新部署见 [§3 一键部署](#3-一键部署新服务器)。若仅停服务未 `--purge`，可直接：

```bash
cd /opt/secondary-school-grade-archive
sudo bash deploy/install.sh
```

---

## 6. 从旧版 PM2 迁移

若之前使用 `grade-api` + `grade-web`（PM2 + Express），执行 `deploy/update.sh` 会：

- 停止并删除 PM2 进程 `grade-api`、`grade-web`  
- 重启 systemd 服务 `grade-archive`  

`.env` 调整建议：

- 保留 `WEB_PORT=23566`  
- 添加 `FRONTEND_DIST=/opt/secondary-school-grade-archive/frontend/dist`  
- 可删除 `API_PORT`、`API_TARGET`（已不再使用）

---

## 7. 超级管理员

| 功能 | 说明 |
|------|------|
| 默认账号 | **admin / admin123**（首次安装后请立即修改） |
| 系统设置 | 超级管理员可修改自己的用户名、密码 |
| 注册开关 | 可开启/关闭登录页公开注册 |
| 用户管理 | 注册关闭时，由管理员添加用户并重置密码 |
| 普通用户 | **不能**自行修改用户名和密码 |

使用说明见 [USAGE.md](./USAGE.md)。

---

## 8. 反向代理（用户自行配置）

本项目**不包含** Nginx / Caddy 等反向代理配置。若需 HTTPS 或域名访问，请在服务器上自行配置，将流量转发到 `127.0.0.1:23566`。

---

## 9. OCR 报错 libGL.so.1

若错题识别失败并提示 `libGL.so.1: cannot open shared object file`，说明服务器缺少 OpenGL 运行库（PaddleOCR/OpenCV 需要）。

在服务器执行：

```bash
cd /opt/secondary-school-grade-archive
git pull
sudo bash deploy/install-ocr-deps.sh
sudo systemctl restart grade-archive
```

或执行完整更新（已包含 OCR 依赖检查）：

```bash
sudo bash deploy/update.sh
```

---

## 10. 更新后无法访问（连接被拒绝）

若浏览器提示 **ERR_CONNECTION_REFUSED**，通常是 `update.sh` 停止了旧 PM2，但 **systemd 服务未成功启动**。

在服务器执行一键修复：

```bash
cd /opt/secondary-school-grade-archive
git pull
sudo bash deploy/repair.sh
```

手动排查：

```bash
systemctl status grade-archive
journalctl -u grade-archive -n 50 --no-pager
ss -tlnp | grep 23566
```

> **注意：** `update.sh` / `repair.sh` 必须用 **root** 运行（`sudo bash ...`）。

---

## 11. 技术支持

微信 **dekun03** · 手机 **18364911125**
