// Package store persists small runtime state (terminal names, login sessions) // to a JSON file with atomic writes. package store import ( "encoding/json" "errors" "os" "sync" "time" ) type Terminal struct { ID string `json:"id"` Name string `json:"name"` } type State struct { Terminals []Terminal `json:"terminals"` Sessions map[string]time.Time `json:"sessions"` // token -> expiry } type Store struct { mu sync.Mutex path string state State } func Open(path string) (*Store, error) { s := &Store{path: path} b, err := os.ReadFile(path) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err } if err == nil { if err := json.Unmarshal(b, &s.state); err != nil { return nil, err } } if s.state.Sessions == nil { s.state.Sessions = map[string]time.Time{} } return s, nil } // Update applies fn under the lock and writes the result to disk. func (s *Store) Update(fn func(*State)) error { s.mu.Lock() defer s.mu.Unlock() fn(&s.state) return s.save() } // View reads state under the lock. func (s *Store) View(fn func(*State)) { s.mu.Lock() defer s.mu.Unlock() fn(&s.state) } func (s *Store) save() error { b, err := json.MarshalIndent(&s.state, "", " ") if err != nil { return err } tmp := s.path + ".tmp" if err := os.WriteFile(tmp, b, 0o600); err != nil { return err } return os.Rename(tmp, s.path) }