package control import ( "encoding/json" "errors" "os" "path/filepath" "sort" "time" ) type NodeState struct { NodeID string `json:"node_id"` BootstrapStatus string `json:"bootstrap_status"` LastBootstrapAt *time.Time `json:"last_bootstrap_at,omitempty"` LastHealthCheckAt *time.Time `json:"last_health_check_at,omitempty"` LastDNSSyncAt *time.Time `json:"last_dns_sync_at,omitempty"` PublicHost string `json:"public_host,omitempty"` Services []ServiceStatus `json:"services,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } type ServiceStatus struct { Type string `json:"type"` Status string `json:"status"` Port int `json:"port"` } func LoadNodeState(dir, nodeID string) (*NodeState, error) { data, err := os.ReadFile(filepath.Join(dir, nodeID+".json")) if err != nil { return nil, err } var state NodeState if err := json.Unmarshal(data, &state); err != nil { return nil, err } return &state, nil } func SaveNodeState(dir string, state NodeState) error { if err := os.MkdirAll(dir, 0o755); err != nil { return err } sort.Slice(state.Services, func(i, j int) bool { return state.Services[i].Type < state.Services[j].Type }) data, err := json.MarshalIndent(state, "", " ") if err != nil { return err } data = append(data, '\n') tmpPath := filepath.Join(dir, state.NodeID+".json.tmp") finalPath := filepath.Join(dir, state.NodeID+".json") if err := os.WriteFile(tmpPath, data, 0o600); err != nil { return err } return os.Rename(tmpPath, finalPath) } func DeleteNodeState(dir, nodeID string) error { err := os.Remove(filepath.Join(dir, nodeID+".json")) if errors.Is(err, os.ErrNotExist) { return nil } return err }