Release URBM 2026.06.15.r010

This commit is contained in:
Mikei386
2026-06-15 07:24:15 +02:00
parent 772358578e
commit 1e9487e6f2
9 changed files with 71 additions and 9 deletions
-1
View File
@@ -1 +0,0 @@
648afa0c0b1348302f882422c5a199e56ecdcdc0f817bc9a402c1a73549bbeea urbm-2026.06.15.r009-x86_64-1.txz
+1
View File
@@ -0,0 +1 @@
7d5a1c417b66e7c318ccc182b61b48fc725dd561e829e34ff593d88434fa334a urbm-2026.06.15.r010-x86_64-1.txz
+6 -2
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [ <!DOCTYPE PLUGIN [
<!ENTITY name "urbm"> <!ENTITY name "urbm">
<!ENTITY author "Michael Roll"> <!ENTITY author "Michael Roll">
<!ENTITY version "2026.06.15.r009"> <!ENTITY version "2026.06.15.r010">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg"> <!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz"> <!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "648afa0c0b1348302f882422c5a199e56ecdcdc0f817bc9a402c1a73549bbeea"> <!ENTITY packageSHA256 "7d5a1c417b66e7c318ccc182b61b48fc725dd561e829e34ff593d88434fa334a">
]> ]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png"> <PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES> <CHANGES>
### 2026.06.15.r010
- Retry repository dashboard statistics once after safely removing stale Restic locks.
- Show the affected repository name and concrete Restic error when statistics still cannot be loaded.
### 2026.06.15.r009 ### 2026.06.15.r009
- Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting. - Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting.
- Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks. - Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks.
+11
View File
@@ -202,6 +202,17 @@ func (r *Runner) Stats(ctx context.Context, repo model.Repository, mode string)
if mode != "" { if mode != "" {
args = append(args, "--mode", mode) args = append(args, "--mode", mode)
} }
stats, err := r.stats(ctx, repo, args)
if err == nil || !isRepositoryLocked(err) {
return stats, err
}
if unlockErr := r.Unlock(ctx, repo); unlockErr != nil {
return Stats{}, fmt.Errorf("repository statistics locked; remove stale lock: %w", unlockErr)
}
return r.stats(ctx, repo, args)
}
func (r *Runner) stats(ctx context.Context, repo model.Repository, args []string) (Stats, error) {
var output []byte var output []byte
if err := r.run(ctx, repo, args, nil, &output); err != nil { if err := r.run(ctx, repo, args, nil, &output); err != nil {
return Stats{}, err return Stats{}, err
+43
View File
@@ -130,6 +130,49 @@ func TestStatsUsesRequestedRepositoryCountingMode(t *testing.T) {
} }
} }
func TestStatsUnlocksStaleRepositoryLockAndRetries(t *testing.T) {
dir := t.TempDir()
logPath := filepath.Join(dir, "commands")
countPath := filepath.Join(dir, "stats-count")
script := filepath.Join(dir, "restic")
body := fmt.Sprintf(`#!/bin/sh
printf '%%s\n' "$*" >> '%s'
case " $* " in
*" stats "*)
count=0
[ -f '%s' ] && count=$(cat '%s')
count=$((count + 1))
printf '%%s' "$count" > '%s'
if [ "$count" -eq 1 ]; then
printf 'Fatal: unable to create lock in backend: repository is already locked\n' >&2
exit 11
fi
printf '%%s\n' '{"total_size":4096,"total_file_count":12}'
;;
esac
`, logPath, countPath, countPath, countPath)
if err := os.WriteFile(script, []byte(body), 0700); err != nil {
t.Fatal(err)
}
runner := &Runner{Binary: script, RuntimeDir: dir, Secrets: fakeSecrets{"password": "secret"}}
repo := model.Repository{Type: model.RepositoryLocal, Location: "/repo", PasswordRef: "password"}
stats, err := runner.Stats(context.Background(), repo, "raw-data")
if err != nil {
t.Fatal(err)
}
if stats.TotalSize != 4096 {
t.Fatalf("unexpected stats: %+v", stats)
}
commands, err := os.ReadFile(logPath)
if err != nil {
t.Fatal(err)
}
lines := strings.Split(strings.TrimSpace(string(commands)), "\n")
if len(lines) != 3 || !strings.Contains(lines[0], "stats") || !strings.Contains(lines[1], "unlock") || !strings.Contains(lines[2], "stats") {
t.Fatalf("expected stats, unlock, stats; got %q", lines)
}
}
func TestForgetUsesAgeAndCalendarRetention(t *testing.T) { func TestForgetUsesAgeAndCalendarRetention(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
argsPath := filepath.Join(dir, "args") argsPath := filepath.Join(dir, "args")
+5 -1
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [ <!DOCTYPE PLUGIN [
<!ENTITY name "urbm"> <!ENTITY name "urbm">
<!ENTITY author "Michael Roll"> <!ENTITY author "Michael Roll">
<!ENTITY version "2026.06.15.r009"> <!ENTITY version "2026.06.15.r010">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg"> <!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz"> <!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&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/URBM/issues" icon="urbm.png"> <PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES> <CHANGES>
### 2026.06.15.r010
- Retry repository dashboard statistics once after safely removing stale Restic locks.
- Show the affected repository name and concrete Restic error when statistics still cannot be loaded.
### 2026.06.15.r009 ### 2026.06.15.r009
- Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting. - Add a per-backup-job CPU core limit using Restic's GOMAXPROCS setting.
- Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks. - Apply the configured limit to normal backups and optional USB raw-image backups without affecting restore or maintenance tasks.
+4 -4
View File
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
<?php <?php
$pluginRoot = '/plugins/urbm'; $pluginRoot = '/plugins/urbm';
?> ?>
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r009"> <link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260615r010">
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php"> <div id="urbm-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/urbm.png?v=20260615r009" alt="URBM-Logo"> <img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260615r010" alt="URBM-Logo">
<div><h1>URBM <span class="bu-version">2026.06.15.r009</span></h1><p>Restic Backup Manager für Unraid</p></div> <div><h1>URBM <span class="bu-version">2026.06.15.r010</span></h1><p>Restic Backup Manager für 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.">Verbindung wird hergestellt...</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.">Verbindung wird hergestellt...</div>
</header> </header>
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div> <div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
</div> </div>
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script> <script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r009.js"></script> <script src="<?= $pluginRoot ?>/assets/urbm-2026.06.15.r010.js"></script>
+1 -1
View File
@@ -338,7 +338,7 @@
const last = [...state.runs].reverse().slice(0, 8); const last = [...state.runs].reverse().slice(0, 8);
const stats=state.repositoryStats; const stats=state.repositoryStats;
const statsErrors=stats.filter(item=>item.error); const statsErrors=stats.filter(item=>item.error);
const loading=state.repositoryStatsLoading?'<div class="bu-muted">Repository-Werte werden geladen…</div>':statsErrors.length?`<div class="bu-warning">${statsErrors.length} Repository(s) konnten nicht gelesen werden.</div>`:''; const loading=state.repositoryStatsLoading?'<div class="bu-muted">Repository-Werte werden geladen…</div>':statsErrors.length?`<div class="bu-warning">${statsErrors.map(item=>`${esc(item.repositoryName)}: ${esc(displayMessage(item.error))}`).join('<br>')}</div>`:'';
return `<div class="bu-grid"> return `<div class="bu-grid">
<section class="bu-card"><div class="bu-muted">Aktive Jobs</div><div class="bu-stat">${state.config.jobs.filter(j => j.enabled).length}</div></section> <section class="bu-card"><div class="bu-muted">Aktive Jobs</div><div class="bu-stat">${state.config.jobs.filter(j => j.enabled).length}</div></section>
<section class="bu-card"><div class="bu-muted">Repositorys</div><div class="bu-stat">${state.config.repositories.length}</div></section> <section class="bu-card"><div class="bu-muted">Repositorys</div><div class="bu-stat">${state.config.repositories.length}</div></section>