provisioning fix 4-18-26

This commit is contained in:
jester 2026-04-18 21:18:26 +00:00
parent 2729226342
commit 7fc267dd0a
9 changed files with 417 additions and 152 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View 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)
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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"
}

BIN
zlh-agent

Binary file not shown.