#!/bin/bash
# MacKit public beta installer
# Usage: curl -fsSL https://mackit.pages.dev/install.sh | bash
set -euo pipefail
BASE_URL="https://mackit.pages.dev"
APP_NAME="MacKit"
VERSION="0.1.0"
MIN_MACOS=13
DEFAULT_INSTALL_DIR="/Applications"
INSTALL_DIR="${MACKIT_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
OPEN_AFTER=1
KEEP_QUARANTINE=0
DRY_RUN=0
EXPECTED_VERSION=""
usage() {
cat <<'USAGE'
MacKit installer (public beta)
curl -fsSL https://mackit.pages.dev/install.sh | bash
Options:
--install-dir
Install location (default: /Applications)
--version Require this installed version (default: latest beta)
--keep-quarantine Do not remove the macOS quarantine flag
--no-open Do not launch after install
--dry-run Print actions without changing anything
-h, --help Show this help
Environment: MACKIT_INSTALL_DIR
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--install-dir) INSTALL_DIR="${2:?}"; shift 2 ;;
--version) EXPECTED_VERSION="${2:?}"; shift 2 ;;
--keep-quarantine) KEEP_QUARANTINE=1; shift ;;
--no-open) OPEN_AFTER=0; shift ;;
--dry-run) DRY_RUN=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
esac
done
say() { printf '\033[1;36m›\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31m✗\033[0m %s\n' "$*" >&2; exit 1; }
[[ "$(uname -s)" == "Darwin" ]] || fail "MacKit requires macOS."
MACOS_MAJOR=$(sw_vers -productVersion | cut -d. -f1)
[[ "$MACOS_MAJOR" -ge "$MIN_MACOS" ]] || fail "macOS $MIN_MACOS or newer is required (found $(sw_vers -productVersion))."
ARCH=$(uname -m)
[[ "$ARCH" == "arm64" || "$ARCH" == "x86_64" ]] || fail "Unsupported architecture: $ARCH"
say "MacKit $VERSION installer · macOS $(sw_vers -productVersion) · $ARCH"
warn "This is a free beta. It is ad-hoc signed and not notarized yet, so the installer removes the quarantine flag after verifying the checksum."
TMP=$(mktemp -d "${TMPDIR:-/tmp}/mackit-install.XXXXXX")
MNT="$TMP/mnt"
cleanup() {
hdiutil detach "$MNT" -quiet >/dev/null 2>&1 || true
rm -rf "$TMP"
}
trap cleanup EXIT
DMG_URL="$BASE_URL/download/MacKit.dmg"
SHA_URL="$BASE_URL/download/MacKit.dmg.sha256"
say "Downloading $DMG_URL"
curl -fL --proto '=https' --tlsv1.2 --retry 3 --progress-bar "$DMG_URL" -o "$TMP/MacKit.dmg" \
|| fail "Download failed: $DMG_URL"
curl -fsSL --proto '=https' --tlsv1.2 --retry 3 "$SHA_URL" -o "$TMP/MacKit.dmg.sha256" \
|| fail "Could not download the checksum."
EXPECTED=$(tr -d '[:space:]' < "$TMP/MacKit.dmg.sha256")
ACTUAL=$(shasum -a 256 "$TMP/MacKit.dmg" | awk '{print $1}')
[[ -n "$EXPECTED" && "$EXPECTED" == "$ACTUAL" ]] || fail "Checksum mismatch. The download may be corrupted."
say "Checksum verified"
if [[ "$DRY_RUN" == "1" ]]; then
say "Dry run: would install to $INSTALL_DIR/$APP_NAME.app"
exit 0
fi
mkdir -p "$MNT"
hdiutil attach "$TMP/MacKit.dmg" -nobrowse -readonly -mountpoint "$MNT" -quiet
[[ -d "$MNT/$APP_NAME.app" ]] || fail "The disk image does not contain $APP_NAME.app."
osascript -e "tell application \"$APP_NAME\" to quit" >/dev/null 2>&1 || true
TARGET_DIR="$INSTALL_DIR"
if [[ ! -d "$TARGET_DIR" ]]; then
mkdir -p "$TARGET_DIR" 2>/dev/null || true
fi
if [[ ! -w "$TARGET_DIR" ]]; then
TARGET_DIR="$HOME/Applications"
warn "$INSTALL_DIR is not writable; installing to $TARGET_DIR instead."
mkdir -p "$TARGET_DIR"
fi
say "Installing to $TARGET_DIR/$APP_NAME.app"
rm -rf "$TARGET_DIR/$APP_NAME.app"
ditto "$MNT/$APP_NAME.app" "$TARGET_DIR/$APP_NAME.app"
if [[ "$KEEP_QUARANTINE" == "0" ]]; then
xattr -dr com.apple.quarantine "$TARGET_DIR/$APP_NAME.app" >/dev/null 2>&1 || true
fi
INSTALLED_VERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$TARGET_DIR/$APP_NAME.app/Contents/Info.plist" 2>/dev/null || echo "?")
say "Installed $APP_NAME $INSTALLED_VERSION"
if [[ -n "$EXPECTED_VERSION" && "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]]; then
fail "Expected version $EXPECTED_VERSION but installed $INSTALLED_VERSION."
fi
if [[ "$OPEN_AFTER" == "1" ]]; then
open "$TARGET_DIR/$APP_NAME.app"
fi
cat <