package models import ( "errors" "fmt" "time" ) func GetAllServices() []Service { mu.RLock() defer mu.RUnlock() result := make([]Service, len(cfg.Services)) copy(result, cfg.Services) return result } func GetService(id string) (Service, error) { mu.RLock() defer mu.RUnlock() for _, s := range cfg.Services { if s.ID == id { return s, nil } } return Service{}, errors.New("service not found") } func AddService(s Service) (Service, error) { mu.Lock() s.ID = fmt.Sprintf("svc_%d", time.Now().UnixNano()) if s.Backup != nil { if s.Backup.Targets == nil { s.Backup.Targets = []BackupTarget{} } if s.Backup.History == nil { s.Backup.History = []BackupExecution{} } } cfg.Services = append(cfg.Services, s) err := persist() mu.Unlock() if err != nil { return Service{}, err } SyncSchedule() return s, nil } func UpdateService(id string, s Service) (Service, error) { mu.Lock() found := false for i, svc := range cfg.Services { if svc.ID == id { s.ID = id if s.Backup != nil && svc.Backup != nil { s.Backup.History = svc.Backup.History } cfg.Services[i] = s found = true break } } if !found { mu.Unlock() return Service{}, errors.New("service not found") } err := persist() mu.Unlock() if err != nil { return Service{}, err } SyncSchedule() return s, nil } func DeleteService(id string) error { mu.Lock() found := false for i, s := range cfg.Services { if s.ID == id { cfg.Services = append(cfg.Services[:i], cfg.Services[i+1:]...) found = true break } } if !found { mu.Unlock() return errors.New("service not found") } err := persist() mu.Unlock() if err != nil { return err } SyncSchedule() return nil } func UpdateServiceStatus(id, status string) error { mu.Lock() defer mu.Unlock() for i, s := range cfg.Services { if s.ID == id { cfg.Services[i].Status = status return persist() } } return errors.New("service not found") } func AppendServiceBackupExecution(id string, ex BackupExecution) error { mu.Lock() defer mu.Unlock() for i, s := range cfg.Services { if s.ID == id { if cfg.Services[i].Backup == nil { cfg.Services[i].Backup = &BackupConfig{ Targets: []BackupTarget{}, History: []BackupExecution{}, } } cfg.Services[i].Backup.History = append(cfg.Services[i].Backup.History, ex) now := time.Now().UTC() cfg.Services[i].Backup.LastRun = &now for j, t := range cfg.Services[i].Backup.Targets { if t.ID == ex.TargetID { cfg.Services[i].Backup.Targets[j].LastRun = &now break } } return persist() } } return errors.New("service not found") } // GetServiceBackupTargets lists a service's individual backup target definitions. func GetServiceBackupTargets(id string) ([]BackupTarget, error) { mu.RLock() defer mu.RUnlock() for _, s := range cfg.Services { if s.ID == id { if s.Backup == nil { return []BackupTarget{}, nil } result := make([]BackupTarget, len(s.Backup.Targets)) copy(result, s.Backup.Targets) return result, nil } } return nil, errors.New("service not found") } // GetServiceBackupTarget returns a single backup target by ID for a service. func GetServiceBackupTarget(id, tid string) (BackupTarget, error) { mu.RLock() defer mu.RUnlock() for _, s := range cfg.Services { if s.ID == id { if s.Backup == nil { return BackupTarget{}, errors.New("backup target not found") } for _, t := range s.Backup.Targets { if t.ID == tid { return t, nil } } return BackupTarget{}, errors.New("backup target not found") } } return BackupTarget{}, errors.New("service not found") } // AddServiceBackupTarget creates a new backup target definition for a service. func AddServiceBackupTarget(id string, t BackupTarget) (BackupTarget, error) { if err := validateBackupTarget(t); err != nil { return BackupTarget{}, err } mu.Lock() idx := -1 for i, s := range cfg.Services { if s.ID == id { idx = i break } } if idx == -1 { mu.Unlock() return BackupTarget{}, errors.New("service not found") } if cfg.Services[idx].Backup == nil { cfg.Services[idx].Backup = &BackupConfig{ Targets: []BackupTarget{}, History: []BackupExecution{}, } } t.ID = fmt.Sprintf("bt_%d", time.Now().UnixNano()) cfg.Services[idx].Backup.Targets = append(cfg.Services[idx].Backup.Targets, t) err := persist() mu.Unlock() if err != nil { return BackupTarget{}, err } SyncSchedule() return t, nil } // UpdateServiceBackupTarget replaces an existing backup target definition for a service. func UpdateServiceBackupTarget(id, tid string, t BackupTarget) (BackupTarget, error) { if err := validateBackupTarget(t); err != nil { return BackupTarget{}, err } mu.Lock() svcIdx := -1 for i, s := range cfg.Services { if s.ID == id { svcIdx = i break } } if svcIdx == -1 || cfg.Services[svcIdx].Backup == nil { mu.Unlock() return BackupTarget{}, errors.New("service not found") } found := false for i, existing := range cfg.Services[svcIdx].Backup.Targets { if existing.ID == tid { t.ID = tid t.LastRun = existing.LastRun cfg.Services[svcIdx].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 } // DeleteServiceBackupTarget removes a backup target definition from a service. func DeleteServiceBackupTarget(id, tid string) error { mu.Lock() svcIdx := -1 for i, s := range cfg.Services { if s.ID == id { svcIdx = i break } } if svcIdx == -1 || cfg.Services[svcIdx].Backup == nil { mu.Unlock() return errors.New("service not found") } found := false for i, t := range cfg.Services[svcIdx].Backup.Targets { if t.ID == tid { cfg.Services[svcIdx].Backup.Targets = append( cfg.Services[svcIdx].Backup.Targets[:i], cfg.Services[svcIdx].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 } // RunServiceBackup runs a service's backup for the given target IDs (nil/empty // means all targets), honoring StopOnBackup, and appends an execution record // per target. func RunServiceBackup(id string, targetIDs []string) error { svc, err := GetService(id) if err != nil { return err } if svc.Backup == nil || len(svc.Backup.Targets) == 0 { return errors.New("no backup targets configured for this service") } targets := filterTargets(svc.Backup.Targets, targetIDs) if len(targets) == 0 { return errors.New("no matching backup targets") } sourcePath := svc.BackupFolder if svc.Backup.SourcePath != "" { sourcePath = svc.Backup.SourcePath } if svc.StopOnBackup { _ = StopService(svc) } results := RunBackup("svc:"+id, targets, sourcePath) for _, ex := range results { _ = AppendServiceBackupExecution(id, ex) } if svc.StopOnBackup { _ = StartService(svc) } return nil }