diff --git a/internal/http/agent.go b/internal/http/agent.go index 83b5d77..8b5b070 100755 --- a/internal/http/agent.go +++ b/internal/http/agent.go @@ -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) diff --git a/internal/provision/minecraft/fabric_proxy.go b/internal/provision/minecraft/fabric_proxy.go index 737c802..349c8ca 100644 --- a/internal/provision/minecraft/fabric_proxy.go +++ b/internal/provision/minecraft/fabric_proxy.go @@ -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) diff --git a/internal/provision/minecraft/fabric_proxy_test.go b/internal/provision/minecraft/fabric_proxy_test.go index 1e10646..eddf212 100644 --- a/internal/provision/minecraft/fabric_proxy_test.go +++ b/internal/provision/minecraft/fabric_proxy_test.go @@ -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) + } + }) + } +} diff --git a/internal/provision/provision.go b/internal/provision/provision.go index fe037ca..c818f54 100644 --- a/internal/provision/provision.go +++ b/internal/provision/provision.go @@ -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) } - - 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) } diff --git a/internal/provision/provision_test.go b/internal/provision/provision_test.go new file mode 100644 index 0000000..233a34a --- /dev/null +++ b/internal/provision/provision_test.go @@ -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) + } +} diff --git a/internal/system/process.go b/internal/system/process.go index 2cb4fe0..c0771dd 100755 --- a/internal/system/process.go +++ b/internal/system/process.go @@ -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) diff --git a/logs/agent.log b/logs/agent.log index 0556b2c..714ee24 100644 --- a/logs/agent.log +++ b/logs/agent.log @@ -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) diff --git a/state/update.json b/state/update.json index 56634b5..bf19a79 100644 --- a/state/update.json +++ b/state/update.json @@ -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" } \ No newline at end of file diff --git a/zlh-agent b/zlh-agent index 02bdb42..1eee8f8 100755 Binary files a/zlh-agent and b/zlh-agent differ