Initial commit: Disaster recovery CLI tool
A Go-based CLI tool for recovering servers from backups to new cloud VMs. Features: - Multi-cloud support: Exoscale, Cloudscale, Hetzner Cloud - Backup sources: Local filesystem, Hetzner Storage Box - 6-stage restore pipeline with /etc whitelist protection - DNS migration with safety checks and auto-rollback - Dry-run by default, requires --yes to execute - Cloud-init for SSH key injection Packages: - cmd/recover-server: CLI commands (recover, migrate-dns, list, cleanup) - internal/providers: Cloud provider implementations - internal/backup: Backup source implementations - internal/restore: 6-stage restore pipeline - internal/dns: Exoscale DNS management - internal/ui: Prompts, progress, dry-run display - internal/config: Environment and host configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
130
internal/restore/pipeline.go
Normal file
130
internal/restore/pipeline.go
Normal file
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user