Loading deploy/bootstrap.sh +10 −14 Changes for deploy/bootstrap.sh: 10 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -3659,16 +3659,12 @@ build_velocity_plugin() { # signed with a matching HMAC. Only protocol 47 was measured; the rest of Via's 1.7-1.12 range # is its own documented support. # # Pinned by hash and not by "latest" on purpose. These three jars sit in front of every packet # on the proxy, and they are the exact bytes FL-007 measured — a moving tag would quietly make # this an unmeasured configuration. Bumping a version means bumping its checksum here. # # The three versions are a set, not three independent pins. ViaRewind is the component that # carries 1.8/1.7 support, and 4.1.2 against ViaVersion/ViaBackwards 5.11.0 fails to load # Protocol1_9To1_8 — the single protocol every 1.8 client needs — with "Invalid version: 1" # at proxy startup. 4.1.3 is the release that adds 5.11.0 compatibility; a two-arm run of the # same proxy image logs that error three times on 4.1.2 and not at all on 4.1.3. Read the # ViaRewind release notes before moving ViaVersion or ViaBackwards. # Pin the three jars as one compatible set. ViaVersion/ViaBackwards 5.12.0 add # the 26.3 protocol used by game-stack.lock; ViaRewind 4.2.0 explicitly supports # that pair. The earlier FL-007 join measured 1.8 against Paper 1.21.11, not every # client on 26.3. Release notes: # https://github.com/ViaVersion/ViaBackwards/releases/tag/5.12.0 # https://github.com/ViaVersion/ViaRewind/releases/tag/4.2.0 install_via_plugins() { prepare_velocity_layout local name version want target url tmp have Loading @@ -3694,12 +3690,12 @@ install_via_plugins() { || die "${name} ${version} checksum mismatch: got ${have}, expected ${want}" atomic_install_file "$tmp" "$target" 0644 root root done <<'EOF' ViaVersion 5.11.0 18d19e90fc9467d68128c076630ae8700449c901402a3ef421837ce006bc8cae ViaBackwards 5.11.0 b21983d561e3f92df257683f0133ab6c68ec68175e8acfd82c6231723bf83587 ViaRewind 4.1.3 2d5970d22b4711c9ab2800932326c7b08acdace25ed7c6bbb8f6ea81054962b4 ViaVersion 5.12.0 72c40a6a702d67f226fc9a0d8ad82aba1483fdabe2e6159bcdddb2dc070750b0 ViaBackwards 5.12.0 194e9250224632274d7b3c17e411e031a9223c1863c6f5138d53c721f07ab78d ViaRewind 4.2.0 d6634ba57bb82d5161c68dfb393571cdf40511a0beb1b04b8c7ed794a3532c6a EOF pin_via_block_connections ok "Via staged; clients from 1.8 up can join under modern forwarding" ok "Via staged with 26.3 support; verify client versions against your chosen backend images" } # pin_via_block_connections turns ViaVersion's serverside block-connection tracking off. Loading deploy/limbo/README.md +17 −0 Changes for deploy/limbo/README.md: 17 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -164,3 +164,20 @@ set them by hand: The Velocity gate/lobby wiring is printed by `felis setup` and enforces the invariant: fresh connections hit `login` first, and only an authenticated release from that gate can enter the post-auth lobby or a remembered user backend. ## Customize in the panel Administrators open **Login & lobby**, select **Login space**, and stop it before editing. The form configures the login book title/author/heading/link text/help, automatic book opening and the login timeout (30–3600 seconds). These settings persist in `/data/felis-experience.json`; an explicit `FELIS_LOGIN_TIMEOUT_SECONDS` environment variable takes precedence. The generated code, generated login URL and chat guidance are preserved. The authentication and transfer destination are not player-facing customization fields. Use the linked file manager to upload a replacement `/data/spawn.schem`, edit Limbo's `server.properties` or add Limbo-compatible plugins, then start the space. Paper world ZIPs and Paper plugins do not work in Limbo. The page also exposes logs, backups/restore and image/resource settings. New joins are unavailable while this front door is stopped; a custom image must retain the login plugin and support the proxy's forwarding protocol. deploy/lobby/README.md +28 −3 Changes for deploy/lobby/README.md: 28 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ this at every layer: ``` docker build -f deploy/lobby/Dockerfile \ --build-arg PAPER_JAR_URL=https://<mirror>/paper-1.21.x-<build>.jar \ --build-arg PAPER_JAR_URL=https://<mirror>/paper-26.3-<build>.jar \ --build-arg PAPER_JAR_SHA256=<sha256 of that jar> \ -t felis-lobby:demo . # Publish into the cluster's registry (on the node; docker treats 127.0.0.1 as Loading @@ -40,6 +40,30 @@ docker push 127.0.0.1:5000/felis/lobby:demo sudo felis setup ``` ## Customize in the panel Administrators open **Login & lobby** (`/admin/lobby`). Stop the selected space before reading or saving its settings, then start it to apply them. The lobby form configures welcome text, menu titles, join behavior, game mode, building protection, damage/hunger/void handling, difficulty, time/weather and world rules. Settings live in `/data/felis-experience.json`, independently of the image, and retain unknown keys when saved. Existing installations without this file use the same protected-lobby defaults as before. The page also exposes the existing file manager (including upload and ZIP extraction), console, backups/restore, builder permissions and image/resource settings. To replace a map: back up and stop the lobby, upload a world ZIP, extract it at the volume root, verify the world directory directly contains `level.dat`, and set `level-name` in `server.properties`. Use `setworldspawn x y z` in the running lobby console to set its spawn. Plugin JARs go in `plugins/` and must match Paper's version; the bundled Felis and LuckPerms JARs are refreshed from the image at boot. A custom image must retain the menu/control plugin. The operator reuses its `init-forwarding` YAML merge for the lobby, preserving custom Paper globals while refreshing mandatory authentication settings. The image only rewrites that file for standalone runs without a managed forwarding initContainer. RCON secrets and the proxy forwarding secret remain managed. ## Configure (deployer's responsibility) - Game port must be `25565` (the CRD `GamePort`). Loading @@ -47,7 +71,7 @@ sudo felis setup - The lobby speaks only the `felis:control` plugin-message channel; it holds no felis-api token by design (spec §12). ## What the lobby allows ## Default lobby behavior felis-paper's `LobbyGuard` keeps the lobby a hub that nobody can hurt, get hurt in, or leave a mark on: Loading @@ -65,6 +89,7 @@ or leave a mark on: LuckPerms (`lp user <name> permission set felis.lobby.build true` on the lobby console) or op them. The entrypoint pins `max-players=200` on every boot, over Paper's default of 20: every The entrypoint seeds `max-players=200` when absent, over Paper's default of 20; subsequent file-editor changes survive restarts: every authenticated player passes through here, and a stopped server's players arrive all at once. deploy/lobby/entrypoint.sh +10 −7 Changes for deploy/lobby/entrypoint.sh: 10 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -76,7 +76,10 @@ set_prop online-mode false # what one node serves at once, and a flood beyond it is refused at the door instead # of running the 1Gi lobby out of memory. What the world itself allows (no damage, no # building, the /menu hint) is felis-paper's LobbyGuard. # Seed capacity once; administrators can tune it in the panel file editor. if ! grep -q '^max-players=' "$PROPS"; then set_prop max-players 200 fi # RCON is the control plane's write channel (spec §8 写=RCON): the operator probes it # for readiness and the player tally, and felis-api runs console/permission commands over Loading Loading @@ -105,21 +108,21 @@ else echo " injects it from the <server>-rcon Secret when spec.rcon.enabled is true." >&2 fi # Rewritten whole, not merged. Paper loads this file and fills every key it does # not find with the default, then writes the full tree back — so a proxies-only file is a # complete, stable input, and the lobby's other globals are simply always the defaults. # That is true of a system server Felis owns end to end; if admins are ever allowed to tune # the lobby's globals, this has to become a real YAML merge (yq) instead. # The operator's existing init-forwarding step merges the proxy keys on every # start, preserving other Paper globals. Standalone runs retain the mandatory # rewrite because no initContainer has verified their forwarding settings. mkdir -p config if [ "${FELIS_MANAGED_FORWARDING:-false}" = true ]; then [ -f config/paper-global.yml ] || { echo "felis-lobby: missing managed forwarding config" >&2; exit 1; } else cat > config/paper-global.yml <<YAML # Written by felis-lobby's entrypoint on every boot. Do not hand-edit: the forwarding # secret is injected from the felis-forwarding-secret Secret and must match the proxy. proxies: velocity: enabled: true online-mode: true secret: "${SECRET}" YAML fi echo "felis-lobby: server-port=${PORT}, velocity modern forwarding on (UUIDs are Mojang-verified)" JAVA_MEMORY_ARG="" Loading docs/openapi.yaml +18 −6 Changes for docs/openapi.yaml: 18 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ info: title: felis-api version: 4.1.0 description: | Staff on the operator console may manage the reserved login/lobby system services through existing management routes, without player ownership rows. Creating and claiming these reserved names remain prohibited. System patches keep public autostart and idle stop disabled. Customization is persisted as felis-experience.json using the existing stopped-server file API. Control plane for the Felis Minecraft orchestration platform. The same binary exposes an internal face (per-caller service tokens, for velocity / backend callbacks, never Zero Trust) and an external face (the felis_session cookie, for Loading Loading @@ -600,9 +606,9 @@ components: type: boolean description: >- True for a platform-provisioned system service (the login gate, the lobby). Their reserved names are rejected by every per-server route, so the cockpit renders them read-only instead of offering actions that would 400. lobby). Staff can manage them through the existing server routes; players see them read-only. Creating, claiming and deleting these reserved names remain prohibited. RetireState: type: object Loading Loading @@ -2437,6 +2443,8 @@ paths: properties: name: { type: string } desiredState: { type: string, const: Stopped } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -2465,6 +2473,8 @@ paths: properties: name: { type: string } claimed: { type: boolean, const: true } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -3182,6 +3192,8 @@ paths: content: application/json: schema: { $ref: '#/components/schemas/ServerInfo' } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -4488,7 +4500,7 @@ paths: recorded when it was written as it streams; a mismatch cuts the download off short of its end. On the way out config/paper-global.yml (the cluster's forwarding secret) is left out and server.properties has its rcon.password redacted, so the download carries no Content-Length. its rcon.password and forwarding-secrets redacted, so the download carries no Content-Length. A user gets 404 for a backup outside their scope, as their list never shows it. One export per user at a time, 2 across the install, 6 per user per hour. Loading Loading @@ -4850,7 +4862,7 @@ paths: type: string pattern: '^[0-9a-f]{64}$' description: >- SHA-256 of the file as stored (before the rcon.password redaction in SHA-256 of the file as stored (before secret redaction in server.properties). Send it back as expect_sha256 on the next write. content_sha256: type: string Loading Loading @@ -5204,7 +5216,7 @@ paths: without one, with symbolic links, devices and sockets left out. config/paper-global.yml, the cluster's forwarding secret, is refused as a file and left out of a folder, and server.properties goes out with its rcon.password redacted; both are matched by the file itself, so a its rcon.password and forwarding-secrets redacted; both are matched by the file itself, so a link to either under another name is guarded too. The server cannot start until the download has ended. Two file downloads per user at a time, 4 across the install, 30 per user per hour, counted apart from Loading Loading
deploy/bootstrap.sh +10 −14 Changes for deploy/bootstrap.sh: 10 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -3659,16 +3659,12 @@ build_velocity_plugin() { # signed with a matching HMAC. Only protocol 47 was measured; the rest of Via's 1.7-1.12 range # is its own documented support. # # Pinned by hash and not by "latest" on purpose. These three jars sit in front of every packet # on the proxy, and they are the exact bytes FL-007 measured — a moving tag would quietly make # this an unmeasured configuration. Bumping a version means bumping its checksum here. # # The three versions are a set, not three independent pins. ViaRewind is the component that # carries 1.8/1.7 support, and 4.1.2 against ViaVersion/ViaBackwards 5.11.0 fails to load # Protocol1_9To1_8 — the single protocol every 1.8 client needs — with "Invalid version: 1" # at proxy startup. 4.1.3 is the release that adds 5.11.0 compatibility; a two-arm run of the # same proxy image logs that error three times on 4.1.2 and not at all on 4.1.3. Read the # ViaRewind release notes before moving ViaVersion or ViaBackwards. # Pin the three jars as one compatible set. ViaVersion/ViaBackwards 5.12.0 add # the 26.3 protocol used by game-stack.lock; ViaRewind 4.2.0 explicitly supports # that pair. The earlier FL-007 join measured 1.8 against Paper 1.21.11, not every # client on 26.3. Release notes: # https://github.com/ViaVersion/ViaBackwards/releases/tag/5.12.0 # https://github.com/ViaVersion/ViaRewind/releases/tag/4.2.0 install_via_plugins() { prepare_velocity_layout local name version want target url tmp have Loading @@ -3694,12 +3690,12 @@ install_via_plugins() { || die "${name} ${version} checksum mismatch: got ${have}, expected ${want}" atomic_install_file "$tmp" "$target" 0644 root root done <<'EOF' ViaVersion 5.11.0 18d19e90fc9467d68128c076630ae8700449c901402a3ef421837ce006bc8cae ViaBackwards 5.11.0 b21983d561e3f92df257683f0133ab6c68ec68175e8acfd82c6231723bf83587 ViaRewind 4.1.3 2d5970d22b4711c9ab2800932326c7b08acdace25ed7c6bbb8f6ea81054962b4 ViaVersion 5.12.0 72c40a6a702d67f226fc9a0d8ad82aba1483fdabe2e6159bcdddb2dc070750b0 ViaBackwards 5.12.0 194e9250224632274d7b3c17e411e031a9223c1863c6f5138d53c721f07ab78d ViaRewind 4.2.0 d6634ba57bb82d5161c68dfb393571cdf40511a0beb1b04b8c7ed794a3532c6a EOF pin_via_block_connections ok "Via staged; clients from 1.8 up can join under modern forwarding" ok "Via staged with 26.3 support; verify client versions against your chosen backend images" } # pin_via_block_connections turns ViaVersion's serverside block-connection tracking off. Loading
deploy/limbo/README.md +17 −0 Changes for deploy/limbo/README.md: 17 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -164,3 +164,20 @@ set them by hand: The Velocity gate/lobby wiring is printed by `felis setup` and enforces the invariant: fresh connections hit `login` first, and only an authenticated release from that gate can enter the post-auth lobby or a remembered user backend. ## Customize in the panel Administrators open **Login & lobby**, select **Login space**, and stop it before editing. The form configures the login book title/author/heading/link text/help, automatic book opening and the login timeout (30–3600 seconds). These settings persist in `/data/felis-experience.json`; an explicit `FELIS_LOGIN_TIMEOUT_SECONDS` environment variable takes precedence. The generated code, generated login URL and chat guidance are preserved. The authentication and transfer destination are not player-facing customization fields. Use the linked file manager to upload a replacement `/data/spawn.schem`, edit Limbo's `server.properties` or add Limbo-compatible plugins, then start the space. Paper world ZIPs and Paper plugins do not work in Limbo. The page also exposes logs, backups/restore and image/resource settings. New joins are unavailable while this front door is stopped; a custom image must retain the login plugin and support the proxy's forwarding protocol.
deploy/lobby/README.md +28 −3 Changes for deploy/lobby/README.md: 28 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ this at every layer: ``` docker build -f deploy/lobby/Dockerfile \ --build-arg PAPER_JAR_URL=https://<mirror>/paper-1.21.x-<build>.jar \ --build-arg PAPER_JAR_URL=https://<mirror>/paper-26.3-<build>.jar \ --build-arg PAPER_JAR_SHA256=<sha256 of that jar> \ -t felis-lobby:demo . # Publish into the cluster's registry (on the node; docker treats 127.0.0.1 as Loading @@ -40,6 +40,30 @@ docker push 127.0.0.1:5000/felis/lobby:demo sudo felis setup ``` ## Customize in the panel Administrators open **Login & lobby** (`/admin/lobby`). Stop the selected space before reading or saving its settings, then start it to apply them. The lobby form configures welcome text, menu titles, join behavior, game mode, building protection, damage/hunger/void handling, difficulty, time/weather and world rules. Settings live in `/data/felis-experience.json`, independently of the image, and retain unknown keys when saved. Existing installations without this file use the same protected-lobby defaults as before. The page also exposes the existing file manager (including upload and ZIP extraction), console, backups/restore, builder permissions and image/resource settings. To replace a map: back up and stop the lobby, upload a world ZIP, extract it at the volume root, verify the world directory directly contains `level.dat`, and set `level-name` in `server.properties`. Use `setworldspawn x y z` in the running lobby console to set its spawn. Plugin JARs go in `plugins/` and must match Paper's version; the bundled Felis and LuckPerms JARs are refreshed from the image at boot. A custom image must retain the menu/control plugin. The operator reuses its `init-forwarding` YAML merge for the lobby, preserving custom Paper globals while refreshing mandatory authentication settings. The image only rewrites that file for standalone runs without a managed forwarding initContainer. RCON secrets and the proxy forwarding secret remain managed. ## Configure (deployer's responsibility) - Game port must be `25565` (the CRD `GamePort`). Loading @@ -47,7 +71,7 @@ sudo felis setup - The lobby speaks only the `felis:control` plugin-message channel; it holds no felis-api token by design (spec §12). ## What the lobby allows ## Default lobby behavior felis-paper's `LobbyGuard` keeps the lobby a hub that nobody can hurt, get hurt in, or leave a mark on: Loading @@ -65,6 +89,7 @@ or leave a mark on: LuckPerms (`lp user <name> permission set felis.lobby.build true` on the lobby console) or op them. The entrypoint pins `max-players=200` on every boot, over Paper's default of 20: every The entrypoint seeds `max-players=200` when absent, over Paper's default of 20; subsequent file-editor changes survive restarts: every authenticated player passes through here, and a stopped server's players arrive all at once.
deploy/lobby/entrypoint.sh +10 −7 Changes for deploy/lobby/entrypoint.sh: 10 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -76,7 +76,10 @@ set_prop online-mode false # what one node serves at once, and a flood beyond it is refused at the door instead # of running the 1Gi lobby out of memory. What the world itself allows (no damage, no # building, the /menu hint) is felis-paper's LobbyGuard. # Seed capacity once; administrators can tune it in the panel file editor. if ! grep -q '^max-players=' "$PROPS"; then set_prop max-players 200 fi # RCON is the control plane's write channel (spec §8 写=RCON): the operator probes it # for readiness and the player tally, and felis-api runs console/permission commands over Loading Loading @@ -105,21 +108,21 @@ else echo " injects it from the <server>-rcon Secret when spec.rcon.enabled is true." >&2 fi # Rewritten whole, not merged. Paper loads this file and fills every key it does # not find with the default, then writes the full tree back — so a proxies-only file is a # complete, stable input, and the lobby's other globals are simply always the defaults. # That is true of a system server Felis owns end to end; if admins are ever allowed to tune # the lobby's globals, this has to become a real YAML merge (yq) instead. # The operator's existing init-forwarding step merges the proxy keys on every # start, preserving other Paper globals. Standalone runs retain the mandatory # rewrite because no initContainer has verified their forwarding settings. mkdir -p config if [ "${FELIS_MANAGED_FORWARDING:-false}" = true ]; then [ -f config/paper-global.yml ] || { echo "felis-lobby: missing managed forwarding config" >&2; exit 1; } else cat > config/paper-global.yml <<YAML # Written by felis-lobby's entrypoint on every boot. Do not hand-edit: the forwarding # secret is injected from the felis-forwarding-secret Secret and must match the proxy. proxies: velocity: enabled: true online-mode: true secret: "${SECRET}" YAML fi echo "felis-lobby: server-port=${PORT}, velocity modern forwarding on (UUIDs are Mojang-verified)" JAVA_MEMORY_ARG="" Loading
docs/openapi.yaml +18 −6 Changes for docs/openapi.yaml: 18 added lines, 6 removed lines. Original line number Diff line number Diff line Loading @@ -62,6 +62,12 @@ info: title: felis-api version: 4.1.0 description: | Staff on the operator console may manage the reserved login/lobby system services through existing management routes, without player ownership rows. Creating and claiming these reserved names remain prohibited. System patches keep public autostart and idle stop disabled. Customization is persisted as felis-experience.json using the existing stopped-server file API. Control plane for the Felis Minecraft orchestration platform. The same binary exposes an internal face (per-caller service tokens, for velocity / backend callbacks, never Zero Trust) and an external face (the felis_session cookie, for Loading Loading @@ -600,9 +606,9 @@ components: type: boolean description: >- True for a platform-provisioned system service (the login gate, the lobby). Their reserved names are rejected by every per-server route, so the cockpit renders them read-only instead of offering actions that would 400. lobby). Staff can manage them through the existing server routes; players see them read-only. Creating, claiming and deleting these reserved names remain prohibited. RetireState: type: object Loading Loading @@ -2437,6 +2443,8 @@ paths: properties: name: { type: string } desiredState: { type: string, const: Stopped } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -2465,6 +2473,8 @@ paths: properties: name: { type: string } claimed: { type: boolean, const: true } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -3182,6 +3192,8 @@ paths: content: application/json: schema: { $ref: '#/components/schemas/ServerInfo' } '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': Loading Loading @@ -4488,7 +4500,7 @@ paths: recorded when it was written as it streams; a mismatch cuts the download off short of its end. On the way out config/paper-global.yml (the cluster's forwarding secret) is left out and server.properties has its rcon.password redacted, so the download carries no Content-Length. its rcon.password and forwarding-secrets redacted, so the download carries no Content-Length. A user gets 404 for a backup outside their scope, as their list never shows it. One export per user at a time, 2 across the install, 6 per user per hour. Loading Loading @@ -4850,7 +4862,7 @@ paths: type: string pattern: '^[0-9a-f]{64}$' description: >- SHA-256 of the file as stored (before the rcon.password redaction in SHA-256 of the file as stored (before secret redaction in server.properties). Send it back as expect_sha256 on the next write. content_sha256: type: string Loading Loading @@ -5204,7 +5216,7 @@ paths: without one, with symbolic links, devices and sockets left out. config/paper-global.yml, the cluster's forwarding secret, is refused as a file and left out of a folder, and server.properties goes out with its rcon.password redacted; both are matched by the file itself, so a its rcon.password and forwarding-secrets redacted; both are matched by the file itself, so a link to either under another name is guarded too. The server cannot start until the download has ended. Two file downloads per user at a time, 4 across the install, 30 per user per hour, counted apart from Loading