Support attaching existing Restic repositories

This commit is contained in:
Mikei386
2026-06-15 12:01:24 +02:00
parent e97dde0ed8
commit 3327f21881
11 changed files with 115 additions and 15 deletions
+1 -1
View File
@@ -414,7 +414,7 @@ func resticError(message string, commandErr error) error {
case strings.Contains(lower, "wrong password") || strings.Contains(lower, "no key found"):
return errors.New("authentication: wrong repository password. Enter the original Restic password under Edit and select Test")
case strings.Contains(lower, "repository does not exist") || strings.Contains(lower, "is there a repository at the following location?"):
return errors.New("repository: destination is not initialized; open Repositories and select Initialize once")
return errors.New("repository: Am konfigurierten Ziel wurde keine Restic-config gefunden. Für ein vorhandenes Repository den exakten Repository-Ordner eintragen; nur ein wirklich neues, leeres Ziel initialisieren")
default:
message = strings.TrimSpace(strings.TrimPrefix(message, "Fatal:"))
return fmt.Errorf("restic: %s: %w", message, commandErr)
+7
View File
@@ -298,6 +298,13 @@ func TestResticErrorExplainsCiphertextVerification(t *testing.T) {
}
}
func TestResticErrorExplainsMissingExistingRepositoryPath(t *testing.T) {
err := resticError("Fatal: repository does not exist", fmt.Errorf("exit status 10"))
if !strings.Contains(err.Error(), "exakten Repository-Ordner") || !strings.Contains(err.Error(), "wirklich neues") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestRunnerPausesResumesAndCancelsProcessGroup(t *testing.T) {
dir := t.TempDir()
ticksPath := filepath.Join(dir, "ticks")
+41
View File
@@ -0,0 +1,41 @@
package service
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestValidateExistingRepositoryPathAcceptsRepositoryRoot(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "config"), []byte("restic"), 0600); err != nil {
t.Fatal(err)
}
if err := validateExistingRepositoryPath(root); err != nil {
t.Fatalf("repository root rejected: %v", err)
}
}
func TestValidateExistingRepositoryPathSuggestsDirectChild(t *testing.T) {
root := t.TempDir()
repository := filepath.Join(root, "server-backup")
if err := os.Mkdir(repository, 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(repository, "config"), []byte("restic"), 0600); err != nil {
t.Fatal(err)
}
err := validateExistingRepositoryPath(root)
if err == nil || !strings.Contains(err.Error(), repository) {
t.Fatalf("missing repository suggestion: %v", err)
}
}
func TestValidateExistingRepositoryPathExplainsMissingConfig(t *testing.T) {
root := t.TempDir()
err := validateExistingRepositoryPath(root)
if err == nil || !strings.Contains(err.Error(), "config, data, index, keys und snapshots") {
t.Fatalf("missing layout guidance: %v", err)
}
}
+33
View File
@@ -262,10 +262,43 @@ func (s *Service) TestRepository(ctx context.Context, repoID string, initialize
if initialize {
return s.restic.Init(ctx, mounted.Repository)
}
if mounted.Repository.Type != model.RepositorySFTP {
if err := validateExistingRepositoryPath(mounted.Repository.Location); err != nil {
return err
}
}
_, err = s.restic.Snapshots(ctx, mounted.Repository)
return err
}
func validateExistingRepositoryPath(location string) error {
configPath := filepath.Join(location, "config")
if info, err := os.Stat(configPath); err == nil && !info.IsDir() {
return nil
}
entries, readErr := os.ReadDir(location)
if readErr != nil {
return fmt.Errorf("repository: Repository-Ordner %q kann nicht gelesen werden: %w", location, readErr)
}
candidates := make([]string, 0, 3)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
candidate := filepath.Join(location, entry.Name())
if info, err := os.Stat(filepath.Join(candidate, "config")); err == nil && !info.IsDir() {
candidates = append(candidates, candidate)
if len(candidates) == 3 {
break
}
}
}
if len(candidates) > 0 {
return fmt.Errorf("repository: Im gewählten Ordner liegt keine Restic-config. Verwende direkt den Repository-Ordner: %s", strings.Join(candidates, ", "))
}
return fmt.Errorf("repository: Im gewählten Ordner %q wurde keine Restic-config gefunden. Ein vorhandenes Repository muss direkt auf den Ordner zeigen, der config, data, index, keys und snapshots enthält", location)
}
func (s *Service) UnlockRepository(ctx context.Context, repoID string) error {
repo, ok := s.repository(repoID)
if !ok {