Files
Hulumath-Web/docs/BAOTA_UBUNTU_FROM_ZERO.md
T
Jacky 1f8822042f
CI / test (push) Canceled after 4s
v0.3.2: minor push
2026-08-08 22:19:23 +08:00

624 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ubuntu + 宝塔 + MySQL 8.0.35 从零部署
本方案允许宝塔原生 MySQL 5.7 与 Docker MySQL 8.0.35 并存:
```text
旧应用(如仍需保留) → MySQL 5.7 :3306
葫芦数学 Django → MySQL 8.0.35 :3307
```
生产拓扑:
```text
用户
↓ HTTPS / WSS
宝塔 Nginx
↓ 127.0.0.1:8000
systemd: Gunicorn + UvicornWorker
├── Django HTTP API
└── Django Channels WebSocket
├── Docker MySQL 8.0.35 :3307
└── Redis :6379
```
约定:
- Ubuntu 22.04 或 24.04。
- Gitea 仓库:`http://117.72.28.96:8765/Jacky/Hulumath-Web.git`
- 项目目录:`/www/wwwroot/Hulumath-Web`
- 应用监听:`127.0.0.1:8000`
- MySQL 8 宿主机端口:`3307`
- systemd 服务:`hulumath-web`
- 示例域名:`math.example.com`,操作时替换为真实域名。
## 1. 服务器资源
8GB 内存服务器建议:
| 服务 | 建议限制 |
| --- | ---: |
| MySQL 8.0.35 | 1024 MB |
| Django 2 workers | 约 200-500 MB |
| Redis | 50-200 MB |
| Nginx | 通常小于 100 MB |
首发负载下压力不大。不要同时为 MySQL 5.7 和 8.0 分配数 GB Buffer Pool。
防火墙只放行:
```text
22 / 80 / 443
```
不要向公网开放:
```text
3306 / 3307 / 6379 / 8000
```
## 2. 备份旧站
```bash
mkdir -p /www/backup/hulumath-legacy
cp -a /www/wwwroot/Hulumath-Web \
"/www/backup/hulumath-legacy/Hulumath-Web.$(date +%Y%m%d_%H%M%S)"
```
旧 SQLite 数据不会自动导入 MySQL。若旧站已有真实用户,需要另做一次性迁移。
## 3. 安装系统依赖
宝塔软件商店保留或安装:
- Nginx
- Redis 7
- Docker 管理器
- Python 项目管理器及 Python 3.12.13
SSH 执行:
```bash
apt update
apt install -y git curl openssl build-essential pkg-config \
default-libmysqlclient-dev mysql-client-8.0 redis-tools
```
若 Ubuntu 软件源没有 `mysql-client-8.0`
```bash
apt install -y default-mysql-client
```
验证:
```bash
redis-cli -h 127.0.0.1 ping
mysql --version
mysqldump --version
ls -l /www/server/pyporject_evn/versions/3.12.13/bin/python*
```
Redis 应返回 `PONG`
如果这里显示宿主机 `mysql/mysqldump` 是 5.7,而 Docker 容器是
MySQL 8.0.35,不需要卸载旧客户端。宿主机 5.7 客户端只用于简单连通性测试;
生产自动备份必须配置 `MYSQL_CONTAINER`,使用容器内部的 MySQL 8
`mysqldump`,避免跨版本备份兼容问题。
## 4. 配置 Docker MySQL 8.0.35
宝塔 `MySQL 多版本管理(Docker 应用)` 中设置:
```text
版本:mysql:8.0.35
数据库名称:mysql_hulumath
MySQL 端口:3307
MySQL root 密码:生成强密码并离线保存
数据目录:保持宝塔默认持久化目录
内存限制:1024 MB
```
`compose 文件` 中确认或补充:
```yaml
services:
mysql:
image: mysql:8.0.35
ports:
- "127.0.0.1:3307:3306"
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
- --default-time-zone=+08:00
- --sql-mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
environment:
MYSQL_ROOT_PASSWORD: 替换为强密码
volumes:
- /www/dk_project/dk_mm_datas:/var/lib/mysql
deploy:
resources:
limits:
memory: 1024M
```
关键点:
- 最好绑定 `127.0.0.1:3307`,而不是 `0.0.0.0:3307`
- 数据目录必须是持久化目录。
- 不要复用旧 MySQL 5.7 的数据目录。
- 不要直接把 5.7 数据目录挂载给 8.0。
查看容器:
```bash
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
```
记录 MySQL 8 容器名:
```bash
MYSQL8_CONTAINER=实际容器名
```
## 5. 创建应用数据库与用户
进入 MySQL 8 容器:
```bash
docker exec -it "$MYSQL8_CONTAINER" mysql -uroot -p
```
执行:
```sql
CREATE DATABASE hulumath
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'hulumath'@'%'
IDENTIFIED WITH mysql_native_password BY 'ReplaceWithStrongPassword';
GRANT ALL PRIVILEGES ON hulumath.* TO 'hulumath'@'%';
FLUSH PRIVILEGES;
SELECT VERSION();
SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
SHOW VARIABLES LIKE 'sql_mode';
```
版本必须是 `8.0.35`,字符集应为 `utf8mb4`
这里使用 `mysql_native_password` 是为了兼容服务器现有 MySQL 5.7 命令行客户端
和系统 `mysqlclient` 动态库。数据库端口只绑定 `127.0.0.1:3307`,该账号不会
暴露公网。
从宿主机验证:
```bash
mysql -h 127.0.0.1 -P 3307 \
-u hulumath -p hulumath \
-e 'SELECT VERSION(), DATABASE();'
```
数据库密码若含有 `@``#``:``/` 等字符,写入 `DATABASE_URL` 前必须 URL 编码。
## 6. 验证服务器可以读取 Gitea 仓库
```bash
git ls-remote \
http://117.72.28.96:8765/Jacky/Hulumath-Web.git \
refs/heads/main
```
该仓库当前允许匿名读取,因此生产服务器无需保存 Gitea 账号密码。若以后改为私有仓库,应使用只读 Deploy Token,并通过 Git credential helper 配置,不要把 Token 写进 workflow 或仓库 URL。
## 7. 重新创建项目目录
```bash
cd /www/wwwroot
if [ -d Hulumath-Web ]; then
mv Hulumath-Web \
"Hulumath-Web.flask.$(date +%Y%m%d_%H%M%S)"
fi
git clone \
http://117.72.28.96:8765/Jacky/Hulumath-Web.git \
Hulumath-Web
cd Hulumath-Web
git checkout main
```
确认:
```bash
test -f backend/manage.py
test -f scripts/deploy_production.sh
test -f deploy/hulumath-web.service
```
## 8. 创建生产环境变量
```bash
cd /www/wwwroot/Hulumath-Web
cp .env.production.example .env.production
openssl rand -hex 48
nano .env.production
```
示例:
```dotenv
DJANGO_SETTINGS_MODULE=config.settings
DJANGO_SECRET_KEY='替换为 openssl 生成的密钥'
DJANGO_DEBUG=false
DJANGO_USE_HTTPS=true
DJANGO_ALLOWED_HOSTS=math.example.com
DATABASE_URL='mysql://hulumath:ReplaceWithStrongPassword@127.0.0.1:3307/hulumath'
REDIS_URL='redis://127.0.0.1:6379/0'
CORS_ALLOWED_ORIGINS=https://math.example.com
CSRF_TRUSTED_ORIGINS=https://math.example.com
API_ANON_RATE=120/minute
API_USER_RATE=600/minute
SECURE_HSTS_SECONDS=31536000
DEPLOY_HEALTH_HOST=math.example.com
MYSQL_CONTAINER=mysql_o0fbdmmf8q
```
`MYSQL_CONTAINER` 必须填写 `docker ps` 显示的 MySQL 8 容器名。你的服务器
当前应填写 `mysql_o0fbdmmf8q`。部署脚本会把临时凭据复制进容器,调用容器
内部的 MySQL 8 `mysqldump`,完成后立即删除。
锁定权限:
```bash
chown root:root .env.production
chmod 600 .env.production
```
仅使用 IP、尚未配置域名和证书时,可临时改为:
```dotenv
DJANGO_USE_HTTPS=false
DJANGO_ALLOWED_HOSTS=117.72.28.96,127.0.0.1,localhost
CORS_ALLOWED_ORIGINS=http://117.72.28.96
CSRF_TRUSTED_ORIGINS=http://117.72.28.96
SECURE_HSTS_SECONDS=0
DEPLOY_HEALTH_HOST=117.72.28.96
```
绑定域名并申请 SSL 后,必须把 `DJANGO_USE_HTTPS` 改回 `true`,并将
Allowed Hosts、CORS、CSRF 和健康检查 Host 全部替换为正式域名。
## 9. 在宝塔创建网站
宝塔 `网站``添加站点`
```text
域名:math.example.com
根目录:/www/wwwroot/Hulumath-Web
PHP:纯静态
数据库:不创建
FTP:不创建
```
站点配置加入:
```nginx
client_max_body_size 10m;
location /ws/ {
proxy_pass http://127.0.0.1:8000;
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 120s;
proxy_send_timeout 120s;
}
location / {
proxy_pass http://127.0.0.1:8000;
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_connect_timeout 10s;
proxy_read_timeout 120s;
}
```
检查并重载:
```bash
nginx -t
/etc/init.d/nginx reload
```
## 10. 配置 SSL
在宝塔站点 `SSL` 页面:
1. 申请 Let's Encrypt。
2. 开启强制 HTTPS。
3. 确认证书域名与 `DJANGO_ALLOWED_HOSTS` 一致。
## 11. 停止旧 Flask
在宝塔 Python 项目管理器停止旧 Flask。
```bash
ss -ltnp | grep ':8000' || true
```
首次 Django 部署前,8000 不应被旧进程占用。
## 12. 首次手动部署
确保系统安装了 mysqlclient 编译依赖:
```bash
apt install -y build-essential pkg-config default-libmysqlclient-dev
```
执行:
```bash
cd /www/wwwroot/Hulumath-Web
chmod +x scripts/deploy_production.sh
PROJECT_DIR=/www/wwwroot/Hulumath-Web \
PYTHON_BIN=/www/server/pyporject_evn/versions/3.12.13/bin/python3 \
bash scripts/deploy_production.sh
```
脚本顺序:
```text
创建虚拟环境
→ 安装 mysqlclient 等依赖
→ mysqldump --single-transaction
→ Django MySQL 检查
→ migrate
→ collectstatic
→ systemd restart
→ HTTP health
→ WebSocket/Redis health
```
查看:
```bash
systemctl status hulumath-web --no-pager
journalctl -u hulumath-web -n 200 --no-pager
```
## 13. 首次初始化业务数据
只在空数据库执行:
```bash
cd /www/wwwroot/Hulumath-Web
set -a
source .env.production
set +a
.venv-production/bin/python backend/manage.py seed_initial_content
.venv-production/bin/python backend/manage.py seed_contests
.venv-production/bin/python backend/manage.py createsuperuser
```
不要把种子命令加入每次自动部署。
## 14. 验证 MySQL 与网站
```bash
cd /www/wwwroot/Hulumath-Web
set -a
source .env.production
set +a
.venv-production/bin/python backend/manage.py check --database default
.venv-production/bin/python backend/manage.py showmigrations
```
检查表引擎与字符集:
```bash
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath -e "
SELECT TABLE_NAME, ENGINE, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA='hulumath'
ORDER BY TABLE_NAME;"
```
所有业务表应使用 `InnoDB``utf8mb4_0900_ai_ci`
健康检查:
```bash
curl -H 'Host: math.example.com' \
-H 'X-Forwarded-Proto: https' \
http://127.0.0.1:8000/health/
curl https://math.example.com/health/
```
## 15. 配置 Gitea Actions
生成 Actions 到服务器的 SSH 密钥:
```bash
ssh-keygen -t ed25519 -C 'gitea-actions-hulumath' \
-f ./hulumath_actions_deploy -N ''
```
把公钥加入生产服务器:
```bash
cat hulumath_actions_deploy.pub >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
```
在 Gitea 仓库 `Settings → Actions → Secrets` 添加:
| Secret | 值 |
| --- | --- |
| `DEPLOY_HOST` | 服务器公网 IP 或 SSH 域名 |
| `DEPLOY_USER` | 推荐首次使用 `root` |
| `DEPLOY_SSH_KEY` | 私钥全文 |
确保有能匹配 `ubuntu-latest` 的在线 Runner。
PR 合并到 `main` 后,workflow 会:
```text
SQLite 快速测试
→ 启动 MySQL 8.0.35 临时容器
→ MySQL 全量迁移和测试
→ SSH 生产服务器
→ MySQL 备份
→ migrate
→ 重启和健康检查
```
## 16. 备份与恢复
自动备份目录:
```text
/www/backup/hulumath/
```
文件格式:
```text
hulumath_YYYYMMDD_HHMMSS.sql.gz
```
保留 14 天。
人工备份建议同样使用容器内的 MySQL 8 客户端。以下命令会提示输入应用数据库密码:
```bash
docker exec -i mysql_o0fbdmmf8q mysqldump \
-u hulumath -p \
--single-transaction --quick \
--triggers --no-tablespaces \
--hex-blob --set-gtid-purged=OFF \
hulumath | gzip > /www/backup/hulumath/manual.sql.gz
```
恢复前必须停止应用并先备份当前库:
```bash
systemctl stop hulumath-web
gunzip -c /www/backup/hulumath/具体备份.sql.gz | \
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
systemctl start hulumath-web
```
## 17. 旧数据迁移边界
不能直接执行:
```text
复制 SQLite 文件到 MySQL
复制 MySQL 5.7 数据目录给 MySQL 8
把 PostgreSQL dump 导入 MySQL
```
如果新 MySQL 8 还是空库,直接迁移 Django schema 并导入种子数据即可。
如果需要保留旧 SQLite 或其他数据库中的业务数据,应通过 Django `dumpdata/loaddata` 或专用 ETL 脚本迁移,并在 Staging 核对:
- 用户数
- MathBTI 结果数
- 剧情存档数
- 比赛记录数
- 卡牌和成长记录数
## 18. 常见问题
### mysqlclient 安装失败
```bash
apt install -y build-essential pkg-config default-libmysqlclient-dev
rm -rf /www/wwwroot/Hulumath-Web/.venv-production
bash scripts/deploy_production.sh
```
### Access denied for user
确认用户允许 Docker 外部宿主机连接:
```sql
SELECT user, host FROM mysql.user WHERE user='hulumath';
SHOW GRANTS FOR 'hulumath'@'%';
```
### Can't connect to MySQL server
```bash
docker ps
ss -ltnp | grep 3307
mysql -h 127.0.0.1 -P 3307 -u hulumath -p hulumath
```
### 部署提示宿主机 mysqldump 版本过低
```bash
docker exec mysql_o0fbdmmf8q mysqldump --version
```
容器内应显示 MySQL 8。然后在 `.env.production` 设置:
```dotenv
MYSQL_CONTAINER=mysql_o0fbdmmf8q
```
### 字符集或 Emoji 写入失败
确认:
```sql
SHOW VARIABLES LIKE 'character_set_server';
SHOW VARIABLES LIKE 'collation_server';
```
应为 `utf8mb4``utf8mb4_0900_ai_ci`
### 实时匹配出现锁等待
确认使用 MySQL 8.0.35 而不是 5.7,并检查索引:
```sql
SHOW INDEX FROM contest_realtimematch;
```
应存在 `matchmaking_lookup_idx`
### 502 Bad Gateway
```bash
systemctl status hulumath-web
journalctl -u hulumath-web -n 200 --no-pager
ss -ltnp | grep 8000
```
### WebSocket 失败
检查 Nginx `/ws/``Upgrade`/`Connection` 头,并确认:
```bash
redis-cli ping
journalctl -u hulumath-web -n 200 --no-pager
```