From 26b62e91cdfd973a57e6720c861ac1461e72b055 Mon Sep 17 00:00:00 2001 From: Minseong Choi Date: Thu, 16 Jul 2026 13:27:04 +0900 Subject: [PATCH] feat(bootstrap): federate LittleSkin by default and let Velocity own its runtime dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point Velocity's authlib (mojang.sessionserver) at the felis-api hasJoined multiplexer so a full install federates Mojang plus the configured [[auth_source]] set out of the box, not just the standalone `felis nano`, and ship a default LittleSkin auth_source in the generated felis.toml (delete the block for a Mojang-only server). Velocity now owns velocity.toml and its working tree — it migrates the config version and extracts localizations on start — while the jar and forwarding secret stay root-owned read-only and ReadWritePaths widens to VELOCITY_DIR; the felis-api-internal ClusterIP lookup is factored into a felis_internal_ip helper shared by the link config and the sessionserver override. Re-include plugins/velocity in the docker context because the felis binary now embeds all four plugin trees for `felis bootstrap-assets game-stack`. --- .dockerignore | 15 ++++++------ deploy/bootstrap.sh | 59 ++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/.dockerignore b/.dockerignore index d14ce17..7b63d15 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,18 +7,19 @@ .git .claude **/node_modules -# plugins/ is code-only and excluded from the MAIN felis image — EXCEPT the source -# trees the two game images compile: the login-limbo image (deploy/limbo/Dockerfile) -# builds plugins/limbo, the lobby image (deploy/lobby/Dockerfile) builds plugins/paper, -# and both srcDir-include the shared link core (plugins/shared). +# The MAIN felis binary //go:embed-s all four plugin source trees (see +# bootstrap_asset.go): `felis bootstrap-assets game-stack` emits them as the tar the +# limbo/lobby images and the host Velocity install build from. Every plugins/ source +# tree the embed names MUST stay in this context or `go build ./cmd/felis` fails with +# "pattern plugins//...: no matching files found". # `plugins/*` excludes each sibling module individually (so plugins/ itself is still -# walked and the negations below can re-include), then the 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 small source dirs is harmless. +# walked and the negations below can re-include), then the embedded source trees are +# pulled back in and their Gradle outputs re-excluded to keep the context lean. plugins/* !plugins/limbo !plugins/paper !plugins/shared +!plugins/velocity plugins/**/build plugins/**/.gradle docs diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index 637071c..27c7d72 100644 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -853,15 +853,17 @@ ensure_velocity_directory() { install -d -o "$owner" -g "$group" -m "$mode" "$path" } -# Config and executable parents stay root-owned. The proxy can write only runtime -# output directories, so it cannot replace a future root-written secret/config path -# with a symlink before a bootstrap re-run. +# Velocity manages its own working directory: on startup it migrates velocity.toml to +# the running config-version, extracts localizations, and creates plugins/bStats — all +# fatal or noisy if it cannot write. So the service owns the tree and velocity.toml. The +# immutable artifacts (velocity.jar, felis-velocity.jar) and the forwarding secret stay +# root-owned and read-only to the proxy; ProtectSystem=strict confines writes to VELOCITY_DIR. prepare_velocity_layout() { id -u "$VELOCITY_USER" >/dev/null 2>&1 \ || useradd --system --home-dir "$VELOCITY_DIR" --shell /usr/sbin/nologin "$VELOCITY_USER" - ensure_velocity_directory "$VELOCITY_DIR" 0750 root "$VELOCITY_USER" - ensure_velocity_directory "${VELOCITY_DIR}/plugins" 0750 root "$VELOCITY_USER" - ensure_velocity_directory "${VELOCITY_DIR}/plugins/felis-link" 0750 root "$VELOCITY_USER" + ensure_velocity_directory "$VELOCITY_DIR" 0750 "$VELOCITY_USER" "$VELOCITY_USER" + ensure_velocity_directory "${VELOCITY_DIR}/plugins" 0750 "$VELOCITY_USER" "$VELOCITY_USER" + ensure_velocity_directory "${VELOCITY_DIR}/plugins/felis-link" 0750 "$VELOCITY_USER" "$VELOCITY_USER" ensure_velocity_directory "${VELOCITY_DIR}/logs" 0750 "$VELOCITY_USER" "$VELOCITY_USER" ensure_velocity_directory "${VELOCITY_DIR}/crash-reports" 0750 "$VELOCITY_USER" "$VELOCITY_USER" } @@ -944,15 +946,23 @@ install_velocity() { configure_velocity_firewall } +# felis_internal_ip echoes the felis-api-internal Service ClusterIP. Cluster DNS does not +# resolve from the host, but a Service ClusterIP DOES route from the node (kube-proxy programs +# the host netns) — the same trick the on-node break-glass console uses. The internal face is +# deliberately ClusterIP-only: it is service-token authenticated and must never be published on +# a node's external IP. Both the felis-link plugin config and the Velocity sessionserver +# override (install_velocity_service) point at it, so the lookup lives here once. +felis_internal_ip() { + local ip + ip="$(kube -n "$CONTROL_NS" get svc felis-api-internal -o jsonpath='{.spec.clusterIP}')" \ + || die "could not resolve the felis-api-internal ClusterIP" + [ -n "$ip" ] || die "felis-api-internal has no ClusterIP" + printf '%s' "$ip" +} + write_velocity_config() { local api_ip tmp - # Cluster DNS does not resolve from the host, but a Service ClusterIP DOES route from - # the node (kube-proxy programs the host netns) — the same trick the on-node break-glass - # console uses. The internal face is deliberately ClusterIP-only: it is service-token - # authenticated and must never be published on a node's external IP. - api_ip="$(kube -n "$CONTROL_NS" get svc felis-api-internal -o jsonpath='{.spec.clusterIP}')" \ - || die "could not resolve the felis-api-internal ClusterIP" - [ -n "$api_ip" ] || die "felis-api-internal has no ClusterIP" + api_ip="$(felis_internal_ip)" prepare_velocity_layout tmp="$(mktemp -d)" @@ -1021,13 +1031,19 @@ EOF ) atomic_install_file "${tmp}/forwarding.secret" "${VELOCITY_DIR}/forwarding.secret" 0640 root "$VELOCITY_USER" - atomic_install_file "${tmp}/velocity.toml" "${VELOCITY_DIR}/velocity.toml" 0640 root "$VELOCITY_USER" + atomic_install_file "${tmp}/velocity.toml" "${VELOCITY_DIR}/velocity.toml" 0640 "$VELOCITY_USER" "$VELOCITY_USER" atomic_install_file "${tmp}/felis-link.properties" \ "${VELOCITY_DIR}/plugins/felis-link/felis-link.properties" 0640 root "$VELOCITY_USER" ok "velocity.toml + forwarding secret + felis-link.properties written (${VELOCITY_DIR})" } install_velocity_service() { + local api_ip + # Point Velocity's authlib (mojang.sessionserver) at the felis-api hasJoined multiplexer so a + # full install federates Mojang + the configured [[auth_source]] set (LittleSkin by default) + # out of the box — not just the standalone `felis nano`. felis-api enforces the reclaim + # blacklist on this route; a loopback nano would bypass it. + api_ip="$(felis_internal_ip)" cat > "$VELOCITY_SERVICE" <