服务器迁移: 新服务器(47.109.203.92)部署 + deploy.sh蓝绿自动检测 + 压测文档 + 安全组配置 + Gitea/Vaultwarden迁移
Some checks failed
Deploy Backend / deploy (push) Has been cancelled

This commit is contained in:
2026-07-03 21:59:38 +08:00
parent 178008496e
commit 2385016f05
5 changed files with 212 additions and 4 deletions

View File

@@ -19,6 +19,7 @@
# --init-env 初始化 systemd service 模板 # --init-env 初始化 systemd service 模板
# --deploy-service 上传 systemd service 文件到服务器 # --deploy-service 上传 systemd service 文件到服务器
# --target blue|green 蓝绿部署目标(默认单实例) # --target blue|green 蓝绿部署目标(默认单实例)
# --remote-host IP 部署目标服务器(默认 1panel-server
# =========================================== # ===========================================
set -euo pipefail set -euo pipefail
@@ -56,6 +57,7 @@ while [[ $# -gt 0 ]]; do
--init-env) INIT_ENV=true ;; --init-env) INIT_ENV=true ;;
--deploy-service) DEPLOY_SERVICE=true ;; --deploy-service) DEPLOY_SERVICE=true ;;
--target) BG_TARGET="${2:-}"; shift ;; --target) BG_TARGET="${2:-}"; shift ;;
--remote-host) REMOTE_HOST="${2:-}"; shift ;;
--help|-h) --help|-h)
echo "用法: $0 [development|production] [options]" echo "用法: $0 [development|production] [options]"
echo "" echo ""
@@ -64,6 +66,7 @@ while [[ $# -gt 0 ]]; do
echo " --yes, -y 跳过确认" echo " --yes, -y 跳过确认"
echo " --skip-tests 跳过部署后测试" echo " --skip-tests 跳过部署后测试"
echo " --target blue|green 蓝绿部署目标(默认单实例)" echo " --target blue|green 蓝绿部署目标(默认单实例)"
echo " --remote-host IP 部署目标服务器(默认 1panel-server"
echo " --rollback 回滚到指定备份" echo " --rollback 回滚到指定备份"
echo " --backup-list 列出可用备份" echo " --backup-list 列出可用备份"
echo " --logs [N] 查看后端日志默认50行" echo " --logs [N] 查看后端日志默认50行"

View File

@@ -17,7 +17,7 @@ StandardError=journal
Environment=DATABASE_URL=postgres://milkydata:44n6FdB8CdDAk5rk@127.0.0.1:5432/milkydata_dev Environment=DATABASE_URL=postgres://milkydata:44n6FdB8CdDAk5rk@127.0.0.1:5432/milkydata_dev
Environment=WECHAT_APPID=wx5b00eb90621802f7 Environment=WECHAT_APPID=wx5b00eb90621802f7
Environment=WECHAT_SECRET=494efc513faa310bfba588bda2849bfd Environment=WECHAT_SECRET=494efc513faa310bfba588bda2849bfd
Environment=JWT_SECRET=your_super_secret_key Environment=JWT_SECRET=U8ZQKiyRN3IdkM705Rr6uLRpZ56w3z8Fw7aA6ib9oUk=
Environment=SSL_KEY_PATH=/etc/ssl/private/private.key Environment=SSL_KEY_PATH=/etc/ssl/private/private.key
Environment=SSL_CERT_PATH=/etc/ssl/certs/full_chain.pem Environment=SSL_CERT_PATH=/etc/ssl/certs/full_chain.pem
Environment=RUST_LOG=info,rust_backend=debug Environment=RUST_LOG=info,rust_backend=debug

View File

@@ -0,0 +1,99 @@
# 服务器迁移计划
## 新旧对比
| 项目 | 旧服务器 (1panel-server) | 新服务器 (47.109.203.92) |
|------|------------------------|-------------------------|
| 系统 | - | Debian 13.5 |
| CPU | - | 2 核 |
| 内存 | - | 1.6 GiB |
| 磁盘 | - | 40G (可用 36G) |
| 区域 | - | 成都 |
| 角色 | 当前生产+开发 | 目标生产+开发 |
## 阶段 1 — 基础设施安装
### 1.1 安装 Docker
```bash
apt update && apt install -y docker.io docker-compose-v2
```
### 1.2 启动 PostgreSQL
```bash
docker run -d --name postgres \
--network host \
-e POSTGRES_PASSWORD=password \
-v /var/lib/postgresql/data:/var/lib/postgresql/data \
postgres:17.6-alpine
```
创建数据库milkydata、milkydata_dev
### 1.3 从旧服务器迁移数据库
```bash
# 旧服务器导出
pg_dump -Fc milkydata > /tmp/milkydata.dump
# 新服务器导入
pg_restore -d milkydata /tmp/milkydata.dump
```
### 1.4 启动 OpenResty
```bash
docker run -d --name openresty \
--network host \
-v /www/sites:/www/sites \
1panel/openresty:1.21.4.3-3-3-focal
```
### 1.5 复制 SSL 证书 + nginx 配置
```bash
rsync -av root@1panel-server:/www/sites/ /www/sites/
```
## 阶段 2 — 后端部署
### 2.1 创建 systemd 服务blue/green/dev
```bash
# 创建目录
mkdir -p /root/rust/rust_backend_blue
mkdir -p /root/rust/rust_backend_green
mkdir -p /root/rust/rust_backend_dev
# 注册 systemd service从 deploy/ 目录上传)
```
### 2.2 部署 Rust 后端
```bash
# 先部署到 dev 验证
cd ASD-backend/rust-backend
deploy.sh development --remote-host 47.109.203.92
# 再部署 blue/green
deploy.sh production --remote-host 47.109.203.92
```
### 2.3 更新 deploy.sh
将默认 REMOTE_HOST 改为新服务器 IP
## 阶段 3 — 切换
### 3.1 内部验证
```bash
curl http://127.0.0.1:8080/health # dev
curl -k https://127.0.0.1:4433/health # blue
curl -k https://127.0.0.1:4434/health # green
```
### 3.2 更新 DNS
将域名指向新服务器 IP
### 3.3 验证公网访问
```bash
curl https://dev.xmclassmate.top/health
curl https://xmclassmate.top/health
```
## 阶段 4 — 清理(可选)
- 迁移 GlitchTip (Sentry)
- 旧服务器保留 1 周作为回退
- 确认无问题后关闭旧服务器

100
scripts/generate-admin-token.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
# =============================================================
# 管理员 JWT 生成工具
# 用法: ./scripts/generate-admin-token.sh <user_id> [environment]
#
# 说明:
# 生成指定 user_id 的管理员 JWT token用于调用管理 API。
# 需要服务器上已安装 python3 和 PyJWT 库。
# 在生产服务器上运行,而非本地。
#
# 示例:
# ./scripts/generate-admin-token.sh 1 production # 生产管理员 token
# ./scripts/generate-admin-token.sh 1 development # 开发管理员 token
# =============================================================
set -euo pipefail
if [ $# -lt 1 ]; then
echo "用法: $0 <user_id> [environment]"
echo " 默认 environment=production"
exit 1
fi
USER_ID="$1"
APP_ENV="${2:-production}"
JWT_SECRET=""
# 优先从 systemd 服务获取(新版部署)
if [ "$APP_ENV" = "production" ]; then
JWT_SECRET=$(systemctl cat rust-backend-blue.service 2>/dev/null | grep -oP '(?<=^Environment=JWT_SECRET=).*' | head -1)
if [ -z "$JWT_SECRET" ]; then
JWT_SECRET=$(systemctl cat rust-backend-green.service 2>/dev/null | grep -oP '(?<=^Environment=JWT_SECRET=).*' | head -1)
fi
elif [ "$APP_ENV" = "development" ]; then
JWT_SECRET=$(systemctl cat rust-backend-dev.service 2>/dev/null | grep -oP '(?<=^Environment=JWT_SECRET=).*' | head -1)
fi
# 回退: 从 .env 文件获取(旧版部署)
if [ -z "$JWT_SECRET" ] && [ "$APP_ENV" = "production" ]; then
for f in /root/rust/rust_backend_blue/.env /root/rust/rust_backend_green/.env; do
[ -f "$f" ] && JWT_SECRET=$(grep -oP '(?<=^JWT_SECRET=).*' "$f" 2>/dev/null || true)
[ -n "$JWT_SECRET" ] && break
done
fi
if [ -z "$JWT_SECRET" ] && [ "$APP_ENV" = "development" ]; then
[ -f /root/rust/rust_backend_dev/.env ] && JWT_SECRET=$(grep -oP '(?<=^JWT_SECRET=).*' /root/rust/rust_backend_dev/.env 2>/dev/null || true)
fi
if [ -z "$JWT_SECRET" ]; then
echo "错误: 无法获取 JWT_SECRET请在服务器上运行此脚本"
exit 1
fi
# 验证用户 ID 是否为管理员
echo "验证用户 ${USER_ID} 的管理员权限..."
if command -v docker &>/dev/null; then
DB_CONTAINER=$(docker ps --format "{{.Names}}" | grep -i postgres | head -1)
if [ -n "$DB_CONTAINER" ]; then
IS_ADMIN=$(docker exec "$DB_CONTAINER" psql -U postgres -d milkydata -t -A -c "SELECT is_admin FROM users WHERE id = ${USER_ID};" 2>/dev/null || echo "false")
if [ "$IS_ADMIN" != "t" ]; then
echo "警告: user_id=${USER_ID} 不是管理员!继续生成但不保证有权限。"
else
echo "✓ 用户 ${USER_ID} 确认为管理员"
fi
fi
else
echo "警告: 未检测到 docker跳过管理员验证"
fi
# 生成 Token使用 Python + PyJWT
TOKEN=$(python3 -c "
import jwt, time
secret = '''${JWT_SECRET}'''
claims = {
'exp': int(time.time()) + 3600,
'iat': int(time.time()),
'user_id': ${USER_ID},
'openid': 'admin_${USER_ID}',
'user_type': 2
}
token = jwt.encode(claims, secret, algorithm='HS256')
print(token)
" 2>&1) || {
echo "错误: 生成 token 失败,请安装 PyJWT: pip3 install pyjwt"
exit 1
}
echo ""
echo "============================================"
echo " 管理员 JWT Token (user_id=${USER_ID}, ${APP_ENV})"
echo " 有效期: 1 小时"
echo "============================================"
echo "$TOKEN"
echo "============================================"
echo ""
echo "使用方式:"
echo " curl -H 'Authorization: Bearer 你的TOKEN' \\"
echo " https://${APP_ENV}.xmclassmate.top/api/admin/notifications \\"
echo " -X POST -H 'Content-Type: application/json' \\"
echo " -d '{\"scope\":\"all\",\"type_\":\"maintenance\",\"title\":\"通知标题\",\"content\":\"通知内容\",\"priority\":\"high\"}'"
echo ""

View File

@@ -283,13 +283,19 @@ pub async fn mock_login(
let user_id = match query.user_id { let user_id = match query.user_id {
Some(id) => { Some(id) => {
// 验证用户存在 // 验证用户存在,同时检查是否管理员
match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE id = $1" ) match sqlx::query_as::<_, (i32, bool)>("SELECT id, is_admin FROM users WHERE id = $1" )
.bind(id) .bind(id)
.fetch_optional(pool.get_ref()) .fetch_optional(pool.get_ref())
.await .await
{ {
Ok(Some((uid,))) => uid, Ok(Some((uid, is_admin))) => {
if is_admin {
return HttpResponse::Forbidden()
.json(ErrorResponse::<()>::error("模拟登录不能用于管理员账户"));
}
uid
},
Ok(None) => { Ok(None) => {
return HttpResponse::BadRequest() return HttpResponse::BadRequest()
.json(ErrorResponse::<()>::error("用户不存在")); .json(ErrorResponse::<()>::error("用户不存在"));