diff --git a/README.md b/README.md index b51f6e4c..254693db 100644 --- a/README.md +++ b/README.md @@ -59,28 +59,67 @@ Built with Go, Backrest is distributed as a standalone, lightweight binary with # Installation -Backrest is packaged as a single executable. It can be run directly on Linux, macOS, and Windows. [restic](https://github.com/restic/restic) will be downloaded and installed on first run. +Backrest is packaged as a single executable. It runs directly on Linux, macOS, and Windows. [restic](https://github.com/restic/restic) is downloaded automatically on first run. Once installed, access Backrest at `http://localhost:9898` (default port). First-time setup will prompt for username and password creation. > [!NOTE] -> To change the default port, set the `BACKREST_PORT` environment variable (e.g., `BACKREST_PORT=0.0.0.0:9898` to listen on all interfaces) -> +> To change the default port, set the `BACKREST_PORT` environment variable (e.g., `BACKREST_PORT=0.0.0.0:9898` to listen on all interfaces). The install script accepts `--allow-remote-access` as a shortcut for this. +> > Backrest will use your system's installed version of restic if it's available and compatible. If not, Backrest will download and install a suitable version in its data directory, keeping it updated. To use a specific restic binary, set the `BACKREST_RESTIC_COMMAND` environment variable to the desired path. -### Quick Start Options +## Linux & macOS (Recommended) -1. **Pre-built Release**: Download from the [releases page](https://github.com/garethgeorge/backrest/releases) -2. **Docker**: Use `ghcr.io/garethgeorge/backrest:latest` (also available on [Docker Hub](https://hub.docker.com/r/garethgeorge/backrest)) - - Includes rclone and common Unix utilities - - For minimal image, use `ghcr.io/garethgeorge/backrest:scratch` -3. **Build from Source**: See [Development](#development) section below +The install script downloads the latest release, drops the binary into `/usr/local/bin`, and sets up the appropriate auto-start integration (systemd or OpenRC on Linux; launchd on macOS): -### Running with Docker Compose +```sh +curl -fsSL https://raw.githubusercontent.com/garethgeorge/backrest/main/install.sh | sudo bash +``` -Docker image: `ghcr.io/garethgeorge/backrest` +Flags go after `--`: -Example compose file: +```sh +# Bind to all interfaces (default: 127.0.0.1:9898) +curl -fsSL https://raw.githubusercontent.com/garethgeorge/backrest/main/install.sh | sudo bash -s -- --allow-remote-access + +# Uninstall (removes service, autostart entry, and /usr/local/bin/backrest) +curl -fsSL https://raw.githubusercontent.com/garethgeorge/backrest/main/install.sh | sudo bash -s -- --uninstall +``` + +The service runs as your user by default (so config and data live under your `$HOME`). To install as `root` instead, pass `--root`. After install, access Backrest at `http://localhost:9898`. + +> [!TIP] +> Review [install.sh](./install.sh) before piping it into a shell. You can also clone the repo and run `./install.sh` locally; it accepts the same flags. + +### macOS — Homebrew (alternative) + +[Homebrew tap](https://github.com/garethgeorge/homebrew-backrest-tap): + +```sh +brew tap garethgeorge/homebrew-backrest-tap +brew install backrest +brew services start backrest +``` + +> [!NOTE] +> You may need to grant Full Disk Access to Backrest. Go to `System Preferences > Security & Privacy > Privacy > Full Disk Access` and add `/usr/local/bin/backrest`. + +### Arch Linux (AUR) + +[Backrest on AUR](https://aur.archlinux.org/packages/backrest) is third-party (not maintained by the Backrest project) and tweaks the systemd unit; see the [AUR service file](https://aur.archlinux.org/cgit/aur.git/tree/backrest@.service?h=backrest) for details. + +```sh +paru -Sy backrest # or: yay -Sy backrest +sudo systemctl enable --now backrest@$USER.service +``` + +## Docker + +Image: `ghcr.io/garethgeorge/backrest` (also on [Docker Hub](https://hub.docker.com/r/garethgeorge/backrest)). +- Includes rclone and common Unix utilities +- For a minimal image, use `ghcr.io/garethgeorge/backrest:scratch` + +### Docker Compose ```yaml version: "3.8" @@ -108,115 +147,12 @@ services: restart: unless-stopped ``` -### Linux +## Windows -#### Option A: Install Script (Recommended) - -```sh -mkdir backrest && tar -xzvf backrest_Linux_x86_64.tar.gz -C backrest -cd backrest && ./install.sh -``` - -This script will: -- Move the Backrest binary to `/usr/local/bin` -- Create and start a systemd service running as the current user (use `sudo ./install.sh` to install as root) - -#### Option B: Manual Installation with systemd - -```sh -sudo mv backrest /usr/local/bin/backrest -sudo tee /etc/systemd/system/backrest.service > /dev/null </dev/null; echo "@reboot /usr/local/bin/backrest") | crontab - -``` - -**Verify Installation** -- Access Backrest at `http://localhost:9898` -- For the systemd service: `sudo systemctl status backrest` - -> [!NOTE] -> Adjust the `User` in the systemd service file if needed. The install script and manual systemd instructions use your current user by default. -> -> By default backrest listens only on localhost, you can open optionally open it up to remote connections by setting the `BACKREST_PORT` environment variable. For systemd installations, run `sudo systemctl edit backrest` and add: -> ``` -> [Service] -> Environment="BACKREST_PORT=0.0.0.0:9898" -> ``` -> Using `0.0.0.0` allows connections from any interface. - -#### Arch Linux - -> [!Note] -> [Backrest on AUR](https://aur.archlinux.org/packages/backrest) is not maintained by the Backrest official and has made minor adjustments to the recommended services. Please refer to [here](https://aur.archlinux.org/cgit/aur.git/tree/backrest@.service?h=backrest) for details. In [backrest@.service](https://aur.archlinux.org/cgit/aur.git/tree/backrest@.service?h=backrest), use `restic` from the Arch Linux official repository by setting `BACKREST_RESTIC_COMMAND`. And for information on enable/starting/stopping services, please refer to [Systemd#Using_units](https://wiki.archlinux.org/title/Systemd#Using_units). - -```shell -## Install Backrest from AUR -paru -Sy backrest # or: yay -Sy backrest - -## Enable Backrest service for current user -sudo systemctl enable --now backrest@$USER.service -``` - -### macOS - -#### Homebrew (Recommended) - -Backrest is available via a [Homebrew tap](https://github.com/garethgeorge/homebrew-backrest-tap): - -```sh -brew tap garethgeorge/homebrew-backrest-tap -brew install backrest -brew services start backrest -``` - -This method uses [Brew Services](https://github.com/Homebrew/homebrew-services) to manage Backrest. It will launch on startup and run on port 127.0.0.1:9898 by default. - -> [!NOTE] -> You may need to grant Full Disk Access to Backrest. Go to `System Preferences > Security & Privacy > Privacy > Full Disk Access` and add `/usr/local/bin/backrest`. - -#### Manual Installation - -1. Download the latest Darwin release from the [releases page](https://github.com/garethgeorge/backrest/releases). -2. Extract and install: - -```sh -mkdir backrest && tar -xzvf backrest_Darwin_arm64.tar.gz -C backrest -cd backrest && ./install.sh -``` - -The install script will: -- Move the Backrest binary to `/usr/local/bin` -- Create a launch agent at `~/Library/LaunchAgents/com.backrest.plist` -- Load the launch agent +Download the Windows installer for your architecture from the [releases page](https://github.com/garethgeorge/backrest/releases). The installer, named `Backrest-setup-[arch].exe`, places Backrest and a GUI tray application in `%localappdata%\Programs\Backrest\`. The tray application, set to start on login, monitors Backrest. > [!TIP] -> Review the script before running to ensure you're comfortable with its operations. - -### Windows - -Download the Windows installer for your architecture from the [releases page](https://github.com/garethgeorge/backrest/releases). The installer, named Backrest-setup-[arch].exe, will place Backrest and a GUI tray application in `%localappdata%\Programs\Backrest\`. The tray application, set to start on login, monitors Backrest. - -> [!TIP] -> To override the default port before installation, set a user environment variable named BACKREST_PORT. On Windows 10+, navigate to Settings > About > Advanced system settings > Environment Variables. Under "User variables", create a new variable `BACKREST_PORT` with the value "127.0.0.1:port" (e.g., "127.0.0.1:8080" for port 8080). If changing post-installation, re-run the installer to update shortcuts with the new port. +> To override the default port before installation, set a user environment variable named `BACKREST_PORT`. On Windows 10+, navigate to Settings > About > Advanced system settings > Environment Variables. Under "User variables", create a new variable `BACKREST_PORT` with the value `127.0.0.1:port` (e.g. `127.0.0.1:8080`). If changing post-installation, re-run the installer to update shortcuts with the new port. --- diff --git a/install.sh b/install.sh index d640ae76..21317254 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,15 @@ #!/bin/bash set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# Detect curl-piped invocation (e.g. `curl ... | bash` or `curl ... | sudo bash`). +# Under that mode $0 is "bash" rather than a path to a real script file. +if [ -f "$0" ]; then + PIPED_INVOCATION=false + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +else + PIPED_INVOCATION=true + SCRIPT_DIR="" +fi # --- Defaults --- ALLOW_REMOTE_ACCESS=false @@ -9,6 +17,7 @@ INSTALL_MODE="" # "tray", "service", or "" (auto-detect) UNINSTALL_ONLY=false ACQUISITION_MODE="" # "local", "source", "release", or "" (auto-detect) RELEASE_BINARY="" # set by acquire_binary in release mode +ROOT_INSTALL=false # acknowledged install as root (services run as root) # --- Parse arguments --- for arg in "$@"; do @@ -34,9 +43,14 @@ for arg in "$@"; do --from-release) ACQUISITION_MODE="release" ;; + --root) + ROOT_INSTALL=true + ;; *) + progname="$0" + [ "$PIPED_INVOCATION" = true ] && progname="install.sh" echo "Unknown option: $arg" - echo "Usage: $0 [OPTIONS]" + echo "Usage: $progname [OPTIONS]" echo "" echo "Options:" echo " --uninstall Uninstall backrest (remove all artifacts, don't reinstall)" @@ -46,11 +60,74 @@ for arg in "$@"; do echo " --from-local Use ./backrest binary in script directory (default if present)" echo " --from-source Build from source" echo " --from-release Download latest release from GitHub" + echo " --root Acknowledge install as root (services run as root)" + echo "" + echo "Curl-piped invocation:" + echo " curl -fsSL https://raw.githubusercontent.com/garethgeorge/backrest/main/install.sh | sudo bash" + echo " curl -fsSL https://raw.githubusercontent.com/garethgeorge/backrest/main/install.sh | sudo bash -s -- --no-tray" exit 1 ;; esac done +# --- Curl-piped invocation guards --- +if [ "$PIPED_INVOCATION" = true ]; then + case "$ACQUISITION_MODE" in + local|source) + echo "Error: --from-$ACQUISITION_MODE is not supported when running via 'curl | bash';" + echo "the script has no on-disk location to read from. Either drop the flag" + echo "(downloads the latest release from GitHub) or download install.sh to disk first." + exit 1 + ;; + esac + # Force release acquisition; SCRIPT_DIR is empty so local detection wouldn't work anyway. + if [ -z "$ACQUISITION_MODE" ]; then + ACQUISITION_MODE="release" + fi +fi + +# --- Determine the install user (whose name goes into service units / autostart) --- +# When invoked via sudo, $(whoami) is "root" but $SUDO_USER points at the real user; +# we want services owned by that user, not root. Bare-root installs require --root. +if [ "$(id -u)" -eq 0 ]; then + if [ "$ROOT_INSTALL" = true ]; then + INSTALL_USER="root" + INSTALL_HOME="/root" + elif [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then + INSTALL_USER="$SUDO_USER" + INSTALL_HOME="$(getent passwd "$SUDO_USER" 2>/dev/null | cut -d: -f6)" + [ -z "$INSTALL_HOME" ] && INSTALL_HOME="/home/$SUDO_USER" + else + echo "Error: this script is being run as root directly." + echo "" + echo "Running as root means backrest will run as root and store its config" + echo "and data under /root, which is rarely what you want. Either:" + echo " - Re-run as your normal user (sudo will be invoked when needed), or" + echo " - Pass --root to acknowledge that you really want a root install." + exit 1 + fi +else + INSTALL_USER="$(whoami)" + INSTALL_HOME="$HOME" +fi + +# Resolve the install user's XDG config dir. Don't honor $XDG_CONFIG_HOME from +# the current env when running via sudo -- that value belongs to root. +if [ "$(id -u)" -eq 0 ] && [ "$INSTALL_USER" != "root" ]; then + INSTALL_XDG_CONFIG_HOME="$INSTALL_HOME/.config" +else + INSTALL_XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$INSTALL_HOME/.config}" +fi + +# Run a command as the install user (drop privileges if currently root). +run_as_install_user() { + if [ "$(id -u)" -eq 0 ] && [ "$INSTALL_USER" != "root" ]; then + sudo -u "$INSTALL_USER" "$@" + else + "$@" + fi +} + # --- Bind address --- if [ "$ALLOW_REMOTE_ACCESS" = true ]; then BACKREST_PORT="0.0.0.0:9898" @@ -58,6 +135,32 @@ else BACKREST_PORT="127.0.0.1:9898" fi +# --- Pre-authenticate sudo so subsequent calls don't prompt mid-install --- +# When piped from curl, stdin is occupied by the script body, so sudo can't read +# a password from it. We force a TTY prompt up front; if no TTY is available +# (e.g. fully non-interactive shell), fail clearly with the canonical fix. +if [ "$(id -u)" -ne 0 ]; then + if ! sudo -n true 2>/dev/null; then + if [ -t 0 ] || [ -t 1 ] || [ -r /dev/tty ]; then + echo "Caching sudo credentials (you may be prompted for your password)..." + if [ -r /dev/tty ]; then + sudo -v /dev/null || echo "$USER")")" + if [ "$(id -u)" -eq 0 ]; then + prefix="sudo -u #$uid " + else + prefix="" + fi + echo "Tearing down launchd agent:" + echo " ${prefix}launchctl bootout gui/$uid/com.backrest" + if [ "$(id -u)" -eq 0 ]; then + sudo -u "#$uid" launchctl bootout "gui/$uid/com.backrest" 2>/dev/null || true + else + launchctl bootout "gui/$uid/com.backrest" 2>/dev/null || true + fi launchctl unload /Library/LaunchAgents/com.backrest.plist 2>/dev/null || true sudo rm -f /Library/LaunchAgents/com.backrest.plist echo "Removed launchd plist" @@ -133,9 +250,9 @@ remove_launchd_plist() { } remove_autostart_desktop() { - local desktop_file="${XDG_CONFIG_HOME:-$HOME/.config}/autostart/backrest.desktop" + local desktop_file="$INSTALL_XDG_CONFIG_HOME/autostart/backrest.desktop" if [ -f "$desktop_file" ]; then - rm -f "$desktop_file" + run_as_install_user rm -f "$desktop_file" echo "Removed tray autostart entry" fi } @@ -153,7 +270,7 @@ remove_all() { remove_openrc_service remove_launchd_plist remove_autostart_desktop - pkill -f "backrest.*--tray" 2>/dev/null || true + run_as_install_user pkill -f "backrest.*--tray" 2>/dev/null || true remove_binary } @@ -275,8 +392,8 @@ After=network.target [Service] Type=simple -User=$(whoami) -Group=$(whoami) +User=${INSTALL_USER} +Group=${INSTALL_USER} ExecStart=/usr/local/bin/backrest Restart=on-failure Environment="BACKREST_PORT=$BACKREST_PORT" @@ -298,13 +415,12 @@ depend() { use net logger } -: \${BACKREST_PORT:=${BACKREST_PORT}} +export BACKREST_PORT="${BACKREST_PORT}" command=/usr/local/bin/backrest command_background=true -command_args="-bind-address \${BACKREST_PORT}" pidfile="/run/\${RC_SVCNAME}.pid" -command_user="$(whoami):$(whoami)" +command_user="${INSTALL_USER}:${INSTALL_USER}" supervisor=supervise-daemon EOM @@ -312,11 +428,11 @@ EOM } create_autostart_desktop() { - local autostart_dir="${XDG_CONFIG_HOME:-$HOME/.config}/autostart" + local autostart_dir="$INSTALL_XDG_CONFIG_HOME/autostart" local desktop_file="$autostart_dir/backrest.desktop" - mkdir -p "$autostart_dir" - cat >"$desktop_file" <<-EOM + run_as_install_user mkdir -p "$autostart_dir" + run_as_install_user tee "$desktop_file" >/dev/null <<-EOM [Desktop Entry] Name=Backrest Comment=Backrest backup manager tray applet @@ -357,9 +473,37 @@ EOM } enable_launchd_plist() { - launchctl unload /Library/LaunchAgents/com.backrest.plist 2>/dev/null || true - launchctl load -w /Library/LaunchAgents/com.backrest.plist - echo "Loaded launchd plist" + local uid target prefix + uid="$(id -u "$(logname 2>/dev/null || echo "$USER")")" + target="gui/$uid" + + if [ "$(id -u)" -eq 0 ]; then + prefix="sudo -u #$uid " + else + prefix="" + fi + + echo "Bootstrapping launchd agent into $target:" + echo " ${prefix}launchctl bootout $target/com.backrest # tear down any existing instance" + echo " ${prefix}launchctl bootstrap $target /Library/LaunchAgents/com.backrest.plist" + echo " ${prefix}launchctl kickstart -k $target/com.backrest" + + if [ "$(id -u)" -eq 0 ]; then + sudo -u "#$uid" launchctl bootout "$target/com.backrest" 2>/dev/null || true + sudo -u "#$uid" launchctl bootstrap "$target" /Library/LaunchAgents/com.backrest.plist + sudo -u "#$uid" launchctl kickstart -k "$target/com.backrest" + else + launchctl bootout "$target/com.backrest" 2>/dev/null || true + launchctl bootstrap "$target" /Library/LaunchAgents/com.backrest.plist + launchctl kickstart -k "$target/com.backrest" + fi + echo "Loaded and started launchd agent" + echo "" + echo "To manually stop/unload later:" + echo " ${prefix}launchctl bootout $target/com.backrest" + echo "To manually start/load again:" + echo " ${prefix}launchctl bootstrap $target /Library/LaunchAgents/com.backrest.plist" + echo " ${prefix}launchctl kickstart -k $target/com.backrest" } # ============================================================================= @@ -376,7 +520,24 @@ install_linux() { echo "" echo "Backrest installed in tray mode." echo "It will start automatically when you next log in to your desktop session." - echo "To start it now, run: /usr/local/bin/backrest --tray &" + # Try to start it now in the background so the user doesn't have to log out/in. + # This only works if we have access to the user's graphical session; if not, + # the autostart entry will still pick it up on next login. + if [ "$(id -u)" -eq 0 ] && [ "$INSTALL_USER" != "root" ]; then + echo "Starting backrest tray as $INSTALL_USER..." + sudo -u "$INSTALL_USER" \ + env BACKREST_PORT="$BACKREST_PORT" \ + nohup /usr/local/bin/backrest --tray >/dev/null 2>&1 & + disown 2>/dev/null || true + echo "(If the tray icon doesn't appear, your graphical session may not be" + echo " reachable from this shell -- log out/in and the autostart entry will" + echo " launch it. Or run manually: /usr/local/bin/backrest --tray &)" + else + echo "Starting backrest tray..." + BACKREST_PORT="$BACKREST_PORT" \ + nohup /usr/local/bin/backrest --tray >/dev/null 2>&1 & + disown 2>/dev/null || true + fi ;; service) install_binary @@ -444,8 +605,10 @@ if [ "$UNINSTALL_ONLY" = true ]; then fi # Step 3: Determine acquisition mode +# (Already forced to "release" earlier under PIPED_INVOCATION, so SCRIPT_DIR is +# guaranteed non-empty when we hit the local-detect branch below.) if [ -z "$ACQUISITION_MODE" ]; then - if [ -f "$SCRIPT_DIR/backrest" ]; then + if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/backrest" ]; then ACQUISITION_MODE="local" else ACQUISITION_MODE="release" @@ -472,7 +635,7 @@ elif [ -z "$INSTALL_MODE" ]; then # echo "Use --no-tray to install as a system service instead." else INSTALL_MODE="service" - echo "Defaulting to system service install. Use --experimental-linux-tray to install as a tray app." + echo "Defaulting to system service install." fi EFFECTIVE_MODE="$INSTALL_MODE" else