diff --git a/deploy/.env.sqlite.example b/deploy/.env.sqlite.example new file mode 100644 index 0000000..56002d1 --- /dev/null +++ b/deploy/.env.sqlite.example @@ -0,0 +1,36 @@ +# --------------------------------------------------------------------------- +# 班级小助手 · Docker 单容器(SQLite)环境变量 +# 复制为 deploy/.env.sqlite 后填写:cp deploy/.env.sqlite.example deploy/.env.sqlite +# 配合 deploy/docker-compose.sqlite.yml 使用 +# --------------------------------------------------------------------------- + +# 服务端对外端口(宿主机)。注意与同机其他服务错开:80/443 常被反代占用 +SERVER_PORT=4000 + +# 应用版本(镜像标签) +APP_VERSION=0.1.0 + +# JWT 签名密钥(必填,必须 ≥32 位;不能包含 secret / change-me 等示例词) +# 生成:node -e "console.log(require('crypto').randomBytes(48).toString('hex'))" +JWT_SECRET= + +# 令牌有效期 +JWT_EXPIRES_IN=7d + +# 允许的跨域来源;用域名访问时建议写成具体来源,例如 https://class.example.com +CORS_ORIGIN=* + +# 首次启动创建的管理员账号(**部署后请立即登录修改密码**) +INITIAL_ADMIN_USERNAME=admin +INITIAL_ADMIN_PASSWORD=admin123 + +# 各类账号的默认初始密码(新增/重置时使用) +DEFAULT_STUDENT_PASSWORD=123456 +DEFAULT_CLASS_PASSWORD=123456 +DEFAULT_TEACHER_PASSWORD=123456 + +# 第 1 教学周的周一(用于"当前周次") +TERM_START_DATE=2026-08-31 + +# 日志级别:debug | info | warn | error +LOG_LEVEL=info diff --git a/deploy/Dockerfile b/deploy/Dockerfile index fe68f70..a6672a1 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -33,7 +33,12 @@ RUN pnpm install --frozen-lockfile --ignore-scripts # 再拷贝源码并构建(只构建服务端与 Web 管理端,桌面客户端不参与) COPY packages ./packages COPY scripts ./scripts + +# 必须先生成 Prisma Client:`pnpm install --ignore-scripts` 跳过了 postinstall, +# 而生成产物 `packages/server/src/generated/prisma` 是 gitignored 的(clone 下来没有), +# 少了这一步下面的 tsc 会报 "Cannot find module '../generated/prisma/client.js'"。 RUN pnpm build:shared \ + && pnpm --filter @classhelper/server run db:generate \ && pnpm --filter @classhelper/server build \ && pnpm --filter @classhelper/web-admin build diff --git a/deploy/docker-compose.sqlite.yml b/deploy/docker-compose.sqlite.yml new file mode 100644 index 0000000..30a136a --- /dev/null +++ b/deploy/docker-compose.sqlite.yml @@ -0,0 +1,56 @@ +# 班级小助手 · Docker 单容器部署(SQLite + 数据卷) +# +# 适用:云服务器 / 与别的服务共用一台机器。相比 deploy/docker-compose.yml(MySQL 变体) +# 少跑一个数据库容器,内存占用小很多;数据落在 Docker 卷 classhelper-data 里。 +# +# 使用: +# 1) cp deploy/.env.sqlite.example deploy/.env.sqlite 并填 JWT_SECRET / 管理员密码 +# (生成密钥:node -e "console.log(require('crypto').randomBytes(48).toString('hex'))") +# 2) docker compose -f deploy/docker-compose.sqlite.yml --env-file deploy/.env.sqlite up -d --build +# 3) 访问:http://<服务器IP>:${SERVER_PORT}/ +# +# 首次启动(AUTO_MIGRATE=true)会自动执行 prisma/migrations 里的建表语句并创建管理员账号, +# 见 packages/server/src/lib/db-bootstrap.ts("Docker 挂载空数据卷"就是它的目标场景之一)。 +# +# 备份:docker run --rm -v classhelper-sqlite_classhelper-data:/data -v "$PWD":/backup alpine \ +# tar czf /backup/classhelper-db.tar.gz -C /data . + +name: classhelper-sqlite + +services: + server: + build: + context: .. + dockerfile: deploy/Dockerfile + target: runtime + image: classhelper-server:${APP_VERSION:-0.1.0} + restart: unless-stopped + environment: + NODE_ENV: production + HOST: 0.0.0.0 + PORT: 4000 + DATABASE_PROVIDER: sqlite + # 必须写绝对路径:容器里 serverRoot 是 /app/server,相对路径会解析到别处 + DATABASE_URL: file:/app/data/classhelper.db + # 首启动自动建表 + 建管理员;生产自检会拒绝 dev.db 与过短的 JWT_SECRET + AUTO_MIGRATE: 'true' + STARTUP_DB_CHECK: 'true' + JWT_SECRET: ${JWT_SECRET:?请在 deploy/.env.sqlite 中设置(≥32 位随机串)} + JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-7d} + CORS_ORIGIN: ${CORS_ORIGIN:-*} + TRUST_PROXY: '1' + RATE_LIMIT_ENABLED: 'true' + LOG_LEVEL: ${LOG_LEVEL:-info} + TERM_START_DATE: ${TERM_START_DATE:-} + INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin} + INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-admin123} + DEFAULT_STUDENT_PASSWORD: ${DEFAULT_STUDENT_PASSWORD:-123456} + DEFAULT_CLASS_PASSWORD: ${DEFAULT_CLASS_PASSWORD:-123456} + DEFAULT_TEACHER_PASSWORD: ${DEFAULT_TEACHER_PASSWORD:-123456} + ports: + - '${SERVER_PORT:-4000}:4000' + volumes: + - classhelper-data:/app/data + +volumes: + classhelper-data: diff --git a/docs/production.md b/docs/production.md index 3f6a624..7677ce9 100644 --- a/docs/production.md +++ b/docs/production.md @@ -125,6 +125,28 @@ docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build - 反向代理上启用 HTTPS:见 `deploy/nginx.conf`,并在服务端环境变量中设置 `TRUST_PROXY=1`、`CORS_ORIGIN=https://你的域名`。 +### 2.1 单容器变体(SQLite,推荐给内存小的机器 / 与别的服务共用一台机器) + +`deploy/docker-compose.sqlite.yml` 只跑**服务端一个容器**,不额外起 MySQL:数据落在 Docker 卷 +`classhelper-data` 的 `/app/data/classhelper.db`,首次启动由 `AUTO_MIGRATE=true` 自动建表并创建管理员 +(`packages/server/src/lib/db-bootstrap.ts` 的"空数据卷"分支)。 + +```bash +cp deploy/.env.sqlite.example deploy/.env.sqlite # 填 JWT_SECRET、管理员密码、端口 +docker compose -f deploy/docker-compose.sqlite.yml --env-file deploy/.env.sqlite up -d --build +``` + +适用场景与注意点: + +- 机器内存紧张(比如 2G 小机器上还跑着别的服务)时,比 MySQL 变体少一个数据库进程; +- 端口用 `SERVER_PORT` 指定,**别抢占 80/443**(那通常是同机反代占用的); +- 生产自检会拒绝 `dev.db` 与过短的 `JWT_SECRET`,因此 `DATABASE_URL` 必须是绝对路径且不含 `dev.db`; +- 备份就是整个卷:`docker run --rm -v classhelper-sqlite_classhelper-data:/data -v "$PWD":/backup alpine tar czf /backup/classhelper-db.tar.gz -C /data .` + +> `deploy/Dockerfile` 的构建阶段会先跑 `pnpm --filter @classhelper/server run db:generate`: +> 生成产物 `packages/server/src/generated/prisma` 是 gitignored 的,而 `--ignore-scripts` 跳过了 +> postinstall,少了这一步 `tsc` 会直接编译失败。 + --- ## 三、Linux 原生部署(形态 C,systemd + SQLite)