266 lines
9.2 KiB
Bash
Executable File
266 lines
9.2 KiB
Bash
Executable File
#!/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
|