Files
ark-stronghold-protocol/docs/DEPLOY.md
T

15 KiB
Raw Blame History

部署指南

目标:在一台家用 Windows 小主机上长期开服,让朋友通过局域网或公网来玩。macOS / Linux / Docker 放在后面。 所有命令都在项目根目录执行。遇到问题先运行 node tools/doctor.mjs(只读诊断)。

0. 资源需求

项目 说明
服务器 CPU 战斗在各玩家浏览器里模拟(DESIGN §14),服务器只负责回合、经济和校验:每个房间每个作战回合约 1 ms CPU。AI 队友 / 掉线玩家的战场由服务器模拟:作战开始时 3 个 AI 战场在开发机上约 0.2–0.5 s CPU,小主机上可能要几秒(分成 8 ms 小片执行,不会卡住其他房间)。SP_VERIFY=all 会复算每个真人战场,CPU 明显增加,小主机建议保持 off 或 sample。
服务器内存 空闲约 100 MB,每个进行中的对局再增加几 MB。
网络 4 人对局中服务器每回合下行约 0.25 MB(DESIGN §14 实测)。首次进入游戏时浏览器要从主机下载所需的图片 / Spine 模型 / 音频(按需加载,之后走浏览器缓存),公网隧道带宽小时第一次会慢一些。
磁盘 素材约 250 MB(public/assets)+ 依赖约 125 MB(node_modules);可选的本地提取约 40 MB(.venv-extract)+ 70 MB 贴图。
玩家设备 支持 WebGL 的现代浏览器(Chrome / Edge / Firefox / Safari 最新版),电脑或手机平板(横屏)。老旧设备可在设置里调低画质或访问 /?board=2d。

服务器无状态:房间和对局只存在内存里,没有数据库和存档,不需要备份。重启服务器会结束正在进行的对局(包括断线后本可在 24 小时内回来继续的独立模拟)。

1. Windows 小主机:一步步

1.1 安装与首次启动

  1. 安装 Node.js 22 LTS 和 Git(在 PowerShell 或「终端」里;用下面的完整包时不需要 Git):
    winget install OpenJS.NodeJS.LTS
    winget install Git.Git
    
    装完关闭并重新打开终端,node -v 应显示 v22 或更高(winget 的 LTS 目前是 v24.x,同样可用)。没有 winget 时从 https://nodejs.org/zh-cn/download 和 https://git-scm.com/download/win 下载安装。
  2. 下载,二选一。建议放在一个固定、短、不在 OneDrive 同步范围内的目录,例如 C:\Stronghold-Protocol:
    • 完整包(推荐):在仓库的 Releases 页面下载最新版本(当前为 v0.1.0)的完整包 zip(已含依赖、前端库和全部素材,包括官方 3D 棋盘),解压后把里面的 Stronghold-Protocol 文件夹放到上述位置。不需要 Git,首次启动也不用再下载素材。素材版权归上海鹰角网络 / Yostar,仅限非商业使用,见 NOTICE.md。
    • 源码:
      git clone https://github.com/sganggs/Stronghold-Protocol.git C:\Stronghold-Protocol
      
  3. 双击 C:\Stronghold-Protocol\scripts\start-windows.bat。首次会:安装依赖(npm ci;完整包已含,跳过)→ 复制前端库 → 下载约 250 MB 素材(完整包已含,跳过;显示进度,中断后再次启动会续传)→ 若检测到本机的明日方舟客户端,询问是否提取官方贴图(可跳过)→ 启动服务器并打开浏览器。
  4. 窗口里会打印朋友可用的地址,例如 http://192.168.1.23:3000。用另一台设备打开它确认能进入。关闭窗口即停止服务器。

等价的手动命令:npm ci、node tools/setup.mjs、npm start。

1.2 防火墙

  • 第一次启动时 Windows 会弹出「Windows 安全中心警报」:勾选专用网络并点「允许访问」。
  • 没弹窗或点错了,用管理员 PowerShell 添加规则(下面的开机自启脚本也会自动添加):
    netsh advfirewall firewall add rule name="Stronghold Protocol" dir=in action=allow protocol=TCP localport=3000 profile=private,domain
    
  • 家里的网络要是「公用网络」,Windows 会拦截入站连接。改成专用(管理员 PowerShell;网卡名用 Get-NetConnectionProfile 查看):
    Set-NetConnectionProfile -InterfaceAlias "以太网" -NetworkCategory Private
    
  • node tools/doctor.mjs 会显示规则是否存在、每个网络的类型,以及朋友可用的地址。

1.3 固定局域网 IP(推荐)

主机 IP 变了,朋友收藏的地址就失效。推荐在路由器后台的「DHCP 静态分配 / 地址保留」里把小主机的 MAC 地址绑定到固定 IP(如 192.168.1.50)。也可以在 Windows「设置 → 网络和 Internet → 属性 → IP 分配 → 编辑」里手动设置(IP、子网掩码、网关、DNS 与路由器一致,且不要与别的设备冲突)。

