Skip to content

Commit

Permalink
chore: fix resticinstaller for windows tests
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed Nov 26, 2024
1 parent 74eb869 commit 4f03427
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Dockerfile.alpine
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ RUN apk --no-cache add tini ca-certificates curl bash rclone openssh tzdata dock
RUN mkdir -p /tmp
COPY backrest /backrest
RUN /backrest --install-deps-only
RUN mkdir -p /bin && mv /root/.local/share/backrest/* /bin
RUN mkdir -p /bin && mv /root/.local/share/backrest/restic /bin/restic

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/backrest", "--bind-address", ":9898"]
2 changes: 1 addition & 1 deletion Dockerfile.scratch
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ RUN apk add --no-cache ca-certificates tini-static
RUN mkdir /tmp-orig
COPY backrest /backrest
RUN /backrest --install-deps-only
RUN mkdir -p /bin && mv /root/.local/share/backrest/* /bin
RUN mkdir -p /bin && mv /root/.local/share/backrest/restic /bin/restic

FROM scratch
LABEL org.opencontainers.image.source="https://github.com/garethgeorge/backrest"
Expand Down
25 changes: 10 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Download options
Backrest is accessible from a web browser. By default it binds to `127.0.0.1:9898` and can be accessed at `http://localhost:9898`. Change the port with the `BACKREST_PORT` environment variable e.g. `BACKREST_PORT=0.0.0.0:9898 backrest` to listen on all network interfaces. On first startup backrest will prompt you to create a default username and password, this can be changed later in the settings page.

> [!Note]
> Backrest installs a specific restic version to ensure that it is compatible. If you wish to use a different version of restic OR if you would prefer to install restic manually, use the `BACKREST_RESTIC_COMMAND` environment variable to specify the path of your restic install.
> Backrest will use your system install of restic if it is available and matches Backrest's required version. Otherwise it will download and install a compatible version of restic in its data directory. Backrest will keep restic up to date with the latest version. You force use of a specific restic binary (or non-standard version) by setting the `BACKREST_RESTIC_COMMAND` environment variable to the path of your restic binary.
## Running with Docker Compose

Expand Down Expand Up @@ -142,7 +142,7 @@ Create a systemd service file at `/etc/systemd/system/backrest.service` with the

```ini
[Unit]
Description=ResticWeb
Description=Backrest
After=network.target

[Service]
Expand Down Expand Up @@ -189,15 +189,14 @@ Backrest is provided as a [homebrew tap](https://github.com/garethgeorge/homebre
brew tap garethgeorge/homebrew-backrest-tap
brew install backrest
brew services start backrest
# optionally, install restic
brew install restic
```

This tap uses [Brew services](https://github.com/Homebrew/homebrew-services) to launch and manage Backrest's lifecycle. Backrest will launch on startup and run on port ':9898` by default.

> [!NOTE]
> You may need to grant Full Disk Access to your restic install. To do this, go to `System Preferences > Security & Privacy > Privacy > Full Disk Access` and add the path to your restic install which is typically ~/.local/share/backrest/restic .
> [!NOTE]
> You may optionally install `restic` through homebrew as well, but you may need to regrant Full Disk Access to the homebrew managed binary on each update. You should ensure that you update backrest and restic together if using homebrew to manage both dependencies.
> You may need to grant Full Disk Access to backrest. To do this, go to `System Preferences > Security & Privacy > Privacy > Full Disk Access` and add the path to backrest (typically /usr/local/bin/backrest).
#### Manually using the install script

Expand All @@ -220,19 +219,15 @@ The install script will:

Read the script before running it to make sure you are comfortable with these operations.

#### Manually

If setting up Backrest manually, it is recommended to install the binary to `/usr/local/bin` and run it manually. You can also create a launch agent to run it on startup or may run it manually when needed.

## Running on Windows

Download a Windows release from the [releases page](https://github.com/garethgeorge/backrest/releases) and install it to `C:\Program Files\Backrest\backrest.exe` (create the path if it does not exist). The binary should be run as administrator on first launch, otherwise the restic installation will fail and the process will terminate.
#### Windows Installer

Download a the Windows installer for your architecture from the [releases page](https://github.com/garethgeorge/backrest/releases). The installer is named Backrest-setup-[arch].exe. Run the installer and follow the prompts.

To run the binary on login, create a shortcut to the binary and place it in the `shell:startup` folder. See [this windows support article](https://support.microsoft.com/en-us/windows/add-an-app-to-run-automatically-at-startup-in-windows-10-150da165-dcd9-7230-517b-cf3c295d89dd) for more details.
The installer will place backrest and a GUI tray application to monitor backrest in `%localappdata%\Programs\Backrest\`. The GUI tray application will start on login by default.

> [!WARNING]
> * If you receive filesystem errors, you may need to run Backrest as an administrator for full filesystem access.
> * Backrest is **not** tested on Windows to the same extent as Linux and macOS. Some features may not work as expected.
> [!NOTE] You can optionally override the default port of the installation by using PowerShell to run the installer with the `BACKREST_PORT` environment variable set to the desired port. E.g. to run backrest on port 8080, run the following command in PowerShell: `BACKREST_PORT=:8080 .\Backrest-setup-x86_64.exe`

# Configuration
Expand Down
98 changes: 57 additions & 41 deletions internal/resticinstaller/resticinstaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ var (
var (
RequiredResticVersion = "0.17.3"

findResticMu sync.Mutex
didTryInstall bool
tryFindRestic sync.Once
findResticErr error
foundResticPath string
)

func getResticVersion(binary string) (string, error) {
Expand Down Expand Up @@ -94,12 +95,6 @@ func verify(sha256 string) error {
}

func installResticIfNotExists(resticInstallPath string) error {
lock := flock.New(filepath.Join(filepath.Dir(resticInstallPath), "install.lock"))
if err := lock.Lock(); err != nil {
return fmt.Errorf("lock %v: %w", lock.Path(), err)
}
defer lock.Unlock()

if _, err := os.Stat(resticInstallPath); err == nil {
// file is now installed, probably by another process. We can return.
return nil
Expand Down Expand Up @@ -148,25 +143,40 @@ func removeOldVersions(installDir string) {
}
}

// FindOrInstallResticBinary first tries to find the restic binary if provided as an environment variable. Otherwise it downloads restic if not already installed.
func FindOrInstallResticBinary() (string, error) {
findResticMu.Lock()
defer findResticMu.Unlock()
func installResticHelper(resticInstallPath string) {
if _, err := os.Stat(resticInstallPath); err == nil {
zap.S().Infof("replacing restic binary in data dir due to failed check: %w", err)
if err := os.Remove(resticInstallPath); err != nil {
zap.S().Errorf("failed to remove old restic binary %v: %v", resticInstallPath, err)
}
}

zap.S().Infof("downloading restic %v to %v...", RequiredResticVersion, resticInstallPath)
if err := installResticIfNotExists(resticInstallPath); err != nil {
zap.S().Errorf("failed to install restic %v: %v", RequiredResticVersion, err)
return
}
zap.S().Infof("installed restic %v", RequiredResticVersion)

// TODO: this check is no longer needed, remove it after a few releases.
removeOldVersions(path.Dir(resticInstallPath))
}

func tryFindOrInstall() (string, error) {
// Check if restic is provided.
resticBin := env.ResticBinPath()
if resticBin != "" {
if err := assertResticVersion(resticBin); err != nil {
zap.S().Warnf("restic binary %q may not be supported by backrest", resticBin, err)
resticBinOverride := env.ResticBinPath()
if resticBinOverride != "" {
if err := assertResticVersion(resticBinOverride); err != nil {
zap.S().Warnf("restic binary %q may not be supported by backrest: %v", resticBinOverride, err)
}

if _, err := os.Stat(resticBin); err != nil {
if _, err := os.Stat(resticBinOverride); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("stat(%v): %w", resticBin, err)
return "", fmt.Errorf("check if restic binary exists at %v: %v", resticBinOverride, err)
}
return "", fmt.Errorf("no binary found at path %v: %w", resticBin, ErrResticNotFound)
return "", fmt.Errorf("no restic binary found at %v", resticBinOverride)
}
return resticBin, nil
return resticBinOverride, nil
}

// Search the PATH for the specific restic version.
Expand All @@ -180,40 +190,46 @@ func FindOrInstallResticBinary() (string, error) {
}

// Check for restic installation in data directory.
resticInstallPath := path.Join(env.DataDir(), "restic")
var resticInstallPath string
if runtime.GOOS == "windows" {
// on windows use a path relative to the executable.
resticInstallPath, _ = filepath.Abs(path.Join(path.Dir(os.Args[0]), "restic"))
resticInstallPath, _ = filepath.Abs(path.Join(path.Dir(os.Args[0]), "restic.exe"))
} else {
resticInstallPath = filepath.Join(env.DataDir(), "restic")
}

if err := os.MkdirAll(path.Dir(resticInstallPath), 0700); err != nil {
if err := os.MkdirAll(filepath.Dir(resticInstallPath), 0700); err != nil {
return "", fmt.Errorf("create restic install directory %v: %w", path.Dir(resticInstallPath), err)
}

// Install restic if not found OR if the version is not the required version
if err := assertResticVersion(resticInstallPath); err != nil {
if _, err := os.Stat(resticInstallPath); err == nil {
zap.S().Infof("reinstalling restic binary in data dir due to failed checks: %w", err)
if err := os.Remove(resticInstallPath); err != nil {
return "", fmt.Errorf("remove old restic binary %v: %w", resticInstallPath, err)
}
}

if didTryInstall {
return "", fmt.Errorf("already tried to install: %w", ErrResticNotFound)
lock := flock.New(filepath.Join(filepath.Dir(resticInstallPath), "install.lock"))
if err := lock.Lock(); err != nil {
return "", fmt.Errorf("acquire lock on restic install dir %v: %v", lock.Path(), err)
}
didTryInstall = true
defer lock.Unlock()

zap.S().Infof("downloading restic %v to %v...", RequiredResticVersion, resticInstallPath)
if err := installResticIfNotExists(resticInstallPath); err != nil {
return "", fmt.Errorf("install restic: %w", err)
// Check again after acquiring the lock.
if err := assertResticVersion(resticInstallPath); err != nil {
installResticHelper(resticInstallPath)
}
zap.S().Infof("installed restic %v", RequiredResticVersion)

// TODO: this check is no longer needed, remove it after a few releases.
removeOldVersions(path.Dir(resticInstallPath))
}

zap.S().Infof("restic binary %v in data dir will be used as no system install matching required version %v is found", resticInstallPath, RequiredResticVersion)
return resticInstallPath, nil
}

// FindOrInstallResticBinary first tries to find the restic binary if provided as an environment variable. Otherwise it downloads restic if not already installed.
func FindOrInstallResticBinary() (string, error) {
tryFindRestic.Do(func() {
foundResticPath, findResticErr = tryFindOrInstall()
})

if findResticErr != nil {
return "", findResticErr
}
if foundResticPath == "" {
return "", ErrResticNotFound
}
return foundResticPath, nil
}

0 comments on commit 4f03427

Please sign in to comment.