From 3a76b40dbf9f8d876958a74dc5658308de9f94bc Mon Sep 17 00:00:00 2001 From: gongzhiyong Date: Mon, 27 Jul 2026 20:48:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Squad=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=20Docker=20=E5=A4=9A=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 包含 docker-compose.yml(实例 2 用 profile 门控)、.env.example 配置模板、 以及基于实测数据的部署说明。 实测确认的关键点: - 镜像仅 134MB 不含游戏文件,约 80-100GB 服务端由 SteamCMD 运行时拉取 - 镜像声明 4 个端口,BEACONPORT=15000 官方 wiki 未提及,host 网络下多实例会冲突 - entry.sh 会自动重写 Rcon.cfg 端口,但 sed 只匹配字面量 21114, 首启后再改 RCONPORT 会静默失败 - entry.sh 每次启动无条件跑 app_update,故共享游戏文件方案有并发写入风险(未验证) - 发现两个未见于文档的环境变量:SERVER_NAME、MULTIHOME - RANDOM 环境变量在本镜像中无效(bash 内置特殊变量覆盖不了),已记录为已知问题 凭据一律使用占位符,.gitignore 挡住 .env / Rcon.cfg / License.cfg 等含密文件。 Co-Authored-By: Claude Opus 5 (1M context) --- .env.example | 80 +++++++++ .gitignore | 17 ++ README.md | 435 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 92 ++++++++++ 4 files changed, 624 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e98dd2a --- /dev/null +++ b/.env.example @@ -0,0 +1,80 @@ +# ───────────────────────────────────────────────────────────── +# Squad 服务端部署配置模板 +# +# 用法: cp .env.example .env 然后填入真实值 +# 注意: .env 已被 .gitignore 挡住,不要强制提交。 +# 本仓库是 public 仓库,任何真实凭据提交后即视为泄露。 +# ───────────────────────────────────────────────────────────── + +# ── 镜像来源 ──────────────────────────────────────────────── +# 目标机所在网络无法访问 Docker Hub,必须走私有注册表。 +# 该镜像与 Docker Hub 上游 digest 一致: +# sha256:8cba47f53df586c2767525a9f062c2996b36f9a34a1d771a35f67a1713c46893 +SQUAD_IMAGE=squaddocker.azurecr.cn/squad-server:v1 + +# ACR 需要先登录才能拉取(凭据不写在这里,见 README §5): +# docker login squaddocker.azurecr.cn -u <用户名> --password-stdin + +# ── 数据存放路径 ─────────────────────────────────────────── +# 游戏本体约 80-100GB,建议指向独立数据盘挂载点,不要放 OS 盘。 +# 目录 owner 必须是 uid 1000(镜像内以 steam 用户运行): +# sudo mkdir -p /srv/squad/inst1 && sudo chown -R 1000:1000 /srv/squad +SQUAD_DATA_ROOT=/srv/squad + +# ── 实例 1 ───────────────────────────────────────────────── +# 端口取镜像默认值(Squad 官方标准端口)。 +# 游戏端口与查询端口各占 N 和 N+1 两个,所以多实例间隔至少 10,不能只差 1。 +# BEACONPORT 容易被漏掉 —— 官方 wiki 没提,但 host 网络下会冲突。 +INST1_NAME=squad1 +INST1_PORT=7787 +INST1_QUERYPORT=27165 +INST1_BEACONPORT=15000 +INST1_RCONPORT=21114 +INST1_MAXPLAYERS=50 +INST1_TICKRATE=50 + +# 服务器名。entry.sh 会把它写入 Server.cfg 的 ServerName。 +# 该变量未见于镜像文档,是从 entry.sh 源码中确认的。 +INST1_SERVER_NAME=My Squad Server + +# 容器内存上限。宿主机无 swap 时必须设,否则 OOM 会拖垮整机。 +# 单实例机器(7.7GB 内存)建议 6g;16GB 机器跑双实例建议各 7g。 +INST1_MEM_LIMIT=6g + +# 可选:绑定指定网卡 IP。留空 / 0.0.0.0 / 127.0.0.1 时被忽略。 +# 多实例若各有独立 IP,可用它替代纯靠端口错开。 +# INST1_MULTIHOME= + +# 可选:创意工坊 Mod。格式是 bash 数组字面量,注意括号: +# INST1_MODS=(1959152751 2016356569) + +# ── 实例 2(默认不启动)──────────────────────────────────── +# 启用前请确认硬件满足 README §2.2:2 个物理核 + 16GB 内存 + 160GB 磁盘。 +# 启用方式: docker compose --profile instance2 up -d +INST2_NAME=squad2 +INST2_PORT=7797 +INST2_QUERYPORT=27175 +INST2_BEACONPORT=15010 +INST2_RCONPORT=21124 +INST2_MAXPLAYERS=50 +INST2_TICKRATE=50 +INST2_SERVER_NAME=My Squad Server 2 +INST2_MEM_LIMIT=6g +# INST2_MULTIHOME= +# INST2_MODS=() + +# ── 可选:测试版分支(需 Offworld 提供的分支名与密码)───────── +# STEAM_BETA_BRANCH= +# STEAM_BETA_PASSWORD= + +# ───────────────────────────────────────────────────────────── +# 不要在此设置的项 +# ───────────────────────────────────────────────────────────── +# +# RCON 密码:镜像没有 RCONPASSWORD 环境变量。entry.sh 只会重写 +# Rcon.cfg 里的端口,不管密码。密码必须在服务器上直接改 +# ServerConfig/Rcon.cfg,详见 README §6.4。 +# +# RANDOM:此镜像中该变量无效。RANDOM 是 bash 内置特殊变量, +# 环境变量覆盖不了它,entry.sh 读到的永远是随机整数。 +# 地图轮换请改用 MapRotation.cfg。详见 README §10。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8ab1b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# ── 凭据:绝不入库 ────────────────────────────────────────── +# 本仓库是 public,任何提交进来的密码都会公开且永久留在 git 历史里 +.env +.env.* +!.env.example +*.secret +secrets/ + +# Squad 服务端配置中含密码/授权的文件(应只存在于服务器本机) +Rcon.cfg +License.cfg +Admins.cfg + +# ── 运行时产物 ──────────────────────────────────────────── +data/ +logs/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fa3cd9 --- /dev/null +++ b/README.md @@ -0,0 +1,435 @@ +# Squad(战术小队)专用服务器 — Docker 部署 + +基于 `cm2network/squad` 镜像的 Squad 专用服务器部署方案,支持单实例与多实例。 + +标注 **[实测]** 的内容来自 2026-07-27 对目标环境和镜像的实际验证,非估算或推测。 +标注 **[未验证]** 的内容尚未在本环境证实,请勿直接用于生产。 + +--- + +## 1. 这套方案解决什么 + +Squad 服务端官方只提供 SteamCMD 裸装方式(配 `screen` 托管)。本方案用 Docker 替代: + +- 进程守护与自动重启交给 Docker,不依赖 `screen` 会话 +- 游戏文件、配置、日志都在挂载卷里,容器可随意重建 +- 多实例通过环境变量声明端口,不用改启动脚本 + +**不解决的问题**:游戏本体下载量和硬件需求 —— 见下节。 + +--- + +## 2. 硬件要求 — 先读这一节 + +### 2.1 镜像不含游戏文件 + +**[实测]** `cm2network/squad` 镜像只有 **134.3 MB**(压缩后,6 层),内容是 Debian 基础层 + SteamCMD + `entry.sh`。 + +**游戏服务端本体(约 80–100 GB)是容器每次启动时由 SteamCMD 拉取的**,落在挂载卷里。 + +两个直接后果: + +- 把镜像搬到私有注册表**不能**省掉这次下载 +- 每个独立安装的实例都要下一份,所以多实例基本上必须共享游戏文件(§7.2) + +### 2.2 最低配置 + +| 资源 | 单实例 | 双实例 | 说明 | +|---|---|---|---| +| **物理核** | 1 | **2** | 看物理核,不是 vCPU。Squad 主 tick 重度单线程,一核跑两实例会双双掉 tick | +| 内存 | 8 GB | **16 GB** | 无 swap 时更吃紧,务必设 `mem_limit` | +| 磁盘(持久) | 120 GB | **160 GB**(共享游戏文件)
200+ GB(独立安装) | 另需留出 SteamCMD 更新的临时空间 | + +> ⚠️ **Azure 用户注意**:`/mnt` 挂的资源盘是**临时盘**,VM deallocate 后清空,不能放游戏文件。 +> `df -hT` 看不出来,要查 `/etc/fstab` 是否有 `azure_resource` 和 `comment=cloudconfig` 字样。 + +### 2.3 首次下载耗时会远超预期 + +**[实测]** 目标环境到 Steam CDN 的速度为 **0.8–1.6 MB/s**(约 7–13 Mbit/s)。 + +按 80 GB 计算,**单次完整下载需要 14–28 小时**。 + +部署前确认: +- 下载完成后磁盘仍有余量(建议留 20 GB 以上) +- 下载期间尽量不重启容器 —— SteamCMD 支持续传,但会重新校验,很耗时 + +--- + +## 3. 前置条件 + +### 3.1 目标机器 + +- Linux x86_64,GLIBC ≥ 2.17 +- Docker Engine + `docker compose` 插件 +- 当前用户在 `docker` 组,或有 sudo + +> Ubuntu 上**不要用 `apt install docker.io`**。**[实测]** 目标环境 `apt-cache policy docker.io` 显示 +> `Candidate: (none)`。请走 Docker 官方源(`download.docker.com`)。 + +### 3.2 Docker Hub 不可达时(中国大陆常见) + +**[实测]** 目标环境 `registry-1.docker.io` / `index.docker.io` / `auth.docker.io` 全部超时 +(DNS 可解析,属网络层拦截)。 + +两种应对: + +**A. 镜像搬运到私有注册表(本方案采用)** — 见 §5 + +**B. 配置 registry mirror** — `/etc/docker/daemon.json`: +```json +{ "registry-mirrors": ["https://docker.m.daocloud.io"] } +``` +**[实测]** 该 mirror 返回 401 正常鉴权挑战,可用。但依赖第三方站点可用性。 + +### 3.3 端口放行 + +主机防火墙之外,**云平台安全组也要放行**。Azure NSG、阿里云安全组这类配置在机器外部, +机内 `iptables` / `ufw` 查不到,是最容易漏的一环 —— 表现为服务端一切正常但玩家搜不到、连不上。 + +规则见 §4.2。 + +--- + +## 4. 端口规划 + +### 4.1 一个容易漏的端口 + +**[实测]** 镜像声明了 **4 个**端口: + +``` +7787/udp 27165/udp 21114/tcp 15000/udp +``` + +最后一个 **`15000/udp`(BEACONPORT)常被漏掉** —— 官方 wiki 服务器安装页完全没有提及。 +在 `network_mode: host` 下两个实例都会去绑 15000,导致第二个实例起不来或行为异常, +而日志里不一定报得清楚。 + +### 4.2 多实例端口表 + +游戏端口与查询端口**各占 `N` 和 `N+1` 两个**,所以实例间至少间隔 10,不能只差 1。 + +| 用途 | 协议 | 实例 1 | 实例 2 | +|---|---|---|---| +| 游戏 | UDP | **7787**, 7788 | **7797**, 7798 | +| Steam 查询 | UDP | **27165**, 27166 | **27175**, 27176 | +| Beacon | UDP | **15000** | **15010** | +| RCON | TCP | **21114** | **21124** | + +安全组入站规则(实例 1): +``` +UDP 7787-7788 允许 +UDP 27165-27166 允许 +UDP 15000 允许 +TCP 21114 允许 —— 建议限制来源 IP +``` + +> RCON 是远程管理通道,**不应对全网开放**,建议只放行管理者 IP。 + +### 4.3 `network_mode: host` 是必需的 + +Squad 依赖 Steam 查询协议,用 `-p` 端口映射经常导致服务器在游戏内浏览器中搜不到。 + +代价:Docker 不再隔离端口,多实例端口必须手工错开。 + +若每个实例有独立 IP,也可以用 `MULTIHOME` 绑定(§8.2),比纯靠端口错开更清晰。 + +--- + +## 5. 镜像搬运到私有注册表 + +在能访问 Docker Hub 的机器上执行(通常是本地开发机)。 + +### 5.1 拉取、打标、推送 + +```bash +# 该镜像只有 linux/amd64。在 Apple Silicon 上必须显式指定 --platform, +# 否则会报找不到匹配平台。搬运不需要运行,所以架构不匹配无妨。 +docker pull --platform linux/amd64 cm2network/squad:latest + +docker login <注册表地址> -u <用户名> --password-stdin # 密码走 stdin,不要用 -p +docker tag cm2network/squad:latest <注册表地址>/squad-server:v1 +docker push <注册表地址>/squad-server:v1 +``` + +> 用 `--password-stdin` 而不是 `-p <密码>`:后者会把密码暴露在进程列表(`ps`)里。 + +### 5.2 核对 digest + +推送后记录 digest,目标机拉取后比对,确认跑的是同一个镜像: + +```bash +docker image inspect --format '{{index .RepoDigests 0}}' <注册表地址>/squad-server:v1 +``` + +**[实测]** 本次搬运结果与 Docker Hub 上游 digest 完全一致: +``` +sha256:8cba47f53df586c2767525a9f062c2996b36f9a34a1d771a35f67a1713c46893 +``` +若注册表无鉴权(任何人可写),这一步是必要的完整性检查,不是可选项。 + +### 5.3 HTTP 明文注册表的额外配置 + +如果私有注册表是 HTTP(非 TLS),Docker 默认拒绝,报 +`http: server gave HTTP response to HTTPS client`。需要: + +**Linux** — `/etc/docker/daemon.json`: +```json +{ "insecure-registries": ["<地址:端口>"] } +``` +改完 `sudo systemctl restart docker`。 + +**macOS(Docker Desktop)** — 写 `~/.docker/daemon.json` 后必须**完整退出并重启 Docker Desktop**。 + +> 踩坑记录:`docker desktop restart` 可能异步返回,导致后续命令连到的还是旧守护进程, +> 配置看似没生效。另外用 `pkill` 强杀 Docker Desktop 会打断它的退出流程, +> 使随后的 `open -a Docker` 失效。稳妥做法:`osascript -e 'quit app "Docker"'` +> → 轮询等 socket 消失 → `open -a Docker` → 轮询等 `docker info` 成功 → 再验证: +> ```bash +> docker info --format '{{json .RegistryConfig.IndexConfigs}}' +> ``` +> 确认目标地址出现且 `"Secure": false`。 + +**使用 ACR / Harbor 等有效 TLS 的注册表时,以上都不需要。** + +### 5.4 拉取凭据的权限收敛 + +目标机 `docker login` 后凭据会以 base64 存进 `~/.docker/config.json`(**不是加密**)。 + +如果用的是注册表管理员凭据,那台机器一旦被入侵,攻击者就获得了**推送**权限, +可以覆盖你的镜像 tag。生产环境建议改用**只读(pull-only)令牌**: + +- Azure ACR:创建 scope map + token,只授 `content/read` +- Harbor:创建 robot account,只勾 pull + +--- + +## 6. 部署 + +### 6.1 准备数据目录 + +```bash +sudo mkdir -p /srv/squad/inst1 +# 镜像内以 uid 1000 (steam) 运行,必须给写权限 +sudo chown -R 1000:1000 /srv/squad +``` + +### 6.2 配置 + +```bash +cp .env.example .env +$EDITOR .env +``` + +### 6.3 启动 + +```bash +docker compose up -d squad1 +docker compose logs -f squad1 +``` + +首次启动进入 SteamCMD 下载阶段(§2.3,14–28 小时)。 +日志出现 `Success! App '403240' fully installed.` 表示下载完成。 + +### 6.4 编辑服务器配置 + +游戏文件下载完成后,配置文件出现在 `/srv/squad/inst1/SquadGame/ServerConfig/`: + +| 文件 | 作用 | +|---|---| +| `Server.cfg` | 服务器名称、人数上限等主配置 | +| `Rcon.cfg` | RCON 端口与**密码** | +| `Admins.cfg` | 管理员与权限组 | +| `MapRotation.cfg` | 地图轮换 | +| `MOTD.cfg` | 每日消息 | +| `ServerMessages.cfg` | 循环公告 | +| `Bans.cfg` | 封禁列表 | +| `License.cfg` | 服务器授权 | + +**RCON 密码必须在这里改。** 镜像没有 `RCONPASSWORD` 环境变量,`entry.sh` 只重写端口、不管密码。 +默认密码不改等于把服务器管理权公开。 + +改完重启生效: +```bash +docker compose restart squad1 +``` + +--- + +## 7. 多实例 + +### 7.1 RCON 端口:自动处理,但有个后续陷阱 + +**[实测]** `entry.sh` 会在启动时自动重写 `Rcon.cfg` 里的端口: + +```bash +sed -i -e 's/Port=21114/'"Port=${RCONPORT}"'/g' "${STEAMAPPDIR}/SquadGame/ServerConfig/Rcon.cfg" +``` + +所以**新建实例时不需要手动改 `Rcon.cfg` 端口** —— 很多教程(包括官方 wiki)说必须手动改, +对这个镜像而言是多余的。 + +**但这个 sed 只匹配字面量 `Port=21114`。** 因此: + +> ⚠️ **首次启动之后再改 `RCONPORT`,Rcon.cfg 不会跟着变** —— +> 此时 `Rcon.cfg` 里已经是旧的自定义端口,不再包含 `21114`,sed 匹配不上,静默失败。 +> 结果是启动参数与配置文件不一致,RCON 连不上。 +> +> 改端口时必须**同时手工改 `Rcon.cfg`**,或者删掉 `Rcon.cfg` 让 SteamCMD 重新生成默认文件。 + +### 7.2 共享游戏文件 **[未验证]** + +游戏本体约 80–100 GB,下载一次 14–28 小时,两个实例各下一份通常不现实。 +思路是游戏本体只留一份共享,每实例独立持有 `ServerConfig` 和日志。 + +> ⚠️ **本方案尚未实测,存在明确的已知风险。** +> +> **[实测]** `entry.sh` 在**每次容器启动时**都会无条件执行 `steamcmd +app_update` +> (源码第 2–17 行,没有"文件已存在则跳过"的判断)。 +> +> 两个容器共享同一游戏目录时,会**并发**对同一批文件跑 SteamCMD 更新,有文件损坏风险。 +> +> 若要采用,稳妥流程是:停掉所有实例 → 单独跑一次更新 → 再一起启动; +> 并且需要改造 `entry.sh` 或覆盖启动命令来跳过第二个实例的更新步骤。 +> +> **在验证完成前,请勿在生产环境使用。** + +### 7.3 启用实例 2 + +```bash +docker compose --profile instance2 up -d +``` + +启用前确认硬件满足 §2.2 的双实例要求。 + +--- + +## 8. 镜像中未见于文档的环境变量 + +以下变量**不在镜像的默认 `ENV` 列表里**,是从 `entry.sh` 源码中确认的 **[实测]**, +但相当有用: + +### 8.1 `SERVER_NAME` + +设置后,`entry.sh` 会把它写入 `Server.cfg` 的 `ServerName`(源码第 22–25 行): + +```bash +sed -i -e "s/^ServerName=.*/ServerName=\"${SERVER_NAME}\"/" .../Server.cfg +``` + +省去手工改配置文件。注意它每次启动都会覆盖 `ServerName`, +所以如果你在 `Server.cfg` 里手改了服务器名,又设了这个变量,手改的会被覆盖。 + +### 8.2 `MULTIHOME` + +绑定到指定网卡 IP(源码第 47–51 行)。留空 / `0.0.0.0` / `127.0.0.1` 时被忽略。 + +多实例若各有独立 IP,用它比纯靠端口错开更清晰,也能让每个实例都用标准端口。 + +--- + +## 9. 日常运维 + +### 更新服务端 + +`entry.sh` 每次启动都跑 SteamCMD 更新,所以游戏发新版本时: +```bash +docker compose restart squad1 +``` + +更新镜像本身: +```bash +docker compose pull && docker compose up -d +``` +游戏文件在卷里,不会丢。 + +### 查看日志 + +```bash +docker compose logs -f --tail=200 squad1 +``` + +服务端自身日志在 `/srv/squad/inst1/SquadGame/Saved/Logs/`。 + +容器日志轮转已在 `docker-compose.yml` 里配置(50MB × 3)。也可以在 +`/etc/docker/daemon.json` 里设为全局默认。 + +### 停止 + +```bash +docker compose stop squad1 +``` + +> **[实测]** `entry.sh` 最后一行是 `bash SquadGameServer.sh ...` 而非 `exec`, +> PID 1 是 bash 脚本。bash 在同步等待子进程时不转发 SIGTERM, +> 所以优雅停止未必真的优雅。`docker-compose.yml` 里设了 +> `stop_grace_period: 120s` 尽量留出落盘时间,但不保证。 +> 重要操作前建议先在游戏内用 RCON 确认无人在线。 + +### 备份 + +只需备份配置,不含游戏本体: +```bash +tar czf squad-config-$(date +%F).tar.gz -C /srv/squad/inst1/SquadGame ServerConfig +``` + +--- + +## 10. 已知问题与限制 + +诚实声明,避免误判。 + +### 10.1 `RANDOM` 环境变量在此镜像中无效 **[实测]** + +镜像默认设了 `RANDOM=NONE`,`entry.sh` 第 61 行把 `${RANDOM}` 传给服务端。 + +但 **`RANDOM` 是 bash 内置特殊变量,环境变量覆盖不了它** —— 每次读取都返回随机整数。 +实测: + +```bash +$ env RANDOM=NONE bash -c 'echo ${RANDOM}' +18575 +$ env RANDOM=NONE bash -c 'echo ${RANDOM}' +2645 +``` + +所以传给 `SquadGameServer.sh` 的实际上是随机数字,不是 `NONE`。 +**设置这个变量没有任何效果**,不管设成什么。 + +地图轮换请改用 `MapRotation.cfg` 控制。 + +### 10.2 其他 + +- **§7.2 共享游戏文件方案未实测**,且已知有并发更新风险,不要直接用于生产 +- **免费服人数上限未经确认**。镜像默认 `FIXEDMAXPLAYERS=80`; + 更高人数是否需要 Offworld 授权(`License.cfg`),请向官方确认,本文不做断言 +- 镜像只有 `linux/amd64`,**无 arm64**。Apple Silicon 上只能搬运,无法本地运行验证 +- `MODS` 变量格式是 bash 数组字面量(如 `(1959152751 2016356569)`), + 不是逗号或空格分隔的普通字符串 —— 源码用 `declare -a MODS="${MODS}"` 解析 +- 宿主机 `ulimit -n` 若为默认 1024 偏低。已在 compose 中给容器单独调高,未改动宿主机 + +--- + +## 11. 排障 + +| 现象 | 原因 | 处理 | +|---|---|---| +| `http: server gave HTTP response to HTTPS client` | 未配 insecure-registries | §5.3 | +| 服务器在游戏内搜不到 | 安全组未放行 UDP,或用了 `-p` 而非 host 网络 | §3.3 / §4.3 | +| 第二实例起不来 | BEACONPORT 或 RCON 端口冲突 | §4.1 | +| 改了 RCONPORT 后 RCON 连不上 | `Rcon.cfg` 没跟着变(sed 只匹配 21114) | §7.1 | +| 容器反复重启 | 内存不足被 OOM kill | `dmesg \| grep -i oom` 确认;降人数或调 `mem_limit` | +| 下载卡住不动 | Steam CDN 限速或中断 | 看日志;SteamCMD 会自动重试 | +| 配置改了不生效 | 未重启容器 | `docker compose restart` | +| 容器内无权限写 | 数据目录 owner 不是 uid 1000 | `sudo chown -R 1000:1000 <数据目录>` | +| 设了 `RANDOM` 没效果 | 镜像 bug,该变量无效 | §10.1 | + +--- + +## 12. 参考 + +- 官方 wiki(中文):https://squad.fandom.com/wiki/Server_Installation/zh +- 镜像仓库:https://hub.docker.com/r/cm2network/squad/ +- Squad AppID:`403240`,创意工坊 ID:`393380` + +> 官方 wiki 中文页用 6301 / 26301 作端口示例,那是部分托管商的习惯值, +> **不是镜像默认值**。本文统一采用镜像默认的 Squad 官方标准端口 7787 / 27165。 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c7f1c0e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,92 @@ +# Squad 专用服务器 — 多实例编排 +# +# 用法: +# cp .env.example .env 然后按注释填写 +# docker compose up -d squad1 仅启动实例 1 +# docker compose --profile instance2 up -d 同时启动实例 2(需先满足硬件要求) +# +# 重要:必须用 network_mode: host。Squad 依赖 Steam 查询协议, +# 用 -p 端口映射会导致服务器在游戏内浏览器中搜不到。 +# 代价是 Docker 不再隔离端口,多实例端口必须手工错开(见 README §4)。 + +x-squad-common: &squad-common + image: ${SQUAD_IMAGE:?请在 .env 中设置 SQUAD_IMAGE} + network_mode: host + restart: unless-stopped + + # entry.sh 以 `bash SquadGameServer.sh` 结尾而非 exec,信号不会完美转发, + # 给足时间让服务端自行落盘。 + stop_grace_period: 120s + + # PID 1 是 bash 脚本,用 init 回收僵尸进程 + init: true + + # 宿主机 ulimit -n 默认 1024 偏低。在容器层面单独调高, + # 不改宿主机配置(最小改动)。 + ulimits: + nofile: + soft: 65535 + hard: 65535 + + # Squad 日志量大,磁盘小的机器不轮转会被占满 + logging: + driver: json-file + options: + max-size: "50m" + max-file: "3" + +services: + # ── 实例 1 ──────────────────────────────────────────────── + squad1: + <<: *squad-common + container_name: ${INST1_NAME:-squad1} + environment: + PORT: ${INST1_PORT:-7787} + QUERYPORT: ${INST1_QUERYPORT:-27165} + BEACONPORT: ${INST1_BEACONPORT:-15000} + RCONPORT: ${INST1_RCONPORT:-21114} + FIXEDMAXPLAYERS: ${INST1_MAXPLAYERS:-50} + FIXEDMAXTICKRATE: ${INST1_TICKRATE:-50} + # SERVER_NAME 会被 entry.sh 写入 Server.cfg 的 ServerName + SERVER_NAME: ${INST1_SERVER_NAME:-} + # MULTIHOME 绑定指定网卡 IP。多实例时可用它进一步隔离, + # 留空 / 0.0.0.0 / 127.0.0.1 时 entry.sh 会忽略该参数。 + MULTIHOME: ${INST1_MULTIHOME:-} + # 可选:创意工坊 Mod,格式为 bash 数组字面量,例如 (1959152751 2016356569) + MODS: ${INST1_MODS:-()} + # 可选:测试版分支 + STEAM_BETA_BRANCH: ${STEAM_BETA_BRANCH:-} + STEAM_BETA_PASSWORD: ${STEAM_BETA_PASSWORD:-} + # 注意:RANDOM 在此镜像中无效,不要设置。原因见 README §10 + volumes: + - ${SQUAD_DATA_ROOT:?请在 .env 中设置 SQUAD_DATA_ROOT}/inst1:/home/steam/squad-dedicated + # 内存上限。宿主机无 swap 时,不设上限会让容器 OOM 拖垮整机。 + mem_limit: ${INST1_MEM_LIMIT:-6g} + + # ── 实例 2 ──────────────────────────────────────────────── + # 默认不启动。启用前请确认硬件满足 README §2.2 的双实例要求 + # (2 个物理核 + 16GB 内存 + 160GB 磁盘),否则两个实例会双双掉 tick。 + squad2: + <<: *squad-common + container_name: ${INST2_NAME:-squad2} + profiles: ["instance2"] + environment: + PORT: ${INST2_PORT:-7797} + QUERYPORT: ${INST2_QUERYPORT:-27175} + BEACONPORT: ${INST2_BEACONPORT:-15010} + RCONPORT: ${INST2_RCONPORT:-21124} + FIXEDMAXPLAYERS: ${INST2_MAXPLAYERS:-50} + FIXEDMAXTICKRATE: ${INST2_TICKRATE:-50} + SERVER_NAME: ${INST2_SERVER_NAME:-} + MULTIHOME: ${INST2_MULTIHOME:-} + MODS: ${INST2_MODS:-()} + STEAM_BETA_BRANCH: ${STEAM_BETA_BRANCH:-} + STEAM_BETA_PASSWORD: ${STEAM_BETA_PASSWORD:-} + volumes: + # 独立安装:各自一份游戏文件(约 80-100GB × 2) + - ${SQUAD_DATA_ROOT}/inst2:/home/steam/squad-dedicated + # + # 共享游戏文件方案见 README §7.2 —— 尚未实测,勿用于生产。 + # entry.sh 每次启动都会跑 SteamCMD app_update,两个容器共享同一 + # 游戏目录时会并发写入,有损坏风险。 + mem_limit: ${INST2_MEM_LIMIT:-6g}