用户系统(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)
6.5 KiB
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 tools/deploy.sh
等价的手动步骤(源码有改动后,在本机执行):
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 才需要:
sudo nginx -t && sudo systemctl reload nginx
从零再搭一遍(本服务器已完成,仅作记录)
前置:DNSPod 增加 A 记录 game → 本机公网 IP(当前 118.89.233.55)。
xiangsilian.com / www 的 Nginx + 证书应已存在。
-
按上一节 rsync 到
/var/www/soundgame。 -
先写 仅 HTTP 的
server_name game.xiangsilian.com,包含location /.well-known/acme-challenge/(root /var/www/letsencrypt),nginx -t后 reload。此时还没有该主机名的证书,不能先开 443。 -
扩证书(会改同一张 live 证书,现站 HTTPS 不停):
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 有时只留下新加的那条,缺了续期会掉名)。 -
换成完整配置(HTTP 301 → HTTPS,443 指
/var/www/soundgame),reload。 当前线上配置见下一节。
Nginx 配置(/etc/nginx/sites-available/soundgame.conf)
# 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(大厅自动降级为本地档案切换,仍可完整游玩)。
冒烟
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。