package models import ( "errors" "fmt" "time" ) func GetLocalNode() NodeInfo { mu.RLock() defer mu.RUnlock() return cfg.Node } func UpdateLocalNode(n NodeInfo) error { mu.Lock() defer mu.Unlock() if n.Hostname != "" { cfg.Node.Hostname = n.Hostname } if n.Description != "" { cfg.Node.Description = n.Description } if n.ResticPassword != "" { cfg.Node.ResticPassword = n.ResticPassword } return persist() } // GetResticPassword returns the node-wide restic repository password used to // encrypt/decrypt every restic backup target on this node. func GetResticPassword() string { mu.RLock() defer mu.RUnlock() return cfg.Node.ResticPassword } func GetUpdateHistory() []UpdateRecord { mu.RLock() defer mu.RUnlock() result := make([]UpdateRecord, len(cfg.Node.UpdateHistory)) copy(result, cfg.Node.UpdateHistory) return result } func GetUpdateRecord(id string) (UpdateRecord, error) { mu.RLock() defer mu.RUnlock() for _, r := range cfg.Node.UpdateHistory { if r.ID == id { return r, nil } } return UpdateRecord{}, errors.New("update record not found") } func AddUpdateRecord(r UpdateRecord) (UpdateRecord, error) { mu.Lock() defer mu.Unlock() r.ID = fmt.Sprintf("upd_%d", time.Now().UnixNano()) r.Timestamp = time.Now().UTC() cfg.Node.UpdateHistory = append(cfg.Node.UpdateHistory, r) return r, persist() } func DeleteUpdateRecord(id string) error { mu.Lock() defer mu.Unlock() for i, r := range cfg.Node.UpdateHistory { if r.ID == id { cfg.Node.UpdateHistory = append(cfg.Node.UpdateHistory[:i], cfg.Node.UpdateHistory[i+1:]...) return persist() } } return errors.New("update record not found") } func GetNodeBackupConfig() BackupConfig { mu.RLock() defer mu.RUnlock() return cfg.Node.Backup } func UpdateNodeBackupConfig(bc BackupConfig) error { mu.Lock() history := cfg.Node.Backup.History cfg.Node.Backup = bc cfg.Node.Backup.History = history err := persist() mu.Unlock() if err != nil { return err } SyncSchedule() return nil } func AppendNodeBackupExecution(ex BackupExecution) error { mu.Lock() defer mu.Unlock() cfg.Node.Backup.History = append(cfg.Node.Backup.History, ex) now := time.Now().UTC() cfg.Node.Backup.LastRun = &now for i, t := range cfg.Node.Backup.Targets { if t.ID == ex.TargetID { cfg.Node.Backup.Targets[i].LastRun = &now break } } return persist() } // GetNodeBackupTargets lists the node's individual backup target definitions. func GetNodeBackupTargets() []BackupTarget { mu.RLock() defer mu.RUnlock() result := make([]BackupTarget, len(cfg.Node.Backup.Targets)) copy(result, cfg.Node.Backup.Targets) return result } // GetNodeBackupTarget returns a single node backup target by ID. func GetNodeBackupTarget(tid string) (BackupTarget, error) { mu.RLock() defer mu.RUnlock() for _, t := range cfg.Node.Backup.Targets { if t.ID == tid { return t, nil } } return BackupTarget{}, errors.New("backup target not found") } // AddNodeBackupTarget creates a new backup target definition for the node. func AddNodeBackupTarget(t BackupTarget) (BackupTarget, error) { if err := validateBackupTarget(t); err != nil { return BackupTarget{}, err } mu.Lock() t.ID = fmt.Sprintf("bt_%d", time.Now().UnixNano()) cfg.Node.Backup.Targets = append(cfg.Node.Backup.Targets, t) err := persist() mu.Unlock() if err != nil { return BackupTarget{}, err } SyncSchedule() return t, nil } // UpdateNodeBackupTarget replaces an existing node backup target definition. func UpdateNodeBackupTarget(tid string, t BackupTarget) (BackupTarget, error) { if err := validateBackupTarget(t); err != nil { return BackupTarget{}, err } mu.Lock() found := false for i, existing := range cfg.Node.Backup.Targets { if existing.ID == tid { t.ID = tid t.LastRun = existing.LastRun cfg.Node.Backup.Targets[i] = t found = true break } } if !found { mu.Unlock() return BackupTarget{}, errors.New("backup target not found") } err := persist() mu.Unlock() if err != nil { return BackupTarget{}, err } SyncSchedule() return t, nil } // DeleteNodeBackupTarget removes a node backup target definition. func DeleteNodeBackupTarget(tid string) error { mu.Lock() found := false for i, t := range cfg.Node.Backup.Targets { if t.ID == tid { cfg.Node.Backup.Targets = append(cfg.Node.Backup.Targets[:i], cfg.Node.Backup.Targets[i+1:]...) found = true break } } if !found { mu.Unlock() return errors.New("backup target not found") } err := persist() mu.Unlock() if err != nil { return err } SyncSchedule() return nil } // RunNodeBackup runs the node's backup for the given target IDs (nil/empty // means all targets), appending an execution record per target. func RunNodeBackup(targetIDs []string) error { bc := GetNodeBackupConfig() targets := filterTargets(bc.Targets, targetIDs) if len(targets) == 0 { return errors.New("no matching backup targets") } sourcePath := bc.SourcePath if sourcePath == "" { sourcePath = ConfigPath() } results := RunBackup("node", targets, sourcePath) for _, ex := range results { _ = AppendNodeBackupExecution(ex) } return nil }