Go 66 lines
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
}