feat(builder): every boot through the node-agent, every lost file counted, Steam cache
What a builder boot sees is now what a node does: - First boots (untouched, or -as-deployed with exactly a new server's settings) are deployed by the node-agent's BootTest in keep mode; the builder only watches the console and inspects what's left. The Go copy of the deploy (its own docker run, mounts and startup prefix) is gone. It had drifted: it gave the image's VOLUME folders their own volumes, which nodes don't, so saves there looked safe while every start loses them. - Files written outside /server and /data are counted whatever their type (it only counted config-like extensions, so .sav or .db saves went unseen), including the image's VOLUME folders; logs, pid and lock files and system folders are left out. - Boot tests ask the server to stay up holdSec (60 s, test.yaml can set it) after its ready line and to listen on every published container port (needs the node-agent's matching BootTest). - Steam apps are downloaded once per build into a gbr.cache volume and copied into each boot, so later boots only check the files (STEAM_CACHE=off). - RUNNER.md no longer suggests docker system prune, which deletes the gbr-boottest network when nothing is using it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e07025f03d
commit
04967a8a16
10 files changed
+424
-171
No files matched your search
+10
-39
@@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -298,46 +297,18 @@ func bootCheck(gamesDir, id, agentImage string, wantOutside bool) (problems, ver
|
||||
// CI. fillBlank also fills blank password-like settings, only to find out whether they are why a draft
|
||||
// fails. problems is set when the template can't be boot-tested at all.
|
||||
func agentBoot(gamesDir, id, agentImage string, fillBlank bool) (output string, passed bool, problems []string, err error) {
|
||||
self, err := os.Executable()
|
||||
payload, err := bootTestPayload(gamesDir, id, fillBlank)
|
||||
var missing *missingSecretError
|
||||
if errors.As(err, &missing) {
|
||||
// The builder has no CI secrets: that's the template's problem to fix, not a reason to stop
|
||||
return "", false, []string{"the boot test can't run: " + missing.Error() + ". Remove 'secrets' from " +
|
||||
"test.yaml; boot tests run with exactly what a new server gets"}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, nil, err
|
||||
}
|
||||
// A boot test that was killed (worker restarted mid-build) leaves its container and volume behind,
|
||||
// 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")
|
||||
args := []string{"boottest-payload", "-games", gamesDir, id}
|
||||
if fillBlank {
|
||||
args = []string{"boottest-payload", "-fill-blank-passwords", "-games", gamesDir, id}
|
||||
}
|
||||
payloadCmd := exec.Command(self, args...)
|
||||
var stderr bytes.Buffer
|
||||
payloadCmd.Stderr = &stderr
|
||||
payload, err := payloadCmd.Output()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) && exitErr.ExitCode() == exitMissingSecret {
|
||||
// The builder has no CI secrets: that's the template's problem to fix, not a reason to stop
|
||||
return "", false, []string{"the boot test can't run: " + strings.TrimSpace(stderr.String()) + ". Remove 'secrets' from " +
|
||||
"test.yaml; boot tests run with exactly what a new server gets"}, nil
|
||||
}
|
||||
return "", false, nil, fmt.Errorf("boottest-payload: %v: %s", err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
cmd := exec.Command("docker", "run", "--rm", "-i", "-v", "/var/run/docker.sock:/var/run/docker.sock",
|
||||
"--entrypoint", "java", agentImage, "-Dloader.main=com.gbreed.gbr.nodeagent.BootTest", "-cp", "app.jar",
|
||||
"org.springframework.boot.loader.launch.PropertiesLauncher")
|
||||
cmd.Stdin = bytes.NewReader(payload)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &teeWriter{&out}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
if runErr := cmd.Run(); runErr != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if !errors.As(runErr, &exitErr) {
|
||||
return "", false, nil, runErr
|
||||
}
|
||||
return out.String(), false, nil, nil
|
||||
}
|
||||
return out.String(), true, nil, nil
|
||||
output, passed, err = runAgentBootTest(payload, agentImage)
|
||||
return output, passed, nil, err
|
||||
}
|
||||
|
||||
// blankRequiredLooking lists the settings a new server gets empty that look like a server might need a
|
||||
@@ -372,7 +343,7 @@ func emptySettingsProblem(keys []string, verdict string) string {
|
||||
|
||||
func firstBootGame(gamesDir, id string) (*bootReport, error) {
|
||||
outDir := filepath.Join("firstboot-out", id+"-draft")
|
||||
if err := runFirstBoot([]string{"-games", gamesDir, "-game", id, "-out", outDir}); err != nil {
|
||||
if err := runFirstBoot([]string{"-games", gamesDir, "-game", id, "-as-deployed", "-out", outDir}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readBootReport(filepath.Join(outDir, "report.json"))
|
||||
|
||||
Reference in new issue
Block a user