syncova-backup/packages/reports/export.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

83 lines
2.4 KiB
Go

package reports
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
)
// WriteJSON schreibt einen Bericht als JSON.
//
// Das Modell wird unveraendert ausgegeben. Eine eigene Darstellung fuer die
// Ausgabe waere ein zweiter Ort, an dem eine Kennzahl ihr Feld „gemessen"
// verlieren koennte.
func WriteJSON(outputWriter io.Writer, report *Report) error {
jsonEncoder := json.NewEncoder(outputWriter)
jsonEncoder.SetIndent("", " ")
return jsonEncoder.Encode(report)
}
// Write gibt einen Bericht im gewaehlten Format aus.
func Write(outputWriter io.Writer, report *Report, outputFormat Format) error {
switch outputFormat {
case FormatCSV:
return WriteCSV(outputWriter, report)
case FormatPDF:
return WritePDF(outputWriter, report)
case FormatJSON:
return WriteJSON(outputWriter, report)
default:
return fmt.Errorf("das format %q ist unbekannt", outputFormat)
}
}
// FileName bildet den Dateinamen eines Berichts.
//
// Enthalten sind Art und Erzeugungszeitpunkt. Ohne den Zeitpunkt liegen nach
// drei Abrufen drei Dateien „bericht.pdf (2)" im Download-Ordner, und keine
// laesst sich mehr zuordnen.
func FileName(report *Report, outputFormat Format) string {
return fmt.Sprintf("syncova-%s-%s.%s", report.Type,
report.GeneratedAt.UTC().Format("20060102-150405"), outputFormat.FileExtension())
}
// SanitizeFileName entfernt Zeichen, die in einem Dateinamen stoeren.
//
// Der Name entsteht zwar aus festen Bestandteilen, geht aber in einen
// Content-Disposition-Kopf ein. Ein Anfuehrungszeichen oder ein Zeilenumbruch
// darin liesse sich zum Einschleusen weiterer Kopffelder nutzen.
func SanitizeFileName(fileName string) string {
var sanitizedName strings.Builder
for _, nameRune := range fileName {
switch {
case nameRune >= 'a' && nameRune <= 'z',
nameRune >= 'A' && nameRune <= 'Z',
nameRune >= '0' && nameRune <= '9',
nameRune == '-', nameRune == '_', nameRune == '.':
sanitizedName.WriteRune(nameRune)
default:
sanitizedName.WriteRune('-')
}
}
return sanitizedName.String()
}
// newReport legt einen Bericht mit den Kopfangaben einer Definition an.
func newReport(definition Definition, periodFrom, periodTo time.Time, generatedBy string) *Report {
return &Report{
Type: definition.Type,
Title: definition.Title,
Description: definition.Description,
PeriodFrom: periodFrom,
PeriodTo: periodTo,
GeneratedAt: time.Now().UTC(),
GeneratedBy: generatedBy,
Sections: make([]Section, 0, 4),
}
}