Last active
October 19, 2026 12:24
-
-
Save friz-xc/c3423e0f918028fb6f4db5424b0a1297 to your computer and use it in GitHub Desktop.
Termless installer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # desc: one-line Termless install for designers: installs Apple's developer tools | |
| # (for git) and GitHub's tool, logs in to GitHub, and puts the latest | |
| # termless.app in Applications. | |
| # usage: /bin/bash -c "$(curl -fsSL https://gist.githubusercontent.com/friz-xc/c3423e0f918028fb6f4db5424b0a1297/raw/termless-install.sh)" | |
| # (`make installer-print-cmd` prints the real line.) | |
| # Standalone on purpose: it's published as a public gist because this repo is | |
| # private. The app itself comes from this repo's GitHub Releases, downloaded | |
| # with gh, so the designer's GitHub account needs access to friz-xc/termless. | |
| # Safe to run again: it updates the app in place. | |
| set -euo pipefail | |
| declare -r REPO="friz-xc/termless" | |
| declare -r APP_NAME="termless.app" | |
| green() { echo -e "\033[32m$*\033[0m"; } | |
| yellow() { echo -e "\033[33m$*\033[0m"; } | |
| red() { echo -e "\033[31m$*\033[0m"; } | |
| dim() { echo -e "\033[2m$*\033[0m"; } | |
| bold() { echo -e "\033[1m$*\033[0m"; } | |
| fn_fail() { | |
| red "✖ $1" >&2 | |
| [[ -n "${2:-}" ]] && dim "$2" >&2 | |
| dim "Nothing is broken. Fix the above and run the same command again." >&2 | |
| exit 1 | |
| } | |
| fn_step() { echo; bold "[$1/4] $2"; } | |
| fn_arch() { | |
| case "$(uname -m)" in | |
| arm64) echo "arm64" ;; | |
| x86_64) echo "x86_64" ;; | |
| *) fn_fail "This Mac's processor type isn't supported." "Ask an engineer for help." ;; | |
| esac | |
| } | |
| # Termless runs the Mac's own git, which comes with Apple's developer tools. | |
| fn_install_dev_tools() { | |
| fn_step 1 "Apple's developer tools" | |
| if xcode-select -p >/dev/null 2>&1; then | |
| green "✔ Already installed." | |
| return | |
| fi | |
| xcode-select --install >/dev/null 2>&1 || true | |
| yellow "● A pop-up opened. Click Install and wait for it to finish." | |
| dim " This window will carry on by itself when it's done." | |
| until xcode-select -p >/dev/null 2>&1; do sleep 5; done | |
| green "✔ Installed." | |
| } | |
| fn_install_gh() { | |
| fn_step 2 "GitHub's tool (gh)" | |
| export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" | |
| if command -v gh >/dev/null 2>&1; then | |
| green "✔ Already installed." | |
| return | |
| fi | |
| local arch zip_url work_dir | |
| case "$(fn_arch)" in | |
| arm64) arch="arm64" ;; | |
| x86_64) arch="amd64" ;; | |
| esac | |
| zip_url=$(curl -fsSL https://api.github.com/repos/cli/cli/releases/latest | | |
| grep -o "https://[^\"]*macOS_${arch}\.zip" | head -1) || true | |
| [[ -n "$zip_url" ]] || | |
| fn_fail "Couldn't find GitHub's tool for this Mac." "Check your internet connection." | |
| work_dir=$(mktemp -d) | |
| echo "Downloading…" | |
| curl -fsSL -o "$work_dir/gh.zip" "$zip_url" || fn_fail "Download failed. Check your internet connection." | |
| unzip -qq "$work_dir/gh.zip" -d "$work_dir" || fn_fail "Couldn't open the downloaded file." | |
| # no sudo needed: it's just a binary in the designer's own folder. Termless | |
| # looks in ~/.local/bin too, so it doesn't need to be on the shell's PATH. | |
| mkdir -p "$HOME/.local/bin" | |
| cp "$work_dir"/gh_*_macOS_"$arch"/bin/gh "$HOME/.local/bin/gh" | |
| chmod +x "$HOME/.local/bin/gh" | |
| rm -rf "$work_dir" | |
| green "✔ Installed." | |
| } | |
| fn_login_github() { | |
| fn_step 3 "Log in to GitHub" | |
| if ! gh auth status --hostname github.com >/dev/null 2>&1; then | |
| yellow "● Your browser will open GitHub." | |
| dim " If asked \"Authenticate Git with your GitHub credentials?\", press Return." | |
| dim " When asked, press Return, then paste the code in the browser (⌘ V)," | |
| dim " click Continue, then Authorize." | |
| echo | |
| gh auth login --hostname github.com --git-protocol https --web --clipboard </dev/tty || | |
| fn_fail "GitHub login didn't finish." | |
| fi | |
| gh auth setup-git --hostname github.com >/dev/null 2>&1 || true | |
| green "✔ Logged in as $(gh api user --jq .login 2>/dev/null || echo "you")." | |
| } | |
| fn_install_app() { | |
| fn_step 4 "Install Termless" | |
| local asset work_dir out dir | |
| asset="termless-macos-$(fn_arch).zip" | |
| work_dir=$(mktemp -d) | |
| trap "rm -rf '$work_dir'" EXIT | |
| echo "Downloading…" | |
| out=$(gh release download --repo "$REPO" --pattern "$asset" --dir "$work_dir" 2>&1) || | |
| fn_fail "Couldn't download Termless." \ | |
| "If it says \"Not Found\", ask an engineer to give your GitHub account | |
| access to ${REPO}. If it says \"no assets match\", there's no build for | |
| this Mac yet: ask an engineer. | |
| ${out}" | |
| ditto -x -k "$work_dir/$asset" "$work_dir" || fn_fail "Couldn't open the downloaded file." | |
| # Same choice as `make install`: /Applications when it's writable (admin), | |
| # otherwise ~/Applications. Syncing into the app folder avoids needing admin | |
| # rights to replace an existing install. | |
| if [[ -w "/Applications/$APP_NAME" ]] || [[ -w /Applications ]]; then | |
| dir=/Applications | |
| else | |
| dir="$HOME/Applications" | |
| fi | |
| osascript -e 'quit app "termless"' >/dev/null 2>&1 || true | |
| mkdir -p "$dir/$APP_NAME" | |
| rsync -a --delete "$work_dir/$APP_NAME/" "$dir/$APP_NAME/" || | |
| fn_fail "Couldn't copy Termless into ${dir}." | |
| xattr -dr com.apple.quarantine "$dir/$APP_NAME" 2>/dev/null || true | |
| green "✔ Installed to ${dir}/${APP_NAME}" | |
| open "$dir/$APP_NAME" || true | |
| } | |
| fn_termless_install() { | |
| [[ "$(uname)" == "Darwin" ]] || fn_fail "Termless is for Macs only." | |
| bold "Installing Termless on your Mac." | |
| fn_install_dev_tools | |
| fn_install_gh | |
| fn_login_github | |
| fn_install_app | |
| echo | |
| green "✔ All done. Termless is open. Run this same line again any time to update it." | |
| } | |
| # runs from `bash -c "$(curl …)"`, where there's no BASH_SOURCE to guard on | |
| fn_termless_install "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment