restic: ask as root whether a directory is there
The first run on the control node called postgres's dumps missing: the holder looked as the runtime's account, which cannot see inside a store's 0700 data directory. Every other act already runs as root; the existence checks do too now.
This commit is contained in:
@@ -184,6 +184,14 @@ type Backups struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// exists is whether a path is there, asked as root: a store's directory is often its own user's
|
||||
// alone (postgres's 0700 data directory), and the runtime's account asking for itself would see
|
||||
// nothing — every such directory "missing", and its module never backed up.
|
||||
func (b *Backups) exists(ctx context.Context, path string) bool {
|
||||
_, err := b.Run(ctx, "test", "-e", path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (b *Backups) restic(ctx context.Context, args ...string) (string, error) {
|
||||
return b.Run(ctx, "restic", append([]string{"--repo", b.Where.Repository, "--password-file", b.Where.PasswordFile, "--no-cache"}, args...)...)
|
||||
}
|
||||
@@ -253,7 +261,7 @@ func (b *Backups) one(ctx context.Context, d Declared) Night {
|
||||
}
|
||||
var missing []string
|
||||
for _, p := range d.Paths {
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
if !b.exists(ctx, p) {
|
||||
missing = append(missing, p)
|
||||
}
|
||||
}
|
||||
@@ -429,7 +437,7 @@ func (b *Backups) Restore(ctx context.Context, module, snapshot, path string) (*
|
||||
r := &Restored{Module: module, From: chosen, Live: "untouched — swapping it in is a person's act"}
|
||||
for _, p := range paths {
|
||||
target := p + ".restored-" + stamp
|
||||
if _, err := os.Stat(target); err == nil {
|
||||
if b.exists(ctx, target) {
|
||||
return nil, fmt.Errorf("%s already exists; nothing is restored over anything", target)
|
||||
}
|
||||
if _, err := b.restic(ctx, "restore", chosen.ID+":"+p, "--target", target); err != nil {
|
||||
|
||||
@@ -75,6 +75,9 @@ func TestANightDumpsBeforeEachSnapshotAndOneFailingModuleFailsOnlyItself(t *test
|
||||
if name == "restic" {
|
||||
line = "restic " + strings.Join(args[5:], " ")
|
||||
}
|
||||
if name == "test" {
|
||||
return "", nil
|
||||
}
|
||||
calls = append(calls, line)
|
||||
switch {
|
||||
case line == "sh -c fail-it":
|
||||
@@ -138,8 +141,13 @@ func TestARepositoryThatWillNotOpenIsNeverReplaced(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestADeclaredDirectoryThatDoesNotExistFailsThatModulesNight(t *testing.T) {
|
||||
b := &Backups{Where: placed(t, "# pg\npath /nowhere/at/all\n"), Run: func(context.Context, string, ...string) (string, error) { return "", nil },
|
||||
Now: time.Now, Say: quiet}
|
||||
run := func(_ context.Context, name string, args ...string) (string, error) {
|
||||
if name == "test" && args[1] == "/nowhere/at/all" {
|
||||
return "", errors.New("exit status 1")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
b := &Backups{Where: placed(t, "# pg\npath /nowhere/at/all\n"), Run: run, Now: time.Now, Say: quiet}
|
||||
outcome, err := b.BackUp(context.Background(), "")
|
||||
must(t, err)
|
||||
if outcome["pg"].OK || !strings.Contains(outcome["pg"].Error, "/nowhere/at/all does not exist") {
|
||||
@@ -226,3 +234,29 @@ func TestWithTheRealResticAMistakeIsUndoneBesideTheLiveData(t *testing.T) {
|
||||
t.Fatalf("a second restore: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A store's directory is often its own user's alone; whether it is there is asked as root, never by
|
||||
// the runtime's account looking for itself — which saw nothing in postgres's 0700 data directory and
|
||||
// called the dumps missing on the first run (2026-10-05).
|
||||
func TestWhetherADirectoryIsThereIsAskedAsRoot(t *testing.T) {
|
||||
var asked []string
|
||||
run := func(_ context.Context, name string, args ...string) (string, error) {
|
||||
if name == "test" {
|
||||
asked = append(asked, strings.Join(args, " "))
|
||||
}
|
||||
if name == "restic" && args[5] == "backup" {
|
||||
return "{\"message_type\":\"summary\",\"snapshot_id\":\"0123456789abcdef\"}\n", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
// A path the account cannot see, which root can.
|
||||
b := &Backups{Where: placed(t, "# pg\npath /root/only/dumps\n"), Run: run, Now: time.Now, Say: quiet}
|
||||
outcome, err := b.BackUp(context.Background(), "")
|
||||
must(t, err)
|
||||
if !outcome["pg"].OK {
|
||||
t.Fatalf("a directory only root sees was called missing: %+v", outcome["pg"])
|
||||
}
|
||||
if !reflect.DeepEqual(asked, []string{"-e /root/only/dumps"}) {
|
||||
t.Errorf("asked %v", asked)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user