diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index abfefbb..7bc504e 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -13,4 +13,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - run: "shellcheck install.sh" + - run: "make shellcheck" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37405b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +unsupported := alpine voidlinux/voidlinux-musl ubuntu_16.04 debian_9 linuxmintd/mint18-amd64 fedora_26 opensuse/leap_42.3 +supported := ubuntu_18.04 debian_10 linuxmintd/mint19-amd64 fedora_28 opensuse/leap_15 opensuse/tumbleweed rockylinux_9 manjarolinux/base # fedora_27 hangs +distros := $(unsupported) $(supported) + +test: $(distros) + +MAKEFLAGS := -rRO +SHELL := $(shell command -v bash) +.SHELLFLAGS := -eEo pipefail -c +.ONESHELL: +$(V).SILENT: +.PHONY: clean shellcheck test $(distros) $(distros:%=%_clean) + +$(distros): distro = $(subst _,:,$@) +$(distros) $(distros:%=%_clean): log = $(subst /,_,$(subst _,:,$(@:%_clean=%))).log + +$(unsupported): + echo -n "Testing $(distro) (unsupported)... " + if ! docker run --rm -v "$$PWD/install.sh:/install.sh" "$(distro)" /install.sh >"$(log)" 2>&1 &&\ + grep -q "Unsupported glibc version" "$(log)"; then + echo OK + else + printf "Failed\n\n" && tail -v "$(log)" && false + fi + +opensuse/tumbleweed: setup = zypper --non-interactive install libglib-2_0-0 + +$(supported): + echo -n "Testing $(distro) (supported)... " + if docker run --rm -v "$$PWD/install.sh:/install.sh" "$(distro)" \ + sh -c '$(or $(setup),true) && /install.sh && brave-browser --version || brave --version' >"$(log)" 2>&1; then + echo OK + else + printf "Failed\n\n" && tail -v "$(log)" && false + fi + +shellcheck: + shellcheck -e SC2086 install.sh + +clean: $(distros:%=%_clean) + +$(distros:%=%_clean): + rm -f "$(log)" diff --git a/README.md b/README.md index c4281c4..e0448cb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Script to detect the OS version and install the recommend version of Brave. +Script to install the Brave browser. It is designed to be piped directly into `sh`. diff --git a/install.sh b/install.sh index 615eaaf..13efb95 100755 --- a/install.sh +++ b/install.sh @@ -3,8 +3,12 @@ # Copyright (c) 2024 The Brave Authors # SPDX-License-Identifier: BSD-3-Clause # -# This script detects the current operating system, and installs -# Brave according to that OS's conventions. +# This script installs the Brave browser using the OS's package manager +# Requires: sh, coreutils, grep, sudo/doas (except macOS) + +# Browser requirements +GLIBC_VER_MIN="2.26" +MACOS_VER_MIN="11.0" set -eu @@ -12,412 +16,119 @@ set -eu # bottom of the file, so that a truncated partial download doesn't end # up executing half a script. main() { - # Step 1: detect the current linux distro, version, and packaging system. - # - # We rely on a combination of 'uname' and /etc/os-release to find - # an OS name and version, and from there work out what - # installation method we should be using. - # - # The end result of this step is that the following three - # variables are populated, if detection was successful. - OS="" - VERSION="" - PACKAGETYPE="" - CHANNEL="${CHANNEL:-release}" - ARCH="$(uname -m)" - - case "$ARCH" in - aarch64|arm64|x86_64) - ;; - *) - echo "Unsupported architecture $ARCH. Only 64-bit x86 or ARM machines are supported." - exit 1 - ;; - esac - - case "$CHANNEL" in - release|beta|nightly) - ;; - *) - echo "Unsupported channel $CHANNEL. Only release, beta and nightly are supported." - exit 1 - ;; - esac - - if [ -f /etc/os-release ]; then - # /etc/os-release populates a number of shell variables. We care about the following: - # - ID: the short name of the OS (e.g. "debian") - # - VERSION_ID: the numeric release version for the OS, if any (e.g. "18.04") - # - VERSION_CODENAME: the codename of the OS release, if any (e.g. "buster") - # - UBUNTU_CODENAME: if it exists, use instead of VERSION_CODENAME - - os() { grep "^${1:?}=" /etc/os-release|cut -d= -f2|tr -d \"\'; } - - ID="$(os ID)" - VERSION_ID="$(os VERSION_ID)" - VERSION_CODENAME="$(os VERSION_CODENAME)" - UBUNTU_CODENAME="$(os UBUNTU_CODENAME)" - - case "$ID" in - ubuntu|pop|neon|zorin|tuxedo) - OS="ubuntu" - if [ "${UBUNTU_CODENAME:-}" != "" ]; then - VERSION="$UBUNTU_CODENAME" - else - VERSION="$VERSION_CODENAME" - fi - PACKAGETYPE="apt" - ;; - debian) - OS="$ID" - VERSION="$VERSION_CODENAME" - PACKAGETYPE="apt" - ;; - linuxmint) - if [ "${UBUNTU_CODENAME:-}" != "" ]; then - OS="ubuntu" - VERSION="$UBUNTU_CODENAME" - elif [ "${DEBIAN_CODENAME:-}" != "" ]; then - OS="debian" - VERSION="$DEBIAN_CODENAME" - else - OS="ubuntu" - VERSION="$VERSION_CODENAME" - fi - PACKAGETYPE="apt" - ;; - elementary) - OS="ubuntu" - VERSION="$UBUNTU_CODENAME" - PACKAGETYPE="apt" - ;; - parrot|mendel) - OS="debian" - PACKAGETYPE="apt" - ;; - galliumos) - OS="ubuntu" - PACKAGETYPE="apt" - VERSION="bionic" - ;; - pureos|kaisen) - OS="debian" - PACKAGETYPE="apt" - VERSION="bullseye" - ;; - raspbian) - OS="$ID" - VERSION="$VERSION_CODENAME" - PACKAGETYPE="apt" - ;; - kali) - OS="debian" - PACKAGETYPE="apt" - YEAR="$(echo "$VERSION_ID" | cut -f1 -d.)" - if [ "$YEAR" -lt 2021 ]; then - # Kali VERSION_ID is "kali-rolling", which isn't distinguishing - VERSION="buster" - else - VERSION="bullseye" - fi - ;; - Deepin) - OS="debian" - PACKAGETYPE="apt" - if [ "$VERSION_ID" -lt 20 ]; then - VERSION="buster" - else - VERSION="bullseye" - fi - ;; - centos) - OS="$ID" - VERSION="$VERSION_ID" - PACKAGETYPE="dnf" - if [ "$VERSION" = "7" ]; then - PACKAGETYPE="yum" - fi - ;; - ol) - OS="oracle" - VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)" - PACKAGETYPE="dnf" - if [ "$VERSION" = "7" ]; then - PACKAGETYPE="yum" - fi - ;; - rhel) - OS="$ID" - VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)" - PACKAGETYPE="dnf" - if [ "$VERSION" = "7" ]; then - PACKAGETYPE="yum" - fi - ;; - fedora) - OS="$ID" - VERSION="" - PACKAGETYPE="dnf" - ;; - rocky|almalinux|nobara|openmandriva|sangoma|risios|cloudlinux|alinux|fedora-asahi-remix) - OS="fedora" - VERSION="" - PACKAGETYPE="dnf" - ;; - amzn) - OS="amazon-linux" - VERSION="$VERSION_ID" - PACKAGETYPE="yum" - ;; - xenenterprise) - OS="centos" - VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)" - PACKAGETYPE="yum" - ;; - opensuse-leap|sles) - OS="opensuse" - VERSION="leap/$VERSION_ID" - PACKAGETYPE="zypper" - ;; - opensuse-tumbleweed) - OS="opensuse" - VERSION="tumbleweed" - PACKAGETYPE="zypper" - ;; - sle-micro-rancher) - OS="opensuse" - VERSION="leap/15.4" - PACKAGETYPE="zypper" - ;; - arch|archarm|endeavouros|blendos|garuda) - OS="arch" - VERSION="" # rolling release - PACKAGETYPE="pacman" - ;; - manjaro|manjaro-arm) - OS="manjaro" - VERSION="" # rolling release - PACKAGETYPE="pacman" - ;; - osmc) - OS="debian" - PACKAGETYPE="apt" - VERSION="bullseye" - ;; - esac - fi - - # If we failed to detect something through os-release, consult - # uname and try to infer things from that. - if [ -z "$OS" ]; then - if type uname >/dev/null 2>&1; then - case "$(uname)" in - Darwin) - OS="macos" - VERSION="$(sw_vers -productVersion | cut -f1-2 -d.)" - PACKAGETYPE="appstore" - ;; - Linux) - OS="other-linux" - VERSION="" - PACKAGETYPE="" - ;; - esac - fi - fi - - # Ideally we want to use curl, but on some installs we - # only have wget. Detect and use what's available. - CURL= - # TODO: disable following redirects? - if type curl >/dev/null; then - CURL="curl -fsSL" - elif type wget >/dev/null; then - CURL="wget -q -O-" - fi - if [ -z "$CURL" ]; then - echo "The installer needs either curl or wget to download files." - echo "Please install either curl or wget to proceed." - exit 1 - fi - - TEST_URL="https://pkgs.tailscale.com/" # TODO: Brave-hosted - RC=0 - TEST_OUT=$($CURL "$TEST_URL" 2>&1) || RC=$? - if [ $RC != 0 ]; then - echo "The installer cannot reach $TEST_URL" - echo "Please make sure that your machine has internet access." - echo "Test output:" - echo "$TEST_OUT" - exit 1 - fi - - # Step 2: having detected an OS we support, is it one of the - # versions we support? - OS_UNSUPPORTED= - case "$OS" in - ubuntu|debian|raspbian|centos|oracle|rhel|amazon-linux|opensuse|photon) - # Check with the package server whether a given version is supported. - URL="https://pkgs.tailscale.com/stable/$OS/$VERSION/installer-supported" # TODO: use a Brave endpoint - $CURL "$URL" 2> /dev/null | grep -q OK || OS_UNSUPPORTED=1 # TODO: check for status codes and error with different message if server is down - ;; - fedora) - # All versions supported, no version checking required. - ;; - arch) - # Rolling release, no version checking needed. - ;; - manjaro) - # Rolling release, no version checking needed. - ;; - macos) - # We delegate macOS installation to the app store, it will - # perform version checks for us. - ;; - other-linux) - OS_UNSUPPORTED=1 - ;; - *) - OS_UNSUPPORTED=1 - ;; - esac - if [ "$OS_UNSUPPORTED" = "1" ]; then - case "$OS" in - other-linux) - echo "Couldn't determine what kind of Linux is running." - ;; - "") - echo "Couldn't determine what operating system you're running." - ;; - *) - echo "$OS $VERSION isn't supported by this script yet." - ;; - esac - echo - echo "If you'd like us to support your system better, please file an issue at " - echo "https://github.com/brave/brave-browser/issues and tell us what OS you're running." - echo - echo "Please include the following information we gathered from your system:" - echo - echo "OS=$OS" - echo "VERSION=$VERSION" - echo "PACKAGETYPE=$PACKAGETYPE" - if type uname >/dev/null 2>&1; then - echo "UNAME=$(uname -a)" - else - echo "UNAME=" - fi - echo - if [ -f /etc/os-release ]; then - cat /etc/os-release - else - echo "No /etc/os-release" - fi - exit 1 - fi - - # Step 3: work out if we can run privileged commands, and if so, - # how. - CAN_ROOT= - SUDO= - if [ "$(id -u)" = 0 ]; then - CAN_ROOT=1 - SUDO="" - elif type sudo >/dev/null; then - CAN_ROOT=1 - SUDO="sudo" - elif type doas >/dev/null; then - CAN_ROOT=1 - SUDO="doas" - fi - if [ "$CAN_ROOT" != "1" ]; then - echo "This installer needs to run commands as root." - echo "We tried looking for 'sudo' and 'doas', but couldn't find them." - echo "Either re-run this script as root, or set up sudo/doas." - exit 1 - fi - - - # Step 4: run the installation. - OSVERSION="$OS" - [ "$VERSION" != "" ] && OSVERSION="$OSVERSION $VERSION" - echo "Installing Brave for $OSVERSION, using method $PACKAGETYPE" - case "$PACKAGETYPE" in - apt) - export DEBIAN_FRONTEND=noninteractive - - set -x - $SUDO mkdir -p --mode=0755 /usr/share/keyrings - $CURL "https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg" | $SUDO tee /usr/share/keyrings/brave-browser-archive-keyring.gpg >/dev/null # TODO: handle other channels - echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"| $SUDO tee /etc/apt/sources.list.d/brave-browser-release.list # TODO: support other channels - - $SUDO apt-get update - $SUDO apt-get install -y brave-browser - set +x - ;; - yum) - if ! command -v yum-config-manager >/dev/null; then - set -x - $SUDO yum install yum-utils -y - set +x - fi - set -x - # TODO: support other channels - $SUDO yum-config-manager -y --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo - $SUDO rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc - $SUDO yum install brave-browser -y - set +x - ;; - dnf) - set -x - # TODO: support other channels - $SUDO dnf install -y 'dnf-command(config-manager)' - $SUDO dnf config-manager --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo - # TODO: https://pkgs.tailscale.com/stable/fedora/tailscale.repo covers the pgp key - should we? - $SUDO rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc - $SUDO dnf install -y brave-browser - set +x - ;; - zypper) - set -x - $SUDO rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc - $SUDO zypper --non-interactive ar -g -r https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo - $SUDO zypper --non-interactive --gpg-auto-import-keys refresh - $SUDO zypper --non-interactive install brave-browser - set +x - ;; - pacman) - # TODO: support beta and nightly - if command -v paru >/dev/null; then - set -x - paru -S brave-bin - set +x - elif command -v pikaur >/dev/null; then - set -x - pikaur -S brave-bin - set +x - elif command -v yay >/dev/null; then - set -x - yay -S brave-bin - set +x - else - # TODO: should we prefix error messages with something like "Error: "? and also print them to stderr? - echo "Could not find an AUR helper to install Brave (see: https://wiki.archlinux.org/title/AUR_helpers)" - exit 1 - fi - ;; - appstore) - echo "Download Brave from https://brave.com/download/" - exit 0 - ;; - *) - echo "unexpected: unknown package type $PACKAGETYPE" - exit 1 - ;; - esac - - echo "Installation complete! Start Brave by typing brave-browser." # TODO: support other distros than just Debian - echo + ## Check if the browser can run on this system + + os="$(uname)" + arch="$(uname -m)" + + case "$os" in + Darwin) macos_ver="$(sw_vers -productVersion 2>/dev/null || true)" + supported macOS "$macos_ver" "$MACOS_VER_MIN";; + *) glibc_ver="$(ldd --version 2>/dev/null|head -n1|grep -oE '[0-9]+\.[0-9]+$' || true)" + supported glibc "$glibc_ver" "$GLIBC_VER_MIN";; + esac + + case "$arch" in + aarch64|arm64|x86_64) ;; + *) error "Unsupported architecture $arch. Only 64-bit x86 or ARM machines are supported.";; + esac + + ## Find and/or install the necessary tools + + if [ "$(id -u)" = 0 ] || [ "$os" = Darwin ]; then + sudo="" + elif available sudo; then + sudo="sudo" + elif available doas; then + sudo="doas" + else + error "Please install sudo or doas to proceed." + fi + + if available curl; then + curl="curl -fsS" + elif available wget; then + curl="wget -qO-" + elif available apt-get; then + curl="curl -fsS" + export DEBIAN_FRONTEND=noninteractive + show $sudo apt-get update + show $sudo apt-get install -y curl + fi + + ## Install the browser + + if available apt-get; then + export DEBIAN_FRONTEND=noninteractive + show $sudo mkdir -p --mode=0755 /usr/share/keyrings + show $curl "https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg"|\ + show $sudo tee /usr/share/keyrings/brave-browser-archive-keyring.gpg >/dev/null + show echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main"|\ + show $sudo tee /etc/apt/sources.list.d/brave-browser-release.list >/dev/null + show $sudo apt-get update + show $sudo apt-get install -y brave-browser + + elif available dnf; then + show $sudo dnf install -y 'dnf-command(config-manager)' + show $sudo dnf config-manager --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo + show $sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc + show $sudo dnf install -y brave-browser + + elif available yum; then + available yum-config-manager || show $sudo yum install yum-utils -y + show $sudo yum-config-manager -y --add-repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo + show $sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc + show $sudo yum install brave-browser -y + + elif available zypper; then + show $sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc + show $sudo zypper --non-interactive addrepo --gpgcheck --repo https://brave-browser-rpm-release.s3.brave.com/brave-browser.repo + show $sudo zypper --non-interactive --gpg-auto-import-keys refresh + show $sudo zypper --non-interactive install brave-browser + + elif available pacman; then + pacman_opts="-Sy --needed --noconfirm" + if pacman -Ss brave-browser >/dev/null 2>&1; then + show $sudo pacman $pacman_opts brave-browser + elif available paru; then + show paru $pacman_opts brave-bin + elif available pikaur; then + show pikaur $pacman_opts brave-bin + elif available yay; then + show yay $pacman_opts brave-bin + else + error "Could not find an AUR helper. Please install paru, pikaur, or yay to proceed." "" \ + "You can find more information about AUR helpers at https://wiki.archlinux.org/title/AUR_helpers" + fi + + elif [ "$os" = Darwin ]; then + if available brew; then + NONINTERACTIVE=1 show brew install --cask brave-browser + else + error "Could not find brew. Please install brew to proceed." ""\ + "A Brave .dmg can also be downloaded from https://brave.com/download/" + fi + + else + error "Could not find a supported package manager. Only apt, dnf, paru/pikaur/yay, yum and zypper are supported." "" \ + "If you'd like us to support your system better, please file an issue at" \ + "https://github.com/brave/install.sh/issues and include the following information:" "" \ + "$(uname -srvmo)" "" \ + "$(cat /etc/os-release)" + fi + + printf "Installation complete! Start Brave by typing " + case "$os" in + Darwin) echo "open -a Brave\ Browser";; + *) basename "$(command -v brave-browser || command -v brave)";; + esac } +# Helpers +available() { command -v "${1:?}" >/dev/null; } +error() { exec >&2; printf "Error: "; printf "%s\n" "${@:?}"; exit 1; } +newer() { [ "$(printf "%s\n%s" "$1" "$2"|sort -V|head -n1)" = "${2:?}" ]; } +show() { (set -x; "$@"); } +supported() { newer "$2" "${3:?}" || error "Unsupported ${1:?} version ${2:-}. Only $1 versions >=$3 are supported."; } + main