syncova-backup/packages/providers/proxmox/storage.go
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

75 lines
2.9 KiB
Go

package proxmox
import (
"context"
"fmt"
"net/url"
"strings"
)
// StoragePath liefert das Verzeichnis eines Proxmox-Speichers auf einem Knoten.
//
// Der SSH-Transport braucht ihn, um aus einer Volumenkennung
// ("local:backup/vzdump-...") einen Dateipfad zu bilden. Die Kennung allein
// genuegt nicht: Wo ein Speicher liegt, weiss nur der Knoten.
//
// Der Provider erfuellt damit die Schnittstelle StoragePathResolver.
func (provider *Provider) StoragePath(resolveContext context.Context, nodeName string,
storageIdentifier string) (string, error) {
statusPath := fmt.Sprintf("/nodes/%s/storage/%s/status",
url.PathEscape(nodeName), url.PathEscape(storageIdentifier))
var storageStatus struct {
// Path ist das Verzeichnis des Speichers auf dem Knoten.
Path string `json:"path"`
// Type benennt die Art des Speichers.
Type string `json:"type"`
}
if statusError := provider.client.get(resolveContext, statusPath, &storageStatus); statusError != nil {
return "", fmt.Errorf("der speicher %q auf knoten %q war nicht abrufbar: %w",
storageIdentifier, nodeName, statusError)
}
if strings.TrimSpace(storageStatus.Path) == "" {
// Ein Speicher ohne Pfad ist kein Verzeichnisspeicher — etwa ZFS, LVM
// oder Ceph. Dort liegt kein Archiv als Datei, und ein geratener Pfad
// fuehrte ins Leere. Der Fall wird benannt, statt einen Pfad zu
// erfinden.
return "", fmt.Errorf("der speicher %q auf knoten %q ist vom typ %q und stellt kein "+
"verzeichnis bereit. sicherungsarchive lassen sich von dort nicht als datei lesen; "+
"waehlen sie einen verzeichnisbasierten speicher (dir, nfs, cifs) fuer die "+
"proxmox-sicherungen", storageIdentifier, nodeName, storageStatus.Type)
}
return storageStatus.Path, nil
}
// RemoveArchive entfernt ein Sicherungsarchiv vom Proxmox-Speicher.
//
// **Der Aufraeumschritt ist keine Nebensaechlichkeit.** vzdump legt das Archiv
// auf dem Knoten ab; Syncova liest es und uebernimmt es ins eigene Repository.
// Bliebe es liegen, fuellte jede Sicherung den Proxmox-Speicher weiter — und
// zwar mit einer zweiten, unverwalteten Kopie derselben Daten, fuer die keine
// Aufbewahrungsregel gilt.
//
// Ein Fehler beim Aufraeumen ist kein Grund, die Sicherung als gescheitert zu
// werten: Die Daten liegen bereits im Repository. Er wird gemeldet, damit
// niemand den vollen Speicher fuer ein Raetsel haelt.
func (provider *Provider) RemoveArchive(removeContext context.Context, nodeName string,
volumeIdentifier string) error {
deletePath := fmt.Sprintf("/nodes/%s/storage/%s/content/%s",
url.PathEscape(nodeName),
url.PathEscape(provider.options.BackupStorageID),
url.PathEscape(volumeIdentifier))
var taskIdentifier TaskIdentifier
if deleteError := provider.client.delete(removeContext, deletePath, &taskIdentifier); deleteError != nil {
return fmt.Errorf("das sicherungsarchiv %q liess sich nicht entfernen: %w",
volumeIdentifier, deleteError)
}
return nil
}