Files
SoundGame/docs/deploy.md
T
xsl ede522dda1 feat(platform/P0): 无感用户系统 + PC 键盘双端一致(两 agent 并行 + 集成收口)
用户系统(B,游戏零改动):
- POST /api/device 首访 0 交互静默建档(自动玩家一/二…,per-IP 频控);断网降级纯本地照玩
- 大厅档案切换器(切换=flush 上云→快照回灌→倒灌→服务器回拉,profileId 分桶修同设备串档;aria menu 编号可听)
- claim 可选认领(命名+scrypt PIN);旧 users/sessions 单向迁移(PIN 明文即刻哈希,已真实数据验证);login 兼容+claim-merge 桶级 LWW;logout;/server/ 静态封禁
- PUT baseUpdatedAt 守卫(legacy 放行+警告);GET 无参全桶回拉(修 7/8 游戏跨设备只 PUT 不 GET)
- web/profiles.js 分桶纯函数 + api-smoke.mjs 7/7(建档/LWW/claim-merge/迁移/频控/封禁/分桶 H1-H4)

键盘双端(A,游戏代码零改动):
- gesture.js 键盘通道 +137 行:←↑↓→/WASD=滑动、空格=单击(走 300ms 双击窗与触摸共享虚拟指针)、回车=即时 doubletap、按住空格 ≥450ms=长按;repeat 抑制/表单守卫/方向键 preventDefault
- settings.input(auto/touch/key)+ gesture.inputMode();遮罩键盘解锁与文案自适应(按空格开始/轻触开始)
- 8 游戏遮罩改用自适应默认文案;大厅设置面板「操作方式」+页脚键位提示
- live control.mjs 键盘注入通道(CDP dispatchKeyEvent);engine-input.test 61 断言

回归:12 套件全绿(8 playtest + guide + engine-voice + engine-input + api-smoke 7/7);8001 服务器已重启加载新 API,/api/device 端到端验证通过

P1 待排:153 句×双性 TTS 键盘语音变体 / 6 位配对码跨设备 / 认领 UI 打磨(research/platform/00-synthesis.md §3)
2026-09-09 23:39:23 +08:00

