Files
router-backup/src/main.go
2025-07-06 22:44:35 +02:00

147 lines
4.0 KiB
Go

// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package main
import (
"fmt"
"log"
"os"
"github.com/spf13/cobra"
)
const Version = "1.2.0"
// Config and SSH types are now in separate packages
// SSH connection methods are now in ssh.go
// YAML processing is now handled by the config package
// SSH helper functions are now in ssh.go
func main() {
var yamlFiles []string
var password string
var keyFile string
var port int
var outputDir string
var hostFilter []string
var rootCmd = &cobra.Command{
Use: "ipng-router-backup",
Short: "SSH Router Backup Tool",
Long: "Connects to routers via SSH and runs commands, saving output to local files.",
Version: Version,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("IPng Networks Router Backup v%s\n", Version)
// Load configuration
cfg, err := ConfigRead(yamlFiles)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
// Check authentication setup
if password == "" && keyFile == "" {
if os.Getenv("SSH_AUTH_SOCK") != "" {
fmt.Println("Using SSH agent for authentication")
} else {
keyFile = findDefaultSSHKey()
if keyFile == "" {
log.Fatal("No SSH key found and no password provided")
}
}
}
// Process devices
if len(cfg.Devices) == 0 {
log.Fatal("No devices found in config file")
}
// Filter devices if --host flags are provided
devicesToProcess := cfg.Devices
if len(hostFilter) > 0 {
devicesToProcess = make(map[string]Device)
for _, hostname := range hostFilter {
if deviceConfig, exists := cfg.Devices[hostname]; exists {
devicesToProcess[hostname] = deviceConfig
} else {
fmt.Printf("Warning: Host '%s' not found in config file\n", hostname)
}
}
}
successCount := 0
totalCount := len(devicesToProcess)
for hostname, deviceConfig := range devicesToProcess {
fmt.Printf("\nProcessing device: %s (type: %s)\n", hostname, deviceConfig.Type)
user := deviceConfig.User
commands := deviceConfig.Commands
deviceType := deviceConfig.Type
// If device has a type, get commands from types section
if deviceType != "" {
if typeConfig, exists := cfg.Types[deviceType]; exists {
commands = typeConfig.Commands
}
}
if user == "" {
fmt.Printf("No user specified for %s, skipping\n", hostname)
continue
}
if len(commands) == 0 {
fmt.Printf("No commands specified for %s, skipping\n", hostname)
continue
}
// Create backup instance
backup := NewRouterBackup(hostname, deviceConfig.Address, user, password, keyFile, port)
// Connect and backup
if err := backup.Connect(); err != nil {
fmt.Printf("Failed to connect to %s: %v\n", hostname, err)
continue
}
err = backup.BackupCommands(commands, outputDir)
backup.Disconnect()
if err != nil {
fmt.Printf("Backup failed for %s: %v\n", hostname, err)
} else {
fmt.Printf("Backup completed for %s\n", hostname)
successCount++
}
}
fmt.Printf("\nOverall summary: %d/%d devices processed successfully\n", successCount, totalCount)
// Set exit code based on results
if successCount == 0 {
os.Exit(11) // All devices failed
} else if successCount < totalCount {
os.Exit(10) // Some devices failed
}
// Exit code 0 (success) when all devices succeeded
},
}
rootCmd.Flags().StringSliceVar(&yamlFiles, "yaml", []string{}, "YAML configuration file paths (required, can be repeated)")
rootCmd.Flags().StringVar(&password, "password", "", "SSH password")
rootCmd.Flags().StringVar(&keyFile, "key-file", "", "SSH private key file path")
rootCmd.Flags().IntVar(&port, "port", 22, "SSH port")
rootCmd.Flags().StringVar(&outputDir, "output-dir", "/tmp", "Output directory for command output files")
rootCmd.Flags().StringSliceVar(&hostFilter, "host", []string{}, "Specific host(s) to process (can be repeated, processes all if not specified)")
rootCmd.MarkFlagRequired("yaml")
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}