Support attaching existing Restic repositories
This commit is contained in:
+15
-6
@@ -57,6 +57,7 @@
|
||||
'new-job': 'Öffnet den Assistenten für einen neuen Backup-Job. Beispiel: tägliches Appdata-Backup.',
|
||||
'submit-job': 'Prüft und speichert diesen Backup-Job.',
|
||||
'new-repo': 'Legt ein neues, isoliertes Restic-Speicherziel an.',
|
||||
'existing-repo': 'Bindet ein bereits initialisiertes Restic-Repository mit seinem ursprünglichen Passwort ein. URBM initialisiert oder überschreibt es nicht.',
|
||||
'submit-repo': 'Speichert Ziel und Zugangsdaten. Das Repository wird dadurch noch nicht automatisch initialisiert.',
|
||||
'init-repo': 'Initialisiert am Ziel einmalig ein neues, leeres Restic-Repository. Bei einem vorhandenen Repository nur das ursprüngliche Passwort eintragen und Test verwenden.',
|
||||
'unlock-repo': 'Entfernt ausschließlich von Restic als veraltet erkannte Repository-Sperren. Aktive Sperren werden nicht gelöscht.',
|
||||
@@ -445,18 +446,19 @@
|
||||
const actions = initialize+button('Testen',`test-repo:${r.id}`)+button('Entsperren',`unlock-repo:${r.id}`)+button('Snapshots',`repo-snapshots:${r.id}`)+button('Prüfen',`maint:check:${r.id}`)+button('Bereinigen',`maint:prune:${r.id}`)+button('Bearbeiten',`edit-repo:${r.id}`)+button('Löschen',`delete-repo:${r.id}`,'danger');
|
||||
return `<tr><td><strong>${esc(r.name)}</strong><br><span class="bu-muted">${esc(r.id)}</span></td><td>${esc(typeLabels[r.type]||r.type)}</td><td>${esc(r.location)}${volatileLocation(r)?'<span class="bu-warning">Flüchtiger Pfad: Die Daten gehen beim Neustart verloren. Verwende /mnt/user/...</span>':''}</td><td>${r.mount?.managed?'Durch URBM':'Direkt / extern'}</td><td>${actionGroup(actions)}</td></tr>`;
|
||||
}).join('');
|
||||
return `<div class="bu-toolbar"><div><h2>Repositorys</h2><div class="bu-muted">Lokale, SMB-, NFS- und SFTP-Ziele.</div></div>${button('Neues Repository','new-repo','primary')}</div><section class="bu-card">${rows ? `<table class="bu-table"><thead><tr><th>Name</th><th>Typ</th><th>Speicherort</th><th>Einbindung</th><th>Aktionen</th></tr></thead><tbody>${rows}</tbody></table>` : '<div class="bu-empty">Keine Repositorys konfiguriert.</div>'}</section>`;
|
||||
return `<div class="bu-toolbar"><div><h2>Repositorys</h2><div class="bu-muted">Lokale, SMB-, NFS- und SFTP-Ziele.</div></div><div class="bu-actions">${button('Vorhandenes einbinden','existing-repo')}${button('Neues Repository','new-repo','primary')}</div></div><section class="bu-card">${rows ? `<table class="bu-table"><thead><tr><th>Name</th><th>Typ</th><th>Speicherort</th><th>Einbindung</th><th>Aktionen</th></tr></thead><tbody>${rows}</tbody></table>` : '<div class="bu-empty">Keine Repositorys konfiguriert.</div>'}</section>`;
|
||||
}
|
||||
|
||||
function repositoryForm(repo = {}) {
|
||||
function repositoryForm(repo = {}, existingRepository = false) {
|
||||
state.repositoryBrowserPath = '/'; state.repositoryDirectories = repositoryRoots; state.repositoryBrowserError = '';
|
||||
const passwordRef=repo.passwordRef||id('repo-password');
|
||||
const credentialRef=repo.credentialRef||id('repo-credential');
|
||||
content.innerHTML = `<section class="bu-card"><h2>Repository ${repo.id?'bearbeiten':'erstellen'}</h2><form id="bu-repo-form" class="bu-form"><input type="hidden" name="id" value="${esc(repo.id||'')}"><input type="hidden" name="passwordRef" value="${esc(passwordRef)}"><input type="hidden" name="credentialRef" value="${esc(credentialRef)}"><input type="hidden" name="hasCredential" value="${repo.credentialRef?'true':'false'}">
|
||||
content.innerHTML = `<section class="bu-card"><h2>${repo.id?'Repository bearbeiten':existingRepository?'Vorhandenes Repository einbinden':'Neues Repository erstellen'}</h2><form id="bu-repo-form" class="bu-form"><input type="hidden" name="id" value="${esc(repo.id||'')}"><input type="hidden" name="existingRepository" value="${existingRepository?'true':'false'}"><input type="hidden" name="passwordRef" value="${esc(passwordRef)}"><input type="hidden" name="credentialRef" value="${esc(credentialRef)}"><input type="hidden" name="hasCredential" value="${repo.credentialRef?'true':'false'}">
|
||||
${existingRepository?'<div class="wide bu-inline-info">Wähle den exakten Ordner des vorhandenen Restic-Repositorys. Darin müssen direkt die Einträge <strong>config</strong>, <strong>data</strong>, <strong>index</strong>, <strong>keys</strong> und <strong>snapshots</strong> liegen. Verwende das ursprüngliche Repository-Passwort. Es wird nichts initialisiert.</div>':''}
|
||||
<label>Name<input name="name" required value="${esc(repo.name||'')}"></label><label>Typ<select name="type">${['local','smb','nfs','sftp'].map(v=>`<option value="${v}" ${v===repo.type?'selected':''}>${esc(typeLabels[v])}</option>`).join('')}</select></label>
|
||||
<div id="bu-repository-specific" class="wide"></div>
|
||||
<fieldset class="wide bu-fieldset"><legend>Restic-Verschlüsselung</legend><div class="bu-form-grid"><label>Repository-Passwort<input name="password" type="password" autocomplete="new-password" ${repo.id?'':'required'} placeholder="${repo.id?'Leer lassen, um das vorhandene Passwort zu behalten':'Ein starkes Repository-Passwort eingeben'}"></label><label>Repository-Passwort wiederholen<input name="passwordConfirm" type="password" autocomplete="new-password" ${repo.id?'':'required'} placeholder="${repo.id?'Bei einer Passwortänderung erneut eingeben':'Repository-Passwort erneut eingeben'}"></label></div><p class="bu-muted">Das Passwort wird von URBM geschützt gespeichert. ${repo.id?'Beide Felder leer lassen, um das vorhandene Passwort zu behalten.':'Bewahre dieses Passwort sicher auf; ohne das Passwort können Backups nicht wiederhergestellt werden.'}</p></fieldset>
|
||||
<div class="wide bu-actions">${button('Speichern','submit-repo','primary')}${button('Abbrechen','cancel-form')}</div></form></section>`;
|
||||
<div class="wide bu-actions">${button(existingRepository?'Einbinden und testen':'Speichern','submit-repo','primary')}${button('Abbrechen','cancel-form')}</div></form></section>`;
|
||||
enhanceTooltips(content);
|
||||
renderRepositorySpecific(repo.type || 'local', repo);
|
||||
}
|
||||
@@ -578,6 +580,7 @@
|
||||
if (name === 'new-job') return jobForm();
|
||||
if (name === 'edit-job') return jobForm(state.config.jobs.find(j=>j.id===a));
|
||||
if (name === 'new-repo') return repositoryForm();
|
||||
if (name === 'existing-repo') return repositoryForm({}, true);
|
||||
if (name === 'edit-repo') return repositoryForm(state.config.repositories.find(r=>r.id===a));
|
||||
if (name === 'cancel-form') return render();
|
||||
if (name === 'refresh') return load();
|
||||
@@ -624,13 +627,14 @@
|
||||
}
|
||||
if (kind === 'submit-repo') {
|
||||
const f=new FormData(document.getElementById('bu-repo-form')); const value=name=>String(f.get(name)||'').trim(); const type=value('type'); const managed=(type==='smb'||type==='nfs')&&f.get('managed')==='on';
|
||||
const existingRepository=value('existingRepository')==='true';
|
||||
let location=managed?value('mountPoint'):value('location');
|
||||
if(!location) throw new Error(managed?'Ein Einhängepunkt ist erforderlich.':'Ein Repository-Speicherort ist erforderlich.');
|
||||
if(managed&&!value('remote')) throw new Error('Für eine verwaltete Einbindung ist eine Netzwerkfreigabe erforderlich.');
|
||||
const usesCredential=type==='sftp'||(type==='smb'&&managed); const credentialRef=usesCredential&&(value('hasCredential')==='true'||Boolean(f.get('credential')))?value('credentialRef'):'';
|
||||
const repo={schemaVersion:1,id:value('id')||id('repo'),name:value('name'),type,location,passwordRef:value('passwordRef'),credentialRef,mount:managed?{managed:true,remote:value('remote'),mountPoint:value('mountPoint')}:null,options:type==='sftp'?{knownHostsPath:value('knownHostsPath')}: {}};
|
||||
const password=String(f.get('password')||''); const passwordConfirm=String(f.get('passwordConfirm')||'');
|
||||
if(!value('id')&&!password) throw new Error('Für ein neues Repository ist ein Passwort erforderlich.');
|
||||
if(!value('id')&&!password) throw new Error(existingRepository?'Zum Einbinden ist das ursprüngliche Repository-Passwort erforderlich.':'Für ein neues Repository ist ein Passwort erforderlich.');
|
||||
if(password!==passwordConfirm) throw new Error('Die beiden Repository-Passwörter stimmen nicht überein.');
|
||||
if (volatileLocation(repo) && !confirm('WARNUNG: Dieses Ziel liegt unter /tmp oder /run und geht nach einem Unraid-Neustart verloren. Trotzdem speichern?')) return;
|
||||
if(password) await api(`/v1/secrets/${encodeURIComponent(repo.passwordRef)}`,{method:'PUT',body:JSON.stringify({type:'restic-password',value:password})});
|
||||
@@ -640,7 +644,12 @@
|
||||
state.config = await api('/v1/config');
|
||||
const saved = state.config.repositories.find(r=>r.id===repo.id);
|
||||
if (!saved || saved.location !== repo.location) throw new Error(`Repository-Speicherort wurde nicht gespeichert. Erwartet: ${repo.location}`);
|
||||
render(); notify(`Repository gespeichert: ${saved.location}`);
|
||||
if(existingRepository) {
|
||||
await api(`/v1/repositories/${repo.id}/test`,{method:'POST'});
|
||||
render(); notify(`Vorhandenes Repository erfolgreich eingebunden: ${saved.location}`);
|
||||
} else {
|
||||
render(); notify(`Repository gespeichert: ${saved.location}`);
|
||||
}
|
||||
}
|
||||
if (kind === 'submit-restore') { const f=new FormData(document.getElementById('bu-restore-form')); await api('/v1/restores',{method:'POST',body:JSON.stringify({schemaVersion:1,id:'',repositoryId:f.get('repositoryId'),snapshotId:f.get('snapshotId'),includes:lines(f.get('includes')),target:f.get('target'),inPlace:f.get('inPlace')==='on',confirmed:f.get('confirmed')==='on'})}); notify('Wiederherstellung wurde eingereiht'); state.view='activity'; render(); }
|
||||
if (kind === 'submit-settings') { const f=new FormData(document.getElementById('bu-settings-form')); state.config.settings={resticPath:f.get('resticPath'),restoreRoot:f.get('restoreRoot'),persistentLogDir:f.get('persistentLogDir'),showLiveLog:f.get('showLiveLog')==='on',catchUpWindowHours:Number(f.get('catchUp')),checkCron:f.get('checkCron'),pruneCron:f.get('pruneCron')}; await saveConfig(); render(); }
|
||||
|
||||
Reference in New Issue
Block a user