From 20b0919676e8b5463a86c9efdb60535ef00eb1cf Mon Sep 17 00:00:00 2001 From: Jerrit Fritzsche Date: Tue, 18 Aug 2026 15:52:37 +0200 Subject: [PATCH] Datei- und Ordnerwiederherstellung, Ordnerbaum, Geist Mono im Paket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Warum keine Wiederherstellung funktionierte.** Der Dienst laeuft mit `ProtectSystem=strict` und `ReadWritePaths` nur auf Repository und Sicherungsordner — alles andere ist fuer ihn schreibgeschuetzt. Jedes Ziel ausserhalb endete mit "mkdir: permission denied", und zwar **nach** der Vorabpruefung. `/tmp` scheiterte anders: Mit `PrivateTmp=yes` hat der Dienst ein eigenes /tmp, und was dort landet, sieht man von aussen gar nicht. `setup.sh` legt jetzt `/srv/syncova-restore` an und traegt es in `ReadWritePaths` ein; `--wiederherstellungsziel` ergaenzt weitere. Der Ort liegt unter /srv und nicht unter /var/lib — Letzteres steht auf der Sperrliste des Zielschutzes. Beide Regeln zugleich zu erfuellen laesst genau /srv uebrig; das ist mir erst aufgefallen, nachdem ich die Flaeche zunaechst falsch gelegt hatte und der eigene Zielschutz sie ablehnte. **Zwei neue Endpunkte** (Vertrag entsprechend erweitert): - `GET /filesystem/browse` — Verzeichnisse mit der Angabe, ob der **Dienst** dort schreiben darf. **Gemessen** durch eine Probedatei, nicht aus den Rechtebits geraten: Unter ProtectSystem=strict sagen die Bits nichts ueber das aus, was der Namensraum zulaesst. Gesperrte Orte werden gezeigt, nicht versteckt — sonst bliebe offen, warum ein Pfad fehlt. - `GET /backups/{id}/contents` — das Manifest als Ebene eines Baums. Der Baum entsteht aus den **Pfaden**, nicht aus Verzeichniseintraegen: Ein Manifest kann eine Datei enthalten, deren Elternverzeichnis nicht als eigener Eintrag vorliegt, und wer nur `directory`-Eintraege auflistet, verliert ganze Teilbaeume. Durch Mutation bestaetigt. **Auswahl statt Textfeld.** Der Assistent hat jetzt einen Ordnerbaum fuer das Ziel und einen Browser fuer den Backup-Inhalt. Ordner **und** einzelne Dateien lassen sich waehlen; beides geht als `path_prefix` in die Anfrage, weil der Server auf Gleichheit oder Praefix mit Verzeichnisgrenze vergleicht. Bewusste Grenze: eine Auswahl je Lauf — eine Liste kennt die API nicht, und mehrere Laeufe vorzutaeuschen ergaebe mehrere Ausgaenge, die niemand mehr erklaeren kann. **Integritaetslauf.** "can't access property toLocaleString, chunks_checked is undefined" — die Ergebnisse liegen unter `details`, und die Felder heissen `missing_chunks`/`corrupted_chunks`, nicht umgekehrt. Betrifft alle vier Pruefendpunkte; sie tragen dieselbe Huelle. Derselbe Fehler wie bei /retention-policies: die Antwortform angenommen statt geprueft. **Geist Mono liegt jetzt im Paket** (drei Schnitte, 128 KB, OFL-Lizenz dabei). Ausgeliefert vom eigenen Ursprung — das verlangt die CSP, und ein Backup-Server, dessen Oberflaeche von einem CDN abhaengt, waere auch ohne CSP falsch. `font-display: swap`, damit der Text sofort steht. Nachgewiesen gegen Debian 12: Vollwiederherstellung (5 Dateien), nur ein Ordner (2 Dateien), nur eine Datei (1 Datei) — alle drei bitgenau. Der Server nennt /srv/syncova-restore als beschreibbar und /etc, /usr, /var als gesperrt. Co-Authored-By: Claude Opus 5 --- apps/api/internal/httpapi/browse_handler.go | 422 ++++++++++++++++++ .../internal/httpapi/browse_handler_test.go | 90 ++++ apps/api/internal/httpapi/contract_routes.txt | 2 + apps/api/internal/httpapi/router.go | 13 + .../src/assets/fonts/GeistMono-Medium.woff2 | Bin 0 -> 43440 bytes .../src/assets/fonts/GeistMono-Regular.woff2 | Bin 0 -> 42420 bytes .../src/assets/fonts/GeistMono-SemiBold.woff2 | Bin 0 -> 43660 bytes apps/web/src/assets/fonts/LICENSE.txt | 92 ++++ .../repositories/RepositoriesPage.tsx | 70 +-- .../features/repositories/repositoriesApi.ts | 80 +++- .../features/restores/BackupContentPicker.tsx | 189 ++++++++ .../src/features/restores/DirectoryPicker.tsx | 220 +++++++++ .../src/features/restores/RestoreWizard.tsx | 48 +- apps/web/src/features/restores/browseApi.ts | 82 ++++ apps/web/src/styles/theme.css | 48 +- scripts/setup.sh | 33 +- untitled.md | 0 17 files changed, 1299 insertions(+), 90 deletions(-) create mode 100644 apps/api/internal/httpapi/browse_handler.go create mode 100644 apps/api/internal/httpapi/browse_handler_test.go create mode 100644 apps/web/src/assets/fonts/GeistMono-Medium.woff2 create mode 100644 apps/web/src/assets/fonts/GeistMono-Regular.woff2 create mode 100644 apps/web/src/assets/fonts/GeistMono-SemiBold.woff2 create mode 100644 apps/web/src/assets/fonts/LICENSE.txt create mode 100644 apps/web/src/features/restores/BackupContentPicker.tsx create mode 100644 apps/web/src/features/restores/DirectoryPicker.tsx create mode 100644 apps/web/src/features/restores/browseApi.ts create mode 100644 untitled.md diff --git a/apps/api/internal/httpapi/browse_handler.go b/apps/api/internal/httpapi/browse_handler.go new file mode 100644 index 0000000..70281c0 --- /dev/null +++ b/apps/api/internal/httpapi/browse_handler.go @@ -0,0 +1,422 @@ +package httpapi + +import ( + "errors" + "net/http" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/google/uuid" + "github.com/syncova/syncova/packages/platform/logging" + "github.com/syncova/syncova/packages/recovery" + "github.com/syncova/syncova/packages/repository" +) + +// browseHandler bedient die beiden Blätterendpunkte. +// +// Sie sind für eine Wiederherstellung gebaut und lösen zwei Probleme, die sich +// mit einem Textfeld nicht lösen lassen: +// +// 1. **Wohin darf zurückgeschrieben werden?** Der Dienst läuft mit +// `ProtectSystem=strict`; außerhalb weniger Pfade ist das Dateisystem für +// ihn schreibgeschützt. Ein Betreiber tippt „/opt/test", bekommt +// „permission denied" und hat keine Möglichkeit zu erkennen, welcher Ort +// überhaupt in Frage kommt. `GET /filesystem/browse` beantwortet genau das +// — es meldet je Verzeichnis, ob der **Dienst** dort schreiben kann, +// geprüft durch einen tatsächlichen Schreibversuch. +// +// 2. **Was steckt in dem Backup?** Ohne Inhaltsverzeichnis lässt sich weder +// eine einzelne Datei noch ein Unterordner gezielt zurückholen. `GET +// /backups/{id}/contents` liefert das Manifest als Ebene eines Baums. +// +// Beide lesen nur. +type browseHandler struct { + restoreHandlerReference *restoreHandler + targetGuard *recovery.TargetGuard +} + +// filesystemEntry ist ein Eintrag des Dateisystems. +type filesystemEntry struct { + // Name ist der letzte Pfadbestandteil. + Name string `json:"name"` + // Path ist der vollständige absolute Pfad. + Path string `json:"path"` + // IsDirectory unterscheidet Verzeichnis von Datei. + IsDirectory bool `json:"is_directory"` + // SizeBytes ist die Größe bei Dateien. + SizeBytes int64 `json:"size_bytes,omitempty"` + // IsWritable meldet, ob der Dienst hier anlegen darf. + // + // Gemessen durch einen Schreibversuch, nicht aus den Rechtebits geraten: + // Unter `ProtectSystem=strict` sagen die Bits nichts über das aus, was der + // Namensraum zulässt. + IsWritable bool `json:"is_writable"` + // ForbiddenReason nennt den Grund, wenn der Zielschutz den Ort ausschließt. + ForbiddenReason string `json:"forbidden_reason,omitempty"` +} + +// browseFilesystemResponse ist die Antwort auf das Blättern im Dateisystem. +type browseFilesystemResponse struct { + // Path ist das aufgelistete Verzeichnis. + Path string `json:"path"` + // ParentPath ist das übergeordnete Verzeichnis; leer bei der Wurzel. + ParentPath string `json:"parent_path,omitempty"` + // Entries sind die enthaltenen Verzeichnisse. + Entries []filesystemEntry `json:"entries"` + // SuggestedPaths sind Orte, an denen der Dienst nachweislich schreiben darf. + // + // Sie stehen in der Antwort, damit die Oberfläche einen brauchbaren + // Startpunkt anbieten kann, statt den Betreiber suchen zu lassen. + SuggestedPaths []string `json:"suggested_paths,omitempty"` +} + +// backupContentEntry ist ein Eintrag im Inhaltsverzeichnis eines Backups. +type backupContentEntry struct { + Name string `json:"name"` + // Path ist der Pfad im Manifest — genau der Wert, den eine + // Wiederherstellung als `path_prefix` erwartet. + Path string `json:"path"` + IsDirectory bool `json:"is_directory"` + EntryType string `json:"entry_type"` + SizeBytes int64 `json:"size_bytes,omitempty"` + ModifiedAt string `json:"modified_at,omitempty"` + Mode string `json:"mode,omitempty"` + // ChildCount ist die Zahl der Einträge unterhalb eines Verzeichnisses. + ChildCount int `json:"child_count,omitempty"` + // TotalBytes ist die Datenmenge unterhalb eines Verzeichnisses. + TotalBytes int64 `json:"total_bytes,omitempty"` +} + +// browseBackupResponse ist die Antwort auf das Blättern im Backup. +type browseBackupResponse struct { + BackupID string `json:"backup_id"` + Path string `json:"path"` + ParentPath string `json:"parent_path,omitempty"` + // Entries sind die Einträge auf dieser Ebene. + Entries []backupContentEntry `json:"entries"` + // TotalEntryCount ist die Zahl aller Einträge im Backup. + TotalEntryCount int `json:"total_entry_count"` +} + +// handleBrowseFilesystem bedient GET /filesystem/browse. +func (handler *browseHandler) handleBrowseFilesystem(responseWriter http.ResponseWriter, request *http.Request) { + requestLogger := logging.WithContext(request.Context(), handler.restoreHandlerReference.logger) + + requestedPath := strings.TrimSpace(request.URL.Query().Get("path")) + if requestedPath == "" { + requestedPath = "/" + } + + if !filepath.IsAbs(requestedPath) { + WriteError(responseWriter, request, requestLogger, + NewValidationError("Der Pfad muss absolut sein.")) + + return + } + + // Symlinks werden aufgelöst, bevor gelesen wird: Sonst ließe sich über + // einen Verweis an jeder Prüfung vorbei in ein fremdes Verzeichnis sehen. + resolvedPath, resolveError := filepath.EvalSymlinks(filepath.Clean(requestedPath)) + if resolveError != nil { + resolvedPath = filepath.Clean(requestedPath) + } + + directoryEntries, readError := os.ReadDir(resolvedPath) + if readError != nil { + if errors.Is(readError, os.ErrNotExist) { + WriteError(responseWriter, request, requestLogger, + NewNotFoundError("Das Verzeichnis wurde nicht gefunden.")) + + return + } + + WriteError(responseWriter, request, requestLogger, + NewValidationError("Das Verzeichnis lässt sich nicht lesen: "+readError.Error())) + + return + } + + entries := make([]filesystemEntry, 0, len(directoryEntries)) + + for _, directoryEntry := range directoryEntries { + // Nur Verzeichnisse: Ein Wiederherstellungsziel ist immer ein + // Verzeichnis, und die Dateien daneben wären nur Rauschen. + if !directoryEntry.IsDir() { + continue + } + + // Versteckte Verzeichnisse bleiben draußen. Wer eines braucht, tippt + // den Pfad — die Liste soll den Normalfall zeigen. + if strings.HasPrefix(directoryEntry.Name(), ".") { + continue + } + + childPath := filepath.Join(resolvedPath, directoryEntry.Name()) + + entry := filesystemEntry{ + Name: directoryEntry.Name(), + Path: childPath, + IsDirectory: true, + IsWritable: directoryIsWritable(childPath), + } + + if handler.targetGuard != nil { + if guardError := handler.targetGuard.Validate(childPath); guardError != nil { + entry.ForbiddenReason = guardError.Error() + // Ein gesperrter Ort ist nie ein zulässiges Ziel, auch wenn das + // Dateisystem ihn zuließe. + entry.IsWritable = false + } + } + + entries = append(entries, entry) + } + + sort.Slice(entries, func(firstIndex, secondIndex int) bool { + return entries[firstIndex].Name < entries[secondIndex].Name + }) + + response := browseFilesystemResponse{ + Path: resolvedPath, + Entries: entries, + SuggestedPaths: writableSuggestions(handler.targetGuard), + } + + if resolvedPath != "/" { + response.ParentPath = filepath.Dir(resolvedPath) + } + + WriteSuccess(responseWriter, request, http.StatusOK, response) +} + +// handleBrowseBackupContents bedient GET /backups/{id}/contents. +func (handler *browseHandler) handleBrowseBackupContents(responseWriter http.ResponseWriter, request *http.Request) { + requestLogger := logging.WithContext(request.Context(), handler.restoreHandlerReference.logger) + + backupIdentifier, parseError := uuid.Parse(request.PathValue("id")) + if parseError != nil { + WriteError(responseWriter, request, requestLogger, + NewValidationError("Die Backup-Kennung ist keine gültige UUID.")) + + return + } + + repositoryPath, backupIDInRepository, resolveError := handler.restoreHandlerReference.resolveBackup( + request.Context(), backupIdentifier) + if resolveError != nil { + WriteError(responseWriter, request, requestLogger, resolveError) + + return + } + + // Schreibgeschützt: Ein Inhaltsverzeichnis liest nur und soll neben einer + // laufenden Sicherung erstellt werden können. + openedRepository, openError := repository.Open(request.Context(), repositoryPath, + repository.OpenOptions{ReadOnly: true}, handler.restoreHandlerReference.logger) + if openError != nil { + WriteError(responseWriter, request, requestLogger, + NewServiceUnavailableError("Das Repository des Backups ist derzeit nicht erreichbar.")) + + return + } + + defer func() { _ = openedRepository.Close() }() + + backupManifest, manifestError := openedRepository.ReadManifest(request.Context(), backupIDInRepository) + if manifestError != nil { + WriteError(responseWriter, request, requestLogger, + NewValidationError("Das Manifest des Backups lässt sich nicht lesen: "+manifestError.Error())) + + return + } + + currentPath := strings.Trim(strings.TrimSpace(request.URL.Query().Get("path")), "/") + + response := browseBackupResponse{ + BackupID: backupIdentifier.String(), + Path: currentPath, + Entries: collectLevel(backupManifest.Entries, currentPath), + TotalEntryCount: len(backupManifest.Entries), + } + + if currentPath != "" { + parentPath := path.Dir(currentPath) + if parentPath == "." { + parentPath = "" + } + + response.ParentPath = parentPath + } + + WriteSuccess(responseWriter, request, http.StatusOK, response) +} + +// collectLevel bildet aus einem flachen Manifest eine Verzeichnisebene. +// +// Das Manifest kennt keine Baumstruktur, sondern eine flache Liste von Pfaden. +// Der Baum entsteht hier — und zwar **ohne** sich auf Verzeichniseinträge zu +// verlassen: Ein Manifest kann eine Datei enthalten, deren Elternverzeichnis +// nicht als eigener Eintrag vorliegt. Wer nur die Einträge vom Typ `directory` +// auflistet, verliert dann ganze Teilbäume. +func collectLevel(manifestEntries []repository.ManifestEntry, currentPath string) []backupContentEntry { + prefix := "" + if currentPath != "" { + prefix = currentPath + "/" + } + + // Verzeichnisse werden über ihre Kinder erkannt und dabei gleich + // aufsummiert: Ein Betreiber will vor dem Zurückholen wissen, wie viel an + // einem Ordner hängt. + directories := make(map[string]*backupContentEntry) + files := make([]backupContentEntry, 0, 32) + + for _, manifestEntry := range manifestEntries { + entryPath := strings.Trim(manifestEntry.Path, "/") + + if prefix != "" && !strings.HasPrefix(entryPath, prefix) { + continue + } + + remainder := strings.TrimPrefix(entryPath, prefix) + if remainder == "" { + continue + } + + separatorIndex := strings.Index(remainder, "/") + + if separatorIndex < 0 { + // Direktes Kind dieser Ebene. + if manifestEntry.EntryType == "directory" { + directoryPath := prefix + remainder + + if _, exists := directories[remainder]; !exists { + directories[remainder] = &backupContentEntry{ + Name: remainder, + Path: directoryPath, + IsDirectory: true, + EntryType: "directory", + Mode: manifestEntry.Mode, + } + } + + continue + } + + files = append(files, backupContentEntry{ + Name: remainder, + Path: entryPath, + IsDirectory: false, + EntryType: manifestEntry.EntryType, + SizeBytes: manifestEntry.SizeBytes, + ModifiedAt: formatOptionalTime(manifestEntry), + Mode: manifestEntry.Mode, + }) + + continue + } + + // Ein Nachfahre: Er belegt, dass es das Verzeichnis gibt, auch wenn + // kein eigener Eintrag dafür existiert. + directoryName := remainder[:separatorIndex] + + existing, exists := directories[directoryName] + if !exists { + existing = &backupContentEntry{ + Name: directoryName, + Path: prefix + directoryName, + IsDirectory: true, + EntryType: "directory", + } + directories[directoryName] = existing + } + + existing.ChildCount++ + existing.TotalBytes += manifestEntry.SizeBytes + } + + entries := make([]backupContentEntry, 0, len(directories)+len(files)) + + for _, directoryEntry := range directories { + entries = append(entries, *directoryEntry) + } + + entries = append(entries, files...) + + // Verzeichnisse zuerst, dann alphabetisch — die Reihenfolge, die jeder + // Dateimanager verwendet. + sort.Slice(entries, func(firstIndex, secondIndex int) bool { + if entries[firstIndex].IsDirectory != entries[secondIndex].IsDirectory { + return entries[firstIndex].IsDirectory + } + + return entries[firstIndex].Name < entries[secondIndex].Name + }) + + return entries +} + +// formatOptionalTime gibt einen Zeitstempel aus, sofern gesetzt. +func formatOptionalTime(manifestEntry repository.ManifestEntry) string { + if manifestEntry.ModifiedAt.IsZero() { + return "" + } + + return manifestEntry.ModifiedAt.UTC().Format("2006-01-02T15:04:05Z") +} + +// directoryIsWritable prüft durch einen echten Schreibversuch. +// +// Die Rechtebits zu lesen genügt nicht: Unter `ProtectSystem=strict` ist das +// Dateisystem für den Dienst außerhalb weniger Pfade schreibgeschützt, und +// davon steht nichts im Modus. Genau diese Lücke hat dazu geführt, dass eine +// Wiederherstellung nach `/opt/test` mit „permission denied" endete, obwohl +// das Verzeichnis dem Anschein nach beschreibbar war. +func directoryIsWritable(directoryPath string) bool { + probeFile, createError := os.CreateTemp(directoryPath, ".syncova-schreibprobe-*") + if createError != nil { + return false + } + + probeName := probeFile.Name() + + _ = probeFile.Close() + _ = os.Remove(probeName) + + return true +} + +// writableSuggestions nennt Orte, an denen der Dienst nachweislich schreiben darf. +// +// Ohne diese Liste sucht ein Betreiber im Blindflug: Die meisten Verzeichnisse +// eines gehärteten Systems scheiden aus, und welche übrig bleiben, hängt an der +// systemd-Einheit — nicht an etwas, das man dem Dateisystem ansieht. +func writableSuggestions(targetGuard *recovery.TargetGuard) []string { + // Reihenfolge ist Absicht: Zuerst die Flaeche, die `setup.sh` anlegt und + // in ReadWritePaths eintraegt. `/var/lib` steht bewusst nicht dabei — es + // ist im Zielschutz gesperrt. + candidates := []string{ + "/srv/syncova-restore", + "/srv", + "/var/tmp", + "/home", + } + + suggestions := make([]string, 0, len(candidates)) + + for _, candidate := range candidates { + if targetGuard != nil { + if guardError := targetGuard.Validate(candidate); guardError != nil { + continue + } + } + + if directoryIsWritable(candidate) { + suggestions = append(suggestions, candidate) + } + } + + return suggestions +} diff --git a/apps/api/internal/httpapi/browse_handler_test.go b/apps/api/internal/httpapi/browse_handler_test.go new file mode 100644 index 0000000..21b3489 --- /dev/null +++ b/apps/api/internal/httpapi/browse_handler_test.go @@ -0,0 +1,90 @@ +package httpapi + +import ( + "testing" + "time" + + "github.com/syncova/syncova/packages/repository" +) + +// TestCollectLevelBuildsTreeWithoutDirectoryEntries haelt fest, dass der Baum +// aus den Pfaden entsteht und nicht aus Verzeichniseintraegen. +// +// Ein Manifest kann eine Datei enthalten, deren Elternverzeichnis nicht als +// eigener Eintrag vorliegt — etwa bei einer Quelle, die nur Dateien meldet. Wer +// nur die Eintraege vom Typ "directory" auflistet, verliert dann ganze +// Teilbaeume, und die Datei ist ueber die Oberflaeche nicht mehr erreichbar. +func TestCollectLevelBuildsTreeWithoutDirectoryEntries(testInstance *testing.T) { + manifestEntries := []repository.ManifestEntry{ + // Kein Eintrag fuer "berichte" selbst. + {Path: "berichte/2026/jahr.pdf", EntryType: "file", SizeBytes: 900}, + {Path: "berichte/2025/jahr.pdf", EntryType: "file", SizeBytes: 100}, + {Path: "notiz.txt", EntryType: "file", SizeBytes: 6}, + } + + rootLevel := collectLevel(manifestEntries, "") + + if len(rootLevel) != 2 { + testInstance.Fatalf("erwartet 2 Eintraege auf der Wurzel, erhalten %d", len(rootLevel)) + } + + // Verzeichnisse stehen vorn. + if !rootLevel[0].IsDirectory || rootLevel[0].Name != "berichte" { + testInstance.Errorf("das Verzeichnis berichte fehlt oder steht nicht vorn: %+v", rootLevel[0]) + } + + // Die Kennzahlen summieren den ganzen Teilbaum: Ein Betreiber will vor dem + // Zurueckholen wissen, wie viel an einem Ordner haengt. + if rootLevel[0].ChildCount != 2 || rootLevel[0].TotalBytes != 1000 { + testInstance.Errorf("Kennzahlen des Ordners falsch: %d Objekte, %d Byte", + rootLevel[0].ChildCount, rootLevel[0].TotalBytes) + } + + // Und eine Ebene tiefer erscheinen die Jahresordner. + deeperLevel := collectLevel(manifestEntries, "berichte") + + if len(deeperLevel) != 2 { + testInstance.Fatalf("erwartet 2 Jahresordner, erhalten %d", len(deeperLevel)) + } +} + +// TestCollectLevelRespectsDirectoryBoundary haelt die Verzeichnisgrenze fest. +// +// "dokumente" darf nicht auch "dokumentation" treffen — sonst holte eine +// Wiederherstellung Daten zurueck, die niemand ausgewaehlt hat. +func TestCollectLevelRespectsDirectoryBoundary(testInstance *testing.T) { + manifestEntries := []repository.ManifestEntry{ + {Path: "dokumente/a.txt", EntryType: "file", SizeBytes: 1}, + {Path: "dokumentation/b.txt", EntryType: "file", SizeBytes: 1}, + } + + level := collectLevel(manifestEntries, "dokumente") + + if len(level) != 1 || level[0].Name != "a.txt" { + testInstance.Errorf("die Verzeichnisgrenze wird nicht beachtet: %+v", level) + } +} + +// TestCollectLevelKeepsFileMetadata prueft die Angaben je Datei. +func TestCollectLevelKeepsFileMetadata(testInstance *testing.T) { + modificationTime := time.Date(2026, 8, 18, 10, 0, 0, 0, time.UTC) + + level := collectLevel([]repository.ManifestEntry{ + {Path: "notiz.txt", EntryType: "file", SizeBytes: 42, Mode: "0644", ModifiedAt: modificationTime}, + }, "") + + if len(level) != 1 { + testInstance.Fatalf("erwartet einen Eintrag, erhalten %d", len(level)) + } + + // Der Pfad ist genau der Wert, den eine Wiederherstellung als + // `path_prefix` erwartet — eine Abweichung faellt sonst erst beim + // Zurueckschreiben auf. + if level[0].Path != "notiz.txt" || level[0].SizeBytes != 42 || level[0].Mode != "0644" { + testInstance.Errorf("Angaben der Datei unvollstaendig: %+v", level[0]) + } + + if level[0].ModifiedAt != "2026-08-18T10:00:00Z" { + testInstance.Errorf("Zeitstempel falsch: %q", level[0].ModifiedAt) + } +} diff --git a/apps/api/internal/httpapi/contract_routes.txt b/apps/api/internal/httpapi/contract_routes.txt index 3136023..65327d0 100644 --- a/apps/api/internal/httpapi/contract_routes.txt +++ b/apps/api/internal/httpapi/contract_routes.txt @@ -122,3 +122,5 @@ POST /api/v1/users users.write POST /api/v1/users/{id}/mfa/disable users.write POST /api/v1/verification verification.write POST /api/v1/verification/{id}/cancel verification.write +GET /api/v1/filesystem/browse restores.read +GET /api/v1/backups/{id}/contents restores.read diff --git a/apps/api/internal/httpapi/router.go b/apps/api/internal/httpapi/router.go index d88a189..d1a31b1 100644 --- a/apps/api/internal/httpapi/router.go +++ b/apps/api/internal/httpapi/router.go @@ -377,6 +377,19 @@ func registerRestoreRoutes(requestMultiplexer *http.ServeMux, routerDependencies requestMultiplexer.Handle("GET "+apiBasePath+"/restores/{id}", protected("restores.read", restoreHandlerInstance.handleGetRestore)) requestMultiplexer.Handle("POST "+apiBasePath+"/restores/{id}/cancel", protected("restores.execute", restoreHandlerInstance.handleCancelRestore)) requestMultiplexer.Handle("POST "+apiBasePath+"/restores/{id}/resume", protected("restores.execute", restoreHandlerInstance.handleResumeRestore)) + + // Blättern in Dateisystem und Backup. + // + // Beide gehören zur Wiederherstellung und tragen deshalb deren Leserecht: + // Wer eine Wiederherstellung vorbereiten darf, muss sehen können, was im + // Backup steckt und wohin sich zurückschreiben lässt. + browseHandlerInstance := &browseHandler{ + restoreHandlerReference: restoreHandlerInstance, + targetGuard: restoreHandlerInstance.targetGuard, + } + + requestMultiplexer.Handle("GET "+apiBasePath+"/filesystem/browse", protected("restores.read", browseHandlerInstance.handleBrowseFilesystem)) + requestMultiplexer.Handle("GET "+apiBasePath+"/backups/{id}/contents", protected("restores.read", browseHandlerInstance.handleBrowseBackupContents)) } // registerVerificationRoutes bindet die Prüfung ein (SYNCOVA_API.md §14). diff --git a/apps/web/src/assets/fonts/GeistMono-Medium.woff2 b/apps/web/src/assets/fonts/GeistMono-Medium.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..ffd43c1a3091acdab7dfb47c3d0919a21116f8da GIT binary patch literal 43440 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

l9VKkNytb{EQqB0 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 literal 0 HcmV?d00001 diff --git a/apps/web/src/assets/fonts/GeistMono-Regular.woff2 b/apps/web/src/assets/fonts/GeistMono-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e62220f2f47cfe939f75a24d669082f5602792e1 GIT binary patch literal 42420 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 IyCwht01l%|4gdfE literal 0 HcmV?d00001 diff --git a/apps/web/src/assets/fonts/GeistMono-SemiBold.woff2 b/apps/web/src/assets/fonts/GeistMono-SemiBold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..9447e1ceace44ec95e77692f619864e28343b3fe GIT binary patch literal 43660 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 AAOHXW literal 0 HcmV?d00001 diff --git a/apps/web/src/assets/fonts/LICENSE.txt b/apps/web/src/assets/fonts/LICENSE.txt new file mode 100644 index 0000000..8d003fe --- /dev/null +++ b/apps/web/src/assets/fonts/LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2023 Vercel, in collaboration with basement.studio + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/apps/web/src/features/repositories/RepositoriesPage.tsx b/apps/web/src/features/repositories/RepositoriesPage.tsx index 55deaa4..29a592d 100644 --- a/apps/web/src/features/repositories/RepositoriesPage.tsx +++ b/apps/web/src/features/repositories/RepositoriesPage.tsx @@ -36,7 +36,7 @@ import { useToast, type TableColumn, } from '@/components/ui'; -import { formatBytes, formatDateTime, formatDuration } from '@/lib/utils'; +import { formatBytes, formatDateTime } from '@/lib/utils'; import { adoptRepository, checkRepositoryHealth, @@ -44,7 +44,7 @@ import { measureEnforcement, rebuildCatalog, startIntegrityScan, - type IntegrityScanResult, + type IntegrityScanDetails, type Repository, } from './repositoriesApi'; @@ -56,7 +56,7 @@ export function RepositoriesPage({ const toast = useToast(); const [isAdoptDialogOpen, setIsAdoptDialogOpen] = useState(false); const [selectedRepository, setSelectedRepository] = useState(null); - const [scanResult, setScanResult] = useState(null); + const [scanResult, setScanResult] = useState(null); const [repositoryPendingScan, setRepositoryPendingScan] = useState(null); const [adoptName, setAdoptName] = useState(''); @@ -96,10 +96,10 @@ export function RepositoriesPage({ // Ein Befund ist ein Ergebnis, kein Fehler des Laufs. Die Meldung // unterscheidet beides — ein Prüfwerkzeug, das grundlos Alarm schlägt, // wird bald nicht mehr ernst genommen. - if (result.chunks_missing > 0 || result.chunks_corrupted > 0) { + if (result.missing_chunks > 0 || result.corrupted_chunks > 0) { toast.showError( 'Der Integritätslauf hat Befunde', - `${result.chunks_missing} Blöcke fehlen, ${result.chunks_corrupted} sind beschädigt.`, + `${result.missing_chunks} Blöcke fehlen, ${result.corrupted_chunks} sind beschädigt.`, ); } else { toast.showSuccess( @@ -119,11 +119,21 @@ export function RepositoriesPage({ }); const healthMutation = useMutation(checkRepositoryHealth, { - onSuccess: (health) => { - toast.showSuccess( - 'Gesundheitsprüfung abgeschlossen', - health.message ?? `Zustand: ${health.status}`, - ); + onSuccess: (checkResponse) => { + // Erreichbarkeit und Befund sind zwei Aussagen. Die erste steht in der + // Hülle, die zweite in `details`. + if (!checkResponse.reachable) { + toast.showError( + 'Das Repository ist nicht erreichbar', + checkResponse.error ?? 'Ohne nähere Angabe.', + ); + } else { + toast.showSuccess( + 'Gesundheitsprüfung abgeschlossen', + checkResponse.details?.message ?? 'Das Repository ist erreichbar.', + ); + } + repositoriesResource.reload(); }, onError: (apiError) => @@ -143,11 +153,10 @@ export function RepositoriesPage({ }); const rebuildMutation = useMutation(rebuildCatalog, { - onSuccess: (result) => { + onSuccess: (checkResponse) => { toast.showSuccess( 'Katalog neu aufgebaut', - result.summary ?? - `${result.backups_found ?? 0} Wiederherstellungspunkte aus den Manifesten gelesen.`, + `${checkResponse.details?.backups_in_catalog ?? 0} Wiederherstellungspunkte aus den Manifesten gelesen.`, ); repositoriesResource.reload(); }, @@ -410,10 +419,10 @@ function IntegrityScanCard({ result, onClose, }: { - readonly result: IntegrityScanResult; + readonly result: IntegrityScanDetails; readonly onClose: () => void; }) { - const hasFindings = result.chunks_missing > 0 || result.chunks_corrupted > 0; + const hasFindings = result.missing_chunks > 0 || result.corrupted_chunks > 0; return ( @@ -429,7 +438,7 @@ function IntegrityScanCard({ {hasFindings ? 'Das Repository weist Befunde auf. Wiederherstellungen aus betroffenen Backups wären unvollständig.' - : 'Ohne Befund. Jeder geprüfte Block stimmt mit seiner Prüfsumme überein.'} + : result.summary}

@@ -437,27 +446,36 @@ function IntegrityScanCard({ {result.chunks_checked.toLocaleString('de-DE')} - 0 ? 'text-critical' : undefined}> - {result.chunks_missing.toLocaleString('de-DE')} + 0 ? 'text-critical' : undefined}> + {result.missing_chunks.toLocaleString('de-DE')} - 0 ? 'text-critical' : undefined}> - {result.chunks_corrupted.toLocaleString('de-DE')} + 0 ? 'text-critical' : undefined}> + {result.corrupted_chunks.toLocaleString('de-DE')} - {formatDuration(result.duration_seconds)} + + {result.backups_healthy} von {result.backups_checked} vollständig +
- {result.findings && result.findings.length > 0 ? ( + {!result.verified_chunk_contents ? ( + + Nur die Kennungen wurden geprüft, nicht die Blockinhalte. Das ist + ein halber Nachweis. + + ) : null} + + {result.affected_backup_ids && result.affected_backup_ids.length > 0 ? (

- Betroffene Objekte + Betroffene Backups

    - {result.findings.map((finding) => ( -
  • - {finding} + {result.affected_backup_ids.map((backupIdentifier) => ( +
  • + {backupIdentifier}
  • ))}
diff --git a/apps/web/src/features/repositories/repositoriesApi.ts b/apps/web/src/features/repositories/repositoriesApi.ts index 613fb24..5ad491c 100644 --- a/apps/web/src/features/repositories/repositoriesApi.ts +++ b/apps/web/src/features/repositories/repositoriesApi.ts @@ -32,21 +32,49 @@ export interface Repository { created_at?: string; } -/** Ergebnis eines Integritätslaufs. */ -export interface IntegrityScanResult { - /** Geprüft. */ +/** + * Antworthülle der Repository-Prüfungen. + * + * **Alle vier Prüfendpunkte antworten in dieser Form** — Integritätslauf, + * Gesundheitsprüfung, Verbindungstest und Katalog-Neuaufbau. Das eigentliche + * Ergebnis steckt unter `details`, nicht an der Oberfläche der Antwort. + * + * Das falsch anzunehmen kostete eine Fehlermeldung, die wie ein Defekt der + * Anlage aussah: „can't access property toLocaleString, chunks_checked is + * undefined — das ist ein Problem der Prüfung, kein Befund am Repository." + * Genau das Gegenteil war der Fall; die Prüfung war einwandfrei gelaufen. + */ +export interface RepositoryCheckResponse { + repository_id: string; + reachable: boolean; + repository_uuid?: string; + error?: string; + details?: TDetails; +} + +/** + * Ergebnis eines Integritätslaufs. + * + * Die Feldnamen stammen aus `repository.ScanReport` und heißen anders herum als + * erwartet: `missing_chunks`, nicht `chunks_missing`. + */ +export interface IntegrityScanDetails { + /** Geprüfte Blöcke. */ chunks_checked: number; - /** Nicht auffindbar. */ - chunks_missing: number; + /** Nicht auffindbar — jeder einzelne verhindert eine Wiederherstellung. */ + missing_chunks: number; /** Prüfsumme stimmt nicht. */ - chunks_corrupted: number; - manifests_checked?: number; - manifests_invalid?: number; - duration_seconds?: number; - /** Zusammenfassung im Klartext. */ - summary?: string; - /** Betroffene Objekte, sofern benennbar. */ - findings?: string[]; + corrupted_chunks: number; + /** Blöcke ohne Verweis aus einem Manifest. */ + orphaned_chunks: number; + backups_checked: number; + backups_healthy: number; + /** Meldet, ob die Blockinhalte gelesen wurden oder nur die Kennungen. */ + verified_chunk_contents: boolean; + healthy: boolean; + summary: string; + /** Betroffene Backups, sofern benennbar. */ + affected_backup_ids?: string[] | null; } /** Ergebnis einer Gesundheitsprüfung. */ @@ -126,8 +154,8 @@ export async function updateRepository( /** Prüft die Erreichbarkeit. */ export async function testRepository( repositoryIdentifier: string, -): Promise { - return requestApi( +): Promise>> { + return requestApi>>( `/repositories/${encodeURIComponent(repositoryIdentifier)}/test`, { method: 'POST' }, ); @@ -136,8 +164,8 @@ export async function testRepository( /** Führt eine Gesundheitsprüfung aus. */ export async function checkRepositoryHealth( repositoryIdentifier: string, -): Promise { - return requestApi( +): Promise> { + return requestApi>( `/repositories/${encodeURIComponent(repositoryIdentifier)}/health-check`, { method: 'POST' }, ); @@ -152,11 +180,21 @@ export async function checkRepositoryHealth( */ export async function startIntegrityScan( repositoryIdentifier: string, -): Promise { - return requestApi( +): Promise { + const response = await requestApi>( `/repositories/${encodeURIComponent(repositoryIdentifier)}/integrity-scan`, { method: 'POST', idempotencyKey: true }, ); + + // Ein nicht erreichbares Repository ist kein Befund am Bestand, sondern ein + // Fehler des Laufs — die Unterscheidung, die diese Seite durchgehend macht. + if (!response.reachable || !response.details) { + throw new Error( + response.error || 'Das Repository war während der Prüfung nicht erreichbar.', + ); + } + + return response.details; } /** @@ -168,8 +206,8 @@ export async function startIntegrityScan( */ export async function rebuildCatalog( repositoryIdentifier: string, -): Promise<{ backups_found?: number; summary?: string }> { - return requestApi<{ backups_found?: number; summary?: string }>( +): Promise> { + return requestApi>( `/repositories/${encodeURIComponent(repositoryIdentifier)}/rebuild-catalog`, { method: 'POST', idempotencyKey: true }, ); diff --git a/apps/web/src/features/restores/BackupContentPicker.tsx b/apps/web/src/features/restores/BackupContentPicker.tsx new file mode 100644 index 0000000..18ee067 --- /dev/null +++ b/apps/web/src/features/restores/BackupContentPicker.tsx @@ -0,0 +1,189 @@ +/** + * Auswahl dessen, was zurückgeholt werden soll. + * + * Bisher gab es nur „alles" — ein Textfeld für einen Teilbaum, das voraussetzte, + * dass man die Pfade im Backup auswendig kennt. Jetzt lässt sich blättern. + * + * Der gewählte Pfad wandert als `path_prefix` in die Anfrage. Das genügt für + * beides: Der Server vergleicht auf Gleichheit **oder** Präfix mit + * Verzeichnisgrenze, trifft also sowohl einen ganzen Ordner als auch eine + * einzelne Datei. „dokumente" trifft dabei nicht „dokumentation". + * + * Bewusste Grenze: **eine** Auswahl je Lauf, kein Mehrfachhaken. Eine Liste + * ausgewählter Pfade kennt die API nicht, und sie vorzutäuschen — etwa durch + * mehrere Läufe hintereinander — ergäbe mehrere Wiederherstellungen mit + * getrenntem Ausgang. Ein „teilweise fehlgeschlagen" ließe sich dann niemandem + * mehr erklären. + */ + +import { ChevronRight, File, Folder, FolderOpen } from 'lucide-react'; +import { useCallback, useState } from 'react'; +import { useApiResource } from '@/api/useApiResource'; +import { describeApiError } from '@/api/useMutation'; +import { Button, Callout, ErrorState, LoadingState } from '@/components/ui'; +import { formatBytes, formatDateTime } from '@/lib/utils'; +import { browseBackupContents } from './browseApi'; + +export function BackupContentPicker({ + backupIdentifier, + selectedPath, + onSelect, +}: { + readonly backupIdentifier: string; + /** Leer bedeutet: das gesamte Backup. */ + readonly selectedPath: string; + readonly onSelect: (contentPath: string) => void; +}) { + const [currentPath, setCurrentPath] = useState(''); + + const contentsResource = useApiResource( + useCallback( + (abortSignal) => browseBackupContents(backupIdentifier, currentPath, abortSignal), + [backupIdentifier, currentPath], + ), + `${backupIdentifier}|${currentPath}`, + ); + + const listing = contentsResource.data; + + return ( +
+ {/* Der Regelfall steht oben und ist vorausgewählt: Die meisten + Wiederherstellungen holen alles zurück. */} +
+ + + {listing ? `${listing.total_entry_count.toLocaleString('de-DE')} Objekte` : ''} + +
+ + {/* Pfadleiste */} +
+ + {currentPath + .split('/') + .filter((segment) => segment !== '') + .map((segment, segmentIndex, allSegments) => ( + + + + + ))} +
+ +
+ {contentsResource.loadState === 'loading' ? ( + + ) : contentsResource.loadState === 'failed' && contentsResource.loadError ? ( + + ) : ( +
    + {currentPath !== '' ? ( +
  • + +
  • + ) : null} + + {(listing?.entries ?? []).map((entry) => ( +
  • + + + +
  • + ))} + + {(listing?.entries ?? []).length === 0 ? ( +
  • + Dieser Ordner ist im Backup leer. +
  • + ) : null} +
+ )} +
+ + {selectedPath ? ( + + Zurückgeholt wird nur: {selectedPath} +

+ Ein auf einen Teilbaum beschränkter Lauf hebt die Einstufung des + Wiederherstellungspunkts nicht — er prüft einen Teil, nicht das + Backup. +

+
+ ) : ( + Zurückgeholt wird das gesamte Backup. + )} + + {listing?.entries.some((entry) => entry.modified_at) ? ( +

+ Stand der Dateien:{' '} + {formatDateTime( + listing.entries.find((entry) => entry.modified_at)?.modified_at, + )} +

+ ) : null} +
+ ); +} diff --git a/apps/web/src/features/restores/DirectoryPicker.tsx b/apps/web/src/features/restores/DirectoryPicker.tsx new file mode 100644 index 0000000..dc07351 --- /dev/null +++ b/apps/web/src/features/restores/DirectoryPicker.tsx @@ -0,0 +1,220 @@ +/** + * Auswahl des Zielverzeichnisses. + * + * Ersetzt das Textfeld, in das man einen Pfad tippte und erst nach der + * Vorabprüfung erfuhr, dass der Dienst dort gar nicht schreiben darf. + * + * Die tragende Angabe ist **`is_writable`**, und sie wird gemessen: Der Server + * legt eine Probedatei an und entfernt sie wieder. Aus den Rechtebits ließe sie + * sich nicht ableiten — der Dienst läuft mit `ProtectSystem=strict`, und davon + * steht nichts im Modus. Genau deshalb endete eine Wiederherstellung nach + * `/opt/test` mit „permission denied", obwohl das Verzeichnis beschreibbar + * aussah. + * + * Nicht beschreibbare Verzeichnisse werden **gezeigt**, nicht versteckt: Sie + * lassen sich betreten, um tiefer zu blättern, aber nicht auswählen. Sie + * wegzulassen ließe den Betreiber im Dunkeln, warum sein Pfad fehlt. + */ + +import { ChevronRight, FolderOpen, Lock, Plus } from 'lucide-react'; +import { useCallback, useState } from 'react'; +import { useApiResource } from '@/api/useApiResource'; +import { describeApiError } from '@/api/useMutation'; +import { + Button, + Callout, + ErrorState, + LoadingState, + TextInput, +} from '@/components/ui'; +import { cn } from '@/lib/utils'; +import { browseFilesystem } from './browseApi'; + +export function DirectoryPicker({ + selectedPath, + onSelect, +}: { + readonly selectedPath: string; + readonly onSelect: (directoryPath: string) => void; +}) { + // Der Startpunkt ist das übergeordnete Verzeichnis der Auswahl, sonst die + // Wurzel — so landet man beim zweiten Öffnen dort, wo man aufgehört hat. + const [currentPath, setCurrentPath] = useState(() => { + const trimmedSelection = selectedPath.trim(); + + if (!trimmedSelection.startsWith('/')) { + return '/'; + } + + const parentPath = trimmedSelection.replace(/\/[^/]*$/, ''); + + return parentPath === '' ? '/' : parentPath; + }); + + const [newFolderName, setNewFolderName] = useState(''); + + const browseResource = useApiResource( + useCallback((abortSignal) => browseFilesystem(currentPath, abortSignal), [currentPath]), + currentPath, + ); + + const listing = browseResource.data; + + return ( +
+ {/* Vorschläge zuerst: Sie sind der einzige Weg, ohne Vorwissen an einen + brauchbaren Ort zu kommen. */} + {listing?.suggested_paths && listing.suggested_paths.length > 0 ? ( +
+

Beschreibbare Orte

+
+ {listing.suggested_paths.map((suggestedPath) => ( + + ))} +
+
+ ) : null} + + {/* Pfadleiste */} +
+ + {currentPath + .split('/') + .filter((segment) => segment !== '') + .map((segment, segmentIndex, allSegments) => ( + + + + + ))} +
+ + {/* Liste */} +
+ {browseResource.loadState === 'loading' ? ( + + ) : browseResource.loadState === 'failed' && browseResource.loadError ? ( + + ) : ( +
    + {listing?.parent_path ? ( +
  • + +
  • + ) : null} + + {(listing?.entries ?? []).map((entry) => ( +
  • + + + {entry.is_writable ? ( + + ) : null} +
  • + ))} + + {(listing?.entries ?? []).length === 0 && !listing?.parent_path ? ( +
  • + Keine Unterverzeichnisse. +
  • + ) : null} +
+ )} +
+ + {/* Neues Unterverzeichnis: Der Dienst legt das Ziel selbst an, wenn er + im übergeordneten Verzeichnis schreiben darf. Der Name wandert einfach + an den aktuellen Pfad. */} +
+ setNewFolderName(changeEvent.target.value)} + placeholder="wiederherstellung-2026-08-18" + hint="Es wird beim Zurückschreiben angelegt." + /> + +
+ + {selectedPath ? ( + + Ziel: {selectedPath} + + ) : null} +
+ ); +} diff --git a/apps/web/src/features/restores/RestoreWizard.tsx b/apps/web/src/features/restores/RestoreWizard.tsx index 7e8c68a..b3cc710 100644 --- a/apps/web/src/features/restores/RestoreWizard.tsx +++ b/apps/web/src/features/restores/RestoreWizard.tsx @@ -41,6 +41,8 @@ import { useToast, } from '@/components/ui'; import { cn, formatBytes, formatDuration } from '@/lib/utils'; +import { BackupContentPicker } from './BackupContentPicker'; +import { DirectoryPicker } from './DirectoryPicker'; import { createRestore, validateRestore, @@ -170,35 +172,15 @@ export function RestoreWizard({ {currentStepIndex === 0 ? ( -
- setTargetPath(changeEvent.target.value)} - hint="Absoluter Pfad auf dem Server. Systemverzeichnisse wie /etc oder /usr werden abgelehnt." - error={ - targetPath.trim() && !targetPath.trim().startsWith('/') - ? 'Der Pfad muss absolut sein und mit / beginnen.' - : undefined - } - /> - - - Am besten in ein leeres Verzeichnis — der Ursprungsort wäre sonst überschrieben. - -
+ ) : null} {currentStepIndex === 1 ? (
- setPathPrefix(changeEvent.target.value)} - hint="Leer lassen, um alles zurückzuschreiben. Ein Teilbaum-Restore hebt die Einstufung des Wiederherstellungspunkts nicht — er prüft einen Teil, nicht das Backup." +
@@ -209,8 +191,8 @@ export function RestoreWizard({ label="Vorhandene Dateien überschreiben" hint={ mayOverwrite - ? 'Ohne dieses Kennzeichen wird ein nicht leeres Ziel abgelehnt. Zusätzlich verlangt der Server danach den wörtlich wiederholten Zielpfad.' - : 'Ihrer Rolle fehlt die Berechtigung restores.overwrite. Sie steckt bewusst nicht in restores.execute.' + ? 'Ohne dieses Kennzeichen wird ein nicht leeres Ziel abgelehnt.' + : 'Ihrer Rolle fehlt das Recht restores.overwrite.' } /> @@ -218,24 +200,16 @@ export function RestoreWizard({ checked={skipPermissions} onCheckedChange={setSkipPermissions} label="Rechte nicht zurückschreiben" - hint="Dateien entstehen mit den Standardrechten des Dienstkontos statt mit den gesicherten." + hint="Dateien entstehen mit den Standardrechten des Dienstkontos." />
- - {skipDeepCheck ? ( - - Ohne Blockprüfung sagt die Vorabprüfung nur, dass das - Manifest lesbar ist — nicht, dass die Daten dazu noch - existieren. Der Bericht weist das aus. - - ) : null}
) : null} diff --git a/apps/web/src/features/restores/browseApi.ts b/apps/web/src/features/restores/browseApi.ts new file mode 100644 index 0000000..26d5978 --- /dev/null +++ b/apps/web/src/features/restores/browseApi.ts @@ -0,0 +1,82 @@ +/** + * Blättern im Dateisystem des Servers und im Inhalt eines Backups. + * + * Beides gehört zur Wiederherstellung: Wohin darf zurückgeschrieben werden, und + * was steckt überhaupt in dem Backup? Ein Textfeld beantwortet weder das eine + * noch das andere. + */ + +import { requestApi } from '../../api/client'; + +/** Ein Verzeichnis auf dem Server. */ +export interface FilesystemEntry { + name: string; + path: string; + is_directory: boolean; + /** + * Meldet, ob der **Dienst** hier anlegen darf. + * + * Gemessen durch einen Schreibversuch, nicht aus den Rechtebits geraten: Der + * Dienst läuft mit `ProtectSystem=strict`, und davon steht nichts im Modus. + */ + is_writable: boolean; + /** Grund, wenn der Zielschutz den Ort ausschließt. */ + forbidden_reason?: string; +} + +/** Antwort auf das Blättern im Dateisystem. */ +export interface BrowseFilesystemResponse { + path: string; + parent_path?: string; + entries: FilesystemEntry[]; + /** Orte, an denen der Dienst nachweislich schreiben darf. */ + suggested_paths?: string[]; +} + +/** Ein Eintrag im Inhaltsverzeichnis eines Backups. */ +export interface BackupContentEntry { + name: string; + /** Genau der Wert, den eine Wiederherstellung als `path_prefix` erwartet. */ + path: string; + is_directory: boolean; + entry_type: string; + size_bytes?: number; + modified_at?: string; + mode?: string; + /** Zahl der Einträge unterhalb eines Verzeichnisses. */ + child_count?: number; + /** Datenmenge unterhalb eines Verzeichnisses. */ + total_bytes?: number; +} + +/** Antwort auf das Blättern im Backup. */ +export interface BrowseBackupResponse { + backup_id: string; + path: string; + parent_path?: string; + entries: BackupContentEntry[]; + total_entry_count: number; +} + +/** Listet die Verzeichnisse unterhalb eines Pfades. */ +export async function browseFilesystem( + directoryPath: string, + abortSignal?: AbortSignal, +): Promise { + return requestApi( + `/filesystem/browse?path=${encodeURIComponent(directoryPath)}`, + abortSignal ? { signal: abortSignal } : {}, + ); +} + +/** Listet den Inhalt eines Backups auf einer Ebene. */ +export async function browseBackupContents( + backupIdentifier: string, + contentPath: string, + abortSignal?: AbortSignal, +): Promise { + return requestApi( + `/backups/${encodeURIComponent(backupIdentifier)}/contents?path=${encodeURIComponent(contentPath)}`, + abortSignal ? { signal: abortSignal } : {}, + ); +} diff --git a/apps/web/src/styles/theme.css b/apps/web/src/styles/theme.css index 441d0fb..8532c56 100644 --- a/apps/web/src/styles/theme.css +++ b/apps/web/src/styles/theme.css @@ -15,11 +15,10 @@ * 2. **Das dunkle Thema hängt an `[data-theme='dark']`, nicht an `.dark`.** Der * Umschalter der Konsole setzt dieses Attribut, und er ist getestet. Beide * Schreibweisen werden unterstützt. - * 3. **Die Schrift lädt nicht nach.** Geist Mono steht zuerst im Stapel und - * wird verwendet, wenn sie auf dem Gerät liegt; sonst greift die - * System-Monospace. Eine Schrift von einem fremden Host zu holen verbietet - * die Content-Security-Policy der Auslieferung — und ein Backup-Server, der - * für seine Oberfläche ins Internet greift, wäre auch ohne CSP falsch. + * 3. **Geist Mono liegt im Paket.** Sie wird vom eigenen Ursprung ausgeliefert, + * nicht von einem CDN: Das verlangt die Content-Security-Policy, und ein + * Backup-Server, dessen Oberfläche von der Erreichbarkeit eines fremden + * Hosts abhängt, wäre auch ohne CSP falsch. * * Die Benennung ist bewusst semantisch (`--surface-card`, `--text-primary`) * statt shadcn-typisch (`--card`, `--foreground`): Die Zuordnung des Presets @@ -29,6 +28,45 @@ @import 'tailwindcss'; +/* + * Geist Mono liegt **im Paket**, nicht auf einem fremden Host. + * + * Vite bündelt die drei Schnitte mit und vergibt ihnen einen Hash; ausgeliefert + * wird ausschließlich vom eigenen Ursprung. Das ist die einzige Form, die die + * Content-Security-Policy zulässt — und die einzige, die für einen + * Backup-Server vertretbar ist: Seine Oberfläche darf nicht davon abhängen, + * dass ein CDN erreichbar ist. Kosten: rund 128 KB für drei Schnitte. + * + * `font-display: swap` zeigt den Text sofort in der System-Monospace und + * tauscht die Schrift nach. Ein unsichtbarer Text, bis eine Schriftdatei da + * ist, wäre in einer Störungskonsole der falsche Kompromiss. + * + * Lizenz: SIL Open Font License 1.1, siehe assets/fonts/LICENSE.txt. + */ +@font-face { + font-family: 'Geist Mono'; + src: url('../assets/fonts/GeistMono-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Geist Mono'; + src: url('../assets/fonts/GeistMono-Medium.woff2') format('woff2'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Geist Mono'; + src: url('../assets/fonts/GeistMono-SemiBold.woff2') format('woff2'); + font-weight: 600; + font-style: normal; + font-display: swap; +} + /* Beide Schreibweisen: Der Umschalter setzt `data-theme`, `.dark` ist die * shadcn-übliche Form und kostet nichts. */ @custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *, .dark, .dark *)); diff --git a/scripts/setup.sh b/scripts/setup.sh index dec54ec..161dbf9 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -40,6 +40,28 @@ readonly defaultRepositoryPath="/srv/syncova-repository" # backupDirectory nimmt Sicherungen von Datenbank und Konfiguration auf. readonly backupDirectory="/var/backups/syncova" +# additionalRestoreRoots sind weitere Ziele fuer Wiederherstellungen. +# +# Wer nach /srv/wiederherstellung oder auf eine Freigabe zurueckschreiben will, +# nennt sie beim Einrichten mit --wiederherstellungsziel. Sie landen in +# ReadWritePaths der Einheit; ohne diesen Eintrag nuetzen die Rechte des +# Verzeichnisses nichts. +additionalRestoreRoots="" + +# restoreDirectory ist die Flaeche, auf die zurueckgeschrieben werden darf. +# +# Sie ist noetig, weil der Dienst mit ProtectSystem=strict laeuft: Ausserhalb +# der in ReadWritePaths genannten Pfade ist das Dateisystem fuer ihn +# schreibgeschuetzt. Ohne eine solche Flaeche endet **jede** Wiederherstellung +# mit "mkdir: permission denied" — und zwar erst nach der Vorabpruefung, also +# an der unangenehmsten Stelle. +# +# Der Ort liegt unter /srv und nicht unter /var/lib: Letzteres steht auf der +# Sperrliste des Zielschutzes (packages/recovery/targetguard.go), weil eine +# Wiederherstellung dorthin den Zustand der Anlage selbst ueberschreiben +# koennte. Beide Regeln zugleich zu erfuellen laesst genau /srv uebrig. +readonly restoreDirectory="/srv/syncova-restore" + # --------------------------------------------------------------------------- # Ausgabe # --------------------------------------------------------------------------- @@ -212,6 +234,10 @@ while [[ $# -gt 0 ]]; do case "$1" in --paket) packageDirectory="${2:-}"; shift 2 ;; --repository) repositoryPath="${2:-}"; shift 2 ;; + --wiederherstellungsziel) + # Mehrfach angebbar. Die Pfade kommen in ReadWritePaths der Einheit; + # ohne diesen Eintrag nuetzen die Rechte des Verzeichnisses nichts. + additionalRestoreRoots="${additionalRestoreRoots} ${2:-}"; shift 2 ;; --adresse) listenAddress="${2:-}"; shift 2 ;; --admin) administratorName="${2:-}"; shift 2 ;; --ohne-haertung) createHardenedRepository="nein"; shift ;; @@ -1047,6 +1073,11 @@ noteCreated "directory:${configurationDirectory}" install -d -m 0700 -o "${serviceAccount}" -g "${serviceAccount}" "${backupDirectory}" +# Die Wiederherstellungsflaeche. Ohne sie scheitert jede Wiederherstellung an +# ProtectSystem=strict — und der Betreiber sucht den Fehler bei den Rechten des +# Zielverzeichnisses statt bei der Haertung des Dienstes. +install -d -m 0750 -o "${serviceAccount}" -g "${serviceAccount}" "${restoreDirectory}" + cp -R "${packageDirectory}/bin" "${installationRoot}/" [[ -d "${packageDirectory}/web" ]] && cp -R "${packageDirectory}/web" "${installationRoot}/" [[ -d "${packageDirectory}/migrations" ]] && cp -R "${packageDirectory}/migrations" "${installationRoot}/" @@ -1296,7 +1327,7 @@ NoNewPrivileges=yes PrivateTmp=${privateTmpSetting} ProtectSystem=strict ProtectHome=yes -ReadWritePaths=${repositoryPath} ${backupDirectory} +ReadWritePaths=${repositoryPath} ${backupDirectory} ${restoreDirectory}${additionalRestoreRoots} ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes diff --git a/untitled.md b/untitled.md new file mode 100644 index 0000000..e69de29