package main import ( "flag" "fmt" "os" "os/exec" "strings" "sync" "time" "path/filepath" "sort" "regexp" "log" ) func main() { // Parse flags vmFlag := flag.Bool("vm", false, "") volumeFlag := flag.Bool("volume", false, "") sourceFlag := flag.String("source", "", "") threadsFlag := flag.Int("threads", 3, "") keepFlag := flag.Int("keep", 0, "") targetFlag := flag.String("target", "", "") excludeFlag := flag.String("exclude", "", "") flag.Usage = func() { fmt.Printf("Nutzung: %s [Optionen]\n", os.Args[0]) fmt.Println() fmt.Println("Optionen:") fmt.Printf(" %-35s %s\n", "--vm", "Sichert alle VMs/Container") fmt.Printf(" %-35s %s\n", "--volume", "Sichert alle Volumes") fmt.Println() fmt.Println("Optionale Parameter:") fmt.Printf(" %-35s %s\n", "--source [source]", "Sichere nur die angegebene Quelle (VM/Container/Volume) anstelle von allen.") fmt.Printf(" %-35s %s\n", "--threads [n]", "Starte n Backups gleichzeitig, wenn --source nicht angegeben ist. Standard: 3") fmt.Printf(" %-35s %s\n", "--keep [n]", "Behalte die letzten n Backups. Standardmäßig alle behalten.") fmt.Printf(" %-35s %s\n", "--target [path]", "Verzeichnis, in das die Backups gespeichert werden sollen.") fmt.Printf(" %-35s %s\n", "--exclude [vm1,vm2,vm3,ct1,ct2,...]", "Liste von VMs oder Containern, die vom Backup ausgeschlossen werden sollen.") fmt.Println() fmt.Println("Beispiele:") fmt.Printf(" %s --vm\n", os.Args[0]) fmt.Printf(" %s --volume\n", os.Args[0]) fmt.Printf(" %s --vm --source my-vm --target /path/to/target\n", os.Args[0]) fmt.Printf(" %s --vm --threads 3 --keep 2\n", os.Args[0]) fmt.Printf(" %s --volume --keep 1\n", os.Args[0]) fmt.Println() fmt.Println("Restore:") fmt.Println(" cat lxd.init.[TIMESTAMP].txt | lxd init --preseed") fmt.Println(" lxc import [CONTAINER-NAME].tar.gz") fmt.Println() fmt.Println("Hinweis:") fmt.Println(" Falls Backups fehlschlagen, stellen Sie sicher, dass Backups erlaubt sind, indem Sie folgendes ausführen:") fmt.Println(" lxc project set default restricted.backups=allow") fmt.Println(" lxc project set default restricted.snapshots=allow") fmt.Println(" lxc project set default restricted=false") fmt.Println() fmt.Printf("Entwickler: Torben M.\n") fmt.Printf("Version 2.3\n") } flag.Parse() // Falls targetFlag leer ist, auf aktuelles Verzeichnis setzen if *targetFlag == "" { var err error *targetFlag, err = os.Getwd() // Aktuelles Verzeichnis holen if err != nil { fmt.Printf("Fehler beim Abrufen des aktuellen Verzeichnisses: %v\n", err) os.Exit(1) } } // Verarbeite die excludeFlag und splitte nach Komma, falls nicht leer var excludeList []string if *excludeFlag != "" { excludeList = strings.Split(*excludeFlag, ",") // Exclude-Flag in Liste umwandeln fmt.Printf("Die folgenden VMs/Container werden ausgeschlossen: %v\n", excludeList) } // Verarbeitung der Parameter if !(*vmFlag || *volumeFlag) { fmt.Println("Ungültige Option. Bitte benutzen Sie --help für Hilfe.") os.Exit(1) } if *threadsFlag < 0 || *keepFlag < 0 { fmt.Println("Ungültige Option für --threads oder --keep. Bitte geben Sie eine positive Zahl an.") os.Exit(1) } // Ausführen der Backup-Funktionen if *vmFlag { backupVMs(*sourceFlag, *threadsFlag, *keepFlag, *targetFlag, excludeList) } else if *volumeFlag { backupVolume(*sourceFlag, *threadsFlag, *keepFlag, *targetFlag, excludeList) } // Aufrufen der Funktion zur Bereinigung der Konfigurationsdateien if *targetFlag != "" { cleanConfig(*targetFlag) } fmt.Println("Backup abgeschlossen.") } func backupVMs(source string, threads int, keep int, target string, excludeList []string) { if threads <= 0 { fmt.Println("Fehler: --threads muss eine positive Zahl sein.") os.Exit(1) } if target != "" { fmt.Printf("Erstelle Zielverzeichnis: %s\n", target) err := os.MkdirAll(target, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses %s: %v\n", target, err) os.Exit(1) } } else { fmt.Println("Mache die Backups im aktuellen Verzeichnis") } // Generiere Timestamp timestamp := time.Now().Format("02.01.2006-150405") fmt.Printf("Timestamp: %s\n", timestamp) // Ausführen von lxd init --dump und speichern der Ausgabe in einer Datei if target != "" { outputFile := fmt.Sprintf("%s/lxd.init.%s.txt", target, timestamp) cmd := exec.Command("lxd", "init", "--dump") out, err := os.Create(outputFile) if err != nil { fmt.Printf("Fehler beim Erstellen der Datei %s: %v\n", outputFile, err) os.Exit(1) } defer out.Close() cmd.Stdout = out cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { fmt.Printf("Fehler beim Ausführen des Befehls: %v\n", err) os.Exit(1) } fmt.Printf("lxd init --dump wurde erfolgreich ausgeführt und die Ausgabe wurde in %s gespeichert.\n", outputFile) } else { fmt.Println("Kein Zielverzeichnis angegeben. Kann lxd init --dump nicht ausführen.") } if source != "" { fmt.Printf("Sichere VMs/Container von Quelle: %s\n", source) // Zielverzeichnis für die Quelle erstellen sourceDir := fmt.Sprintf("%s/%s", target, source) err := os.MkdirAll(sourceDir, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses für Quelle %s: %v\n", source, err) os.Exit(1) } // Dateiname für den Export exportFileName := fmt.Sprintf("%s/vm.%s.%s.tar.gz", sourceDir, source, timestamp) // Befehl zum Exportieren der VM/Container cmd := exec.Command("lxc", "export", source, exportFileName, "--optimized-storage", "--instance-only") err = cmd.Run() if err != nil { fmt.Printf("Fehler beim Exportieren von %s: %v\n", source, err) os.Exit(1) } fmt.Printf("VM/Container %s wurde erfolgreich exportiert nach %s.\n", source, exportFileName) if keep > 0 { fmt.Printf("Behalte die letzten %d Backups\n", keep) backupFiles := fmt.Sprintf("%s/vm.%s.*.tar.gz", sourceDir, source) deleteOldBackups(backupFiles, keep) } } else { fmt.Println("Sichere alle VMs/Container") // Befehl für lxc list ausführen, um alle Container zu erhalten cmd := exec.Command("lxc", "list", "-f", "csv", "-c", "n") output, err := cmd.Output() if err != nil { fmt.Printf("Fehler beim Ausführen von lxc list: %v\n", err) os.Exit(1) } // Die Namen der Container aus der Ausgabe extrahieren containerNames := parseContainerListOutput(string(output)) filteredContainerNames := []string{} excludeSet := make(map[string]struct{}) // Erstelle ein Set für die excludeList zur schnellen Suche for _, exclude := range excludeList { excludeSet[exclude] = struct{}{} } // Behalte nur die Container, die nicht in der excludeList sind for _, name := range containerNames { if _, found := excludeSet[name]; !found { filteredContainerNames = append(filteredContainerNames, name) } } // Die Variable containerNames enthält jetzt die gefilterte Liste containerNames = filteredContainerNames // Kanal für die gleichzeitige Ausführung der Befehle semaphore := make(chan struct{}, threads) var wg sync.WaitGroup // Backup für jeden Container durchführen for _, container := range containerNames { wg.Add(1) semaphore <- struct{}{} // Füge ein Element zum Semaphore hinzu (erhöht den Zähler) go func(container string) { defer func() { <-semaphore // Entferne ein Element aus dem Semaphore (verringert den Zähler) wg.Done() }() // Zielverzeichnis für jede Quelle erstellen sourceDir := fmt.Sprintf("%s/%s", target, container) err := os.MkdirAll(sourceDir, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses für Quelle %s: %v\n", container, err) return } // Dateiname für den Export exportFileName := fmt.Sprintf("%s/vm.%s.%s.tar.gz", sourceDir, container, timestamp) // Befehl zum Exportieren der VM/Container cmd := exec.Command("lxc", "export", container, exportFileName, "--optimized-storage", "--instance-only") err = cmd.Run() if err != nil { fmt.Printf("Fehler beim Exportieren von %s: %v\n", container, err) return } fmt.Printf("VM/Container %s wurde erfolgreich exportiert nach %s.\n", container, exportFileName) if keep > 0 { fmt.Printf("Behalte die letzten %d Backups\n", keep) backupFiles := fmt.Sprintf("%s/vm.%s.*.tar.gz", sourceDir, container) deleteOldBackups(backupFiles, keep) } }(container) } wg.Wait() // Warten, bis alle Go-Routinen beendet sind close(semaphore) // Kanal schließen, wenn alle Go-Routinen fertig sind } } func backupVolume(source string, threads int, keep int, target string, excludeList []string) { if threads <= 0 { fmt.Println("Fehler: --threads muss eine positive Zahl sein.") os.Exit(1) } if target != "" { fmt.Printf("Erstelle Zielverzeichnis: %s\n", target) err := os.MkdirAll(target, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses %s: %v\n", target, err) os.Exit(1) } } else { fmt.Println("Mache die Backups im aktuellen Verzeichnis") } // Generiere Timestamp timestamp := time.Now().Format("02.01.2006-150405") fmt.Printf("Timestamp: %s\n", timestamp) // Ausführen von lxd init --dump und speichern der Ausgabe in einer Datei if target != "" { outputFile := fmt.Sprintf("%s/lxd.init.%s.txt", target, timestamp) cmd := exec.Command("lxd", "init", "--dump") out, err := os.Create(outputFile) if err != nil { fmt.Printf("Fehler beim Erstellen der Datei %s: %v\n", outputFile, err) os.Exit(1) } defer out.Close() cmd.Stdout = out cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { fmt.Printf("Fehler beim Ausführen des Befehls: %v\n", err) os.Exit(1) } fmt.Printf("lxd init --dump wurde erfolgreich ausgeführt und die Ausgabe wurde in %s gespeichert.\n", outputFile) } else { fmt.Println("Kein Zielverzeichnis angegeben. Kann lxd init --dump nicht ausführen.") } // Funktion zum Parsen der Volume-Liste parseVolumeListOutput := func(output string) [][3]string { lines := strings.Split(output, "\n") var volumes [][3]string for _, line := range lines { if strings.Contains(line, ",custom,") { fields := strings.Split(line, ",") if len(fields) == 4 && fields[3] != "iso" { volumes = append(volumes, [3]string{fields[0], fields[1], fields[2]}) } } } return volumes } if source != "" { fmt.Printf("Sichere Volume: %s\n", source) // Zielverzeichnis für die Quelle erstellen sourceDir := fmt.Sprintf("%s/%s", target, source) err := os.MkdirAll(sourceDir, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses für Quelle %s: %v\n", source, err) os.Exit(1) } // Volumes auflisten und filtern cmd := exec.Command("lxc", "storage", "volume", "list", "-f", "csv", "-c", "ptn") output, err := cmd.Output() if err != nil { fmt.Printf("Fehler beim Ausführen von lxc storage volume list: %v\n", err) os.Exit(1) } volumes := parseVolumeListOutput(string(output)) var found bool for _, volume := range volumes { if volume[2] == source && volume[1] == "custom" { storagePool := volume[0] volumeName := volume[2] exportFileName := fmt.Sprintf("%s/volume.%s.%s.tar.gz", sourceDir, volumeName, timestamp) // Befehl zum Exportieren des Volumes cmd := exec.Command("lxc", "storage", "volume", "export", storagePool, volumeName, exportFileName, "--optimized-storage") err := cmd.Run() if err != nil { fmt.Printf("Fehler beim Exportieren von %s: %v\n", volumeName, err) os.Exit(1) } fmt.Printf("Volume %s wurde erfolgreich exportiert nach %s.\n", volumeName, exportFileName) if keep > 0 { fmt.Printf("Behalte die letzten %d Backups\n", keep) backupFiles := fmt.Sprintf("%s/volume.%s.*.tar.gz", sourceDir, source) deleteOldBackups(backupFiles, keep) } found = true break } } if !found { fmt.Printf("Volume %s nicht gefunden oder ist nicht vom Typ 'custom'.\n", source) os.Exit(1) } } else { fmt.Println("Sichere alle Volumes vom Typ 'custom'") // Volumes auflisten cmd := exec.Command("lxc", "storage", "volume", "list", "-f", "csv", "-c", "ptnc") output, err := cmd.Output() if err != nil { fmt.Printf("Fehler beim Ausführen von lxc storage volume list: %v\n", err) os.Exit(1) } volumes := parseVolumeListOutput(string(output)) // Erstelle ein Set für die excludeList zur schnellen Suche excludeSet := make(map[string]struct{}) for _, exclude := range excludeList { excludeSet[exclude] = struct{}{} } // Filtere die Volumes, um die Einträge aus excludeList zu entfernen filteredVolumes := volumes[:0] // Setze filteredVolumes auf ein leeres Slice mit der Kapazität von volumes for _, volume := range volumes { if _, found := excludeSet[volume[0]]; !found { // Hier den richtigen Index verwenden filteredVolumes = append(filteredVolumes, volume) } } // Die Variable volumes enthält jetzt die gefilterten Volumes volumes = filteredVolumes // Kanal für die gleichzeitige Ausführung der Befehle semaphore := make(chan struct{}, threads) var wg sync.WaitGroup // Backup für jedes Volume durchführen for _, volume := range volumes { wg.Add(1) semaphore <- struct{}{} // Füge ein Element zum Semaphore hinzu (erhöht den Zähler) go func(volume [3]string) { defer func() { <-semaphore // Entferne ein Element aus dem Semaphore (verringert den Zähler) wg.Done() }() storagePool := volume[0] volumeName := volume[2] // Zielverzeichnis für jedes Volume erstellen sourceDir := fmt.Sprintf("%s/%s", target, volumeName) err := os.MkdirAll(sourceDir, 0755) if err != nil { fmt.Printf("Fehler beim Erstellen des Zielverzeichnisses für Volume %s: %v\n", volumeName, err) return } // Dateiname für den Export exportFileName := fmt.Sprintf("%s/volume.%s.%s.tar.gz", sourceDir, volumeName, timestamp) // Befehl zum Exportieren des Volumes cmd := exec.Command("lxc", "storage", "volume", "export", storagePool, volumeName, exportFileName, "--optimized-storage") err = cmd.Run() if err != nil { fmt.Printf("Fehler beim Exportieren von %s: %v\n", volumeName, err) return } fmt.Printf("Volume %s wurde erfolgreich exportiert nach %s.\n", volumeName, exportFileName) if keep > 0 { fmt.Printf("Behalte die letzten %d Backups\n", keep) backupFiles := fmt.Sprintf("%s/volume.%s.*.tar.gz", sourceDir, volumeName) deleteOldBackups(backupFiles, keep) } }(volume) } wg.Wait() // Warten, bis alle Go-Routinen beendet sind close(semaphore) // Kanal schließen, wenn alle Go-Routinen fertig sind } if keep > 0 { fmt.Printf("Behalte die letzten %d Backups\n", keep) } } func deleteOldBackups(backupPattern string, keep int) { // Finden aller Dateien, die dem Muster entsprechen files, err := filepath.Glob(backupPattern) if err != nil { fmt.Printf("Fehler beim Suchen von Backups: %v\n", err) return } // Sortiere die Dateien nach dem Änderungszeitpunkt (älteste zuerst) sort.Slice(files, func(i, j int) bool { infoI, err := os.Stat(files[i]) if err != nil { fmt.Printf("Fehler beim Lesen der Datei-Info: %v\n", err) return false } infoJ, err := os.Stat(files[j]) if err != nil { fmt.Printf("Fehler beim Lesen der Datei-Info: %v\n", err) return true } return infoI.ModTime().Before(infoJ.ModTime()) }) // Wenn mehr Dateien als die Anzahl zu behaltender Backups vorhanden sind, lösche die ältesten if len(files) > keep { for _, file := range files[:len(files)-keep] { err := os.Remove(file) if err != nil { fmt.Printf("Fehler beim Löschen der Datei %s: %v\n", file, err) } else { fmt.Printf("Altes Backup %s wurde gelöscht.\n", file) } } } } func parseContainerListOutput(output string) []string { lines := strings.Split(output, "\n") var containers []string for _, line := range lines { fields := strings.Split(line, ",") if len(fields) > 0 && fields[0] != "" { containers = append(containers, fields[0]) } } return containers } func cleanConfig(target string) { fmt.Println("Bereinige alte Konfigurationsdateien ohne zugehörige Backup-Datei...") files, err := os.ReadDir(target) if err != nil { log.Fatalf("Fehler beim Lesen des Verzeichnisses %s: %v", target, err) } // Regular Expression zum Extrahieren des Timestamps aus dem Dateinamen // Annahme: Dateiname hat die Form lxd.init..txt re := regexp.MustCompile(`lxd\.init\.(\d{2}\.\d{2}\.\d{4}-\d{6})\.txt`) for _, file := range files { if !file.IsDir() && strings.HasPrefix(file.Name(), "lxd.init.") && strings.HasSuffix(file.Name(), ".txt") { // Extrahiere den Timestamp aus dem Dateinamen matches := re.FindStringSubmatch(file.Name()) if len(matches) != 2 { fmt.Printf("Fehler beim Extrahieren des Timestamps aus der Datei %s\n", file.Name()) continue } timestamp := matches[1] // Suche nach Backup-Dateien mit dem entsprechenden Timestamp pattern := fmt.Sprintf("*%s*", timestamp) backups, err := filepath.Glob(filepath.Join(target, "*", pattern)) if err != nil { fmt.Printf("Fehler beim Suchen nach Backup-Dateien für %s: %v\n", timestamp, err) continue } // Wenn keine Backup-Dateien gefunden wurden, lösche die Konfigurationsdatei if len(backups) == 0 { configFile := filepath.Join(target, file.Name()) fmt.Printf("Konfigurationsdatei %s wurde gelöscht, da keine zugehörige Backup-Datei gefunden wurde.\n", configFile) if err := os.Remove(configFile); err != nil { fmt.Printf("Fehler beim Löschen der Konfigurationsdatei %s: %v\n", configFile, err) } } } } fmt.Println("Bereinigung abgeschlossen.") }