Clarify existing repository errors
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
6438688ae2884c69f23c7e2ec11eb7bab95d138b6f7d25c3c0335a6e319470a0 backupper-2026.06.14.9.6-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
91325b4f694386ba23ad5ea43e51f6f3b41ae02ce5e66cf0bb04b0473fce9231 backupper-2026.06.14.9.7-x86_64-1.txz
|
||||||
Vendored
+6
-2
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "backupper">
|
<!ENTITY name "backupper">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.14.9.6">
|
<!ENTITY version "2026.06.14.9.7">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
||||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "6438688ae2884c69f23c7e2ec11eb7bab95d138b6f7d25c3c0335a6e319470a0">
|
<!ENTITY packageSHA256 "91325b4f694386ba23ad5ea43e51f6f3b41ae02ce5e66cf0bb04b0473fce9231">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues" icon="backupper.png">
|
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues" icon="backupper.png">
|
||||||
<CHANGES>
|
<CHANGES>
|
||||||
|
### 2026.06.14.9.7
|
||||||
|
- Explain already initialized repositories and incorrect or damaged Restic keys with actionable errors.
|
||||||
|
- Strengthen the Initialize confirmation for existing repository destinations.
|
||||||
|
|
||||||
### 2026.06.14.9.6
|
### 2026.06.14.9.6
|
||||||
- Hide technical secret IDs and manage them automatically for repositories, SMB, SFTP, and Gotify.
|
- Hide technical secret IDs and manage them automatically for repositories, SMB, SFTP, and Gotify.
|
||||||
- Require a Restic password when creating a new repository while preserving existing secrets during edits.
|
- Require a Restic password when creating a new repository while preserving existing secrets during edits.
|
||||||
|
|||||||
@@ -239,18 +239,32 @@ func (r *Runner) run(ctx context.Context, repo model.Repository, args []string,
|
|||||||
*capture = captured
|
*capture = captured
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
message := redact(string(errBytes), repo)
|
message := strings.TrimSpace(redact(string(errBytes), repo))
|
||||||
if errors.Is(ctx.Err(), context.Canceled) {
|
if errors.Is(ctx.Err(), context.Canceled) {
|
||||||
return fmt.Errorf("cancelled: %w", ctx.Err())
|
return fmt.Errorf("cancelled: %w", ctx.Err())
|
||||||
}
|
}
|
||||||
if strings.Contains(message, "repository does not exist") || strings.Contains(message, "Is there a repository at the following location?") {
|
return resticError(message, err)
|
||||||
return errors.New("repository: destination is not initialized; open Repositories and select Initialize")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("restic: %s: %w", strings.TrimSpace(message), err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resticError(message string, commandErr error) error {
|
||||||
|
lower := strings.ToLower(message)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(lower, "config file already exists") || strings.Contains(lower, "repository master key and config already initialized"):
|
||||||
|
return errors.New("repository: destination is already initialized; do not select Initialize again. Enter the existing repository password and select Test")
|
||||||
|
case strings.Contains(lower, "ciphertext verification failed"):
|
||||||
|
return errors.New("authentication: the saved repository password does not match this existing repository, or its Restic key is damaged. Enter the original repository password under Edit and select Test")
|
||||||
|
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")
|
||||||
|
default:
|
||||||
|
message = strings.TrimSpace(strings.TrimPrefix(message, "Fatal:"))
|
||||||
|
return fmt.Errorf("restic: %s: %w", message, commandErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func repositoryLocation(repo model.Repository) string {
|
func repositoryLocation(repo model.Repository) string {
|
||||||
if repo.Type == model.RepositorySFTP && !strings.HasPrefix(repo.Location, "sftp:") {
|
if repo.Type == model.RepositorySFTP && !strings.HasPrefix(repo.Location, "sftp:") {
|
||||||
return "sftp:" + repo.Location
|
return "sftp:" + repo.Location
|
||||||
|
|||||||
@@ -74,3 +74,17 @@ func TestForgetUsesAgeAndCalendarRetention(t *testing.T) {
|
|||||||
t.Fatalf("disabled daily retention was included: %s", args)
|
t.Fatalf("disabled daily retention was included: %s", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResticErrorExplainsExistingRepository(t *testing.T) {
|
||||||
|
err := resticError("Fatal: create repository failed: config file already exists", fmt.Errorf("exit status 1"))
|
||||||
|
if !strings.Contains(err.Error(), "already initialized") || !strings.Contains(err.Error(), "select Test") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResticErrorExplainsCiphertextVerification(t *testing.T) {
|
||||||
|
err := resticError(`{"message_type":"exit_error","message":"Fatal: config or key abc is damaged: ciphertext verification failed"}`, fmt.Errorf("exit status 1"))
|
||||||
|
if !strings.Contains(err.Error(), "password does not match") || strings.Contains(err.Error(), "abc") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "backupper">
|
<!ENTITY name "backupper">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.06.14.9.6">
|
<!ENTITY version "2026.06.14.9.7">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
<!ENTITY pluginURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper.plg">
|
||||||
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
<!ENTITY packageURL "https://git.casaderoll.de/michael/BackUpper/raw/branch/main/dist/backupper-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues" icon="backupper.png">
|
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/BackUpper/issues" icon="backupper.png">
|
||||||
<CHANGES>
|
<CHANGES>
|
||||||
|
### 2026.06.14.9.7
|
||||||
|
- Explain already initialized repositories and incorrect or damaged Restic keys with actionable errors.
|
||||||
|
- Strengthen the Initialize confirmation for existing repository destinations.
|
||||||
|
|
||||||
### 2026.06.14.9.6
|
### 2026.06.14.9.6
|
||||||
- Hide technical secret IDs and manage them automatically for repositories, SMB, SFTP, and Gotify.
|
- Hide technical secret IDs and manage them automatically for repositories, SMB, SFTP, and Gotify.
|
||||||
- Require a Restic password when creating a new repository while preserving existing secrets during edits.
|
- Require a Restic password when creating a new repository while preserving existing secrets during edits.
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
|
|||||||
<?php
|
<?php
|
||||||
$pluginRoot = '/plugins/backupper';
|
$pluginRoot = '/plugins/backupper';
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=2026061496">
|
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/backupper.css?v=2026061497">
|
||||||
<div id="backupper-app" data-api="<?= $pluginRoot ?>/api.php">
|
<div id="backupper-app" data-api="<?= $pluginRoot ?>/api.php">
|
||||||
<header class="bu-header">
|
<header class="bu-header">
|
||||||
<div class="bu-brand">
|
<div class="bu-brand">
|
||||||
<img class="bu-logo" src="<?= $pluginRoot ?>/images/backupper.png?v=2026061496" alt="URBM logo">
|
<img class="bu-logo" src="<?= $pluginRoot ?>/images/backupper.png?v=2026061497" alt="URBM logo">
|
||||||
<div><h1>URBM <span class="bu-version">2026.06.14.9.6</span></h1><p>Restic Backup Manager for Unraid</p></div>
|
<div><h1>URBM <span class="bu-version">2026.06.14.9.7</span></h1><p>Restic Backup Manager for Unraid</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Connecting...</div>
|
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Connecting...</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/backupper';
|
|||||||
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>window.BACKUPPER_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
<script>window.BACKUPPER_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
|
||||||
<script src="<?= $pluginRoot ?>/assets/backupper-2026.06.14.9.6.js"></script>
|
<script src="<?= $pluginRoot ?>/assets/backupper-2026.06.14.9.7.js"></script>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
'submit-job': 'Prüft und speichert diesen Backup-Job.',
|
'submit-job': 'Prüft und speichert diesen Backup-Job.',
|
||||||
'new-repo': 'Legt ein neues, isoliertes Restic-Speicherziel an.',
|
'new-repo': 'Legt ein neues, isoliertes Restic-Speicherziel an.',
|
||||||
'submit-repo': 'Speichert Ziel und Zugangsdaten. Das Repository wird dadurch noch nicht automatisch initialisiert.',
|
'submit-repo': 'Speichert Ziel und Zugangsdaten. Das Repository wird dadurch noch nicht automatisch initialisiert.',
|
||||||
'init-repo': 'Initialisiert am konfigurierten Ziel ein neues, leeres Restic-Repository. Beispiel: nach dem erstmaligen Speichern eines neuen Backup-Ziels. Nicht für ein bereits vorhandenes Repository verwenden.',
|
'init-repo': 'Initialisiert am Ziel einmalig ein neues, leeres Restic-Repository. Bei einem vorhandenen Repository nur das ursprüngliche Passwort eintragen und Test verwenden.',
|
||||||
'load-snapshots': 'Lädt alle Sicherungsstände des ausgewählten Repositorys.',
|
'load-snapshots': 'Lädt alle Sicherungsstände des ausgewählten Repositorys.',
|
||||||
'restore-selected': 'Übernimmt die markierten Dateien oder Ordner in den Restore-Assistenten.',
|
'restore-selected': 'Übernimmt die markierten Dateien oder Ordner in den Restore-Assistenten.',
|
||||||
'submit-restore': 'Stellt den Restore in die globale Warteschlange; Backups und Restores laufen nacheinander.',
|
'submit-restore': 'Stellt den Restore in die globale Warteschlange; Backups und Restores laufen nacheinander.',
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
'duplicate-job': 'Erstellt eine deaktivierte Kopie als Vorlage. Danach Name, Repository und Quellen anpassen.',
|
'duplicate-job': 'Erstellt eine deaktivierte Kopie als Vorlage. Danach Name, Repository und Quellen anpassen.',
|
||||||
'delete-job': 'Löscht die Job-Konfiguration. Vorhandene Restic-Snapshots im Repository bleiben erhalten.',
|
'delete-job': 'Löscht die Job-Konfiguration. Vorhandene Restic-Snapshots im Repository bleiben erhalten.',
|
||||||
'test-repo': 'Prüft Erreichbarkeit, Passwort und Restic-Zugriff, ohne ein Backup zu starten.',
|
'test-repo': 'Prüft Erreichbarkeit, Passwort und Restic-Zugriff, ohne ein Backup zu starten.',
|
||||||
'init-repo': 'Erstellt die Restic-Struktur am Speicherziel. Diese Aktion ist einmalig für ein neues, leeres Ziel erforderlich.',
|
'init-repo': 'Erstellt einmalig ein neues Restic-Repository. Nicht erneut ausführen und nicht für vorhandene Restic-Repositories verwenden.',
|
||||||
'repo-snapshots': 'Wechselt zu den Sicherungsständen dieses Repositorys.',
|
'repo-snapshots': 'Wechselt zu den Sicherungsständen dieses Repositorys.',
|
||||||
'maint': action.includes(':check:') ? 'Prüft Struktur und Integrität des Restic-Repositorys.' : 'Entfernt nach Retention nicht mehr benötigte Datenblöcke und gibt Speicher frei.',
|
'maint': action.includes(':check:') ? 'Prüft Struktur und Integrität des Restic-Repositorys.' : 'Entfernt nach Retention nicht mehr benötigte Datenblöcke und gibt Speicher frei.',
|
||||||
'edit-repo': 'Öffnet Ziel-, Mount- und Zugangseinstellungen dieses Repositorys.',
|
'edit-repo': 'Öffnet Ziel-, Mount- und Zugangseinstellungen dieses Repositorys.',
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
if (name === 'run-job') { await api(`/v1/jobs/${a}/run`,{method:'POST'}); notify('Backup queued'); return load(); }
|
if (name === 'run-job') { await api(`/v1/jobs/${a}/run`,{method:'POST'}); notify('Backup queued'); return load(); }
|
||||||
if (name === 'cancel-run') { await api(`/v1/runs/${a}/cancel`,{method:'POST'}); return load(); }
|
if (name === 'cancel-run') { await api(`/v1/runs/${a}/cancel`,{method:'POST'}); return load(); }
|
||||||
if (name === 'test-repo') { await api(`/v1/repositories/${a}/test`,{method:'POST'}); return notify('Repository connection succeeded'); }
|
if (name === 'test-repo') { await api(`/v1/repositories/${a}/test`,{method:'POST'}); return notify('Repository connection succeeded'); }
|
||||||
if (name === 'init-repo') { if(confirm('Initialize a new Restic repository at this destination? Use this only for a new, empty backup destination.')) { await api(`/v1/repositories/${a}/init`,{method:'POST'}); notify('Repository initialized successfully'); } return; }
|
if (name === 'init-repo') { if(confirm('Create a NEW Restic repository here?\n\nOnly continue if this destination does not already contain a Restic repository. For an existing repository, enter its original password under Edit and use Test.')) { await api(`/v1/repositories/${a}/init`,{method:'POST'}); notify('Repository initialized successfully'); } return; }
|
||||||
if (name === 'maint') { await api(`/v1/repositories/${b}/${a}`,{method:'POST'}); notify(`${a} queued`); return load(); }
|
if (name === 'maint') { await api(`/v1/repositories/${b}/${a}`,{method:'POST'}); notify(`${a} queued`); return load(); }
|
||||||
if (name === 'repo-snapshots') { state.view='snapshots'; document.querySelector('[data-view="snapshots"]').click(); render(); setTimeout(()=>{document.getElementById('bu-snapshot-repo').value=a; handle('load-snapshots');},0); return; }
|
if (name === 'repo-snapshots') { state.view='snapshots'; document.querySelector('[data-view="snapshots"]').click(); render(); setTimeout(()=>{document.getElementById('bu-snapshot-repo').value=a; handle('load-snapshots');},0); return; }
|
||||||
if (name === 'load-snapshots') { const repo=document.getElementById('bu-snapshot-repo').value; state.snapshots=await api(`/v1/repositories/${repo}/snapshots`); state.snapshotRepo=repo; return render(); }
|
if (name === 'load-snapshots') { const repo=document.getElementById('bu-snapshot-repo').value; state.snapshots=await api(`/v1/repositories/${repo}/snapshots`); state.snapshotRepo=repo; return render(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user