Skip to content

Installation Linux

boyism80 edited this page Jul 3, 2026 · 3 revisions

Linux Installation

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.

Overview

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:

  1. CI (recommended) — push to release/** triggers image build and Pulumi deploy (.github/workflows/build.yml).
  2. Manual full cycle — run tools/build.sh on a build machine (build, push, set host, pulumi up).
  3. Deploy only — images already in the registry; run Pulumi with the correct host value.

Prerequisites

Kubernetes cluster

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.

Build machine (image build)

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.

Deploy machine (Pulumi)

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).


What you must customize

The repository ships with settings for a specific production environment. Before your first deploy, review at least the following.

1. Pulumi secret host (required)

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: ...}").

2. infra/pulumi/develop.json

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.

3. Container image registry

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:

  1. Build and tag images (or change tags in build.sh / CI).
  2. Replace image strings in infra/pulumi/*.js (gateway, login, game, internal, write-back, log, marketplace, admin-tool, bot).
  3. Ensure cluster nodes can docker pull / crictl pull those images (imagePullPolicy: "Always").

4. Kubernetes node labels and storage

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.

5. Credentials (optional hardening)

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.

6. Pulumi stack and backend

  • CI stack: dev
  • Create/select locally: pulumi stack init dev or pulumi stack select dev
  • Login: pulumi login (cloud) or configure a self-hosted backend

Path A — CI deploy (release branch)

On push to release/** (excluding markdown-only changes), .github/workflows/build.yml:

  1. build-linux — builds and pushes all images to ghcr.io/<owner>/fb/* on runners labeled linux, build, epyc.
  2. Per-service jobs — parallel image builds (gateway, login, game, …).
  3. deploy — on runner linux, master: pulumi destroy then pulumi up on stack dev with host from REMOTE_IP secret.

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.


Path B — Manual build and deploy (tools/build.sh)

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 Release

What the script does:

  1. tools/prune-build-cache.sh — prune old BuildKit cache (default older than 168h).
  2. Build and push images (in order): databuildgateway, login, game, bot, internal, write-back, admin-tool, log, marketplace.
  3. cd infra/pulumi
  4. pulumi config set --secret host "$EXTERNAL_IP"critical for client connectivity
  5. pulumi down -y && pulumi up -ydestroys then recreates the entire stack

Warning: pulumi down deletes all resources managed by this stack (pods, services, stateful sets). Use only when you intend a full redeploy. For incremental updates, use pulumi up without down.

Override external IP without editing the script:

cd infra/pulumi
npm install
pulumi config set --secret host "203.0.113.10"
pulumi up -y

Path C — Deploy only (no image rebuild)

When 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 -y

Use pulumi refresh if cluster state drifted. Use pulumi destroy to tear down the fb namespace resources (same as CI shutdown step).


Client connection ports (defaults)

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) 3003030038 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.


Boot order (Pulumi)

infra/pulumi/index.js orchestrates:

  1. Namespace fb
  2. MySQL, Redis, RabbitMQ (parallel)
  3. Internal, Write-back, Admin Tool, Log, Marketplace
  4. Gateway, Login, Game (after HTTP services)
  5. Bot (commented out in index.js)

Worlds with "deploy": false are stripped before resources are created.


Verify deployment

kubectl -n fb get pods
kubectl -n fb get svc
pulumi stack output   # if outputs are defined

Check Gateway listens on the node:

nc -zv <host> 30000

Confirm host matches what clients will use:

cd infra/pulumi
pulumi config get host

After first start with database.autoMigration: true, Internal applies migrations from infra/db/migrations/ against unified and world databases.


Troubleshooting

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)

Related files

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.

Clone this wiki locally