Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Dry-run downloads bounded release metadata and the checksum sidecar to validate
asset selection and checksum availability. It does not download the executable
archive, replace the binary, or update the automatic-check timestamp.

The update destination must not be writable by its group or by other users. If
`--update` reports that the replacement directory is writable, remove those
permissions with `chmod go-w <directory>` (use `sudo` if needed), or install
`fetch` in a private directory such as `~/.local/bin`.

## Automatic checks

Set an interval in the configuration file:
Expand Down
3 changes: 2 additions & 1 deletion internal/update/update_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ func validateReplacementDirectory(target string) error {
return errors.New("replacement directory is not a real directory")
}
if info.Mode().Perm()&022 != 0 {
return fmt.Errorf("replacement directory is writable by group or others (mode %04o)", info.Mode().Perm())
dir := filepath.Dir(target)
return fmt.Errorf("replacement directory %q is writable by group or others (mode %04o); remove group/other write permission by running `chmod go-w` on this directory (use `sudo` if needed), or install fetch in a private directory such as `~/.local/bin`", dir, info.Mode().Perm())
}
return nil
}
Expand Down
19 changes: 19 additions & 0 deletions internal/update/update_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"os"
"path/filepath"
"strings"
"testing"

"github.com/klauspost/compress/gzip"
Expand Down Expand Up @@ -170,6 +171,24 @@ func TestSelfReplaceIsAtomicAndRejectsSymlink(t *testing.T) {
}
}

func TestValidateReplacementDirectory_ActionableError(t *testing.T) {
dir := t.TempDir()
if err := os.Chmod(dir, 0775); err != nil {
t.Fatal(err)
}

err := validateReplacementDirectory(filepath.Join(dir, "fetch"))
if err == nil {
t.Fatal("validateReplacementDirectory accepted a group-writable directory")
}
message := err.Error()
for _, want := range []string{"writable by group or others", "0775", dir, "chmod go-w", "private directory"} {
if !strings.Contains(message, want) {
t.Errorf("error = %q, want it to contain %q", message, want)
}
}
}

func TestCanReplaceFile_ReadOnlyFileWritableDirectory(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "fetch")
Expand Down
Loading