1.4 开机自动在后台运行

先关闭 start-windows.bat 的窗口(否则端口冲突),然后在项目目录运行(会自动请求管理员权限):

powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1

它会:运行一次 tools/setup.mjs → 把设置写入 scripts\service.env.cmd(node.exe 路径、端口等)→ 注册计划任务 StrongholdProtocol(开机 20 秒后以 SYSTEM 身份运行 scripts\run-server.cmd,无需登录;服务器退出后 5 秒自动重启)→ 添加防火墙规则 → 立即启动并显示状态。日志在 logs\server.log(超过 10 MB 自动轮换)。

需求 命令(都加在 powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 之后)
换端口 / 其他设置 -Port 8080、-Verify sample、-Combat server、-BindHost 127.0.0.1(只给反向代理用)
公用网络也放行 -AllowPublicNetwork(一般不需要;Tailscale 网卡被识别为公用网络时可能需要)
查看状态和最近日志 -Status
重启(更新代码后) -Restart
停止 -Stop(下次开机仍会自动启动)
卸载 -Uninstall(删除计划任务、防火墙规则和 service.env.cmd)

建议同时关闭睡眠,否则小主机会在无人操作时休眠:powercfg /change standby-timeout-ac 0。

替代方案:用 NSSM 注册成真正的 Windows 服务
winget install NSSM.NSSM            # 或从 https://nssm.cc 下载
nssm install StrongholdProtocol "C:\Program Files\nodejs\node.exe" server\index.js
nssm set StrongholdProtocol AppDirectory C:\Stronghold-Protocol
nssm set StrongholdProtocol AppEnvironmentExtra PORT=3000 HOST=0.0.0.0
nssm set StrongholdProtocol AppStdout C:\Stronghold-Protocol\logs\server.log
nssm set StrongholdProtocol AppStderr C:\Stronghold-Protocol\logs\server.log
nssm start StrongholdProtocol

防火墙规则仍需按 1.2 手动添加。两种方式只选一种。

1.5 更新

cd C:\Stronghold-Protocol
powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Stop   # 装了开机自启时
git checkout -- data/assets.json    # 素材清单由 setup 重新生成,先还原以免 git pull 冲突
git pull
npm ci
node tools/setup.mjs                # 补下载新增的素材(已有文件会跳过)
powershell -ExecutionPolicy Bypass -File scripts\install-service-windows.ps1 -Restart

没装开机自启的话,最后一步改成重新双击 start-windows.bat。用 Releases 完整包的:停止服务器,把新版本的完整包解压到新目录后从那里启动即可(素材已包含;装了开机自启的,在新目录重新运行一次 install-service-windows.ps1)。用 GitHub「Download ZIP」源码包的:解压新版本后,把旧目录里的 public\assets、public\fonts、.cache 和 data\local-assets.json(若有)复制过去,可避免重新下载。

2. 让不在同一网络的朋友加入

2.1 Tailscale / ZeroTier(推荐给家用小主机)

组一个虚拟局域网:不需要公网 IP、不需要改路由器、不暴露到互联网。

  • Tailscale:主机和朋友都安装 https://tailscale.com/download(Windows:winget install Tailscale.Tailscale)并登录。朋友用自己的账号时,在 Tailscale 管理后台把这台主机「Share」给他们,或邀请他们加入你的 tailnet。朋友访问 http://<主机的 100.x.y.z 地址>:3000(tailscale ip -4 查看;开了 MagicDNS 也可以用 http://<主机名>:3000)。
  • ZeroTier:在 https://my.zerotier.com 创建网络,主机和朋友安装客户端并加入同一个 Network ID,在后台勾选授权成员;访问 http://<主机的 ZeroTier IP>:3000。
  • 连不上时运行 node tools/doctor.mjs:看 VPN 网卡是否被 Windows 识别为「公用网络」,是的话按 1.2 改为专用,或安装自启时加 -AllowPublicNetwork。

2.2 cloudflared 临时隧道(朋友什么都不用装)

winget install --id Cloudflare.cloudflared      # macOS: brew install cloudflared
cloudflared tunnel --url http://localhost:3000

把输出的 https://xxxx.trycloudflare.com 发给朋友。页面是 https 时客户端自动改用 wss://,不需要任何配置;服务器会通过隧道转发的 CF-Connecting-IP 识别真实来源(TRUST_PROXY=auto)。临时隧道每次启动地址都不同,且没有可用性保证;需要固定地址请使用 Cloudflare 账号 + 自己域名的「命名隧道」。

2.3 路由器端口转发

仅当你有公网 IPv4(很多宽带是运营商级 NAT,没有公网 IP,此时请用 2.1 / 2.2):

  1. 先按 1.3 固定主机的局域网 IP。
  2. 路由器「虚拟服务器 / 端口转发」:外部端口 3000(或任意端口)→ 内部 主机IP:3000,TCP。
  3. 朋友访问 http://<你的公网 IP>:外部端口。

