From 862ff3929fc451d236ad7b1a6c98766fcb0792ed Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Mon, 5 Oct 2026 00:23:35 +0800 Subject: [PATCH] fix(ci): preserve installer compatibility with older releases --- cmd/felis/bootstrap_assets.go | 11 +++++++++-- cmd/felis/run_test.go | 19 +++++++++++++++++++ deploy/bootstrap.sh | 9 ++++++++- deploy/bootstrap_test.sh | 14 +++++++++++++- internal/fileedit/k8sjobs.go | 19 ------------------- 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/cmd/felis/bootstrap_assets.go b/cmd/felis/bootstrap_assets.go index 6c091ad..c7f40f7 100644 --- a/cmd/felis/bootstrap_assets.go +++ b/cmd/felis/bootstrap_assets.go @@ -9,10 +9,17 @@ import ( func cmdBootstrapAssets(args []string, stdout, stderr io.Writer) int { if len(args) != 1 { - fmt.Fprintln(stderr, "felis bootstrap-assets: usage: felis bootstrap-assets crd|game-stack") + fmt.Fprintln(stderr, "felis bootstrap-assets: usage: felis bootstrap-assets crd|game-stack|config-keys") return 2 } switch args[0] { + case "config-keys": + // Optional keys the installer may emit; older binaries reject this verb. + _, err := fmt.Fprintln(stdout, "velocity.game_version") + if err != nil { + fmt.Fprintf(stderr, "felis bootstrap-assets: write: %v\n", err) + return 1 + } case "crd": crd, err := felis.MinecraftServerCRD() if err != nil { @@ -32,7 +39,7 @@ func cmdBootstrapAssets(args []string, stdout, stderr io.Writer) int { return 1 } default: - fmt.Fprintln(stderr, "felis bootstrap-assets: usage: felis bootstrap-assets crd|game-stack") + fmt.Fprintln(stderr, "felis bootstrap-assets: usage: felis bootstrap-assets crd|game-stack|config-keys") return 2 } return 0 diff --git a/cmd/felis/run_test.go b/cmd/felis/run_test.go index 019ffef..45188d9 100644 --- a/cmd/felis/run_test.go +++ b/cmd/felis/run_test.go @@ -5,8 +5,27 @@ import ( "os" "strings" "testing" + + "github.com/BurntSushi/toml" + + "felis.lolicon.best/internal/config" ) +func TestBootstrapConfigKeys(t *testing.T) { + var out, errBuf bytes.Buffer + if code := run([]string{"bootstrap-assets", "config-keys"}, &out, &errBuf); code != 0 { + t.Fatalf("exit code = %d: %s", code, errBuf.String()) + } + var cfg config.Config + meta, err := toml.Decode(strings.TrimSpace(out.String())+` = "26.3"`, &cfg) + if err != nil || len(meta.Undecoded()) != 0 { + t.Fatalf("advertised config key is unsupported: %v, undecoded: %v", err, meta.Undecoded()) + } + if cfg.Velocity.GameVersion != "26.3" { + t.Fatalf("advertised key did not set the game version: %q", cfg.Velocity.GameVersion) + } +} + func TestRunNoArgsPrintsUsage(t *testing.T) { var out, errBuf bytes.Buffer if code := run(nil, &out, &errBuf); code != 2 { diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index e93886f..2d67680 100644 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -4707,6 +4707,13 @@ offsite_enabled() { # database password. write_felis_toml() { local target="$1" db_addr="$2" deployment="${3:-}" deployment_line="" smtp_block auth_body auth_source_blocks registry_block archive_block offsite_section + local config_keys game_version_line="" + # main's installer also installs older releases, whose strict TOML parser rejects + # newer optional keys. Ask the installed binary rather than guessing from its stamp. + config_keys="$("$HOST_BIN" bootstrap-assets config-keys 2>/dev/null || true)" + if grep -Fxq 'velocity.game_version' <<<"$config_keys"; then + game_version_line="game_version = \"${MC_VERSION:-}\"" + fi if [ -n "$deployment" ]; then # Starts with the newline that ends the url line, so the pod copy has no blank line there. deployment_line=" @@ -4762,7 +4769,7 @@ login_image = "${FELIS_LIMBO_IMAGE}" lobby_image = "${FELIS_LOBBY_IMAGE}" # The public port players connect on; the panel shows it in server addresses. game_port = ${FELIS_GAME_PORT} -game_version = "${MC_VERSION:-}" +${game_version_line} [registry] url = "${REGISTRY_URL}" diff --git a/deploy/bootstrap_test.sh b/deploy/bootstrap_test.sh index 37f24af..827023c 100644 --- a/deploy/bootstrap_test.sh +++ b/deploy/bootstrap_test.sh @@ -2042,11 +2042,16 @@ run_write() { # out-file [state-dir] [database-deployment]; under the installer' [ -z "${CAT_FAILS:-}" ] || cat() { head -c 40; return 1; } persisted_smtp_block() { :; } persisted_auth_source_blocks() { :; } + felis() { + [ "$*" = "bootstrap-assets config-keys" ] || return 2 + [ "${OLD_CONFIG:-0}" != 1 ] || return 2 + printf "%s\n" velocity.game_version + } . "$FNFILE" FELIS_ROOT_DOMAIN=r.example.com DB_USER=u DB_PASSWORD=p DB_NAME=d MINECRAFT_NS=minecraft \ FELIS_EGRESS_MODE=nodeport FELIS_LIMBO_IMAGE=li FELIS_LOBBY_IMAGE=lo FELIS_GAME_PORT=25570 MC_VERSION=26.3 \ REGISTRY_URL=registry.felis.svc:5000 BUILD_NS=felis-build FELIS_ARCHIVE_LOCAL_PATH=/a \ - FELIS_OFFSITE_BUCKET= write_felis_toml "$OUT_TOML" 127.0.0.1:15432 "$DEPLOY"' + HOST_BIN=felis FELIS_OFFSITE_BUCKET= write_felis_toml "$OUT_TOML" 127.0.0.1:15432 "$DEPLOY"' } run_write "$rdir/out.toml" @@ -2086,6 +2091,13 @@ expect "a re-run carries the scheduled backup retention" 'scheduled_retention = expect "the archive mount stays installer-owned" 'local_path = "/a"' "$out" expect "the panel learns the public game port" 'game_port = 25570' "$out" expect "the panel learns the built login protocol" 'game_version = "26.3"' "$out" +(OLD_CONFIG=1 run_write "$rdir/release.toml") +release_config="$(cat "$rdir/release.toml")" +case "$release_config" in + *game_version*) echo "FAIL an older binary must not receive velocity.game_version"; fails=$((fails + 1)) ;; + *) echo "PASS an older binary receives no unsupported game-version key" ;; +esac +expect "an older binary still receives the public game port" 'game_port = 25570' "$release_config" expect "a re-run keeps the off-site bucket, set apart from the next section" '[offsite] endpoint = "https://objects.example" bucket = "felis-offsite" diff --git a/internal/fileedit/k8sjobs.go b/internal/fileedit/k8sjobs.go index c82ea63..3de917b 100644 --- a/internal/fileedit/k8sjobs.go +++ b/internal/fileedit/k8sjobs.go @@ -157,25 +157,6 @@ func (k *K8sRunner) awaitPod(ctx context.Context, p JobParams) (*corev1.Pod, err } } -// podLog reads a finished Pod's log. Follow is off — the Pod has already -// terminated, so the log is complete and a follow would merely block until the -// stream closed. -func (k *K8sRunner) podLog(ctx context.Context, namespace, pod string) (string, error) { - stream, err := k.cs.CoreV1().Pods(namespace).GetLogs(pod, &corev1.PodLogOptions{ - Container: containerName, - }).Stream(ctx) - if err != nil { - return "", fmt.Errorf("fileedit: read file job log: %w", err) - } - defer stream.Close() - - b, err := io.ReadAll(io.LimitReader(stream, maxLogBytes)) - if err != nil && !errors.Is(err, io.EOF) { - return "", fmt.Errorf("fileedit: read file job log: %w", err) - } - return string(b), nil -} - // extractResult finds the marked payload in a Pod log. It scans for the LAST line // carrying ResultPrefix because pods/log returns stdout and stderr MERGED: a Go // runtime warning or a libc message can appear anywhere in the stream, so the