From 72a4280d447c5002a2daed15e1d4df18aa83b1c8 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 25 Oct 2026 21:14:17 -0400 Subject: [PATCH] fix(builder): boot drafts with the settings a new server really gets The builder's boot test filled every blank password-like setting with a throwaway value. A real deploy only fills x-generate fields, so a draft whose server refuses to run with an empty join password passed the builder and CI, and then shut down on its first real deploy. The builder now boots drafts with boottest-payload -as-deployed: only x-generate fields are filled, everything else exactly as the template's defaults. A server that won't run with its own defaults fails the build, and the model is told that empty settings stay empty, so the value must come from a default or x-generate. CI's boot test is unchanged. Co-Authored-By: Claude Opus 5.5 --- cmd/gbrgames/boottest.go | 7 +++++-- cmd/gbrgames/boottest_test.go | 16 ++++++++++++++++ cmd/gbrgames/build.go | 6 ++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/cmd/gbrgames/boottest.go b/cmd/gbrgames/boottest.go index 7ad80b4..6fdaadc 100644 --- a/cmd/gbrgames/boottest.go +++ b/cmd/gbrgames/boottest.go @@ -32,6 +32,8 @@ func runBootTestPayload(args []string) error { fs := flag.NewFlagSet("boottest-payload", flag.ExitOnError) gamesDir := fs.String("games", "games", "directory holding one folder per game") out := fs.String("out", "", "file to write (default stdout)") + asDeployed := fs.Bool("as-deployed", false, "settings exactly as a new server gets them: only x-generate fields filled, "+ + "no throwaway passwords (the AI builder boots drafts this way)") fs.Parse(args) if fs.NArg() != 1 { return errors.New("boottest-payload needs a game id") @@ -72,10 +74,11 @@ func runBootTestPayload(args []string) error { } } // Like the panel fills blank x-generate fields on deploy; other blank passwords (which an owner - // would type in, and some games refuse to start without) get a throwaway value too + // would type in, and some games refuse to start without) get a throwaway value too, except + // -as-deployed: a draft must start with exactly what a new server gets for key, p := range schemaProperties(g) { prop, _ := p.(map[string]any) - if prop["x-generate"] == nil && !passwordKey.MatchString(key) { + if prop["x-generate"] == nil && (*asDeployed || !passwordKey.MatchString(key)) { continue } // Only missing or empty text: ports, booleans and other values are never touched diff --git a/cmd/gbrgames/boottest_test.go b/cmd/gbrgames/boottest_test.go index 21e6fb9..4890028 100644 --- a/cmd/gbrgames/boottest_test.go +++ b/cmd/gbrgames/boottest_test.go @@ -63,4 +63,20 @@ configSchema: if c["MOTD"] != "gbr-boot-test on 25565" { t.Errorf("placeholders not resolved: %v", c["MOTD"]) } + + // As a new server gets it (what the AI builder boots): only x-generate is filled, a blank join password stays blank + if err := runBootTestPayload([]string{"-as-deployed", "-games", filepath.Join(dir, "games"), "-out", out, "demo"}); err != nil { + t.Fatal(err) + } + raw, _ = os.ReadFile(out) + payload.GameConfig = nil + if err := json.Unmarshal(raw, &payload); err != nil { + t.Fatal(err) + } + if s, _ := payload.GameConfig["RCON_PASSWORD"].(string); !strings.HasPrefix(s, "boot-") { + t.Errorf("as deployed: x-generate RCON_PASSWORD not filled: %v", payload.GameConfig["RCON_PASSWORD"]) + } + if payload.GameConfig["ServerPassword"] != "" { + t.Errorf("as deployed: ServerPassword should stay empty, got %v", payload.GameConfig["ServerPassword"]) + } } diff --git a/cmd/gbrgames/build.go b/cmd/gbrgames/build.go index fd81ae8..fd26077 100644 --- a/cmd/gbrgames/build.go +++ b/cmd/gbrgames/build.go @@ -240,7 +240,7 @@ func bootCheck(gamesDir, id, agentImage string, wantOutside bool) (problems, ver // and the next deploy under the same name would trip over them runCmd("docker", "rm", "-f", "boottest-"+id) runCmd("docker", "volume", "rm", "-f", "boottest-"+id+"-data") - payloadCmd := exec.Command(self, "boottest-payload", "-games", gamesDir, id) + payloadCmd := exec.Command(self, "boottest-payload", "-as-deployed", "-games", gamesDir, id) var stderr bytes.Buffer payloadCmd.Stderr = &stderr payload, err := payloadCmd.Output() @@ -310,7 +310,9 @@ func bootFailure(output string) string { lines = append(lines[:1], lines[len(lines)-59:]...) } return "booting your template with the node-agent (the real deploy code) failed: " + verdict + - ". Find the cause in the server's output below and fix the template (startup command, settings, " + + ". It was started with your defaults exactly as a new server gets them: settings left empty stay empty " + + "(only x-generate fields are filled), so a server that needs a value must get it from a default or " + + "x-generate. Find the cause in the server's output below and fix the template (startup command, settings, " + "ready pattern, memory):\n" + strings.Join(lines, "\n") } -- 2.30.2