#!/usr/bin/env bash # CrushPaper installer # curl -fsSL https://crushpaper.pages.dev/install.sh | bash # # Downloads the latest CrushPaper.zip from crushpaper.pages.dev, checks its # SHA-256 against version.json, installs to /Applications (or ~/Applications) # and opens it. Files fetched with curl are not quarantined, so there's no # "can't be opened" dialog. Read this script before running it; it's short. set -euo pipefail SITE="${CRUSHPAPER_SITE:-https://crushpaper.pages.dev}" APP_NAME="CrushPaper.app" BUNDLE_ID="com.crushpaper.app" bold=$'\033[1m'; dim=$'\033[2m'; yellow=$'\033[33m'; red=$'\033[31m'; reset=$'\033[0m' say() { printf '%s\n' "${yellow}▍${reset} $*"; } fail() { printf '%s\n' "${red}✗ $*${reset}" >&2; exit 1; } [[ "$(uname -s)" == "Darwin" ]] || fail "CrushPaper is a Mac app." major="$(sw_vers -productVersion | cut -d. -f1)" (( major >= 14 )) || fail "Needs macOS 14 Sonoma or later (you have $(sw_vers -productVersion))." tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT say "Checking latest version…" curl -fsSL "$SITE/version.json" -o "$tmp/version.json" || fail "Couldn't reach $SITE" field() { plutil -extract "$1" raw -o - "$tmp/version.json" 2>/dev/null || true; } version="$(field version)" zip_url="$(field zip)" expected_sha="$(field sha256)" [[ -n "$zip_url" ]] || zip_url="$SITE/download/CrushPaper.zip" [[ "$zip_url" == https://* ]] || fail "Refusing non-HTTPS download: $zip_url" say "Downloading CrushPaper ${version:-latest}…" curl -fL --progress-bar "$zip_url" -o "$tmp/CrushPaper.zip" || fail "Download failed." if [[ -n "$expected_sha" ]]; then actual_sha="$(shasum -a 256 "$tmp/CrushPaper.zip" | awk '{print $1}')" [[ "$actual_sha" == "$expected_sha" ]] || fail "Checksum mismatch, not installing. Expected $expected_sha, got $actual_sha." say "Checksum OK." fi ditto -x -k "$tmp/CrushPaper.zip" "$tmp/unpacked" [[ -d "$tmp/unpacked/$APP_NAME" ]] || fail "Zip didn't contain $APP_NAME." dest="/Applications" [[ -w "$dest" ]] || { dest="$HOME/Applications"; mkdir -p "$dest"; } if pgrep -xq CrushPaper; then say "Quitting running CrushPaper…" osascript -e "quit app id \"$BUNDLE_ID\"" >/dev/null 2>&1 || true for _ in 1 2 3 4 5 6 7 8 9 10; do pgrep -xq CrushPaper || break; sleep 0.3; done fi rm -rf "${dest:?}/$APP_NAME" ditto "$tmp/unpacked/$APP_NAME" "$dest/$APP_NAME" xattr -dr com.apple.quarantine "$dest/$APP_NAME" 2>/dev/null || true say "Installed to ${bold}$dest/$APP_NAME${reset}" open "$dest/$APP_NAME" printf '%s\n' "" " ${bold}⌘⇧T${reset} opens your board. Crush something." \ " ${dim}Uninstall: rm -rf \"$dest/$APP_NAME\"${reset}" ""