Release URBM 2026.06.15.r010
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
648afa0c0b1348302f882422c5a199e56ecdcdc0f817bc9a402c1a73549bbeea urbm-2026.06.15.r009-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
7d5a1c417b66e7c318ccc182b61b48fc725dd561e829e34ff593d88434fa334a urbm-2026.06.15.r010-x86_64-1.txz
|
||||||
Vendored
+6
-2
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user