package restore import ( "context" "fmt" "time" "recover-server/internal/backup" "recover-server/internal/providers" ) // Stage represents a restore stage type Stage int const ( StageSync Stage = iota + 1 StageEtc StageSelectiveEtc StageSSHKeys StageServices StageHealth ) func (s Stage) String() string { names := map[Stage]string{ StageSync: "Sync /root and /opt", StageEtc: "Stage /etc backup", StageSelectiveEtc: "Selective /etc restore", StageSSHKeys: "Merge SSH keys", StageServices: "Start services", StageHealth: "Health verification", } return names[s] } // StageResult contains the result of a stage execution type StageResult struct { Stage Stage Success bool Message string Duration time.Duration Error error } // Pipeline orchestrates the restore process type Pipeline struct { VM *providers.VM BackupSource backup.BackupSource HostName string SSHKeyPath string // Path to ephemeral private key SSHUser string // Usually "root" DryRun bool Verbose bool results []StageResult } // NewPipeline creates a new restore pipeline func NewPipeline(vm *providers.VM, source backup.BackupSource, host, sshKeyPath string) *Pipeline { return &Pipeline{ VM: vm, BackupSource: source, HostName: host, SSHKeyPath: sshKeyPath, SSHUser: "root", results: make([]StageResult, 0), } } // Run executes all stages func (p *Pipeline) Run(ctx context.Context) error { stages := []struct { stage Stage fn func(context.Context) error }{ {StageSync, p.runSync}, {StageEtc, p.runEtcStaging}, {StageSelectiveEtc, p.runSelectiveEtc}, {StageSSHKeys, p.runSSHKeyMerge}, {StageServices, p.runServices}, {StageHealth, p.runHealth}, } for _, s := range stages { start := time.Now() if p.Verbose { fmt.Printf("\n=== Stage %d: %s ===\n", s.stage, s.stage) } if p.DryRun { p.results = append(p.results, StageResult{ Stage: s.stage, Success: true, Message: "[DRY RUN] Would execute: " + s.stage.String(), Duration: 0, }) continue } err := s.fn(ctx) result := StageResult{ Stage: s.stage, Success: err == nil, Duration: time.Since(start), Error: err, } if err != nil { result.Message = err.Error() p.results = append(p.results, result) return fmt.Errorf("stage %d (%s) failed: %w", s.stage, s.stage, err) } result.Message = "Completed successfully" p.results = append(p.results, result) } return nil } // Results returns all stage results func (p *Pipeline) Results() []StageResult { return p.results } // sshTarget returns the SSH target string func (p *Pipeline) sshTarget() string { return fmt.Sprintf("%s@%s", p.SSHUser, p.VM.PublicIP) }