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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions docs/configuration/destinations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ Names may contain letters, digits, `.`, `_` and `-`.
```yaml
prefix: servers/{hostname} # → servers/web01/<job>/...
```

If the value **starts** with `{`, quote it, or YAML reads it as a map:

```yaml
prefix: "{hostname}"
```
</ParamField>

## S3 options
Expand Down
76 changes: 75 additions & 1 deletion docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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`.
</Tip>

## 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
```

<Warning>
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.
</Warning>

<Steps>
<Step title="Download and install the latest Go">
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`.
</Step>
<Step title="Open a new SSH session">
`/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`.
</Step>
<Step title="Install NoBackups from source">
```bash
cd NoBackups
sudo sh install.sh
```

Look for `==> building … from source with go1.25.1` in the output.
</Step>
</Steps>

### Trying an unreleased fix

With Go installed, you can run any branch before it's released:

```bash
cd NoBackups
git fetch origin
git checkout <branch-name>
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

<Tabs>
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
14 changes: 14 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading