-
Notifications
You must be signed in to change notification settings - Fork 1
Installation Linux
This guide covers deploying the fb server stack on Linux using Docker (image build) and Pulumi (Kubernetes deployment).
Compared to Windows installation, you do not install MySQL, Redis, RabbitMQ, or individual server binaries on the host. Infrastructure and services run as containers in Kubernetes, configured from infra/pulumi/.
For architecture overview, see Architecture and Technology Stack.
| Layer | How it runs on Linux |
|---|---|
| Build | Docker images (server/*/Dockerfile) pushed to GHCR |
| Deploy | Pulumi program (infra/pulumi/) creates K8s resources in namespace fb
|
| Config |
infra/pulumi/develop.json + Pulumi secret host
|
| Data |
fb/data image (Excel tables → JSON); maps bundled in game image |
Typical workflows:
-
CI (recommended) — push to
release/**triggers image build and Pulumi deploy (.github/workflows/build.yml). -
Manual full cycle — run
tools/build.shon a build machine (build, push, sethost,pulumi up). -
Deploy only — images already in the registry; run Pulumi with the correct
hostvalue.
Pulumi targets the cluster from your current kubeconfig (kubectl config current-context).
The stock Pulumi manifests assume a self-hosted cluster with:
| Requirement | Used by | Notes |
|---|---|---|
Node label infra=true
|
MySQL, Redis, RabbitMQ StatefulSets |
Required (requiredDuringSchedulingIgnoredDuringExecution) |
Node label cpu=epyc
|
Gateway, Login, Game, Internal, … | Preferred only; pods still schedule without it |
Host path /mnt/fb/
|
MySQL, Redis data volumes | Created automatically (DirectoryOrCreate) on infra nodes |
| NodePort range | All client-facing services | Ports are fixed in develop.json (e.g. gateway 30000) |
If your cluster uses different labels or storage, update the affinity / hostPath blocks in infra/pulumi/*.js before deploying.
| Tool | Purpose |
|---|---|
| Docker + buildx | Multi-stage image builds (tools/build.sh, CI) |
| Git | Clone repo with submodules (tools/data-converter, tools/flatbuffer-ex) |
| Registry login | Push to ghcr.io (or your own registry after retagging) |
tools/build.sh uses sudo docker when the current user cannot talk to the Docker daemon directly.
| Tool | Purpose |
|---|---|
| Node.js (LTS) |
npm install in infra/pulumi/
|
| Pulumi CLI |
pulumi up / pulumi destroy
|
| kubectl | Cluster access (same context Pulumi will use) |
| Pulumi access token | Required if using Pulumi Cloud backend (pulumi login) |
CI uses stack name dev (see build.yml deploy job).
The repository ships with settings for a specific production environment. Before your first deploy, review at least the following.
host is the IP or hostname that game clients use to reach Gateway, Login, and Game. It is not stored in develop.json; it is injected at deploy time:
conf.host = new pulumi.Config().require('host')That value is written into service configs (gateway entrypoints, login IP, game login redirect, etc.).
Set it before every deploy when the public address may have changed:
cd infra/pulumi
pulumi stack select dev # or your stack name
pulumi config set --secret host "<YOUR_EXTERNAL_IP_OR_HOSTNAME>"tools/build.sh does this automatically using the machine's public IP:
echo "Retrieving external IP..."
EXTERNAL_IP=$(curl -s https://ifconfig.me)pulumi config set --secret host "$EXTERNAL_IP"
pulumi down -y && pulumi up -y| Situation | What to do |
|---|---|
| Clients connect via public IP | Use that IP, or let build.sh call ifconfig.me
|
| Clients on LAN only | Set host to the node's LAN IP (not 127.0.0.1) |
| DNS name | Set host to the FQDN clients resolve |
| Behind NAT / wrong auto-detect |
Do not rely on ifconfig.me — set host manually |
CI passes the same secret as REMOTE_IP (build.yml deploy job, config-map: "{host: ...}").
Main static configuration: worlds, ports, replicas, client metadata.
| Section | What to change |
|---|---|
worlds.<name>.deploy |
true to deploy a world, false to skip (removed in index.js) |
worlds.<name>.login.port |
NodePort for that world's Login service |
worlds.<name>.game.containers |
Game shards and NodePorts (one StatefulSet per container) |
worlds.<name>.mysql / redis
|
Per-world DB/cache ports (cluster = in-cluster, node = NodePort) |
gateway.port |
Gateway NodePort (default 30000) |
gateway.replicas |
Gateway pod count |
gateway.client.version / nation
|
Client protocol version and nation code |
internal.port, marketplace.port, admin-tool.port
|
HTTP services NodePorts |
unified-infra |
Shared MySQL, Redis, RabbitMQ ports and replica counts |
database.autoMigration |
true runs EF migrations on Internal / Log / Marketplace startup |
admin-tool.security.elevationSecret |
Change for non-dev admin tool |
Default deploy flags: only world-1 and world-2 have "deploy": true. The other worlds in the file are templates with "deploy": false.
Port collisions: node values must be unique across the cluster and within the Kubernetes NodePort range (typically 30000–32767). Adjust if they clash with other services.
Pulumi manifests reference ghcr.io/boyism80/fb/<service>:latest (e.g. gateway.js, game.js). tools/build.sh pushes to the same paths.
To use your own registry:
- Build and tag images (or change tags in
build.sh/ CI). - Replace image strings in
infra/pulumi/*.js(gateway, login, game, internal, write-back, log, marketplace, admin-tool, bot). - Ensure cluster nodes can
docker pull/crictl pullthose images (imagePullPolicy: "Always").
Without infra=true on at least one node, MySQL/Redis/RabbitMQ pods stay Pending.
Persistent data paths on those nodes:
/mnt/fb/mysql/<world>/...
/mnt/fb/redis/<world>/...
Plan disk space on infra nodes accordingly.
MySQL and RabbitMQ users/passwords are defined in Pulumi (fb / admin, root admin). Change them in infra/pulumi/mysql.js, rabbitmq.js, and matching connection strings in internal.js / write-back.js / etc. if you need production-grade secrets.
- CI stack:
dev - Create/select locally:
pulumi stack init devorpulumi stack select dev - Login:
pulumi login(cloud) or configure a self-hosted backend
On push to release/** (excluding markdown-only changes), .github/workflows/build.yml:
-
build-linux — builds and pushes all images to
ghcr.io/<owner>/fb/*on runners labeledlinux,build,epyc. - Per-service jobs — parallel image builds (gateway, login, game, …).
-
deploy — on runner
linux,master:pulumi destroythenpulumi upon stackdevwithhostfromREMOTE_IPsecret.
Required GitHub secrets:
| Secret | Purpose |
|---|---|
PULUMI_ACCESS_TOKEN |
Pulumi Cloud API |
REMOTE_IP |
Value for host (client-facing address) |
Self-hosted runners must have Docker, kubeconfig, and network access to the cluster and GHCR.
From the repository root, on a machine that can push images and run Pulumi against your cluster:
# Optional: Debug or Release (default Debug)
bash tools/build.sh ReleaseWhat the script does:
-
tools/prune-build-cache.sh— prune old BuildKit cache (default older than168h). - Build and push images (in order):
data→build→gateway,login,game,bot,internal,write-back,admin-tool,log,marketplace. cd infra/pulumi-
pulumi config set --secret host "$EXTERNAL_IP"— critical for client connectivity -
pulumi down -y && pulumi up -y— destroys then recreates the entire stack
Warning:
pulumi downdeletes all resources managed by this stack (pods, services, stateful sets). Use only when you intend a full redeploy. For incremental updates, usepulumi upwithoutdown.
Override external IP without editing the script:
cd infra/pulumi
npm install
pulumi config set --secret host "203.0.113.10"
pulumi up -yWhen images are already in the registry (e.g. after CI or a previous build.sh):
cd infra/pulumi
npm install
pulumi stack select dev
pulumi config set --secret host "<YOUR_EXTERNAL_IP_OR_HOSTNAME>"
pulumi up -yUse pulumi refresh if cluster state drifted. Use pulumi destroy to tear down the fb namespace resources (same as CI shutdown step).
With stock develop.json and world-1 + world-2 deployed:
| Service | NodePort | Notes |
|---|---|---|
| Gateway | 30000 |
Client entry; lists worlds from host
|
| Login (world-1) | 30010 |
World "연" |
| Login (world-2) | 30011 |
World "무휼" |
| Game (world-1) |
30030–30038
|
One port per container in game.containers
|
| Internal (HTTP) | 30200 |
Ops / API |
| Admin tool | 30210 |
|
| Marketplace | 30220 |
Game and Login addresses sent to the client use the Pulumi host value, not in-cluster DNS names.
MySQL/Redis NodePorts (30100+, 31000+, …) are mainly for administration and debugging from outside the cluster.
infra/pulumi/index.js orchestrates:
- Namespace
fb - MySQL, Redis, RabbitMQ (parallel)
- Internal, Write-back, Admin Tool, Log, Marketplace
- Gateway, Login, Game (after HTTP services)
- Bot (commented out in
index.js)
Worlds with "deploy": false are stripped before resources are created.
kubectl -n fb get pods
kubectl -n fb get svc
pulumi stack output # if outputs are definedCheck Gateway listens on the node:
nc -zv <host> 30000Confirm host matches what clients will use:
cd infra/pulumi
pulumi config get hostAfter first start with database.autoMigration: true, Internal applies migrations from infra/db/migrations/ against unified and world databases.
| Symptom | Likely cause |
|---|---|
| MySQL/Redis pods Pending | No node with label infra=true
|
| Client cannot connect | Wrong host secret; fix with pulumi config set --secret host and pulumi up
|
ImagePullBackOff |
Registry auth or wrong image name; check ghcr.io/boyism80/fb/*
|
| NodePort already allocated | Duplicate node port in develop.json or conflict with another app |
| Full data loss after deploy |
pulumi down / destroy removed StatefulSets; hostPath data remains under /mnt/fb/ unless deleted on disk |
ifconfig.me wrong IP |
Set host manually (NAT, cloud LB, or LAN-only clients) |
| Path | Role |
|---|---|
tools/build.sh |
Build all images, set host, Pulumi down/up |
tools/prune-build-cache.sh |
Docker BuildKit cache cleanup |
infra/pulumi/develop.json |
Worlds, ports, replicas, feature flags |
infra/pulumi/index.js |
Deploy graph and host injection |
infra/pulumi/*.js |
Per-service Kubernetes manifests |
.github/workflows/build.yml |
CI build + deploy (stack-name: dev) |
server/*/Dockerfile |
Service image definitions |
For local development on a single machine without Kubernetes, use Windows installation (Runner or native build). Linux CI images are intended for cluster deployment, not interactive dev on one host.