317 lines
7.0 KiB
Go
317 lines
7.0 KiB
Go
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
|
|
}
|