注意:游戏没有账号系统,知道地址的人都能进来。服务器对来自互联网的连接有按网络的数量限制(每个网络最多 64 个连接,房间 / 对局数量也有上限),但仍建议不玩时关掉转发,或优先用 Tailscale。

2.4 反向代理与 HTTPS(有域名时)

必须部署在域名根路径(客户端使用 /data/、/vendor/、/ws 等绝对路径,不支持挂在子路径下)。代理需要转发 WebSocket 升级(路径 /ws)。建议让服务器只监听本机:HOST=127.0.0.1(Windows 自启:-BindHost 127.0.0.1)。

Caddy(自动申请 HTTPS 证书,WebSocket 无需额外配置):

game.example.com {
    reverse_proxy 127.0.0.1:3000
}

Nginx:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
server {
    listen 443 ssl;
    server_name game.example.com;
    ssl_certificate     /etc/letsencrypt/live/game.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/game.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $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 1h;      # WebSocket 长连接
    }
}

https / wss 说明:页面通过 https 打开时客户端自动连接 wss://同一域名/ws;http 时用 ws://。服务器本身只提供 http,证书由代理 / 隧道负责。代理与服务器在同一台机器或内网时,TRUST_PROXY=auto 会信任它的 X-Forwarded-For / X-Real-IP;代理在公网另一台机器上时设 TRUST_PROXY=1(同时确保游戏端口只对代理开放)。

3. Docker

# A) 构建时下载素材(需要联网,约 250 MB)
docker build -t stronghold-protocol --build-arg FETCH_ASSETS=1 .
docker run -d --name stronghold -p 3000:3000 --restart unless-stopped stronghold-protocol

# B) 不把素材打进镜像:先在宿主机运行 node tools/setup.mjs,然后挂载
docker build -t stronghold-protocol .
docker run -d --name stronghold -p 3000:3000 --restart unless-stopped \
  -v "$PWD/public/assets:/app/public/assets:ro" stronghold-protocol

镜像基于 node:22-alpine,多阶段构建,只含生产依赖;public/vendor 在构建时生成。.dockerignore 排除了 public/assets(不会把宿主机素材打进构建上下文);public/fonts、data/assets.json 和 data/local-assets.json 若存在会被复制进去。环境变量同 README(-e SP_VERIFY=sample 等)。健康检查:GET /healthz。

docker compose 示例:

services:
  stronghold:
    build:
      context: .
      args: { FETCH_ASSETS: "1" }
    ports: ["3000:3000"]
    restart: unless-stopped
    environment:
      SP_VERIFY: "off"

4. macOS / Linux 常驻

  • 临时开服:scripts/start.sh(或 npm start),保持终端窗口打开。macOS 首次会询问是否允许 node 接受传入连接,选「允许」。

  • Linux systemd(/etc/systemd/system/stronghold.service,路径与用户按实际修改):

    [Unit]
    Description=Stronghold Protocol game server
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    WorkingDirectory=/opt/Stronghold-Protocol
    ExecStart=/usr/bin/node server/index.js
    Environment=PORT=3000 HOST=0.0.0.0
    Restart=always
    RestartSec=5
    User=stronghold
    
    [Install]
    WantedBy=multi-user.target
    

    sudo systemctl daemon-reload && sudo systemctl enable --now stronghold;日志 journalctl -u stronghold -f;防火墙 sudo ufw allow 3000/tcp。

5. 排错

现象 处理
任何问题 node tools/doctor.mjs:Node 版本、依赖、素材完整性、端口、局域网地址、防火墙、网络类型
端口已被占用 / EADDRINUSE 已经有一个服务器在运行(自启任务?)或其他程序占用 3000:换端口 scripts\start-windows.bat --port 3001
朋友打不开页面 防火墙规则 / 网络类型(1.2);确认用的是 LAN 地址而不是 localhost;访客 Wi-Fi 常开启「AP 隔离」;不在同一网络请看第 2 节
画面是占位图、没有声音 素材没下完:重新运行 node tools/setup.mjs(会续传);缺失明细在 .cache/assets-report.json。GitHub 原始地址访问失败时会自动改用 jsDelivr 镜像
素材下载很慢 / 失败 网络问题可随时中断,重新运行会跳过已完成的文件;node tools/fetch-assets.mjs --concurrency=4 降低并发
本地提取失败 不影响游戏。确认客户端已下载全部资源;Python 版本太新导致依赖安装失败时,安装 Python 3.12 后删除 .venv-extract 再运行 node tools/setup.mjs --local
3D 棋盘没出现 需要本地提取的棋盘贴图(node tools/doctor.mjs 会显示「3D 棋盘可用」),以及支持 WebGL2 的浏览器
断线 同盟模拟 10 分钟内、独立模拟 24 小时内(config.constants.singleReconnectTime)用同一浏览器重新打开页面,自动回到原座位。同盟掉线期间按原阵容自动作战、到时自动准备(不会代为购买;想让 AI 代打请用「离开模拟 → 暂离(AI 托管)」);独立模拟不计时,等你回来