Skip to content

Commit fff4d50

Browse files
Lokins577claude
andcommitted
docs: 部署文档
从零上线的完整步骤,重点标注两个容易踩空的地方: - Prism 应用必须用团队应用入口创建。建在个人名下时,受限账号在 /authorize 阶段被静默拒绝,没有任何面向用户的报错,只表现为点了登录没反应 - 三个 webhook 事件都要订阅,且它不是可选优化:模组玩家可能长期不访问网页, validate 读的是本地 role_key,没有 webhook 只能等 License 自然过期 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3a61adb commit fff4d50

2 files changed

Lines changed: 161 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ schema 只通过 `migrations/` 下的有序文件演进,**不在请求路径
6969

7070
## 文档
7171

72+
- [部署](docs/deployment.md) —— 从零上线的完整步骤与验证清单
7273
- [模组授权设计](docs/mod-authorization.md)
7374

7475
## 进度

docs/deployment.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# 部署
2+
3+
从零把这个分支跑到线上需要的全部步骤。顺序不能打乱 —— Prism 应用必须先建在
4+
NSUK 团队名下,否则受限账号在授权阶段会被静默拒绝。
5+
6+
---
7+
8+
## 一、创建 Cloudflare 资源
9+
10+
```bash
11+
wrangler d1 create nsuk
12+
wrangler kv namespace create KV
13+
wrangler r2 bucket create nsuk-uploads
14+
```
15+
16+
把返回的 `database_id` 与 KV `id` 填进 `wrangler.toml`。R2 桶名已写死为
17+
`nsuk-uploads`,改名的话记得同步。
18+
19+
## 二、在 Prism 侧建应用
20+
21+
**必须用团队应用的入口创建**`POST /api/teams/:id/apps` 或团队页面的「新建应用」),
22+
不能用个人应用入口。
23+
24+
原因见 Prism 的团队邀请链接注册方案 §8②:受限账号只能授权「来源团队及其后代
25+
拥有的应用」。NSUK 用户绝大多数通过 `/join/<teamId>` 注册,属于受限账号;应用若
26+
建在个人名下,这批人在 `/authorize` 阶段直接被拒,而且**没有任何面向用户的报错**
27+
只会表现为登录点了没反应。
28+
29+
|||
30+
|---|---|
31+
| 客户端类型 | 机密 |
32+
| 重定向 URI | `https://<官网域名>/api/auth/callback`(匹配方式:等于) |
33+
| 追加重定向 URI | `http://127.0.0.1:3000/api/auth/callback`(本地开发) |
34+
| allowed_scopes | `openid` `profile` `email` `offline_access` `teams:read` |
35+
36+
`teams:read` 是过度授权(会带上用户所有团队的 membership claim),但在 Prism 的
37+
P1(应用预绑定团队的窄 scope)落地前没有更窄的选择 —— 单团队 scope 要求授权者是
38+
团队 admin 以上,普通赞助者授不了。P1 上线后把 scope 换掉即可,官网侧只读
39+
`groups_in_team_<id>`,不依赖其余 claim。
40+
41+
### 团队身份组
42+
43+
在 NSUK 团队开启 `enable_groups`,创建这些 slug(**创建后不可改**):
44+
45+
| slug | 对应角色 | level |
46+
|---|---|---|
47+
| `sponsor` | 赞助者 | 1 |
48+
| `staff` | 客服 | 2 |
49+
| `developer` | 开发者 | 3 |
50+
| `admin` | 管理员 | 4 |
51+
52+
没有任何 group 的成员是 `guest`(level 0),这是正常状态而非异常。
53+
54+
### 审计 webhook
55+
56+
团队 owner 在 `/api/audit/team/:teamId/webhooks` 创建 general 类型 webhook:
57+
58+
- URL:`https://<官网域名>/api/hooks/prism/audit`
59+
- Header:`X-NSUK-Webhook-Secret: <与 WEBHOOK_SECRET 相同的值>`
60+
- Body 模板:
61+
62+
```json
63+
{"event":"{event}","resource_id":"{resource_id}","scope_id":"{scope_id}","metadata":{metadata},"timestamp":"{timestamp}"}
64+
```
65+
66+
- 订阅事件:`team.member.groups_change``team.member.remove``team.group.delete`
67+
68+
**三个都要订**。只订 `groups_change` 会漏掉被整个移出团队的人;只订前两个,
69+
身份组定义被删除时不会有任何通知。
70+
71+
这个 webhook 不是可选优化:模组玩家可能长期不访问网页,`validate` 读的是本地
72+
`role_key`,没有 webhook 就只能等 License 自然过期(最长 4 天)才失效。
73+
74+
## 三、生成 License 签名密钥
75+
76+
```bash
77+
node -e "
78+
const {generateKeyPairSync}=require('crypto');
79+
const {privateKey,publicKey}=generateKeyPairSync('rsa',{modulusLength:2048,
80+
privateKeyEncoding:{type:'pkcs8',format:'pem'},
81+
publicKeyEncoding:{type:'spki',format:'pem'}});
82+
require('fs').writeFileSync('license-public.pem', publicKey);
83+
console.log(privateKey.replace(/-----[^-]+-----/g,'').replace(/\s+/g,''));
84+
"
85+
```
86+
87+
输出的单行 base64 就是 `MOD_LICENSE_PRIVATE_KEY` 的值;`license-public.pem`
88+
交给模组开发者内置进 jar。**私钥不要提交到任何仓库。**
89+
90+
密钥轮换意味着所有已签发的 License 立刻失效,且旧版模组无法验证新 License ——
91+
只能随模组版本一起换。
92+
93+
## 四、写入配置
94+
95+
`wrangler.toml``[vars]`
96+
97+
```toml
98+
SITE_URL = "https://<官网域名>"
99+
PRISM_ISSUER = "https://<prism 域名>"
100+
PRISM_CLIENT_ID = "<Client ID>"
101+
PRISM_TEAM_ID = "<NSUK 团队 ID>"
102+
PRISM_JOIN_URL = "https://<prism 域名>/join/<团队ID>?continue=https://<官网域名>/"
103+
```
104+
105+
secrets:
106+
107+
```bash
108+
wrangler secret put PRISM_CLIENT_SECRET
109+
wrangler secret put MOD_LICENSE_PRIVATE_KEY
110+
wrangler secret put WEBHOOK_SECRET
111+
```
112+
113+
## 五、建表与部署
114+
115+
```bash
116+
pnpm db:migrate:remote
117+
pnpm deploy
118+
```
119+
120+
不需要迁移旧数据 —— 旧库已放弃,空库起步。
121+
122+
### 第一个管理员
123+
124+
角色由 Prism 身份组决定,官网没有提权入口,所以**第一个管理员必须在 Prism 侧
125+
给自己打上 `admin`**,然后登录官网一次即可生效。
126+
127+
## 六、上线后验证
128+
129+
```bash
130+
# 绑定自检:三个绑定都应为 true,roles 应为 5
131+
curl https://<域名>/api/_ping
132+
133+
# 登录链路(浏览器里走一遍)
134+
https://<域名>/api/auth/login
135+
136+
# webhook 密钥校验(无 header 应 401)
137+
curl -X POST https://<域名>/api/hooks/prism/audit -d '{}'
138+
139+
# 结构文件不可直接下载(应 403)
140+
curl https://<域名>/uploads/workshop/<任一id>/files/<任一文件>
141+
```
142+
143+
逐项确认:
144+
145+
- [ ] `/api/_ping` 三个绑定为 true,`roles: 5`
146+
- [ ] 能用 Prism 账号登录,账号中心显示正确的身份组
147+
- [ ] 赞助者能生成模组令牌,非赞助者看到「面向赞助者开放」提示
148+
- [ ] 模组 `validate` 返回 License,用公钥能验签通过
149+
- [ ] 在 Prism 移除某人的 `sponsor` 组,几秒内其 License 被吊销、模组 `validate`
150+
返回 `NOT_SPONSOR`
151+
- [ ] 工坊结构文件的直链返回 403,作品页下载按钮只指向站外链接
152+
- [ ] 审核员能通过后台下载结构文件核对,且 `audit_log` 有记录
153+
154+
## 七、回滚
155+
156+
新站与旧站是两套 Cloudflare 资源,互不影响。出问题把域名的路由指回旧 Worker
157+
即可,新站的 D1/KV/R2 原样保留。
158+
159+
由于两边账号体系不同(旧站自建密码,新站 Prism),**回滚后新站期间产生的用户
160+
数据不会出现在旧站**。切换前应确认新站稳定,避免来回切。

0 commit comments

Comments
 (0)