176 lines
6.5 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.
# SoundGame — 公网部署
线上地址:<https://game.xiangsilian.com/>
个人简历站 <https://xiangsilian.com/> 首页「深入了解」与页脚有入口。本机 Nginx
同时托管这两个站点:根域名仍是简历站,游戏走独立子域名,互不覆盖。
## 方案摘要
公网**只托管静态文件**,不启动 `web/server/server.js`。
原因:用户系统是家庭局域网玩具级认证(PIN 明文、token 永不过期、无速率限制),
见 `web/server/README.md`。游戏本身不依赖 API——未登录时设置和进度写在浏览器
`localStorage`。大厅在 `https://*.xiangsilian.com` 下会隐藏登录面板
(`web/index.html` 里的 `PUBLIC_STATIC`)。本地 `./web/serve.sh` 仍显示登录。
```
手机浏览器
│ HTTPS
▼
Nginx 80/443
├── xiangsilian.com → /var/www/xiangsilian (简历站,已有)
└── game.xiangsilian.com → /var/www/soundgame (本游戏)
```
证书是同一张 Let’s Encrypt(SAN:`xiangsilian.com`、`www`、`game`),webroot
验证目录 `/var/www/letsencrypt`。`certbot.timer` 负责续期。
## 本机路径
| 用途 | 路径 |
|---|---|
| 源码 | `/home/ubuntu/SoundGame/web/` |
| 线上静态根 | `/var/www/soundgame/`(`www-data` 可读;家目录 `750`,Nginx 读不了源码目录) |
| Nginx 站点 | `/etc/nginx/sites-available/soundgame.conf` → `sites-enabled/` |
| 简历站静态根 | `/var/www/xiangsilian/` |
| 简历站源码 | `/home/ubuntu/xiangsilian/` |
未发布:`harness/`、`*/gen/`、`*/test/`、`server/`、`template/`,以及 `*.py` /
`*.md` / `*.sh`。Android 工程、`tools/`、研究文档也不在线上。
## 更新游戏文件
上述流程已封装为一键脚本 `tools/deploy.sh`(git pull --ff-only + rsync +
chown + 冒烟验证),在部署服务器上执行:
```bash
bash tools/deploy.sh
```
等价的手动步骤(源码有改动后,在本机执行):
```bash
sudo rsync -a --delete \
--exclude harness/ --exclude gen/ --exclude test/ \
--exclude server/ --exclude template/ \
--exclude '*.py' --exclude '*.md' --exclude '*.sh' \
/home/ubuntu/SoundGame/web/ /var/www/soundgame/
sudo chown -R www-data:www-data /var/www/soundgame
```
静态文件即时生效,不必 reload Nginx;配合全站 `no-cache` 缓存策略,用户下次
打开页面即可拿到新文件(2026-09-07 之前 js/css 曾是 24h 强缓存,部署后旧 JS
在用户浏览器里滞留一天,表现为"改了却没生效")。改了 `soundgame.conf` 才需要:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
## 从零再搭一遍(本服务器已完成,仅作记录)
前置:DNSPod 增加 A 记录 `game` → 本机公网 IP(当前 `118.89.233.55`)。
`xiangsilian.com` / `www` 的 Nginx + 证书应已存在。
1. 按上一节 rsync 到 `/var/www/soundgame`。
2. 先写 **仅 HTTP** 的 `server_name game.xiangsilian.com`,包含
`location /.well-known/acme-challenge/`(`root /var/www/letsencrypt`),
`nginx -t` 后 reload。此时还没有该主机名的证书,不能先开 443。
3. 扩证书(会改同一张 live 证书,现站 HTTPS 不停):
```bash
sudo certbot certonly --webroot -w /var/www/letsencrypt \
-d xiangsilian.com -d www.xiangsilian.com -d game.xiangsilian.com \
--expand --non-interactive --agree-tos
```
扩完后确认 `/etc/letsencrypt/renewal/xiangsilian.com.conf` 的
`[[webroot_map]]` 含三个域名(certbot 有时只留下新加的那条,缺了续期会掉名)。
4. 换成完整配置(HTTP 301 → HTTPS,443 指 `/var/www/soundgame`),reload。
当前线上配置见下一节。
## Nginx 配置(`/etc/nginx/sites-available/soundgame.conf`)
```nginx
# game.xiangsilian.com — SoundGame HTML5 static site
server {
listen 80;
listen [::]:80;
server_name game.xiangsilian.com;
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://game.xiangsilian.com$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name game.xiangsilian.com;
ssl_certificate /etc/letsencrypt/live/xiangsilian.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xiangsilian.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/soundgame;
index index.html;
# 全站 no-cache:可缓存但必须回源验证(ETag→304)。
# 2026-09-07 教训:此前 js/css 是 max-age=86400 public,引擎修复部署后
# 用户浏览器 24h 内仍执行旧 audio.js,句尾截断"修不好"。静态小站回源
# 验证成本可忽略,换取每次部署立即生效。
add_header Cache-Control "no-cache";
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;
location ~ /\. {
deny all;
}
location / {
try_files $uri $uri/ =404;
}
location ~* \.(ogg|m4a|mp3|wav|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
add_header Cache-Control "no-cache";
add_header X-Content-Type-Options nosniff;
access_log off;
}
location ~* \.(js|css)$ {
add_header Cache-Control "no-cache";
add_header X-Content-Type-Options nosniff;
}
}
```
音频走 Nginx 原生 Range(`Accept-Ranges`),不要反代到 Node。系统
`mime.types` 已有 `audio/ogg` 与 `audio/x-m4a`。
## 以后若要开放用户系统
不要把 `server.js` 绑到 `0.0.0.0`。至少做到:监听 `127.0.0.1`、Nginx 只反代
`/api/`、加 `limit_req`、数据目录定期备份。PIN 哈希(scrypt)与 token 过期/吊销
已在 P0-B 无感档案系统落地(`web/server/README.md`),公网开放前剩余前置:
限流、单实例约束、备份与隐私政策。在此之前保持静态-only(大厅自动降级为本地档案切换,仍可完整游玩)。
## 冒烟
```bash
curl -sI https://game.xiangsilian.com/ # 200
curl -sI http://game.xiangsilian.com/ # 301 → https
curl -sI https://game.xiangsilian.com/rpg/ # 200
curl -sI https://game.xiangsilian.com/api/login # 404
curl -sI -H 'Range: bytes=0-1023' \
https://game.xiangsilian.com/rpg/audio/bgm/boss.ogg # 206
curl -sI https://xiangsilian.com/ # 200,简历站仍在
```
真机:手机浏览器打开大厅 → 点进一款游戏 → 「轻触开始」后应出声(建议耳机)。
iOS 用 m4a,Android 用 ogg。