feat(deploy): login-limbo and lobby images with game-port pinning
deploy/limbo assembles LOOHP/Limbo from its loose CI artifacts plus the felis-limbo plugin (and the shared link core), with an entrypoint that pins server-port to the operator's GamePort (25565) on every start. deploy/lobby carries the Paper + felis-paper hub image. .dockerignore re-includes plugins/limbo and plugins/shared so the plugin image build sees them.
This commit is contained in:
6 files changed
+361
-1
No files matched your search
+12
-1
@@ -7,7 +7,18 @@
|
|||||||
.git
|
.git
|
||||||
.claude
|
.claude
|
||||||
**/node_modules
|
**/node_modules
|
||||||
plugins
|
# plugins/ is code-only and excluded from the MAIN felis image — EXCEPT the two
|
||||||
|
# source trees the login-limbo image (deploy/limbo/Dockerfile) compiles: its own
|
||||||
|
# module (plugins/limbo) and the shared link core it srcDir-includes (plugins/shared).
|
||||||
|
# `plugins/*` excludes each sibling module individually (so plugins/ itself is still
|
||||||
|
# walked and the negations below can re-include), then the two build trees are pulled
|
||||||
|
# back in and their Gradle outputs re-excluded to keep the context lean. The main
|
||||||
|
# image copies none of plugins/, so carrying these two small source dirs is harmless.
|
||||||
|
plugins/*
|
||||||
|
!plugins/limbo
|
||||||
|
!plugins/shared
|
||||||
|
plugins/**/build
|
||||||
|
plugins/**/.gradle
|
||||||
docs
|
docs
|
||||||
*.md
|
*.md
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Felis login-limbo image: LOOHP/Limbo + the felis-limbo readiness plugin.
|
||||||
|
#
|
||||||
|
# CODE-ONLY in this repo — it is not built by the Go CI. It packages the always-on
|
||||||
|
# "login" auth gate the setup provisioner points [velocity] login_image at.
|
||||||
|
#
|
||||||
|
# LOOHP/Limbo has no official image and no release zip. Its CI
|
||||||
|
# (ci.loohpjames.com/job/Limbo) publishes two LOOSE artifacts per build:
|
||||||
|
# - target/Limbo-<ver>.jar (the server jar; Main-Class com.loohp.limbo.Limbo)
|
||||||
|
# - spawn.schem (the default spawn schematic)
|
||||||
|
# There is no bundled server.properties — Limbo writes a default on first run.
|
||||||
|
# So the runtime is assembled from those two URLs (not a zip) via --build-arg:
|
||||||
|
#
|
||||||
|
# docker build -f deploy/limbo/Dockerfile \
|
||||||
|
# --build-arg LIMBO_JAR_URL=https://ci.loohpjames.com/job/Limbo/<n>/artifact/target/Limbo-<ver>.jar \
|
||||||
|
# --build-arg LIMBO_SCHEM_URL=https://ci.loohpjames.com/job/Limbo/<n>/artifact/spawn.schem \
|
||||||
|
# --build-arg LIMBO_VERSION=<maven-api-version> \
|
||||||
|
# -t felis-limbo:demo .
|
||||||
|
#
|
||||||
|
# Note LIMBO_VERSION (the maven API version the plugin compiles against, e.g.
|
||||||
|
# 2026.0.2-ALPHA) differs from the jar's CI build-qualified filename (e.g.
|
||||||
|
# Limbo-2026.0.2-ALPHA-26.2.jar): the CI qualifier is not published to the maven
|
||||||
|
# repo. Then import it the same way as the control-plane image (k3s ctr import)
|
||||||
|
# and set [velocity] login_image = "felis-limbo:demo" in felis.toml before setup.
|
||||||
|
#
|
||||||
|
# Two contracts the operator depends on:
|
||||||
|
# 1. The game server listens on 25565 (the CRD's GamePort).
|
||||||
|
# 2. The felis-limbo readiness endpoint listens on 8080 (StartupSpec.HealthHTTPPort,
|
||||||
|
# overridable via FELIS_HEALTH_PORT) and returns 200 only after the first tick.
|
||||||
|
|
||||||
|
# ---- build the felis-limbo plugin jar ----
|
||||||
|
# gradle:*-jdk21 — an official Gradle image on JDK 21. JDK 21 is required because
|
||||||
|
# current LOOHP/Limbo releases ship Java 21 API classes (class-file major 65); a
|
||||||
|
# JDK 17 fails to read them with "wrong version 65.0, should be 61.0". The image
|
||||||
|
# also provides the `gradle` binary (this tree vendors no Gradle wrapper).
|
||||||
|
# build.gradle still targets release 17 bytecode so the plugin loads on Java 17+.
|
||||||
|
FROM gradle:8.14-jdk21 AS plugin
|
||||||
|
WORKDIR /src
|
||||||
|
# Copy what the limbo module needs: its own tree plus the shared link core it
|
||||||
|
# srcDir-includes (../shared/src/main/java → /src/plugins/shared/src/main/java), so
|
||||||
|
# the account-link client + config loader compile straight into the jar.
|
||||||
|
COPY plugins/limbo/ ./plugins/limbo/
|
||||||
|
COPY plugins/shared/ ./plugins/shared/
|
||||||
|
ARG LIMBO_VERSION=+
|
||||||
|
RUN cd plugins/limbo \
|
||||||
|
&& (test -x ./gradlew && ./gradlew --no-daemon -PlimboVersion="$LIMBO_VERSION" build \
|
||||||
|
|| gradle --no-daemon -PlimboVersion="$LIMBO_VERSION" build) \
|
||||||
|
&& cp build/libs/*.jar /felis-limbo.jar
|
||||||
|
|
||||||
|
# ---- assemble the runtime ----
|
||||||
|
# 21-jre: the Limbo jar is Java 21 bytecode (class-file major 65), so a Java 17
|
||||||
|
# JRE cannot run it (UnsupportedClassVersionError). A 21 JRE also runs the
|
||||||
|
# plugin's release-17 bytecode fine.
|
||||||
|
FROM eclipse-temurin:21-jre
|
||||||
|
ARG LIMBO_JAR_URL
|
||||||
|
ARG LIMBO_SCHEM_URL
|
||||||
|
WORKDIR /limbo
|
||||||
|
# Pull the two loose LOOHP/Limbo CI artifacts: the server jar (required, saved as
|
||||||
|
# Limbo.jar) and the default spawn schematic (optional). Fail loudly if the jar
|
||||||
|
# URL was not supplied.
|
||||||
|
RUN set -eu; \
|
||||||
|
if [ -z "${LIMBO_JAR_URL:-}" ]; then \
|
||||||
|
echo "ERROR: --build-arg LIMBO_JAR_URL=<Limbo server jar> is required" >&2; exit 1; \
|
||||||
|
fi; \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates; \
|
||||||
|
curl -fSL "$LIMBO_JAR_URL" -o /limbo/Limbo.jar; \
|
||||||
|
if [ -n "${LIMBO_SCHEM_URL:-}" ]; then \
|
||||||
|
curl -fSL "$LIMBO_SCHEM_URL" -o /limbo/spawn.schem; \
|
||||||
|
fi; \
|
||||||
|
apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*; \
|
||||||
|
mkdir -p /limbo/plugins
|
||||||
|
# Drop the login+readiness plugin in beside Limbo.jar.
|
||||||
|
COPY --from=plugin /felis-limbo.jar /limbo/plugins/felis-limbo.jar
|
||||||
|
# The entrypoint pins the game port to the operator's GamePort before launching Limbo.
|
||||||
|
COPY deploy/limbo/entrypoint.sh /usr/local/bin/felis-entrypoint.sh
|
||||||
|
|
||||||
|
ENV FELIS_HEALTH_PORT=8080
|
||||||
|
# FELIS_GAME_PORT is the port the entrypoint pins Limbo to; it MUST equal the operator's
|
||||||
|
# GamePort (internal/operator/builders.go). Default 25565 — override only in lockstep
|
||||||
|
# with the operator.
|
||||||
|
ENV FELIS_GAME_PORT=25565
|
||||||
|
EXPOSE 25565 8080
|
||||||
|
# felis-entrypoint.sh pins server-port then execs `java -jar Limbo.jar --nogui` (headless:
|
||||||
|
# the pod has no console). Limbo writes the rest of server.properties on first run and
|
||||||
|
# loads ./spawn.schem as the spawn world. Invoked via `sh` so no +x bit is needed from the
|
||||||
|
# (Windows) build host.
|
||||||
|
ENTRYPOINT ["/bin/sh", "/usr/local/bin/felis-entrypoint.sh"]
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Login-limbo image (LOOHP/Limbo + felis-limbo)
|
||||||
|
|
||||||
|
The always-on **login** auth gate. `felis setup` provisions it as a system
|
||||||
|
service (`DesiredState=Running`, reaper-exempt) when `[velocity] login_image` is
|
||||||
|
set in `felis.toml`. Every fresh connection lands here first; it is the only safe
|
||||||
|
fallback (a stopped/starting backend routes here, never past authentication).
|
||||||
|
|
||||||
|
> **Code-only.** This image is not built by the Go CI. It compiles the
|
||||||
|
> `plugins/limbo` plugin (plus the shared `plugins/shared` link core it
|
||||||
|
> srcDir-includes) and bundles it with a LOOHP/Limbo release.
|
||||||
|
|
||||||
|
## What the plugin does
|
||||||
|
|
||||||
|
`felis-limbo` does two jobs — readiness and the in-game login flow.
|
||||||
|
|
||||||
|
### Readiness
|
||||||
|
|
||||||
|
LOOHP/Limbo has no RCON, so Felis cannot use its usual RCON readiness probe
|
||||||
|
(spec §5). A bare TCP check would report "ready" the instant the socket binds.
|
||||||
|
The plugin instead serves an HTTP readiness endpoint that flips to `200` only
|
||||||
|
after the first server tick — i.e. once the limbo has genuinely started. The
|
||||||
|
`login` MinecraftServer sets `spec.startup.healthHTTPPort: 8080`, so the pod's
|
||||||
|
HTTP readinessProbe (and, with RCON disabled, the operator's Ready gate) follows
|
||||||
|
that true signal.
|
||||||
|
|
||||||
|
- Endpoint: `GET /healthz` on `:8080` (override with `FELIS_HEALTH_PORT`).
|
||||||
|
- `503 starting` before the first tick, `200 ok` after.
|
||||||
|
- Fail-closed: if the endpoint cannot bind, readiness never turns green and the
|
||||||
|
operator keeps the gate in `Starting` — it never advertises an unstarted gate.
|
||||||
|
|
||||||
|
### In-game login (spec §B3)
|
||||||
|
|
||||||
|
A player reaching the limbo has been UUID-verified upstream (Velocity online-mode)
|
||||||
|
but is not yet linked to a web account. On join, off the tick thread, the plugin:
|
||||||
|
|
||||||
|
1. checks the username-collision **blacklist** and disconnects a barred squatter
|
||||||
|
UUID (the genuine Mojang player — different UUID — passes);
|
||||||
|
2. mints a one-time **Bind Code** for the verified UUID via the felis-api internal
|
||||||
|
face;
|
||||||
|
3. opens a **book** with a clickable link to `console.<root_domain>` plus a chat
|
||||||
|
line carrying the code, and tells the player to finish in their **system**
|
||||||
|
browser — never the WeChat/QQ in-app browser, where passkey/WebAuthn does not
|
||||||
|
work (the web entry additionally guards this; see `internal/panel`);
|
||||||
|
4. **polls** `link/status/{uuid}` until the player redeems the code on the web
|
||||||
|
console, then **transfers** them to the lobby via a BungeeCord `Connect` plugin
|
||||||
|
message on `bungeecord:main`;
|
||||||
|
5. **disconnects (fail-closed)** on blacklist, on a mint/transport failure, or when
|
||||||
|
the login window elapses — "rather refuse than admit unauthenticated".
|
||||||
|
|
||||||
|
Configuration (deployment inputs, never compiled in; env wins over a
|
||||||
|
`felis-link.properties` template written in the plugin data dir on first run):
|
||||||
|
|
||||||
|
| Env | Meaning | Default |
|
||||||
|
| --- | ------- | ------- |
|
||||||
|
| `FELIS_API_BASE_URL` | felis-api **internal** face base URL | *(required for login)* |
|
||||||
|
| `FELIS_SERVICE_TOKEN` | internal service token (secret) | *(required for login)* |
|
||||||
|
| `FELIS_ROOT_DOMAIN` | deployment zone, builds `https://console.<zone>` | *(required for login)* |
|
||||||
|
| `FELIS_LOBBY_SERVER` | Velocity server name to transfer to | `lobby` |
|
||||||
|
| `FELIS_LOGIN_TIMEOUT_SECONDS` | login window (clamped 30–3600) | `300` |
|
||||||
|
| `FELIS_HEALTH_PORT` | readiness port | `8080` |
|
||||||
|
|
||||||
|
If the API config **or** the root domain is absent the login flow stays **OFF** and
|
||||||
|
the plugin runs readiness-only (the same "load un-crippled" fail-safe the other
|
||||||
|
Felis plugins use), so a bare image still boots — production must supply the config
|
||||||
|
for the gate to authenticate. Transfer requires Velocity to accept the BungeeCord
|
||||||
|
plugin-message channel (`bungee-plugin-message-channel` on the proxy).
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
LOOHP/Limbo has no official image and no release zip. Its CI
|
||||||
|
(`ci.loohpjames.com/job/Limbo`) publishes two **loose** artifacts per build —
|
||||||
|
`target/Limbo-<ver>.jar` and `spawn.schem` — so the image is assembled from those
|
||||||
|
two URLs (there is no bundled `server.properties`; Limbo writes a default on first
|
||||||
|
run):
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f deploy/limbo/Dockerfile \
|
||||||
|
--build-arg LIMBO_JAR_URL=https://ci.loohpjames.com/job/Limbo/<n>/artifact/target/Limbo-<ver>.jar \
|
||||||
|
--build-arg LIMBO_SCHEM_URL=https://ci.loohpjames.com/job/Limbo/<n>/artifact/spawn.schem \
|
||||||
|
--build-arg LIMBO_VERSION=<maven-api-version> \
|
||||||
|
-t felis-limbo:demo .
|
||||||
|
```
|
||||||
|
|
||||||
|
- `LIMBO_JAR_URL` (required) — the server jar; it is saved as `Limbo.jar`.
|
||||||
|
- `LIMBO_SCHEM_URL` (optional) — the default spawn schematic, saved as
|
||||||
|
`spawn.schem` and loaded as the spawn world.
|
||||||
|
- `LIMBO_VERSION` — the Limbo **maven** API version the plugin compiles against
|
||||||
|
(Gradle `-PlimboVersion`). This differs from the jar's CI build-qualified
|
||||||
|
filename: e.g. the jar `Limbo-2026.0.2-ALPHA-26.2.jar` corresponds to maven
|
||||||
|
version `2026.0.2-ALPHA` (the `-26.2` CI qualifier is not published to the
|
||||||
|
maven repo).
|
||||||
|
|
||||||
|
Import into k3s and point config at it:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker save felis-limbo:demo | sudo k3s ctr images import -
|
||||||
|
# felis.toml → [velocity] login_image = "felis-limbo:demo"
|
||||||
|
sudo felis setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ports (handled for you)
|
||||||
|
|
||||||
|
The entrypoint (`deploy/limbo/entrypoint.sh`) pins Limbo's `server-port` to
|
||||||
|
`FELIS_GAME_PORT` (default **25565**, the operator's `GamePort`) on every start —
|
||||||
|
LOOHP/Limbo would otherwise default to `30000`, unreachable through the Velocity
|
||||||
|
`NetworkPolicy` / Service / probe the operator drives off that one const. It is
|
||||||
|
idempotent, so a persisted world volume keeps all its other `server.properties`
|
||||||
|
settings. Do **not** override `FELIS_GAME_PORT` except in lockstep with the operator.
|
||||||
|
|
||||||
|
## Configure (deployer's responsibility)
|
||||||
|
|
||||||
|
One setting this image does **not** guess (it keeps the release's own default):
|
||||||
|
|
||||||
|
- **Player forwarding** — align Limbo's forwarding with the off-cluster Velocity
|
||||||
|
proxy so authenticated players hand off cleanly, and enable the BungeeCord
|
||||||
|
plugin-message channel on the proxy so the login gate's `Connect` transfer to the
|
||||||
|
lobby lands.
|
||||||
|
|
||||||
|
The login flow's own inputs (`FELIS_API_BASE_URL`, `FELIS_ROOT_DOMAIN`,
|
||||||
|
`FELIS_LOBBY_SERVER`, `FELIS_SERVICE_TOKEN`; see
|
||||||
|
**[In-game login](#in-game-login-specb3)** above) are wired in for you — you do not
|
||||||
|
set them by hand:
|
||||||
|
|
||||||
|
- The three **non-secret** vars are baked into the `login` MinecraftServer's
|
||||||
|
`spec.env` by `felis setup` (`cmd/felis` derives the internal API URL from the
|
||||||
|
control namespace — the platform default `felis`; a renamed control namespace must
|
||||||
|
be reflected by hand — and the root domain from `felis.toml`).
|
||||||
|
- `FELIS_SERVICE_TOKEN` is a **secret**, so it is never written into the CRD. `felis
|
||||||
|
setup` replicates the `felis-service-token` Secret from the control namespace into
|
||||||
|
the minecraft namespace, and the operator injects it into the `login` pod (only)
|
||||||
|
via a `secretKeyRef`, keyed off the reserved `login` name. Until the token is
|
||||||
|
present the plugin fail-safes to readiness-only, so the gate is never broken — it
|
||||||
|
simply does not authenticate yet.
|
||||||
|
- **NetworkPolicy:** none is required today — neither the minecraft-namespace egress
|
||||||
|
nor the control-namespace ingress is policy-locked, so the login pod's call to the
|
||||||
|
API internal port is already reachable. If a future deployment adds a minecraft
|
||||||
|
egress lock or a control-namespace ingress fence, it must also open the
|
||||||
|
login-pod → felis-api internal-port (8081) path.
|
||||||
|
|
||||||
|
The Velocity default-landing and waiting-park wiring is printed by `felis setup`
|
||||||
|
and enforces the invariant: fresh connections hit `login` first; nothing falls
|
||||||
|
back to the lobby.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Felis login-limbo entrypoint.
|
||||||
|
#
|
||||||
|
# Pin Limbo's game port to the pod-facing port the operator contract uses. LOOHP/Limbo
|
||||||
|
# defaults server-port to 30000, but the Felis operator drives everything — the Service
|
||||||
|
# Port/TargetPort, the TCP/HTTP readiness probe, the container port and the Velocity
|
||||||
|
# NetworkPolicy — off a single GamePort const (25565). A backend that bound 30000 would
|
||||||
|
# be unreachable through that fence. Limbo writes a full server.properties on first run
|
||||||
|
# and merges any partial we leave in place, so seeding/patching just server-port here is
|
||||||
|
# enough; the spawn schematic still loads from ./spawn.schem.
|
||||||
|
#
|
||||||
|
# Idempotent by design: it runs on every start and rewrites only the server-port line,
|
||||||
|
# so a persisted world volume that already carries a server.properties keeps all its
|
||||||
|
# other settings.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PORT="${FELIS_GAME_PORT:-25565}"
|
||||||
|
PROPS="server.properties"
|
||||||
|
|
||||||
|
if [ -f "$PROPS" ]; then
|
||||||
|
if grep -q '^server-port=' "$PROPS"; then
|
||||||
|
sed -i "s/^server-port=.*/server-port=${PORT}/" "$PROPS"
|
||||||
|
else
|
||||||
|
printf 'server-port=%s\n' "$PORT" >> "$PROPS"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf 'server-port=%s\n' "$PORT" > "$PROPS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "felis-limbo: pinned server-port=${PORT} (operator GamePort)"
|
||||||
|
exec java -jar Limbo.jar --nogui "$@"
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Felis lobby image: Paper + the felis-paper /menu plugin.
|
||||||
|
#
|
||||||
|
# CODE-ONLY in this repo — not built by the Go CI. It packages the always-on
|
||||||
|
# "lobby" hub the setup provisioner points [velocity] lobby_image at. The lobby is
|
||||||
|
# the POST-auth /menu hub: it is reached only when the login gate transfers an
|
||||||
|
# authenticated player onward, and it must never be a fallback target.
|
||||||
|
#
|
||||||
|
# Build:
|
||||||
|
# docker build -f deploy/lobby/Dockerfile \
|
||||||
|
# --build-arg PAPER_JAR_URL=https://<mirror>/paper-1.21.x-<build>.jar \
|
||||||
|
# -t felis-lobby:demo .
|
||||||
|
# docker save felis-lobby:demo | sudo k3s ctr images import -
|
||||||
|
# # felis.toml → [velocity] lobby_image = "felis-lobby:demo"
|
||||||
|
#
|
||||||
|
# Contract: the game server listens on 25565 (the CRD GamePort). The lobby speaks
|
||||||
|
# only the felis:control plugin-message channel (spec §12) — it holds no felis-api
|
||||||
|
# token.
|
||||||
|
|
||||||
|
# ---- build the felis-paper plugin jar (Paper API is Java 21) ----
|
||||||
|
FROM eclipse-temurin:21-jdk AS plugin
|
||||||
|
WORKDIR /src
|
||||||
|
COPY plugins/paper/ ./plugins/paper/
|
||||||
|
COPY plugins/shared/ ./plugins/shared/
|
||||||
|
RUN cd plugins/paper \
|
||||||
|
&& (test -x ./gradlew && ./gradlew --no-daemon build \
|
||||||
|
|| gradle --no-daemon build) \
|
||||||
|
&& cp build/libs/*.jar /felis-paper.jar
|
||||||
|
|
||||||
|
# ---- assemble the runtime ----
|
||||||
|
FROM eclipse-temurin:21-jre
|
||||||
|
ARG PAPER_JAR_URL
|
||||||
|
WORKDIR /paper
|
||||||
|
RUN set -eu; \
|
||||||
|
if [ -z "${PAPER_JAR_URL:-}" ]; then \
|
||||||
|
echo "ERROR: --build-arg PAPER_JAR_URL=<paper jar> is required" >&2; exit 1; \
|
||||||
|
fi; \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates; \
|
||||||
|
curl -fSL "$PAPER_JAR_URL" -o /paper/paper.jar; \
|
||||||
|
apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*; \
|
||||||
|
mkdir -p /paper/plugins; \
|
||||||
|
echo "eula=true" > /paper/eula.txt
|
||||||
|
COPY --from=plugin /felis-paper.jar /paper/plugins/felis-paper.jar
|
||||||
|
|
||||||
|
EXPOSE 25565
|
||||||
|
# nogui headless; the first boot generates server.properties (align online-mode /
|
||||||
|
# forwarding with the Velocity proxy afterwards — see README).
|
||||||
|
ENTRYPOINT ["java", "-jar", "paper.jar", "--nogui"]
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Lobby image (Paper + felis-paper)
|
||||||
|
|
||||||
|
The always-on **lobby** hub. `felis setup` provisions it as a system service
|
||||||
|
(`DesiredState=Running`, reaper-exempt) when `[velocity] lobby_image` is set in
|
||||||
|
`felis.toml`.
|
||||||
|
|
||||||
|
> **Code-only.** Not built by the Go CI. It compiles the `plugins/paper`
|
||||||
|
> `/menu` face and bundles it onto a Paper server.
|
||||||
|
|
||||||
|
## Topology & the one invariant
|
||||||
|
|
||||||
|
```
|
||||||
|
connect → login (limbo auth gate) → lobby (/menu hub) → target backend
|
||||||
|
```
|
||||||
|
|
||||||
|
The lobby is reached **only** when the login gate transfers an authenticated
|
||||||
|
player onward. It is never a fallback or waiting-park target — routing a fresh
|
||||||
|
connection to the lobby would drop the player past authentication. Felis enforces
|
||||||
|
this at every layer:
|
||||||
|
|
||||||
|
- the login system service has **no** fallback (refuse if down);
|
||||||
|
- the lobby and every user server fall back to **login**, never to the lobby;
|
||||||
|
- `buildSystemServer` refuses to construct any service whose fallback is `lobby`;
|
||||||
|
- `felis setup` prints the off-cluster Velocity wiring: default landing and
|
||||||
|
waiting-park both point at `login`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -f deploy/lobby/Dockerfile \
|
||||||
|
--build-arg PAPER_JAR_URL=https://<mirror>/paper-1.21.x-<build>.jar \
|
||||||
|
-t felis-lobby:demo .
|
||||||
|
docker save felis-lobby:demo | sudo k3s ctr images import -
|
||||||
|
# felis.toml → [velocity] lobby_image = "felis-lobby:demo"
|
||||||
|
sudo felis setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configure (deployer's responsibility)
|
||||||
|
|
||||||
|
- Game port must be `25565` (the CRD `GamePort`).
|
||||||
|
- Align `online-mode` / player forwarding with the off-cluster Velocity proxy.
|
||||||
|
- The lobby speaks only the `felis:control` plugin-message channel; it holds no
|
||||||
|
felis-api token by design (spec §12).
|
||||||
Reference in new issue
Block a user