#!/bin/bash set -euo pipefail readonly RELEASE_API="https://api.github.com/repos/alacritty/alacritty/releases/latest" readonly APPLICATIONS_DIR="/Applications" readonly APP_NAME="Alacritty.app" temp_dir="" mount_point="" cleanup() { if [[ -n "$mount_point" && -d "$mount_point" ]]; then hdiutil detach "$mount_point" >/dev/null 2>&1 || true fi if [[ -n "$temp_dir" && -d "$temp_dir" ]]; then rm -rf "$temp_dir" fi } trap cleanup EXIT INT TERM die() { echo "Alacritty update failed: $*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" } app_version() { /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" \ "$1/Contents/Info.plist" 2>/dev/null || true } version_is_newer() { local left="${1%%-*}" local right="${2%%-*}" local left_major left_minor left_patch local right_major right_minor right_patch IFS=. read -r left_major left_minor left_patch <<< "$left" IFS=. read -r right_major right_minor right_patch <<< "$right" [[ "$left_major" =~ ^[0-9]+$ && "$left_minor" =~ ^[0-9]+$ && "$left_patch" =~ ^[0-9]+$ ]] || return 1 [[ "$right_major" =~ ^[0-9]+$ && "$right_minor" =~ ^[0-9]+$ && "$right_patch" =~ ^[0-9]+$ ]] || return 1 (( 10#$left_major > 10#$right_major )) && return 0 (( 10#$left_major < 10#$right_major )) && return 1 (( 10#$left_minor > 10#$right_minor )) && return 0 (( 10#$left_minor < 10#$right_minor )) && return 1 (( 10#$left_patch > 10#$right_patch )) } run_for_applications() { if [[ -w "$APPLICATIONS_DIR" ]]; then "$@" else sudo "$@" fi } safe_link() { local source="$1" local destination="$2" if [[ -e "$destination" && ! -L "$destination" ]]; then die "will not replace non-symlink: $destination" fi ln -sfn "$source" "$destination" } assert_linkable() { local destination="$1" if [[ -e "$destination" && ! -L "$destination" ]]; then die "will not replace non-symlink: $destination" fi } validate_shell_artifacts() { local app="$1" local brew_prefix="$2" local resources="$app/Contents/Resources" local source manpage section for source in \ "$app/Contents/MacOS/alacritty" \ "$resources/completions/alacritty.bash" \ "$resources/completions/alacritty.fish" \ "$resources/completions/_alacritty" \ "$resources/61/alacritty" \ "$resources/61/alacritty-direct"; do [[ -f "$source" ]] || die "required artifact is missing: ${source#"$app/"}" done assert_linkable "$brew_prefix/bin/alacritty" assert_linkable "$brew_prefix/etc/bash_completion.d/alacritty" assert_linkable "$brew_prefix/share/fish/vendor_completions.d/alacritty.fish" assert_linkable "$brew_prefix/share/zsh/site-functions/_alacritty" assert_linkable "$HOME/.terminfo/61/alacritty" assert_linkable "$HOME/.terminfo/61/alacritty-direct" for section in 1 5 7; do for manpage in "$resources"/*."$section".gz; do [[ -e "$manpage" ]] || continue assert_linkable "$brew_prefix/share/man/man$section/${manpage##*/}" done done } install_shell_artifacts() { local app="$APPLICATIONS_DIR/$APP_NAME" local resources="$app/Contents/Resources" local brew_prefix="$1" local manpage section mkdir -p \ "$brew_prefix/bin" \ "$brew_prefix/etc/bash_completion.d" \ "$brew_prefix/share/fish/vendor_completions.d" \ "$brew_prefix/share/zsh/site-functions" \ "$brew_prefix/share/man/man1" \ "$brew_prefix/share/man/man5" \ "$brew_prefix/share/man/man7" \ "$HOME/.terminfo/61" safe_link "$app/Contents/MacOS/alacritty" "$brew_prefix/bin/alacritty" safe_link "$resources/completions/alacritty.bash" "$brew_prefix/etc/bash_completion.d/alacritty" safe_link "$resources/completions/alacritty.fish" "$brew_prefix/share/fish/vendor_completions.d/alacritty.fish" safe_link "$resources/completions/_alacritty" "$brew_prefix/share/zsh/site-functions/_alacritty" safe_link "$resources/61/alacritty" "$HOME/.terminfo/61/alacritty" safe_link "$resources/61/alacritty-direct" "$HOME/.terminfo/61/alacritty-direct" for section in 1 5 7; do for manpage in "$resources"/*."$section".gz; do [[ -e "$manpage" ]] || continue safe_link "$manpage" "$brew_prefix/share/man/man$section/${manpage##*/}" done done } restore_previous_app() { local destination="$1" local backup="$2" run_for_applications rm -rf "$destination" if [[ -d "$backup" ]]; then run_for_applications /usr/bin/ditto "$backup" "$destination" fi } main() { local destination="$APPLICATIONS_DIR/$APP_NAME" local release_json latest_tag latest_version asset_name asset_url asset_digest local expected_sha actual_sha dmg staged_app staged_version installed_version local previous_app brew_prefix brew_managed=false [[ "$(uname -s)" == "Darwin" ]] || die "this installer only supports macOS" require_command brew require_command curl require_command jq require_command hdiutil require_command codesign brew_prefix="$(brew --prefix)" if brew list --cask alacritty >/dev/null 2>&1; then brew_managed=true fi temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/alacritty-update.XXXXXX")" release_json="$temp_dir/release.json" curl --fail --location --silent --show-error --retry 3 \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "$RELEASE_API" -o "$release_json" latest_tag="$(jq -er '.tag_name' "$release_json")" latest_version="${latest_tag#v}" asset_name="Alacritty-${latest_tag}.dmg" asset_url="$(jq -er --arg asset "$asset_name" \ '.assets[] | select(.name == $asset) | .browser_download_url' "$release_json")" asset_digest="$(jq -er --arg asset "$asset_name" \ '.assets[] | select(.name == $asset) | .digest' "$release_json")" [[ "$asset_digest" == sha256:* ]] || die "GitHub did not provide a SHA-256 digest for $asset_name" expected_sha="$(printf '%s' "${asset_digest#sha256:}" | tr '[:upper:]' '[:lower:]')" [[ "$expected_sha" =~ ^[[:xdigit:]]{64}$ ]] || die "invalid SHA-256 digest for $asset_name" installed_version="" if [[ -d "$destination" ]]; then installed_version="$(app_version "$destination")" fi if [[ "$installed_version" == "$latest_version" && "$brew_managed" == false ]]; then validate_shell_artifacts "$destination" "$brew_prefix" install_shell_artifacts "$brew_prefix" echo "Alacritty $installed_version is already up to date." return fi if [[ -n "$installed_version" ]] && version_is_newer "$installed_version" "$latest_version"; then echo "Installed Alacritty $installed_version is newer than stable $latest_version; leaving it unchanged." return fi echo "Installing Alacritty $latest_version from the upstream release." dmg="$temp_dir/$asset_name" curl --fail --location --silent --show-error --retry 3 "$asset_url" -o "$dmg" actual_sha="$(shasum -a 256 "$dmg" | awk '{print $1}')" [[ "$actual_sha" == "$expected_sha" ]] || die "SHA-256 mismatch for $asset_name" mount_point="$temp_dir/mount" mkdir -p "$mount_point" hdiutil attach "$dmg" -nobrowse -readonly -mountpoint "$mount_point" >/dev/null [[ -d "$mount_point/$APP_NAME" ]] || die "$APP_NAME is missing from $asset_name" staged_app="$temp_dir/$APP_NAME" /usr/bin/ditto "$mount_point/$APP_NAME" "$staged_app" hdiutil detach "$mount_point" >/dev/null mount_point="" staged_version="$(app_version "$staged_app")" [[ "$staged_version" == "$latest_version" ]] || die "release metadata says $latest_version, app says $staged_version" codesign --verify --deep --strict "$staged_app" || die "the staged app has an invalid ad-hoc signature" xattr -dr com.apple.quarantine "$staged_app" 2>/dev/null || true validate_shell_artifacts "$staged_app" "$brew_prefix" previous_app="$temp_dir/previous/$APP_NAME" if [[ -d "$destination" ]]; then mkdir -p "${previous_app%/*}" /usr/bin/ditto "$destination" "$previous_app" fi if [[ "$brew_managed" == true ]]; then echo "Migrating Alacritty away from the disabled Homebrew cask." if ! brew uninstall --cask alacritty; then restore_previous_app "$destination" "$previous_app" install_shell_artifacts "$brew_prefix" || true die "Homebrew could not uninstall its Alacritty cask" fi fi if ! run_for_applications rm -rf "$destination" || \ ! run_for_applications /usr/bin/ditto "$staged_app" "$destination"; then restore_previous_app "$destination" "$previous_app" die "could not install $destination" fi if ! codesign --verify --deep --strict "$destination" || \ [[ "$(app_version "$destination")" != "$latest_version" ]]; then restore_previous_app "$destination" "$previous_app" die "installed app validation failed; restored the previous app" fi install_shell_artifacts "$brew_prefix" echo "Alacritty $latest_version installed successfully." } if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then main "$@" fi