package selfupdate import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "github.com/ajhahnde/eeco/internal/config" ) // ledgerName is the reversibility record for binary swaps; sibling to // state/hooks.json so the same shape applies. const ledgerName = "binary.json" // Ledger is the persisted state of the most recent binary swap. A swap // is the one allowed write outside the workspace (Constraint 1), so // every recorded field lets the operator reverse it by hand. type Ledger struct { Installed bool `json:"installed"` FromVersion string `json:"from_version,omitempty"` ToVersion string `json:"to_version,omitempty"` RunningPath string `json:"running_path,omitempty"` Backup string `json:"backup,omitempty"` SHA256 string `json:"sha256,omitempty"` At string `json:"at,omitempty"` } func ledgerPath(cfg *config.Config) string { return filepath.Join(cfg.Workspace, "state", ledgerName) } func writeLedger(cfg *config.Config, l Ledger) error { dir := filepath.Join(cfg.Workspace, "state") if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("binary ledger dir: %w", err) } b, err := json.MarshalIndent(l, "", " ") if err != nil { return err } return os.WriteFile(ledgerPath(cfg), append(b, '\n'), 0o644) } // LoadLedger reads the current ledger record. Used by callers that // surface "what was the last swap" (doctor probes, status digest). // A missing file is not an error. func LoadLedger(cfg *config.Config) (Ledger, error) { var l Ledger b, err := os.ReadFile(ledgerPath(cfg)) if err != nil { if errors.Is(err, os.ErrNotExist) { return l, nil } return l, err } if len(b) == 0 { return l, nil } if err := json.Unmarshal(b, &l); err != nil { return Ledger{}, nil } return l, nil }