provisioning fix 4-18-26
This commit is contained in:
parent
2729226342
commit
7fc267dd0a
@ -127,6 +127,94 @@ func runProvisionPipeline(cfg *state.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func startProvisionedGame(cfg *state.Config, started time.Time) error {
|
||||
state.SetState(state.StateStarting)
|
||||
state.SetReadyState(false, "", "")
|
||||
lifecycleLog(cfg, "start", 1, started, "start_requested")
|
||||
|
||||
if isForgeLikeMinecraft(*cfg) {
|
||||
return startForgeFirstRun(cfg, started)
|
||||
}
|
||||
|
||||
if err := system.StartServerReady(cfg); err != nil {
|
||||
log.Printf("[http] vmid=%d start error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(cfg, "start", 1, started, "start_failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
lifecycleLog(cfg, "start", 1, started, "process_started")
|
||||
return nil
|
||||
}
|
||||
|
||||
func startForgeFirstRun(cfg *state.Config, started time.Time) error {
|
||||
if err := system.StartServer(cfg); err != nil {
|
||||
log.Printf("[http] vmid=%d start error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(cfg, "start", 1, started, "start_failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
lifecycleLog(cfg, "start", 1, started, "process_started")
|
||||
lifecycleLog(cfg, "forge_post", 1, started, "begin")
|
||||
|
||||
if err := waitForForgeServerProperties(*cfg, 2*time.Minute); err != nil {
|
||||
log.Printf("[http] vmid=%d forge post-start error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return err
|
||||
}
|
||||
|
||||
_ = system.StopServer()
|
||||
if err := system.WaitForServerExit(20 * time.Second); err != nil {
|
||||
log.Printf("[http] vmid=%d forge stop wait error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(cfg, "forge_post", 1, started, "stop_wait_failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := minecraft.EnforceForgeServerProperties(*cfg); err != nil {
|
||||
log.Printf("[http] vmid=%d forge post-start error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(cfg, "forge_post", 1, started, "enforce_failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
state.SetState(state.StateStarting)
|
||||
state.SetReadyState(false, "", "")
|
||||
if err := system.StartServerReady(cfg); err != nil {
|
||||
log.Printf("[http] vmid=%d restart error: %v", cfg.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(cfg, "forge_post", 1, started, "restart_failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
lifecycleLog(cfg, "forge_post", 1, started, "complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForForgeServerProperties(cfg state.Config, timeout time.Duration) error {
|
||||
propsPath := filepath.Join(provision.ServerDir(cfg), "server.properties")
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
if _, err := os.Stat(propsPath); err == nil {
|
||||
return nil
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("forge server.properties not found before timeout")
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func isForgeLikeMinecraft(cfg state.Config) bool {
|
||||
game := strings.ToLower(cfg.Game)
|
||||
variant := strings.ToLower(cfg.Variant)
|
||||
return game == "minecraft" && (variant == "forge" || variant == "neoforge")
|
||||
}
|
||||
|
||||
/*
|
||||
--------------------------------------------------------------------------
|
||||
ensureProvisioned() — idempotent, unified
|
||||
@ -288,72 +376,9 @@ func handleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if c.ContainerType != "dev" {
|
||||
state.SetState(state.StateStarting)
|
||||
state.SetReadyState(false, "", "")
|
||||
lifecycleLog(&c, "start", 1, started, "start_requested")
|
||||
if err := system.StartServerReady(&c); err != nil {
|
||||
log.Printf("[http] vmid=%d start error: %v", c.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(&c, "start", 1, started, "start_failed err=%v", err)
|
||||
if err := startProvisionedGame(&c, started); err != nil {
|
||||
return
|
||||
}
|
||||
lifecycleLog(&c, "start", 1, started, "process_started")
|
||||
|
||||
// -------------------------------------------------
|
||||
// FORGE / NEOFORGE: wait → stop → patch → restart
|
||||
// -------------------------------------------------
|
||||
game := strings.ToLower(c.Game)
|
||||
variant := strings.ToLower(c.Variant)
|
||||
|
||||
if game == "minecraft" && (variant == "forge" || variant == "neoforge") {
|
||||
lifecycleLog(&c, "forge_post", 1, started, "begin")
|
||||
|
||||
// Wait for server.properties to exist before enforcing
|
||||
propsPath := filepath.Join(provision.ServerDir(c), "server.properties")
|
||||
propsDeadline := time.Now().Add(2 * time.Minute)
|
||||
for {
|
||||
if _, err := os.Stat(propsPath); err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(propsDeadline) {
|
||||
err := fmt.Errorf("forge server.properties not found before timeout")
|
||||
log.Printf("[http] vmid=%d forge post-start error: %v", c.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
_ = system.StopServer()
|
||||
if err := system.WaitForServerExit(20 * time.Second); err != nil {
|
||||
log.Printf("[http] vmid=%d forge stop wait error: %v", c.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(&c, "forge_post", 1, started, "stop_wait_failed err=%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := minecraft.EnforceForgeServerProperties(c); err != nil {
|
||||
log.Printf("[http] vmid=%d forge post-start error: %v", c.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(&c, "forge_post", 1, started, "enforce_failed err=%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
state.SetState(state.StateStarting)
|
||||
state.SetReadyState(false, "", "")
|
||||
if err := system.StartServerReady(&c); err != nil {
|
||||
log.Printf("[http] vmid=%d restart error: %v", c.VMID, err)
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
lifecycleLog(&c, "forge_post", 1, started, "restart_failed err=%v", err)
|
||||
return
|
||||
}
|
||||
lifecycleLog(&c, "forge_post", 1, started, "complete")
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[http] vmid=%d async provision+start complete", c.VMID)
|
||||
|
||||
@ -20,7 +20,7 @@ import (
|
||||
|
||||
var (
|
||||
fabricProxyArtifactRoot = "minecraft/fabric/fabric-proxy-lite"
|
||||
fabricAPIArtifactPath = "minecraft/fabric/fabric-api/fabric-api.jar"
|
||||
fabricAPIArtifactRoot = "minecraft/fabric/fabric-api"
|
||||
fabricProxyConfigPath = "minecraft/fabric/FabricProxy-Lite.toml"
|
||||
)
|
||||
|
||||
@ -31,36 +31,9 @@ var (
|
||||
)
|
||||
|
||||
func resolveProxyVersion(mcVersion string) (string, error) {
|
||||
mappings, err := loadFabricProxyMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mcVersion = strings.TrimSpace(mcVersion)
|
||||
if mcVersion == "" {
|
||||
return "", fmt.Errorf("empty minecraft version")
|
||||
}
|
||||
|
||||
compareVersion, err := parseVersion(mcVersion)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid minecraft version %q: %w", mcVersion, err)
|
||||
}
|
||||
|
||||
if version, ok := mappings[mcVersion]; ok {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
for spec, version := range mappings {
|
||||
start, end, ok := parseVersionRange(spec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if compareVersionSlices(compareVersion, start) >= 0 && compareVersionSlices(compareVersion, end) <= 0 {
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no fabricproxy-lite mapping for minecraft version %q", mcVersion)
|
||||
return resolveMappedArtifactPath(mcVersion, loadFabricProxyMap, "fabricproxy-lite", func(value string) string {
|
||||
return value
|
||||
})
|
||||
}
|
||||
|
||||
func InjectFabricProxyLite(cfg state.Config) error {
|
||||
@ -90,8 +63,13 @@ func InjectFabricProxyLite(cfg state.Config) error {
|
||||
log.Printf("[mods] vmid=%d type=system name=fabricproxy-lite installed=true", cfg.VMID)
|
||||
|
||||
fabricAPIPath := filepath.Join(modsDir, "fabric-api.jar")
|
||||
fabricAPISource, err := resolveFabricAPIArtifactURL(cfg.Version)
|
||||
if err != nil {
|
||||
log.Printf("[mods] vmid=%d type=system name=fabric-api error=%v", cfg.VMID, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("[mods] vmid=%d type=system name=fabric-api source=artifact", cfg.VMID)
|
||||
if err := installSystemMod(provcommon.BuildArtifactURL(fabricAPIArtifactPath), fabricAPIPath, modsDir); err != nil {
|
||||
if err := installSystemMod(fabricAPISource, fabricAPIPath, modsDir); err != nil {
|
||||
err = fmt.Errorf("install fabric-api: %w", err)
|
||||
log.Printf("[mods] vmid=%d type=system name=fabric-api error=%v", cfg.VMID, err)
|
||||
return err
|
||||
@ -113,6 +91,9 @@ func shouldInjectFabricProxyLite(cfg state.Config) bool {
|
||||
if !strings.EqualFold(cfg.Game, "minecraft") {
|
||||
return false
|
||||
}
|
||||
if !strings.EqualFold(cfg.Variant, "vanilla") {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.EqualFold(cfg.InternalProfile, "vanilla-fabric")
|
||||
}
|
||||
@ -125,37 +106,7 @@ func loadFabricProxyMap() (map[string]string, error) {
|
||||
return
|
||||
}
|
||||
|
||||
var mappings map[string]string
|
||||
if err := json.Unmarshal(raw, &mappings); err != nil {
|
||||
fabricProxyMapErr = fmt.Errorf("parse fabricproxy-lite map: %w", err)
|
||||
return
|
||||
}
|
||||
if len(mappings) == 0 {
|
||||
fabricProxyMapErr = fmt.Errorf("fabricproxy-lite map is empty")
|
||||
return
|
||||
}
|
||||
|
||||
for spec, version := range mappings {
|
||||
spec = strings.TrimSpace(spec)
|
||||
version = strings.TrimSpace(version)
|
||||
if spec == "" || version == "" {
|
||||
fabricProxyMapErr = fmt.Errorf("fabricproxy-lite map contains empty key or value")
|
||||
return
|
||||
}
|
||||
if start, end, ok := parseVersionRange(spec); ok {
|
||||
if compareVersionSlices(start, end) > 0 {
|
||||
fabricProxyMapErr = fmt.Errorf("invalid fabricproxy-lite range %q", spec)
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := parseVersion(spec); err != nil {
|
||||
fabricProxyMapErr = fmt.Errorf("invalid fabricproxy-lite version key %q: %w", spec, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fabricProxyMap = mappings
|
||||
fabricProxyMap, fabricProxyMapErr = parseArtifactMap(raw, "fabricproxy-lite")
|
||||
})
|
||||
|
||||
if fabricProxyMapErr != nil {
|
||||
@ -177,6 +128,81 @@ func proxyArtifactURL(version string) string {
|
||||
return provcommon.BuildArtifactURL(rel)
|
||||
}
|
||||
|
||||
func resolveFabricAPIArtifactURL(mcVersion string) (string, error) {
|
||||
mcVersion = strings.TrimSpace(mcVersion)
|
||||
if mcVersion == "" {
|
||||
return "", fmt.Errorf("empty minecraft version")
|
||||
}
|
||||
if _, err := parseVersion(mcVersion); err != nil {
|
||||
return "", fmt.Errorf("invalid minecraft version %q: %w", mcVersion, err)
|
||||
}
|
||||
|
||||
rel := filepath.ToSlash(filepath.Join(fabricAPIArtifactRoot, mcVersion, "fabric-api.jar"))
|
||||
return provcommon.BuildArtifactURL(rel), nil
|
||||
}
|
||||
|
||||
func resolveMappedArtifactPath(mcVersion string, loadMap func() (map[string]string, error), name string, valueToPath func(string) string) (string, error) {
|
||||
mappings, err := loadMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mcVersion = strings.TrimSpace(mcVersion)
|
||||
if mcVersion == "" {
|
||||
return "", fmt.Errorf("empty minecraft version")
|
||||
}
|
||||
|
||||
compareVersion, err := parseVersion(mcVersion)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid minecraft version %q: %w", mcVersion, err)
|
||||
}
|
||||
|
||||
if value, ok := mappings[mcVersion]; ok {
|
||||
return valueToPath(value), nil
|
||||
}
|
||||
|
||||
for spec, value := range mappings {
|
||||
start, end, ok := parseVersionRange(spec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if compareVersionSlices(compareVersion, start) >= 0 && compareVersionSlices(compareVersion, end) <= 0 {
|
||||
return valueToPath(value), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no %s mapping for minecraft version %q", name, mcVersion)
|
||||
}
|
||||
|
||||
func parseArtifactMap(raw []byte, name string) (map[string]string, error) {
|
||||
var mappings map[string]string
|
||||
if err := json.Unmarshal(raw, &mappings); err != nil {
|
||||
return nil, fmt.Errorf("parse %s map: %w", name, err)
|
||||
}
|
||||
if len(mappings) == 0 {
|
||||
return nil, fmt.Errorf("%s map is empty", name)
|
||||
}
|
||||
|
||||
for spec, version := range mappings {
|
||||
spec = strings.TrimSpace(spec)
|
||||
version = strings.TrimSpace(version)
|
||||
if spec == "" || version == "" {
|
||||
return nil, fmt.Errorf("%s map contains empty key or value", name)
|
||||
}
|
||||
if start, end, ok := parseVersionRange(spec); ok {
|
||||
if compareVersionSlices(start, end) > 0 {
|
||||
return nil, fmt.Errorf("invalid %s range %q", name, spec)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := parseVersion(spec); err != nil {
|
||||
return nil, fmt.Errorf("invalid %s version key %q: %w", name, spec, err)
|
||||
}
|
||||
}
|
||||
|
||||
return mappings, nil
|
||||
}
|
||||
|
||||
func ensureSystemModsDir(cfg state.Config) (string, string, error) {
|
||||
serverDir := provcommon.ServerDir(cfg)
|
||||
info, err := os.Stat(serverDir)
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
func TestResolveProxyVersion(t *testing.T) {
|
||||
@ -76,3 +78,101 @@ func TestResolveProxyVersion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldInjectFabricProxyLiteOnlyForVanillaProfile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg state.Config
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "vanilla fabric profile",
|
||||
cfg: state.Config{
|
||||
ContainerType: "game",
|
||||
Game: "minecraft",
|
||||
Variant: "vanilla",
|
||||
InternalProfile: "vanilla-fabric",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "fabric variant with stale vanilla profile",
|
||||
cfg: state.Config{
|
||||
ContainerType: "game",
|
||||
Game: "minecraft",
|
||||
Variant: "fabric",
|
||||
InternalProfile: "vanilla-fabric",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "vanilla without internal profile",
|
||||
cfg: state.Config{
|
||||
ContainerType: "game",
|
||||
Game: "minecraft",
|
||||
Variant: "vanilla",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := shouldInjectFabricProxyLite(tc.cfg); got != tc.want {
|
||||
t.Fatalf("shouldInjectFabricProxyLite() = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveFabricAPIArtifactURL(t *testing.T) {
|
||||
baseURL := "http://artifacts.test"
|
||||
origRoot := fabricAPIArtifactRoot
|
||||
fabricAPIArtifactRoot = "zpacks/minecraft/fabric/fabric-api"
|
||||
t.Cleanup(func() {
|
||||
fabricAPIArtifactRoot = origRoot
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "version expands to artifact path",
|
||||
version: "1.21.1",
|
||||
want: baseURL + "/zpacks/minecraft/fabric/fabric-api/1.21.1/fabric-api.jar",
|
||||
},
|
||||
{
|
||||
name: "latest offered version",
|
||||
version: "1.21.7",
|
||||
want: baseURL + "/zpacks/minecraft/fabric/fabric-api/1.21.7/fabric-api.jar",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
version: "",
|
||||
wantErr: "empty minecraft version",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("ZLH_ARTIFACT_BASE_URL", baseURL)
|
||||
|
||||
got, err := resolveFabricAPIArtifactURL(tc.version)
|
||||
if tc.wantErr != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Fatalf("expected error containing %q, got %v", tc.wantErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("resolveFabricAPIArtifactURL(%q) error = %v", tc.version, err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Fatalf("resolveFabricAPIArtifactURL(%q) = %q, want %q", tc.version, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,14 +195,20 @@ func normalizeMinecraftConfig(cfg *state.Config) {
|
||||
if !strings.EqualFold(cfg.Game, "minecraft") {
|
||||
return
|
||||
}
|
||||
if !strings.EqualFold(cfg.Variant, "vanilla") {
|
||||
return
|
||||
}
|
||||
|
||||
variant := strings.ToLower(strings.TrimSpace(cfg.Variant))
|
||||
switch variant {
|
||||
case "vanilla":
|
||||
cfg.Runtime = "fabric"
|
||||
cfg.InternalProfile = "vanilla-fabric"
|
||||
cfg.ArtifactPath = fmt.Sprintf("minecraft/fabric/%s/server.jar", strings.TrimSpace(cfg.Version))
|
||||
log.Printf("[provision] vmid=%d action=normalize original_variant=vanilla runtime=fabric profile=vanilla-fabric", cfg.VMID)
|
||||
log.Printf("[provision] vmid=%d variant=vanilla normalized_runtime=fabric", cfg.VMID)
|
||||
log.Printf("[provision] vmid=%d artifact_override=true source=fabric path=%s", cfg.VMID, cfg.ArtifactPath)
|
||||
case "fabric":
|
||||
cfg.Runtime = "fabric"
|
||||
cfg.InternalProfile = ""
|
||||
cfg.ArtifactPath = fmt.Sprintf("minecraft/fabric/%s/server.jar", strings.TrimSpace(cfg.Version))
|
||||
log.Printf("[provision] vmid=%d action=normalize variant=fabric runtime=fabric profile=none", cfg.VMID)
|
||||
log.Printf("[provision] vmid=%d artifact_override=true source=fabric path=%s", cfg.VMID, cfg.ArtifactPath)
|
||||
}
|
||||
}
|
||||
|
||||
52
internal/provision/provision_test.go
Normal file
52
internal/provision/provision_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"zlh-agent/internal/state"
|
||||
)
|
||||
|
||||
func TestNormalizeMinecraftConfigVanillaUsesFabricProfile(t *testing.T) {
|
||||
cfg := &state.Config{
|
||||
ContainerType: "game",
|
||||
Game: "minecraft",
|
||||
Variant: "vanilla",
|
||||
Version: "1.21.1",
|
||||
}
|
||||
|
||||
normalizeMinecraftConfig(cfg)
|
||||
|
||||
if cfg.Runtime != "fabric" {
|
||||
t.Fatalf("Runtime = %q, want fabric", cfg.Runtime)
|
||||
}
|
||||
if cfg.InternalProfile != "vanilla-fabric" {
|
||||
t.Fatalf("InternalProfile = %q, want vanilla-fabric", cfg.InternalProfile)
|
||||
}
|
||||
if cfg.ArtifactPath != "minecraft/fabric/1.21.1/server.jar" {
|
||||
t.Fatalf("ArtifactPath = %q", cfg.ArtifactPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMinecraftConfigFabricUsesFabricArtifactOnly(t *testing.T) {
|
||||
cfg := &state.Config{
|
||||
ContainerType: "game",
|
||||
Game: "minecraft",
|
||||
Variant: "fabric",
|
||||
Version: "1.21.1",
|
||||
Runtime: "vanilla",
|
||||
InternalProfile: "vanilla-fabric",
|
||||
ArtifactPath: "bad/path/server.jar",
|
||||
}
|
||||
|
||||
normalizeMinecraftConfig(cfg)
|
||||
|
||||
if cfg.Runtime != "fabric" {
|
||||
t.Fatalf("Runtime = %q, want fabric", cfg.Runtime)
|
||||
}
|
||||
if cfg.InternalProfile != "" {
|
||||
t.Fatalf("InternalProfile = %q, want empty", cfg.InternalProfile)
|
||||
}
|
||||
if cfg.ArtifactPath != "minecraft/fabric/1.21.1/server.jar" {
|
||||
t.Fatalf("ArtifactPath = %q", cfg.ArtifactPath)
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -24,6 +23,7 @@ import (
|
||||
)
|
||||
|
||||
const ReadinessTimeout = 60 * time.Second
|
||||
const ForgeReadinessTimeout = 5 * time.Minute
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
GLOBAL PROCESS STATE
|
||||
@ -98,7 +98,7 @@ func StartServerReady(cfg *state.Config) error {
|
||||
if err := StartServer(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := WaitForReady(cfg, ReadinessTimeout); err != nil {
|
||||
if err := WaitForReady(cfg, readinessTimeoutForConfig(cfg)); err != nil {
|
||||
state.SetError(err)
|
||||
state.SetState(state.StateError)
|
||||
return err
|
||||
@ -106,6 +106,19 @@ func StartServerReady(cfg *state.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readinessTimeoutForConfig(cfg *state.Config) time.Duration {
|
||||
if cfg == nil {
|
||||
return ReadinessTimeout
|
||||
}
|
||||
variant := strings.ToLower(strings.TrimSpace(cfg.Variant))
|
||||
if strings.EqualFold(cfg.ContainerType, "game") &&
|
||||
strings.EqualFold(cfg.Game, "minecraft") &&
|
||||
(variant == "forge" || variant == "neoforge") {
|
||||
return ForgeReadinessTimeout
|
||||
}
|
||||
return ReadinessTimeout
|
||||
}
|
||||
|
||||
func WaitForReady(cfg *state.Config, timeout time.Duration) error {
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("config required")
|
||||
@ -155,18 +168,8 @@ func buildServerCommand(cfg *state.Config, dir, startScript string) (*exec.Cmd,
|
||||
|
||||
func resolveMinecraftRuntimeJar(runtimeType, dir string) (string, error) {
|
||||
if runtimeType == "fabric" {
|
||||
matches, err := filepath.Glob(filepath.Join(dir, ".fabric", "server", "fabric-loader-server-*.jar"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fabric runtime selected but loader scan failed: %w", err)
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
log.Printf("[runtime] ERROR missing fabric loader jar")
|
||||
return "", fmt.Errorf("fabric runtime selected but no fabric loader jar found")
|
||||
}
|
||||
sort.Strings(matches)
|
||||
loader := matches[len(matches)-1]
|
||||
log.Printf("[runtime] type=fabric loader=%s", loader)
|
||||
return loader, nil
|
||||
log.Printf("[runtime] type=fabric jar=server.jar")
|
||||
return filepath.Join(dir, "server.jar"), nil
|
||||
}
|
||||
|
||||
log.Printf("[runtime] type=%s jar=server.jar", runtimeType)
|
||||
|
||||
@ -460,3 +460,56 @@
|
||||
2026/04/18 16:52:55 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://10.60.0.251:8080/agents/manifest.json": dial tcp 10.60.0.251:8080: connect: no route to host
|
||||
2026/04/18 17:22:55 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://10.60.0.251:8080/agents/manifest.json": dial tcp 10.60.0.251:8080: connect: no route to host
|
||||
2026/04/18 17:52:55 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://10.60.0.251:8080/agents/manifest.json": dial tcp 10.60.0.251:8080: connect: no route to host
|
||||
2026/04/18 18:22:55 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://10.60.0.251:8080/agents/manifest.json": dial tcp 10.60.0.251:8080: connect: no route to host
|
||||
2026/04/18 18:52:55 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://10.60.0.251:8080/agents/manifest.json": dial tcp 10.60.0.251:8080: connect: no route to host
|
||||
2026/04/18 19:13:50 [agent] shutdown signal received
|
||||
2026/04/18 19:13:50 [agent] http server stopped gracefully
|
||||
2026/04/18 19:13:50 [agent] exiting
|
||||
2026/04/18 19:13:50 [agent] file logging enabled
|
||||
2026/04/18 19:13:50 [agent] lifecycle logging enabled
|
||||
2026/04/18 19:13:50 [agent] routes registered
|
||||
2026/04/18 19:13:50 [autostart] disabled (ok)
|
||||
2026/04/18 19:13:50 [update] periodic checks enabled (mode=notify interval=30m0s)
|
||||
2026/04/18 19:13:50 [agent] listening on :18888
|
||||
2026/04/18 19:14:10 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
2026/04/18 19:44:20 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
2026/04/18 20:03:58 [agent] shutdown signal received
|
||||
2026/04/18 20:03:58 [agent] http server stopped gracefully
|
||||
2026/04/18 20:03:58 [agent] exiting
|
||||
2026/04/18 20:03:58 [agent] file logging enabled
|
||||
2026/04/18 20:03:58 [agent] lifecycle logging enabled
|
||||
2026/04/18 20:03:58 [agent] routes registered
|
||||
2026/04/18 20:03:58 [autostart] disabled (ok)
|
||||
2026/04/18 20:03:58 [update] periodic checks enabled (mode=notify interval=30m0s)
|
||||
2026/04/18 20:03:58 [agent] listening on :18888
|
||||
2026/04/18 20:04:18 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
2026/04/18 20:22:04 [agent] shutdown signal received
|
||||
2026/04/18 20:22:04 [agent] http server stopped gracefully
|
||||
2026/04/18 20:22:04 [agent] exiting
|
||||
2026/04/18 20:22:04 [agent] file logging enabled
|
||||
2026/04/18 20:22:04 [agent] lifecycle logging enabled
|
||||
2026/04/18 20:22:04 [agent] routes registered
|
||||
2026/04/18 20:22:04 [autostart] disabled (ok)
|
||||
2026/04/18 20:22:04 [update] periodic checks enabled (mode=notify interval=30m0s)
|
||||
2026/04/18 20:22:04 [agent] listening on :18888
|
||||
2026/04/18 20:22:24 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
2026/04/18 20:45:41 [agent] shutdown signal received
|
||||
2026/04/18 20:45:41 [agent] http server stopped gracefully
|
||||
2026/04/18 20:45:41 [agent] exiting
|
||||
2026/04/18 20:45:42 [agent] file logging enabled
|
||||
2026/04/18 20:45:42 [agent] lifecycle logging enabled
|
||||
2026/04/18 20:45:42 [agent] routes registered
|
||||
2026/04/18 20:45:42 [autostart] disabled (ok)
|
||||
2026/04/18 20:45:42 [update] periodic checks enabled (mode=notify interval=30m0s)
|
||||
2026/04/18 20:45:42 [agent] listening on :18888
|
||||
2026/04/18 20:46:02 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
2026/04/18 21:01:44 [agent] shutdown signal received
|
||||
2026/04/18 21:01:44 [agent] http server stopped gracefully
|
||||
2026/04/18 21:01:44 [agent] exiting
|
||||
2026/04/18 21:01:44 [agent] file logging enabled
|
||||
2026/04/18 21:01:44 [agent] lifecycle logging enabled
|
||||
2026/04/18 21:01:44 [agent] routes registered
|
||||
2026/04/18 21:01:44 [autostart] disabled (ok)
|
||||
2026/04/18 21:01:44 [update] periodic checks enabled (mode=notify interval=30m0s)
|
||||
2026/04/18 21:01:44 [agent] listening on :18888
|
||||
2026/04/18 21:02:04 [update] notify check failed status=error current=0.0.0-dev target= err=Get "http://zlh-artifacts.internal.zlh:8080/agents/manifest.json": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"status": "error",
|
||||
"current": "0.0.0-dev",
|
||||
"error": "Get \"http://10.60.0.251:8080/agents/manifest.json\": dial tcp 10.60.0.251:8080: connect: no route to host",
|
||||
"checked_at_utc": "2026-04-18T17:52:52Z"
|
||||
"error": "Get \"http://zlh-artifacts.internal.zlh:8080/agents/manifest.json\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)",
|
||||
"checked_at_utc": "2026-04-18T21:01:54Z"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user