diff --git a/dist/backupper-0.1.7-x86_64-1.txz.sha256 b/dist/backupper-0.1.7-x86_64-1.txz.sha256 deleted file mode 100644 index 3dfbf73..0000000 --- a/dist/backupper-0.1.7-x86_64-1.txz.sha256 +++ /dev/null @@ -1 +0,0 @@ -39bbc54f47b643569b81d1d1fc7b65c49b8387ea03d8d8889c8db1427fbfafe4 backupper-0.1.7-x86_64-1.txz diff --git a/dist/backupper-0.1.7-x86_64-1.txz b/dist/backupper-0.1.8-x86_64-1.txz similarity index 55% rename from dist/backupper-0.1.7-x86_64-1.txz rename to dist/backupper-0.1.8-x86_64-1.txz index 21cbe87..6c2eaf7 100644 Binary files a/dist/backupper-0.1.7-x86_64-1.txz and b/dist/backupper-0.1.8-x86_64-1.txz differ diff --git a/dist/backupper-0.1.8-x86_64-1.txz.sha256 b/dist/backupper-0.1.8-x86_64-1.txz.sha256 new file mode 100644 index 0000000..00a3135 --- /dev/null +++ b/dist/backupper-0.1.8-x86_64-1.txz.sha256 @@ -0,0 +1 @@ +3b3bfce71438a9489a978405ff88fe8c7013f7c90962387ff5e51453d060d6cb backupper-0.1.8-x86_64-1.txz diff --git a/dist/backupper.plg b/dist/backupper.plg index 81773ec..1af2224 100644 --- a/dist/backupper.plg +++ b/dist/backupper.plg @@ -2,13 +2,16 @@ - + - + ]> +### 0.1.8 +- Force a fresh WebGUI asset after updates, display the installed version, and warn about volatile /tmp or /run repositories. + ### 0.1.7 - Add an explicit repository initialization action and a clear error for uninitialized destinations. diff --git a/packaging/build-package.sh b/packaging/build-package.sh index daea870..bb58a4f 100755 --- a/packaging/build-package.sh +++ b/packaging/build-package.sh @@ -31,6 +31,7 @@ chmod 0755 "$STAGE/usr/local/libexec/backupper/restic" "$STAGE/usr/local/sbin/ba cp "$ROOT/webgui/Backupper.page" "$STAGE/usr/local/emhttp/plugins/backupper/" cp "$ROOT/webgui/api.php" "$STAGE/usr/local/emhttp/plugins/backupper/" cp "$ROOT/webgui/assets/"* "$STAGE/usr/local/emhttp/plugins/backupper/assets/" +cp "$ROOT/webgui/assets/backupper.js" "$STAGE/usr/local/emhttp/plugins/backupper/assets/backupper-${VERSION}.js" cp "$ROOT/plugin/rc.backupper" "$STAGE/etc/rc.d/rc.backupper" cp "$ROOT/plugin/doinst.sh" "$ROOT/plugin/slack-desc" "$STAGE/install/" chmod 0755 "$STAGE/etc/rc.d/rc.backupper" "$STAGE/install/doinst.sh" diff --git a/plugin/backupper.plg b/plugin/backupper.plg index 8ee198a..5edd547 100644 --- a/plugin/backupper.plg +++ b/plugin/backupper.plg @@ -2,13 +2,16 @@ - + ]> +### 0.1.8 +- Force a fresh WebGUI asset after updates, display the installed version, and warn about volatile /tmp or /run repositories. + ### 0.1.7 - Add an explicit repository initialization action and a clear error for uninitialized destinations. diff --git a/webgui/Backupper.page b/webgui/Backupper.page index 4df712c..21bdf60 100644 --- a/webgui/Backupper.page +++ b/webgui/Backupper.page @@ -9,10 +9,10 @@ Tag="backup restic snapshots restore" - +
-

Backupper

Encrypted Restic backups for Unraid

+

Backupper 0.1.8

Encrypted Restic backups for Unraid

