From af24aae5362521c40da777bd53cdafc91ba65713 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:33:23 +0000 Subject: [PATCH 1/2] Docs: add instructions for installing Go Installation page gets an 'Installing Go (optional)' section: why it's optional, the official go.dev tarball install for any distro and CPU, trying unreleased branches, and upgrading/removing Go. README gets the short version; the development page links to it and adds macOS and Windows commands. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TXNwZBkaVRn9LobyrUJcGA --- README.md | 20 +++++++++ docs/installation.mdx | 76 +++++++++++++++++++++++++++++++++- docs/reference/development.mdx | 4 +- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ce54b90..bd9d000 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,26 @@ sudo sh install.sh If the server has Go 1.24+, the installer builds from source. Otherwise it downloads the release binary for the server's CPU and verifies its checksum. Upgrading is `git pull && sudo sh install.sh`. +Want the installer to build from source? Install Go 1.24+ first (as root). Distro packages like `apt install golang` are usually too old, so use the official release: + +```sh +GO_VERSION=$(curl -fsSL "https://go.dev/VERSION?m=text" | head -1) +case $(uname -m) in + x86_64) GO_ARCH=amd64 ;; + aarch64) GO_ARCH=arm64 ;; + armv7l) GO_ARCH=armv6l ;; + *) GO_ARCH=$(uname -m) ;; +esac +curl -fsSLO "https://go.dev/dl/${GO_VERSION}.linux-${GO_ARCH}.tar.gz" +rm -rf /usr/local/go && tar -C /usr/local -xzf "${GO_VERSION}.linux-${GO_ARCH}.tar.gz" +rm "${GO_VERSION}.linux-${GO_ARCH}.tar.gz" +echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh +. /etc/profile.d/go.sh +go version +``` + +Go is optional; see the [installation docs](docs/installation.mdx) for details. + Other options: ```sh diff --git a/docs/installation.mdx b/docs/installation.mdx index e08490c..13b6b8e 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -16,7 +16,7 @@ sudo sh install.sh The installer picks the best way to get a binary: -- **Go 1.24+ on the server**: builds from the source you just cloned. +- **Go 1.24+ on the server**: builds from the source you just cloned. See [Installing Go](#installing-go-optional) if you want this. - **No Go**: downloads the matching release binary for the server's CPU (amd64, arm64 or armv7) and verifies its SHA-256 checksum. You don't need Go or make. Then it installs the binary, config and systemd unit, as described in [What gets installed](#what-gets-installed). @@ -25,6 +25,80 @@ Then it installs the binary, config and systemd unit, as described in [What gets Already logged in as root? Drop the `sudo`: `sh install.sh`. +## Installing Go (optional) + +You only need Go if you want the installer to **build from source**. Without it, the installer downloads a prebuilt release binary instead, which works just as well. Building from source is handy when you want to run a fix from a branch before it's released, or when you'd rather not run prebuilt binaries. + +Check whether you already have a recent enough version: + +```bash +go version # needs go1.24 or newer +``` + + + Avoid `apt install golang` and other distro packages: they're often too old (Ubuntu 24.04 ships Go 1.22, for example). Use the official release from go.dev below. + + + + + Run as root on the server. This detects the CPU (amd64, arm64 or armv7), downloads the current release from [go.dev](https://go.dev/dl/), and installs it to `/usr/local/go`: + + ```bash + GO_VERSION=$(curl -fsSL "https://go.dev/VERSION?m=text" | head -1) + case $(uname -m) in + x86_64) GO_ARCH=amd64 ;; + aarch64) GO_ARCH=arm64 ;; + armv7l) GO_ARCH=armv6l ;; + *) GO_ARCH=$(uname -m) ;; + esac + curl -fsSLO "https://go.dev/dl/${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + rm -rf /usr/local/go && tar -C /usr/local -xzf "${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + rm "${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh + . /etc/profile.d/go.sh + go version + ``` + + You should see something like `go version go1.25.1 linux/amd64`. + + + `/etc/profile.d/go.sh` adds Go to your `PATH` for every future login. If `go` reports `command not found` in another shell, log out and back in. + + You don't need to configure anything for `sudo`: `install.sh` looks in `/usr/local/go/bin` itself, even when `sudo` resets your `PATH`. + + + ```bash + cd NoBackups + sudo sh install.sh + ``` + + Look for `==> building … from source with go1.25.1` in the output. + + + +### Trying an unreleased fix + +With Go installed, you can run any branch before it's released: + +```bash +cd NoBackups +git fetch origin +git checkout +sudo sh install.sh +``` + +Switch back with `git checkout master && git pull && sudo sh install.sh`. + +### Upgrading or removing Go + +Re-run the commands above to upgrade; they replace `/usr/local/go` with the latest release. To remove Go entirely: + +```bash +rm -rf /usr/local/go /etc/profile.d/go.sh +``` + +NoBackups keeps working after Go is removed, because the installed binary has no dependencies. Future `install.sh` runs will download release binaries instead of building. + ## Other ways to install diff --git a/docs/reference/development.mdx b/docs/reference/development.mdx index 37f8fdb..527700d 100644 --- a/docs/reference/development.mdx +++ b/docs/reference/development.mdx @@ -6,8 +6,8 @@ icon: "code" ## Requirements -- Go 1.24+ -- GNU make +- Go 1.24+: on Linux see [Installing Go](/installation#installing-go-optional); on macOS `brew install go`; on Windows `winget install GoLang.Go` +- GNU make (on Windows, use WSL for the make targets) - Node.js 20+ (only for working on these docs) ## Make targets From e5aff4c28328431bddea6330d44aa49a462e8072 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 01:34:47 +0000 Subject: [PATCH 2/2] Hint at quoting when a config value starting with { fails to parse 'prefix: {hostname}' is a YAML flow map, which produced the cryptic 'cannot unmarshal !!map into string'. Add a hint naming the fix, a test, and a note in the destinations docs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TXNwZBkaVRn9LobyrUJcGA --- docs/configuration/destinations.mdx | 6 ++++++ internal/config/config.go | 3 +++ internal/config/config_test.go | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/docs/configuration/destinations.mdx b/docs/configuration/destinations.mdx index 6a4267e..da8154e 100644 --- a/docs/configuration/destinations.mdx +++ b/docs/configuration/destinations.mdx @@ -34,6 +34,12 @@ Names may contain letters, digits, `.`, `_` and `-`. ```yaml prefix: servers/{hostname} # → servers/web01//... ``` + + If the value **starts** with `{`, quote it, or YAML reads it as a map: + + ```yaml + prefix: "{hostname}" + ``` ## S3 options diff --git a/internal/config/config.go b/internal/config/config.go index ec55d07..112e861 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -173,6 +173,9 @@ func Parse(raw []byte, path string) (*Config, error) { cfg := &Config{} if root.Kind != 0 { if err := root.Decode(cfg); err != nil { + if strings.Contains(err.Error(), "cannot unmarshal !!map into string") { + return nil, fmt.Errorf("parse %s: %w\nhint: a value starting with { must be quoted, e.g. prefix: \"{hostname}\"", path, err) + } return nil, fmt.Errorf("parse %s: %w", path, err) } } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 68725e5..94a3f94 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -86,3 +86,17 @@ jobs: } } } + +func TestUnquotedBraceValueHint(t *testing.T) { + cfg := strings.Replace(minimal, "prefix: /srv/{hostname}/", "prefix: {hostname}", 1) + t.Setenv("NB_TEST_KEY", "k") + _, err := Parse([]byte(cfg), "test.yaml") + if err == nil || !strings.Contains(err.Error(), `prefix: "{hostname}"`) { + t.Fatalf("expected a quoting hint, got %v", err) + } + quoted := strings.Replace(minimal, "prefix: /srv/{hostname}/", `prefix: "{hostname}"`, 1) + c, err := Parse([]byte("hostname: web01\n"+quoted), "test.yaml") + if err != nil || c.Destinations["s3"].Prefix != "web01" { + t.Fatalf("quoted prefix: %v %+v", err, c) + } +}