package state import ( "encoding/json" "log" "os" "sync" "time" ) /* -------------------------------------------------------------------------- CONFIG STRUCT ----------------------------------------------------------------------------*/ type Config struct { VMID int `json:"vmid"` Game string `json:"game"` Variant string `json:"variant"` Version string `json:"version"` World string `json:"world"` Ports []int `json:"ports"` ArtifactPath string `json:"artifact_path"` JavaPath string `json:"java_path"` MemoryMB int `json:"memory_mb"` // Steam + admin credentials SteamUser string `json:"steam_user,omitempty"` SteamPass string `json:"steam_pass,omitempty"` SteamAuth string `json:"steam_auth,omitempty"` AdminUser string `json:"admin_user,omitempty"` AdminPass string `json:"admin_pass,omitempty"` } /* -------------------------------------------------------------------------- AGENT STATE ENUM ----------------------------------------------------------------------------*/ type AgentState string const ( StateIdle AgentState = "idle" StateInstalling AgentState = "installing" StateStarting AgentState = "starting" StateRunning AgentState = "running" StateStopping AgentState = "stopping" StateCrashed AgentState = "crashed" StateError AgentState = "error" ) /* -------------------------------------------------------------------------- GLOBAL STATE STRUCT ----------------------------------------------------------------------------*/ type agentStatus struct { mu sync.Mutex state AgentState lastChange time.Time installStep string lastError error crashCount int lastCrash time.Time } var global = &agentStatus{ state: StateIdle, lastChange: time.Now(), } /* -------------------------------------------------------------------------- STATE GETTERS ----------------------------------------------------------------------------*/ func GetState() AgentState { global.mu.Lock() defer global.mu.Unlock() return global.state } func GetInstallStep() string { global.mu.Lock() defer global.mu.Unlock() return global.installStep } func GetError() error { global.mu.Lock() defer global.mu.Unlock() return global.lastError } func GetCrashCount() int { global.mu.Lock() defer global.mu.Unlock() return global.crashCount } func GetLastChange() time.Time { global.mu.Lock() defer global.mu.Unlock() return global.lastChange } /* -------------------------------------------------------------------------- STATE SETTERS — unified with logging ----------------------------------------------------------------------------*/ func SetState(s AgentState) { global.mu.Lock() defer global.mu.Unlock() if global.state != s { log.Printf("[state] %s → %s\n", global.state, s) global.state = s global.lastChange = time.Now() } } func SetInstallStep(step string) { global.mu.Lock() defer global.mu.Unlock() global.installStep = step } func SetError(err error) { global.mu.Lock() defer global.mu.Unlock() global.lastError = err } func RecordCrash(err error) { global.mu.Lock() defer global.mu.Unlock() log.Printf("[state] crash detected: %v", err) global.state = StateCrashed global.lastError = err global.crashCount++ global.lastCrash = time.Now() } /* -------------------------------------------------------------------------- CONFIG SAVE / LOAD ----------------------------------------------------------------------------*/ const configPath = "/opt/zlh-agent/config/payload.json" func SaveConfig(cfg *Config) error { if err := os.MkdirAll("/opt/zlh-agent/config", 0o755); err != nil { return err } b, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } return os.WriteFile(configPath, b, 0o644) } func LoadConfig() (*Config, error) { b, err := os.ReadFile(configPath) if err != nil { return nil, err } var cfg Config if err := json.Unmarshal(b, &cfg); err != nil { return nil, err } return &cfg, nil }