Connecting...
- + diff --git a/webgui/assets/backupper.css b/webgui/assets/backupper.css index 5a4ed12..fef038f 100644 --- a/webgui/assets/backupper.css +++ b/webgui/assets/backupper.css @@ -22,6 +22,7 @@ .bu-header { align-items: center; display: flex; justify-content: space-between; margin: 6px 0 22px; } #backupper-app .bu-header h1 { color: var(--bu-text) !important; font-size: 30px; font-weight: 700; line-height: 1.2; margin: 0; } +.bu-version { border: 1px solid var(--bu-border); border-radius: 999px; display: inline-block; font-size: 12px; font-weight: 600; padding: 2px 7px; vertical-align: middle; } #backupper-app .bu-header p { color: var(--bu-muted) !important; font-size: 16px; margin: 5px 0 0; opacity: .78; } .bu-health { background: var(--bu-panel); border: 1px solid var(--bu-border); border-radius: 999px; color: var(--bu-text); font-weight: 600; padding: 9px 15px; } .bu-health.ok { color: var(--bu-good); } @@ -59,6 +60,7 @@ #backupper-app .bu-muted { color: var(--bu-muted) !important; opacity: .72; } .bu-toolbar { align-items: center; display: flex; flex-wrap: wrap; gap: 8px; justify-content: space-between; margin-bottom: 14px; } .bu-actions { display: flex; flex-wrap: wrap; gap: 7px; } +.bu-warning { color: var(--bu-bad) !important; display: block; font-size: 13px; font-weight: 700; margin-top: 5px; } #backupper-app .bu-button { appearance: none; diff --git a/webgui/assets/backupper.js b/webgui/assets/backupper.js index 9e86fc6..a15f6b7 100644 --- a/webgui/assets/backupper.js +++ b/webgui/assets/backupper.js @@ -79,6 +79,7 @@ }; const id = (prefix) => `${prefix}-${crypto.randomUUID()}`; const repoName = (repoId) => state.config.repositories.find(r => r.id === repoId)?.name || repoId; + const volatileLocation = (repo) => repo.type === 'local' && (/^\/tmp(?:\/|$)/.test(repo.location) || /^\/run(?:\/|$)/.test(repo.location)); const tipAttr = (text) => text ? ` data-tooltip="${esc(text)}"` : ''; const actionTooltip = (action, label) => { const [name] = action.split(':'); @@ -227,7 +228,7 @@ } function renderRepositories() { - const rows = state.config.repositories.map(r => `${esc(r.name)}
${esc(r.id)}${esc(r.type)}${esc(r.location)}${r.mount?.managed?'Managed':'Direct / external'}${button('Initialize',`init-repo:${r.id}`,'primary')}${button('Test',`test-repo:${r.id}`)}${button('Snapshots',`repo-snapshots:${r.id}`)}${button('Check',`maint:check:${r.id}`)}${button('Prune',`maint:prune:${r.id}`)}${button('Edit',`edit-repo:${r.id}`)}${button('Delete',`delete-repo:${r.id}`,'danger')}`).join(''); + const rows = state.config.repositories.map(r => `${esc(r.name)}
${esc(r.id)}${esc(r.type)}${esc(r.location)}${volatileLocation(r)?'Volatile path: data is lost after reboot. Use /mnt/user/...':''}${r.mount?.managed?'Managed':'Direct / external'}${button('Initialize',`init-repo:${r.id}`,'primary')}${button('Test',`test-repo:${r.id}`)}${button('Snapshots',`repo-snapshots:${r.id}`)}${button('Check',`maint:check:${r.id}`)}${button('Prune',`maint:prune:${r.id}`)}${button('Edit',`edit-repo:${r.id}`)}${button('Delete',`delete-repo:${r.id}`,'danger')}`).join(''); return `

Repositories

Local, SMB, NFS and SFTP destinations.
${button('New repository','new-repo','primary')}
${rows ? `${rows}
NameTypeLocationMountActions
` : '
No repositories configured.
'}
`; } @@ -311,6 +312,7 @@ } if (kind === 'submit-repo') { const f=new FormData(document.getElementById('bu-repo-form')); const repo={schemaVersion:1,id:f.get('id')||id('repo'),name:f.get('name'),type:f.get('type'),location:f.get('location'),passwordRef:f.get('passwordRef'),credentialRef:f.get('credentialRef')||'',mount:f.get('managed')==='on'?{managed:true,remote:f.get('remote'),mountPoint:f.get('mountPoint')}:null,options:f.get('type')==='sftp'?{knownHostsPath:f.get('knownHostsPath')}:{}}; + if (volatileLocation(repo) && !confirm('WARNING: This destination is under /tmp or /run and will be lost after an Unraid reboot. Save it anyway?')) return; if(f.get('password')) await api(`/v1/secrets/${encodeURIComponent(repo.passwordRef)}`,{method:'PUT',body:JSON.stringify({type:'restic-password',value:f.get('password')})}); if(f.get('credential')) { if(!repo.credentialRef) repo.credentialRef=id('mount-credential'); await api(`/v1/secrets/${encodeURIComponent(repo.credentialRef)}`,{method:'PUT',body:JSON.stringify({type:'mount-credential',value:f.get('credential')})}); } state.config.repositories=state.config.repositories.filter(r=>r.id!==repo.id).concat(repo); await saveConfig(); render();