Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions cmd/create/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,53 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

eos "github.com/CodeMonkeyCybersecurity/eos/pkg/eos_cli"
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_io"
"github.com/CodeMonkeyCybersecurity/eos/pkg/interaction"
"github.com/uptrace/opentelemetry-go-extra/otelzap"

"github.com/spf13/cobra"
"go.uber.org/zap"
)

// CreateBackupCmd represents the create backup command
type backupOptions struct {
Host string
User string
RepoDir string
PasswordFile string
Paths []string
}

var (
backupOpts backupOptions
pathsFlag string
)

var CreateBackupCmd = &cobra.Command{
Use: "backup",
Short: "Create a new restic backup",
Long: `This command initializes a restic repository if not already initialized,
then creates a backup of specified directories.`,
RunE: eos.Wrap(func(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []string) error {

backupOpts.Host, _ = interaction.PromptIfMissing(rc.Ctx, cmd, "host", "Enter backup host", false)
backupOpts.User, _ = interaction.PromptIfMissing(rc.Ctx, cmd, "user", "Enter backup user", false)
backupOpts.RepoDir, _ = interaction.PromptIfMissing(rc.Ctx, cmd, "repo-dir", "Enter remote restic repo directory", false)
backupOpts.PasswordFile, _ = interaction.PromptIfMissing(rc.Ctx, cmd, "password-file", "Enter restic password file", false)
pathsFlag, _ = interaction.PromptIfMissing(rc.Ctx, cmd, "paths", "Enter paths to backup (comma separated)", false)
if pathsFlag != "" {
for _, p := range strings.Split(pathsFlag, ",") {
p = strings.TrimSpace(p)
if p != "" {
backupOpts.Paths = append(backupOpts.Paths, p)
}
}
}

// Step 1: Ensure Restic is Installed
otelzap.Ctx(rc.Ctx).Info("Ensuring Restic is installed")
if err := ensureResticInstalled(rc); err != nil {
Expand Down Expand Up @@ -87,25 +117,25 @@ func ensureResticInstalled(rc *eos_io.RuntimeContext) error {

// generateSSHKeys generates SSH keys for accessing the backup server
func generateSSHKeys() error {
// You may want to add checks to avoid overwriting existing keys.
cmd := exec.Command("ssh-keygen", "-q", "-N", "", "-f", "/home/eos_user/.ssh/id_rsa")
keyPath := filepath.Join("/home", backupOpts.User, ".ssh", "id_rsa")
cmd := exec.Command("ssh-keygen", "-q", "-N", "", "-f", keyPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

// copySSHKeys copies the generated SSH keys to the backup server
func copySSHKeys() error {
// Use standard "user@host" syntax; adjust if needed.
cmd := exec.Command("ssh-copy-id", "eos_user@backup")
target := fmt.Sprintf("%s@%s", backupOpts.User, backupOpts.Host)
cmd := exec.Command("ssh-copy-id", target)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

// initializeResticRepo initializes the Restic repository
func initializeResticRepo(rc *eos_io.RuntimeContext) error {
repoPath := fmt.Sprintf("sftp:eos_user@backup:/srv/restic-repos/%s", hostname(rc))
repoPath := buildRepoPath(rc)
cmd := exec.Command("restic", "-r", repoPath, "init")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand All @@ -114,16 +144,12 @@ func initializeResticRepo(rc *eos_io.RuntimeContext) error {

// performResticBackup performs the Restic backup
func performResticBackup(rc *eos_io.RuntimeContext) error {
repoPath := fmt.Sprintf("sftp:eos_user@backup:/srv/restic-repos/%s", hostname(rc))
password := getResticPassword(rc)

cmd := exec.Command(
repoPath := buildRepoPath(rc)
password := getResticPassword(rc, backupOpts.PasswordFile)

"restic", "-r", repoPath,
"--password-file=/home/eos_user/.restic-password",
"--verbose", "backup",
"/home", "/var", "/etc", "/srv", "/usr", "/opt",
)
args := []string{"-r", repoPath, "--password-file=" + backupOpts.PasswordFile, "--verbose", "backup"}
args = append(args, backupOpts.Paths...)
cmd := exec.Command("restic", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), "RESTIC_PASSWORD="+password)
Expand All @@ -132,17 +158,16 @@ func performResticBackup(rc *eos_io.RuntimeContext) error {
}

// getResticPassword retrieves and stores the Restic repository password
func getResticPassword(rc *eos_io.RuntimeContext) string {
func getResticPassword(rc *eos_io.RuntimeContext, file string) string {

var password string
fmt.Print("Enter your Restic repository password: ")
_, err := fmt.Scanln(&password)
var err error
password, err = interaction.PromptSecret(rc.Ctx, "Enter your Restic repository password")
if err != nil {
otelzap.Ctx(rc.Ctx).Error("Failed to retrieve password", zap.Error(err))
os.Exit(1)
}

file := "/home/eos_user/.restic-password" // Updated path
err = os.WriteFile(file, []byte(password), 0600)
if err != nil {
otelzap.Ctx(rc.Ctx).Error("Failed to write password file", zap.Error(err))
Expand All @@ -162,7 +187,17 @@ func hostname(rc *eos_io.RuntimeContext) string {
return name
}

func buildRepoPath(rc *eos_io.RuntimeContext) string {
hostName := hostname(rc)
return fmt.Sprintf("sftp:%s@%s:%s/%s", backupOpts.User, backupOpts.Host, backupOpts.RepoDir, hostName)
}

func init() {
// Register this backup command under the create command
CreateCmd.AddCommand(CreateBackupCmd)
CreateBackupCmd.Flags().StringVar(&backupOpts.Host, "host", "", "Backup server host")
CreateBackupCmd.Flags().StringVar(&backupOpts.User, "user", "", "SSH user for backup host")
CreateBackupCmd.Flags().StringVar(&backupOpts.RepoDir, "repo-dir", "", "Remote restic repository directory")
CreateBackupCmd.Flags().StringVar(&backupOpts.PasswordFile, "password-file", "", "Path to restic password file")
CreateBackupCmd.Flags().StringVar(&pathsFlag, "paths", "", "Comma-separated list of paths to backup")
}
Loading