#!/bin/sh # Brimline installer, for macOS 14 (Sonoma) or later, on Apple silicon or Intel. # # curl -fsSL https://brimline.thanaelfontaine.eu/install.sh | sh # # Downloads the latest release from GitHub (https://github.com/ThanaelFontaine/Brimline/releases), # checks it against its published SHA-256, then installs it: # Brimline.app in /Applications (or ~/Applications when /Applications is not writable, or # where Brimline already is), the `brimline` command linked at ~/.local/bin/brimline, # then opens the app (it lives in the menu bar, not in the Dock). # Running it again updates Brimline. Your settings and accounts are kept. # Nothing here needs sudo. Brimline is a Mac app only: there is no Windows or Linux version. # # Options (environment variables): BRIMLINE_VERSION=0.2.1 installs that version, # BRIMLINE_INSTALL_DIR=/path installs the app somewhere else. set -eu # Everything runs from main, called on the last line: if the download of this script stops half # way (curl | sh runs lines as they arrive), nothing runs at all. main() { REPO="ThanaelFontaine/Brimline" SITE="https://brimline.thanaelfontaine.eu" FILE="Brimline.dmg" say() { printf '%s\n' "$*"; } fail() { printf 'Brimline: %s\n' "$*" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || fail "this installer needs $1."; } case "$(uname -s)" in Darwin) ;; *) fail "Brimline runs on macOS only (14 Sonoma or later). There is no Windows or Linux version." ;; esac need curl need hdiutil need ditto need shasum major="$(sw_vers -productVersion 2>/dev/null | cut -d . -f 1)" case "$major" in ''|*[!0-9]*) fail "could not read the macOS version." ;; esac [ "$major" -ge 14 ] || fail "Brimline needs macOS 14 (Sonoma) or later; this Mac has macOS $(sw_vers -productVersion)." # The version: the one asked for, or the latest release (GitHub redirects # /releases/latest to /releases/tag/vX.Y.Z). version="${BRIMLINE_VERSION:-}" if [ -z "$version" ]; then latest="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest")" \ || fail "could not reach GitHub. Check your connection and try again." version="${latest##*/tag/v}" fi version="${version#v}" case "$version" in *[!0-9.]*|"") fail "could not find the latest version (got: $version)." ;; esac url="https://github.com/$REPO/releases/download/v$version/$FILE" tmp="$(mktemp -d 2>/dev/null || mktemp -d -t brimline)" mnt="" cleanup() { if [ -n "$mnt" ]; then hdiutil detach -quiet "$mnt" >/dev/null 2>&1 || true; fi rm -rf "$tmp" } trap cleanup EXIT trap 'exit 130' INT TERM say "Downloading Brimline $version..." curl -fL --progress-bar -o "$tmp/$FILE" "$url" || fail "download failed: $url" curl -fsSL -o "$tmp/$FILE.sha256" "$url.sha256" || fail "checksum download failed: $url.sha256" # The .sha256 file reads " Brimline.dmg". expected="$(cut -d ' ' -f 1 < "$tmp/$FILE.sha256" | tr 'A-F' 'a-f')" actual="$(shasum -a 256 "$tmp/$FILE" | cut -d ' ' -f 1)" [ -n "$expected" ] && [ "$expected" = "$actual" ] || fail "the download does not match its SHA-256 checksum. Nothing was installed." say "Checksum verified." # Where: the folder asked for, else where Brimline already is, else /Applications when it is # writable, else ~/Applications. if [ -n "${BRIMLINE_INSTALL_DIR:-}" ]; then dest="$BRIMLINE_INSTALL_DIR" elif [ -d "/Applications/Brimline.app" ]; then dest="/Applications" elif [ -d "$HOME/Applications/Brimline.app" ]; then dest="$HOME/Applications" elif [ -w "/Applications" ]; then dest="/Applications" else dest="$HOME/Applications" fi mkdir -p "$dest" dest="$(cd "$dest" && pwd -P)" [ -w "$dest" ] || fail "cannot write to $dest. Set BRIMLINE_INSTALL_DIR to a folder you own." mnt="$tmp/mnt" mkdir -p "$mnt" hdiutil attach -nobrowse -noautoopen -readonly -quiet -mountpoint "$mnt" "$tmp/$FILE" || { mnt=""; fail "could not open the disk image."; } [ -d "$mnt/Brimline.app" ] || fail "Brimline.app is missing from the disk image." # Quit the running copy (found by its full path, nothing else), so that it can be replaced. reopen=0 for pid in $(pgrep -f "^$dest/Brimline.app/Contents/MacOS/Brimline" 2>/dev/null || true); do kill "$pid" 2>/dev/null || true reopen=1 done [ "$reopen" = 0 ] || sleep 1 rm -rf "$dest/Brimline.app.new" ditto "$mnt/Brimline.app" "$dest/Brimline.app.new" rm -rf "$dest/Brimline.app" mv "$dest/Brimline.app.new" "$dest/Brimline.app" hdiutil detach -quiet "$mnt" >/dev/null 2>&1 || true mnt="" # The command for agents and the terminal: a link into the app, never over a file that is not a link. bin="$HOME/.local/bin" mkdir -p "$bin" if [ -e "$bin/brimline" ] && [ ! -L "$bin/brimline" ]; then say "($bin/brimline exists and is not a link: left as it is. The command is in $dest/Brimline.app/Contents/Helpers/brimline.)" else ln -sfh "$dest/Brimline.app/Contents/Helpers/brimline" "$bin/brimline" fi state="and running: look for its gauge in the menu bar, at the top right of the screen" open "$dest/Brimline.app" || state="(open it from $dest: it lives in the menu bar)" say "" say "Brimline $version is installed in $dest/Brimline.app $state." say "Command: $bin/brimline" case ":$PATH:" in *":$bin:"*) ;; *) say "($bin is not in your PATH: add it to type just \"brimline\".)" ;; esac say "" say "To update Brimline later, run the same command again." say "Next: link your accounts, $SITE/docs/accounts/" } main "$@"