mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
148 lines
3.3 KiB
Go
148 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const hostRuntimeFileName = "host_runtime_hosts.json"
|
|
|
|
// HostRuntimeStore persists the last known host-agent state so hosts remain
|
|
// visible across server restarts, even while agents are offline.
|
|
type HostRuntimeStore struct {
|
|
mu sync.RWMutex
|
|
hosts map[string]models.Host // keyed by host ID
|
|
dataPath string
|
|
fs FileSystem
|
|
}
|
|
|
|
// NewHostRuntimeStore creates a host runtime store and loads existing data.
|
|
func NewHostRuntimeStore(dataPath string, fs FileSystem) *HostRuntimeStore {
|
|
store := &HostRuntimeStore{
|
|
hosts: make(map[string]models.Host),
|
|
dataPath: dataPath,
|
|
fs: fs,
|
|
}
|
|
|
|
if store.fs == nil {
|
|
store.fs = defaultFileSystem{}
|
|
}
|
|
|
|
if err := store.Load(); err != nil {
|
|
log.Warn().Err(err).Msg("Failed to load host runtime store")
|
|
}
|
|
|
|
return store
|
|
}
|
|
|
|
// Load reads host runtime data from disk.
|
|
func (s *HostRuntimeStore) Load() error {
|
|
filePath := filepath.Join(s.dataPath, hostRuntimeFileName)
|
|
|
|
data, err := s.fs.ReadFile(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to read host runtime file: %w", err)
|
|
}
|
|
|
|
var decoded map[string]models.Host
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
return fmt.Errorf("failed to decode host runtime file: %w", err)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.hosts = make(map[string]models.Host, len(decoded))
|
|
for hostID, host := range decoded {
|
|
if hostID == "" {
|
|
continue
|
|
}
|
|
host.ID = hostID
|
|
s.hosts[hostID] = host
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAll returns a copy of all persisted hosts keyed by ID.
|
|
func (s *HostRuntimeStore) GetAll() map[string]models.Host {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make(map[string]models.Host, len(s.hosts))
|
|
for hostID, host := range s.hosts {
|
|
result[hostID] = host
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Upsert inserts or updates a persisted host entry.
|
|
func (s *HostRuntimeStore) Upsert(host models.Host) error {
|
|
hostID := host.ID
|
|
if hostID == "" {
|
|
return fmt.Errorf("host id is required")
|
|
}
|
|
|
|
host.ID = hostID
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.hosts[hostID] = host
|
|
return s.saveLocked()
|
|
}
|
|
|
|
// Delete removes a persisted host entry.
|
|
func (s *HostRuntimeStore) Delete(hostID string) error {
|
|
if hostID == "" {
|
|
return nil
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.hosts, hostID)
|
|
return s.saveLocked()
|
|
}
|
|
|
|
// Clear removes all persisted host runtime entries.
|
|
func (s *HostRuntimeStore) Clear() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.hosts = make(map[string]models.Host)
|
|
return s.saveLocked()
|
|
}
|
|
|
|
func (s *HostRuntimeStore) saveLocked() error {
|
|
filePath := filepath.Join(s.dataPath, hostRuntimeFileName)
|
|
|
|
data, err := json.Marshal(s.hosts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode host runtime file: %w", err)
|
|
}
|
|
|
|
if err := s.fs.MkdirAll(s.dataPath, 0755); err != nil {
|
|
return fmt.Errorf("failed to ensure host runtime directory: %w", err)
|
|
}
|
|
|
|
tempPath := filePath + ".tmp"
|
|
if err := s.fs.WriteFile(tempPath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write host runtime temp file: %w", err)
|
|
}
|
|
|
|
if err := s.fs.Rename(tempPath, filePath); err != nil {
|
|
return fmt.Errorf("failed to replace host runtime file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|