Debian 13 runtime with restic 0.18.1 pinned outside the OS; restic assumptions re-proven; felhom-samba 1.2.0 (R-1105, R-972)
gates / gates (push) Successful in 49s
gates / gates (push) Successful in 49s
This commit is contained in:
@@ -12,8 +12,8 @@ import (
|
||||
// TierOffsite capture set (recovery unit + MANDATORY userdata only) into the extra absolute paths
|
||||
// appended to the app's restic snapshot, plus the Hungarian customer warnings for LOUD capture gaps.
|
||||
//
|
||||
// SP-3.4 is law here: restic 0.14.0 does NOT error on a missing source path — it skips with a warning,
|
||||
// exits 0, and silently writes a partial snapshot. So a skipped/missing MANDATORY path is detected in
|
||||
// SP-3.4 is law here: restic does NOT error on a missing source path — it skips with a warning, exits 0,
|
||||
// and silently writes a partial snapshot (0.14.0, and re-measured on 0.18.1 for R-1105, 2026-10-16). So a skipped/missing MANDATORY path is detected in
|
||||
// THIS function (the structural-guard Skipped list + an os.Stat filter) and surfaced in BOTH the
|
||||
// English log and the Hungarian LastWarning. A restic exit code proves nothing about a missing path.
|
||||
|
||||
|
||||
@@ -63,7 +63,8 @@ type OffboxProgress struct {
|
||||
// message_types (status, summary, error, verbose_status); anything that is not "status" is ignored
|
||||
// here rather than treated as garbage, because restic adds new types between versions and an unknown
|
||||
// type must never break the run. Schema captured from
|
||||
// restic 0.14.0 (the version in the controller image) via a live `backup --dry-run --json`:
|
||||
// restic 0.14.0 via a live `backup --dry-run --json` — and the same field names on 0.18.1, the version the image pins
|
||||
// since v0.323.0 (R-1105; testdata/restic/):
|
||||
//
|
||||
// {"message_type":"status","percent_done":0,"total_files":1,"total_bytes":112}
|
||||
// {"message_type":"status","percent_done":0.558,"total_files":173,"files_done":87,
|
||||
|
||||
@@ -51,9 +51,10 @@ import (
|
||||
// to write. The spike established what "read-only" actually costs here, by observation rather than by
|
||||
// reading the code:
|
||||
//
|
||||
// - `restic restore` in 0.14.0 takes NO repository lock (measured on demo-hp with a lock sampler
|
||||
// that was positively controlled first: it saw a lock appear and vanish across a real `check`, and
|
||||
// zero across two restores);
|
||||
// - `restic restore` WITHOUT `--no-lock` DOES write a lock — corrected 2026-10-16 (R-1105): against a
|
||||
// read-only repository both 0.14.0 and 0.18.1 fail on `Save(<lock/…>)`, and both restore with
|
||||
// `--no-lock`. (The 2026-08 sampler on demo-hp saw zero locks across two restores — a short-lived
|
||||
// lock between samples; the read-only repository cannot miss it.) The flag below is what does it;
|
||||
// - `restic check` DOES take one — so the comment claiming the check never writes is wrong (R-407);
|
||||
// - the PRODUCT's restore path writes anyway, because `unlockStale` runs `restic unlock` — a delete
|
||||
// verb against `locks/` — before every restore (`offbox_restore.go`).
|
||||
@@ -306,9 +307,8 @@ func (m *Manager) nextProofTarget(ctx context.Context) (stack, id, unitPath stri
|
||||
// repository.
|
||||
//
|
||||
// Three differences from `RestoreOffboxScratch`, each deliberate and each named:
|
||||
// - `--no-lock`, so restic does not create a lock file (it does not for `restore` in 0.14.0 anyway
|
||||
// — measured — but the flag makes that a guarantee rather than an observed behaviour of one
|
||||
// version);
|
||||
// - `--no-lock`, so restic does not create a lock file (a plain `restore` DOES, on 0.14.0 and 0.18.1 —
|
||||
// measured against a read-only repository, R-1105; with the flag both versions restore from it);
|
||||
// - no `unlockStale`, so no delete verb is issued against `locks/`;
|
||||
// - `m.runner()` rather than `resticStep`, so the `unlock --remove-all` escalation is unreachable
|
||||
// rather than merely unlikely.
|
||||
|
||||
@@ -82,7 +82,8 @@ var retentionPolicy = []string{"--group-by", "host,tags",
|
||||
"--keep-daily", strconv.Itoa(keepDaily), "--keep-weekly", strconv.Itoa(keepWeekly), "--keep-monthly", strconv.Itoa(keepMonthly)}
|
||||
|
||||
// calendarDaysBefore: how many calendar days s lies before now, both read in s's own zone — the zone
|
||||
// restic 0.14.0 buckets a snapshot's day in (the offset stored with the snapshot). Edge, recorded: in the
|
||||
// restic buckets a snapshot's day in (the offset stored with the snapshot; 0.14.0, and 0.18.1 gives the same plan on a
|
||||
// 502-snapshot, 230-day set made in UTC and Budapest time — R-1105). Edge, recorded: in the
|
||||
// hour after local midnight on a DST change, a box whose snapshots carry two different offsets can read one
|
||||
// day short; the guard then REFUSES (the safe direction) and the next week's window passes.
|
||||
func calendarDaysBefore(s, now time.Time) int {
|
||||
@@ -182,7 +183,9 @@ func (m *Manager) planRemovals(ctx context.Context, base, env []string) ([]guard
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("forget --dry-run: %w: %s", err, truncate(out))
|
||||
}
|
||||
// restic 0.14.0 prints the JSON array on stdout; the runner may combine stderr — take the array.
|
||||
// restic prints the JSON array on stdout; the runner may combine stderr — take the array. (R-1105: on 0.18.1 the
|
||||
// plan can be SMALLER than 0.14.0's — while a --keep-monthly window has unused slots it also keeps each group's
|
||||
// oldest snapshot. Only ever more kept; pinned by TestR1105_TheForgetPlanOnlyEverKeepsMore.)
|
||||
js := string(out)
|
||||
if i := strings.Index(js, "["); i > 0 {
|
||||
js = js[i:]
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// R-1105: the off-site code was measured against restic 0.14.0; v0.323.0 ships 0.18.1. The fixtures in
|
||||
// testdata/restic/<version>/ are REAL outputs of both official binaries, captured 2026-10-16 on one shared scratch
|
||||
// repository (made by 0.14.0, so both versions read the same snapshot set; paths rewritten to /data/x). Each
|
||||
// assumption the code makes about restic's output is asserted for BOTH versions through the code's own functions —
|
||||
// so a future restic that rewords one of them fails here, by name, instead of moving a failure to `unknown` on a
|
||||
// customer's box (07-backup-architecture.md §6.7, §6.8).
|
||||
var r1105Versions = []string{"0.14.0", "0.18.1"}
|
||||
|
||||
func r1105Fixture(t *testing.T, version, name string) []byte {
|
||||
t.Helper()
|
||||
b, err := os.ReadFile(filepath.Join("testdata", "restic", version, name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// fixtureRunner answers each restic subcommand with a captured output; err != nil when restic exited non-zero.
|
||||
func r1105Runner(t *testing.T, version string, answers map[string]string) offboxRunner {
|
||||
return func(_ context.Context, _ []string, args ...string) ([]byte, error) {
|
||||
joined := " " + strings.Join(args, " ") + " "
|
||||
for _, key := range sortedKeys(answers) {
|
||||
if strings.Contains(joined, " "+key+" ") {
|
||||
name := answers[key]
|
||||
var err error
|
||||
if strings.HasPrefix(name, "!") { // restic exited non-zero for this one
|
||||
name, err = name[1:], errors.New("exit status 1")
|
||||
}
|
||||
return r1105Fixture(t, version, name), err
|
||||
}
|
||||
}
|
||||
t.Fatalf("restic %s: no fixture for %q", version, joined)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]string) []string {
|
||||
k := make([]string, 0, len(m))
|
||||
for s := range m {
|
||||
k = append(k, s)
|
||||
}
|
||||
sort.Slice(k, func(i, j int) bool { return len(k[i]) > len(k[j]) }) // the longest (most specific) match first
|
||||
return k
|
||||
}
|
||||
|
||||
// The probe and the failure classifier: each captured failure lands in the class it has always landed in.
|
||||
func TestR1105_EachResticFailureKeepsItsClass(t *testing.T) {
|
||||
cases := []struct {
|
||||
fixture string
|
||||
probe string // classifyResticProbe on `cat config`
|
||||
runClass OffsiteFailureClass // ClassifyOffsiteFailure on the error ensureOffboxRepo returns ("" = not reached)
|
||||
}{
|
||||
{"wrong-password.txt", "orphaned", ""},
|
||||
{"no-repo.txt", "norepo", ""},
|
||||
{"sftp-refused-port.txt", "other", OffsiteFailTransport},
|
||||
{"sftp-refused-key.txt", "other", OffsiteFailTransport},
|
||||
{"sftp-unreachable.txt", "other", OffsiteFailTransport},
|
||||
{"rclone-dead-ssh.txt", "other", OffsiteFailTransport},
|
||||
}
|
||||
for _, v := range r1105Versions {
|
||||
for _, c := range cases {
|
||||
out := r1105Fixture(t, v, c.fixture)
|
||||
if got := classifyResticProbe(out, errors.New("exit status 1")); got != c.probe {
|
||||
t.Errorf("restic %s %s: probe class %q, want %q", v, c.fixture, got, c.probe)
|
||||
}
|
||||
if c.runClass == "" {
|
||||
continue
|
||||
}
|
||||
m, _ := newOffboxManager(t)
|
||||
m.SetOffboxRunner(r1105Runner(t, v, map[string]string{"cat": "!" + c.fixture}))
|
||||
err := m.ensureOffboxRepo(context.Background(), []string{"-r", "x"}, nil)
|
||||
if got := ClassifyOffsiteFailure(err); got != c.runClass {
|
||||
t.Errorf("restic %s %s: run class %q, want %q (err: %v)", v, c.fixture, got, c.runClass, err)
|
||||
}
|
||||
}
|
||||
// A lock left by a crashed run on another hostname (the C2 shape), both forms.
|
||||
for _, f := range []string{"locked-shared.txt", "locked-exclusive.txt"} {
|
||||
out := r1105Fixture(t, v, f)
|
||||
if !offboxLockRe.Match(out) {
|
||||
t.Errorf("restic %s %s: the lock text is no longer recognised — resticStep would not self-heal", v, f)
|
||||
}
|
||||
if got := ClassifyOffsiteFailure(errors.New(string(out))); got != OffsiteFailLocked {
|
||||
t.Errorf("restic %s %s: class %q, want locked", v, f, got)
|
||||
}
|
||||
}
|
||||
// The integrity check: a damaged store reads as damage, a healthy one does not.
|
||||
if !looksLikeRepositoryDamage(r1105Fixture(t, v, "check-damaged.txt")) {
|
||||
t.Errorf("restic %s: a damaged pack is no longer recognised as damage", v)
|
||||
}
|
||||
if looksLikeRepositoryDamage(r1105Fixture(t, v, "check-ok.txt")) {
|
||||
t.Errorf("restic %s: a healthy check reads as damage", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A first run that races another init: „already exists" is still read as success, so the run proceeds.
|
||||
func TestR1105_InitRaceIsStillSuccess(t *testing.T) {
|
||||
for _, v := range r1105Versions {
|
||||
m, _ := newOffboxManager(t)
|
||||
m.SetOffboxRunner(r1105Runner(t, v, map[string]string{"cat": "!no-repo.txt", "init": "!init-exists.txt"}))
|
||||
if err := m.ensureOffboxRepo(context.Background(), []string{"-r", "x"}, nil); err != nil {
|
||||
t.Errorf("restic %s: an init that finds the repository already made must be success, got %v", v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The JSON the code parses: snapshot lists, the newest snapshot of a tag, the sizes, the progress lines.
|
||||
func TestR1105_TheJSONTheCodeReadsStillParses(t *testing.T) {
|
||||
counts := map[string]int{}
|
||||
for _, v := range r1105Versions {
|
||||
m, sett := newOffboxManager(t)
|
||||
m.SetOffboxRunner(r1105Runner(t, v, map[string]string{
|
||||
"snapshots": "snapshots.json", "latest": "snapshots-latest-tag.json",
|
||||
"stats": "stats-id.json", "raw-data": "stats-raw.json",
|
||||
}))
|
||||
snaps, err := m.listGuardSnaps(context.Background(), nil, nil)
|
||||
if err != nil || len(snaps) == 0 {
|
||||
t.Fatalf("restic %s: snapshot list: %d, %v", v, len(snaps), err)
|
||||
}
|
||||
for _, s := range snaps {
|
||||
if s.ID == "" || s.ShortID == "" || s.Time.IsZero() || s.Hostname == "" || len(s.Tags) == 0 {
|
||||
t.Fatalf("restic %s: a snapshot lost a field the guard reads: %+v", v, s)
|
||||
}
|
||||
}
|
||||
counts[v] = len(snaps)
|
||||
id, paths, err := m.offboxLatestSnapshot(context.Background(), "a")
|
||||
if err != nil || id == "" || len(paths) == 0 {
|
||||
t.Errorf("restic %s: newest snapshot of a tag: %q %v %v", v, id, paths, err)
|
||||
}
|
||||
if size, err := m.offboxSnapshotSize(context.Background(), id); err != nil || size <= 0 {
|
||||
t.Errorf("restic %s: snapshot size: %d %v", v, size, err)
|
||||
}
|
||||
n, ok := m.offboxRecordStats(context.Background(), nil, nil)
|
||||
if !ok || n != counts[v] || sett.GetOffboxTarget().RepoSizeBytes <= 0 {
|
||||
t.Errorf("restic %s: repo stats: count %d ok %v size %d", v, n, ok, sett.GetOffboxTarget().RepoSizeBytes)
|
||||
}
|
||||
moved := false
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(r1105Fixture(t, v, "backup-status.jsonl"))), "\n") {
|
||||
p, ok := parseResticStatus(line)
|
||||
if !ok {
|
||||
t.Errorf("restic %s: a status line no longer parses: %s", v, line)
|
||||
}
|
||||
if p.TotalBytes > 0 && p.TotalFiles > 0 {
|
||||
moved = true
|
||||
}
|
||||
}
|
||||
if !moved {
|
||||
t.Errorf("restic %s: no status line carried the totals the progress bar shows", v)
|
||||
}
|
||||
}
|
||||
if counts["0.14.0"] != counts["0.18.1"] {
|
||||
t.Errorf("the two versions list a different number of snapshots of ONE repository: %v", counts)
|
||||
}
|
||||
}
|
||||
|
||||
// The clean-up plan — the ONE assumption that MOVED. On the same snapshot set, 0.18.1 removes a SUBSET of what 0.14.0
|
||||
// removes: since restic 0.17, while a --keep-monthly window has unused slots, restic also keeps the group's OLDEST
|
||||
// snapshot (reason „oldest monthly snapshot"). Measured: a 41-day history → 0.14.0 removes 62, 0.18.1 removes 60 (one
|
||||
// per group); a 230-day history (every monthly slot used) → identical plans (474 of 502, audits/debian13-restic-
|
||||
// 2026-10-16/). It only ever KEEPS more, never removes more, and what it keeps extra is old — so the fake-snapshot
|
||||
// guard (R-822/R-867), which refuses a plan that removes a YOUNG snapshot, passes the 0.18.1 plan whenever it passes
|
||||
// the 0.14.0 one. Pinned here: subset, the difference is exactly the „oldest" keeps, and the guard's verdict holds.
|
||||
func TestR1105_TheForgetPlanOnlyEverKeepsMore(t *testing.T) {
|
||||
plans := map[string]map[string]bool{}
|
||||
var all []guardSnap
|
||||
for _, v := range r1105Versions {
|
||||
m, _ := newOffboxManager(t)
|
||||
m.SetOffboxRunner(r1105Runner(t, v, map[string]string{"forget": "forget-dryrun.json", "snapshots": "snapshots.json"}))
|
||||
plan, err := m.planRemovals(context.Background(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("restic %s: %v", v, err)
|
||||
}
|
||||
plans[v] = map[string]bool{}
|
||||
for _, s := range plan {
|
||||
plans[v][s.ID] = true
|
||||
}
|
||||
// Independent read of the same file: the parser drops nothing restic planned.
|
||||
var groups []struct {
|
||||
Remove []json.RawMessage `json:"remove"`
|
||||
}
|
||||
if err := json.Unmarshal(r1105Fixture(t, v, "forget-dryrun.json"), &groups); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := 0
|
||||
for _, g := range groups {
|
||||
want += len(g.Remove)
|
||||
}
|
||||
if len(plan) != want || want == 0 {
|
||||
t.Errorf("restic %s: the parsed plan holds %d removals, restic planned %d", v, len(plan), want)
|
||||
}
|
||||
// The guard, at the minute the plans were captured: both plans pass it.
|
||||
if all == nil {
|
||||
if all, err = m.listGuardSnaps(context.Background(), nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
captured := time.Date(2026, 10, 16, 18, 20, 0, 0, time.UTC)
|
||||
if _, refuse := offsiteGuard(all, plan, captured, time.Time{}, -1); refuse != "" {
|
||||
t.Errorf("restic %s: the guard refuses the honest plan: %s", v, refuse)
|
||||
}
|
||||
}
|
||||
for id := range plans["0.18.1"] {
|
||||
if !plans["0.14.0"][id] {
|
||||
t.Errorf("0.18.1 removes snapshot %s, which 0.14.0 keeps — the new restic removes MORE", id)
|
||||
}
|
||||
}
|
||||
// Every snapshot only 0.14.0 removes is one 0.18.1 keeps as the „oldest" of a window with unused slots.
|
||||
var groups []struct {
|
||||
Reasons []struct {
|
||||
Snapshot struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"snapshot"`
|
||||
Matches []string `json:"matches"`
|
||||
} `json:"reasons"`
|
||||
}
|
||||
if err := json.Unmarshal(r1105Fixture(t, "0.18.1", "forget-dryrun.json"), &groups); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
why := map[string]string{}
|
||||
for _, g := range groups {
|
||||
for _, r := range g.Reasons {
|
||||
why[r.Snapshot.ID] = strings.Join(r.Matches, ",")
|
||||
}
|
||||
}
|
||||
extra := 0
|
||||
for id := range plans["0.14.0"] {
|
||||
if plans["0.18.1"][id] {
|
||||
continue
|
||||
}
|
||||
extra++
|
||||
if !strings.Contains(why[id], "oldest") {
|
||||
t.Errorf("0.18.1 keeps snapshot %s that 0.14.0 removes, for a reason other than “oldest”: %q", id, why[id])
|
||||
}
|
||||
}
|
||||
if extra == 0 {
|
||||
t.Error("the fixture no longer shows the 0.17+ “oldest” keep — it no longer tests the difference it documents")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"message_type":"status","percent_done":0,"total_files":1,"total_bytes":41943040}
|
||||
{"message_type":"status","percent_done":0.6679942601232657,"total_files":3,"files_done":2,"total_bytes":41943061,"bytes_done":28017724,"current_files":["/data/x/u/a/big.tar"]}
|
||||
@@ -0,0 +1,14 @@
|
||||
using temporary cache in /data/x/cache/restic-check-cache-279810929
|
||||
create exclusive lock for repository
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
error for tree 49327794:
|
||||
decrypting blob 49327794eaad1ff31fb67ceb6bad31d812456993a7543d5fdf7ec8dad187e156 failed: ciphertext verification failed
|
||||
[0:00] 100.00% 82 / 82 snapshots
|
||||
|
||||
read all data
|
||||
Pack ID does not match, want fc6016f0ad5e780ab7d800bd3012c8a602eb1dc9e291dedd61ea3d027befae72, got 39ddedb2fc2555d1e386d3eb7c440142ec3d7c02484f3d10db46105343393948
|
||||
[0:00] 100.00% 123 / 123 packs
|
||||
|
||||
Fatal: repository contains errors
|
||||
@@ -0,0 +1,8 @@
|
||||
using temporary cache in /data/x/cache/restic-check-cache-1995998052
|
||||
create exclusive lock for repository
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
[0:00] 100.00% 82 / 82 snapshots
|
||||
|
||||
no errors were found
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
Fatal: create repository at /data/x/repo failed: config file already exists
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
unable to create lock in backend: repository is already locked exclusively by PID 2773051 on other-box by root (UID 0, GID 0)
|
||||
lock was created at 2026-10-16 18:20:13 (1.310283004s ago)
|
||||
storage ID 8cbd92f5
|
||||
the `unlock` command can be used to remove stale locks
|
||||
@@ -0,0 +1,4 @@
|
||||
unable to create lock in backend: repository is already locked by PID 2772696 on other-box by root (UID 0, GID 0)
|
||||
lock was created at 2026-10-16 18:20:11 (1.294783283s ago)
|
||||
storage ID a2cb4ce8
|
||||
the `unlock` command can be used to remove stale locks
|
||||
@@ -0,0 +1,3 @@
|
||||
Fatal: unable to open config file: stat /data/x/nothing-here/config: no such file or directory
|
||||
Is there a repository at the following location?
|
||||
/data/x/nothing-here
|
||||
@@ -0,0 +1,2 @@
|
||||
rclone: ssh: connect to host 127.0.0.1 port 1: Connection refused
|
||||
Fatal: unable to open repository at rclone:x: error talking HTTP to rclone: Get "http://localhost/file-5577006791947779410": unexpected EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: u@127.0.0.1: Permission denied (publickey,password).
|
||||
Fatal: unable to open repository at sftp:u@127.0.0.1:/x: unable to start the sftp session, error: EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: ssh: connect to host 127.0.0.1 port 1: Connection refused
|
||||
Fatal: unable to open repository at sftp:u@127.0.0.1:/x: unable to start the sftp session, error: EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: ssh: connect to host 10.255.255.1 port 22: Connection timed out
|
||||
Fatal: unable to open repository at sftp:u@10.255.255.1:/x: unable to start the sftp session, error: EOF
|
||||
@@ -0,0 +1 @@
|
||||
[{"time":"2026-10-16T02:30:00Z","parent":"16f5cbf1aab3d6aaf201e9cf15f90b780e5db3854751f745883a6bd6e707d2dd","tree":"286f8da9b1b553de264161006b6f14be594a3eef691d3980954808cf02387f21","paths":["/data/x/u/a"],"hostname":"box","username":"root","uid":1000,"gid":1000,"tags":["felhom-offbox","a"],"id":"3e5346879326eed55c3c0ab2abcce53b9207c9f1da1a4f3ad1ee0ab831063651","short_id":"3e534687"}]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"total_size":21,"total_file_count":12,"snapshots_count":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"total_size":217738,"total_file_count":0,"total_blob_count":863,"snapshots_count":82}
|
||||
@@ -0,0 +1 @@
|
||||
Fatal: wrong password or no key found
|
||||
@@ -0,0 +1,4 @@
|
||||
{"message_type":"status","percent_done":0.056467624048707366,"total_files":3,"files_done":2,"total_bytes":41943061,"bytes_done":2368425,"current_files":["/data/x/u/a/big.tar"]}
|
||||
{"message_type":"status","percent_done":0.5571281981541595,"total_files":3,"files_done":2,"total_bytes":41943061,"bytes_done":23367662,"current_files":["/data/x/u/a/big.tar"]}
|
||||
{"message_type":"status","percent_done":1,"total_files":3,"files_done":2,"total_bytes":41943061,"bytes_done":41943061}
|
||||
{"message_type":"status","percent_done":1,"total_files":3,"files_done":3,"total_bytes":41943061,"bytes_done":41943061}
|
||||
@@ -0,0 +1,19 @@
|
||||
using temporary cache in /data/x/cache/restic-check-cache-3291579747
|
||||
create exclusive lock for repository
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
error for tree 49327794:
|
||||
decrypting blob <tree/49327794> from fc6016f0 failed: ciphertext verification failed
|
||||
[0:00] 100.00% 82 / 82 snapshots
|
||||
read all data
|
||||
pack fc6016f0ad5e780ab7d800bd3012c8a602eb1dc9e291dedd61ea3d027befae72 contains 2 errors: [blob 49327794eaad1ff31fb67ceb6bad31d812456993a7543d5fdf7ec8dad187e156: decrypting blob <tree/49327794> from fc6016f0 failed: ciphertext verification failed unexpected pack id 39ddedb2fc2555d1e386d3eb7c440142ec3d7c02484f3d10db46105343393948]
|
||||
[0:00] 100.00% 123 / 123 packs
|
||||
|
||||
The repository contains damaged pack files. These damaged files must be removed to repair the repository. This can be done using the following commands. Please read the troubleshooting guide at https://restic.readthedocs.io/en/stable/077_troubleshooting.html first.
|
||||
|
||||
restic repair packs fc6016f0ad5e780ab7d800bd3012c8a602eb1dc9e291dedd61ea3d027befae72
|
||||
restic repair snapshots --forget
|
||||
|
||||
Damaged pack files can be caused by backend problems, hardware problems or bugs in restic. Please open an issue at https://github.com/restic/restic/issues/new/choose for further troubleshooting!
|
||||
Fatal: repository contains errors
|
||||
@@ -0,0 +1,7 @@
|
||||
using temporary cache in /data/x/cache/restic-check-cache-4211187088
|
||||
create exclusive lock for repository
|
||||
load indexes
|
||||
check all packs
|
||||
check snapshots, trees and blobs
|
||||
[0:00] 100.00% 82 / 82 snapshots
|
||||
no errors were found
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
Fatal: create repository at /data/x/repo failed: Fatal: unable to open repository at /data/x/repo: config file already exists
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
repo already locked, waiting up to 0s for the lock
|
||||
unable to create lock in backend: repository is already locked exclusively by PID 2775667 on other-box by root (UID 0, GID 0)
|
||||
lock was created at 2026-10-16 18:20:26 (1.312752495s ago)
|
||||
storage ID be8a3f47
|
||||
the `unlock` command can be used to remove stale locks
|
||||
@@ -0,0 +1,5 @@
|
||||
repo already locked, waiting up to 0s for the lock
|
||||
unable to create lock in backend: repository is already locked by PID 2775299 on other-box by root (UID 0, GID 0)
|
||||
lock was created at 2026-10-16 18:20:25 (1.301138628s ago)
|
||||
storage ID b41afa86
|
||||
the `unlock` command can be used to remove stale locks
|
||||
@@ -0,0 +1,3 @@
|
||||
Fatal: repository does not exist: unable to open config file: stat /data/x/nothing-here/config: no such file or directory
|
||||
Is there a repository at the following location?
|
||||
/data/x/nothing-here
|
||||
@@ -0,0 +1,2 @@
|
||||
rclone: ssh: connect to host 127.0.0.1 port 1: Connection refused
|
||||
Fatal: unable to open repository at rclone:x: error talking HTTP to rclone: exit status 255
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: u@127.0.0.1: Permission denied (publickey,password).
|
||||
Fatal: unable to open repository at sftp:u@127.0.0.1:/x: unable to start the sftp session, error: error receiving version packet from server: server unexpectedly closed connection: unexpected EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: ssh: connect to host 127.0.0.1 port 1: Connection refused
|
||||
Fatal: unable to open repository at sftp:u@127.0.0.1:/x: unable to start the sftp session, error: error receiving version packet from server: server unexpectedly closed connection: unexpected EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
subprocess ssh: ssh: connect to host 10.255.255.1 port 22: Connection timed out
|
||||
Fatal: unable to open repository at sftp:u@10.255.255.1:/x: unable to start the sftp session, error: error receiving version packet from server: server unexpectedly closed connection: unexpected EOF
|
||||
@@ -0,0 +1 @@
|
||||
[{"time":"2026-10-16T02:30:00Z","parent":"16f5cbf1aab3d6aaf201e9cf15f90b780e5db3854751f745883a6bd6e707d2dd","tree":"286f8da9b1b553de264161006b6f14be594a3eef691d3980954808cf02387f21","paths":["/data/x/u/a"],"hostname":"box","username":"root","uid":1000,"gid":1000,"tags":["felhom-offbox","a"],"id":"3e5346879326eed55c3c0ab2abcce53b9207c9f1da1a4f3ad1ee0ab831063651","short_id":"3e534687"}]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"total_size":21,"total_file_count":12,"snapshots_count":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"total_size":217738,"total_uncompressed_size":345762,"compression_ratio":1.587972701136228,"compression_progress":100,"compression_space_saving":37.02662525089513,"total_blob_count":863,"snapshots_count":82}
|
||||
@@ -0,0 +1 @@
|
||||
Fatal: wrong password or no key found
|
||||
@@ -39,7 +39,7 @@ const (
|
||||
FileBrowserKeptMount = "megorzott"
|
||||
// SambaImage is our own pinned LAN-sharing image (R-7 slice 1). Built by
|
||||
// controller/scripts/build-samba-image.sh from controller/infra-images/samba/. NEVER :latest.
|
||||
SambaImage = "gitea.dooplex.hu/admin/felhom-samba:1.1.0"
|
||||
SambaImage = "gitea.dooplex.hu/admin/felhom-samba:1.2.0"
|
||||
)
|
||||
|
||||
// Images returns every controller-managed infra image, derived from the pins above so there is
|
||||
|
||||
Reference in New Issue
Block a user