Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,27 @@ Night mode · Disk cleaning · Screen, keyboard and trackpad cleaning
## Install

Perch is free, open source, and needs macOS 14 or later. The binary is
universal — Apple silicon and Intel — and about 4 MB.
universal — Apple silicon and Intel — and about 6 MB.

### One command

```bash
curl -fsSL https://ishanmalu.github.io/Perch/install.sh | bash
```

Fetches the latest release, checks it against the published checksum, installs
it, clears the flag macOS puts on downloads, and starts it. It asks for no
privileges and touches nothing outside the app.

Piping a script into a shell means trusting it, which is a fair thing to be
wary of. Read it first if you would rather:

```bash
curl -fsSL https://ishanmalu.github.io/Perch/install.sh -o install.sh
less install.sh && bash install.sh
```

Updates after this install themselves — press Check in the panel footer.

### Download the DMG

Expand Down
23 changes: 21 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -899,12 +899,31 @@ <h2>Install</h2>
<p class="section-sub">Free, and takes about a minute.</p>

<div class="tabs" role="tablist" style="justify-content:center">
<button class="tab" role="tab" aria-selected="true" data-panel="p-dmg">Download</button>
<button class="tab" role="tab" aria-selected="true" data-panel="p-cmd">One command</button>
<button class="tab" role="tab" aria-selected="false" data-panel="p-dmg">Download</button>
<button class="tab" role="tab" aria-selected="false" data-panel="p-brew">Homebrew</button>
<button class="tab" role="tab" aria-selected="false" data-panel="p-src">Build it</button>
</div>

<div class="panel" id="p-dmg" style="max-width:640px;margin:0 auto">
<div class="panel" id="p-cmd" style="max-width:640px;margin:0 auto">
<p class="section-sub" style="margin-bottom:14px">
Fetches the latest release, checks it against the published checksum, installs it,
clears the flag macOS puts on downloads, and starts it. Asks for no privileges.
</p>
<pre>curl -fsSL https://ishanmalu.github.io/Perch/install.sh | bash</pre>
<p style="margin-top:16px;color:var(--muted);font-size:14.4px">
Piping a script into a shell means trusting it, which is a fair thing to be wary of.
Read it first if you would rather:
</p>
<pre>curl -fsSL https://ishanmalu.github.io/Perch/install.sh -o install.sh
less install.sh &amp;&amp; bash install.sh</pre>
<p style="margin-top:16px;color:var(--muted);font-size:14.4px">
Then grant Accessibility access under System Settings → Privacy &amp; Security →
Accessibility, and press <kbd>⌃⌥Space</kbd>. Updates after this install themselves.
</p>
</div>

<div class="panel" id="p-dmg" hidden style="max-width:640px;margin:0 auto">
<ol class="steps">
<li>
<h4>Download the DMG</h4>
Expand Down
90 changes: 90 additions & 0 deletions docs/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
#
# Installs the latest Perch release.
#
# curl -fsSL https://ishanmalu.github.io/Perch/install.sh | bash
#
# Perch is signed but not notarized, because notarizing needs a paid Apple
# Developer account. macOS therefore quarantines the download and blocks the
# first launch. This does what you would otherwise do by hand: fetch the
# release, check it against the published checksum, install it, clear the
# quarantine flag, and start it.
#
# It asks for no privileges and touches nothing outside the app itself. If you
# would rather read it before running it, that is the better habit:
#
# curl -fsSL https://ishanmalu.github.io/Perch/install.sh -o install.sh
# less install.sh && bash install.sh
#
set -euo pipefail

REPO="ishanmalu/Perch"
API="https://api.github.com/repos/$REPO/releases/latest"

say() { printf '\033[1m==>\033[0m %s\n' "$1"; }
die() { printf '\033[1;31m!!\033[0m %s\n' "$1" >&2; exit 1; }

[ "$(uname -s)" = "Darwin" ] || die "Perch is a macOS app."
major="$(sw_vers -productVersion | cut -d. -f1)"
[ "$major" -ge 14 ] || die "Perch needs macOS 14 or newer; this is $(sw_vers -productVersion)."

say "Finding the latest release"
json="$(curl -fsSL "$API")" || die "Could not reach GitHub."
version="$(printf '%s' "$json" | sed -n 's/.*"tag_name": *"v\{0,1\}\([^"]*\)".*/\1/p' | head -1)"
[ -n "$version" ] || die "Could not read the latest version."

dmg_url="$(printf '%s' "$json" | grep -o 'https://[^"]*\.dmg' | head -1)"
sums_url="$(printf '%s' "$json" | grep -o 'https://[^"]*SHA256SUMS\.txt' | head -1)"
[ -n "$dmg_url" ] || die "That release has no disk image."
[ -n "$sums_url" ] || die "That release publishes no checksum, so it will not be installed."

tmp="$(mktemp -d)"
trap 'hdiutil detach "$tmp/mnt" -quiet -force 2>/dev/null || true; rm -rf "$tmp"' EXIT

say "Downloading Perch $version"
curl -fsSL "$dmg_url" -o "$tmp/Perch.dmg" || die "Download failed."
curl -fsSL "$sums_url" -o "$tmp/SHA256SUMS.txt" || die "Could not fetch the checksum."

say "Checking it against the published checksum"
expected="$(awk '{print $1}' "$tmp/SHA256SUMS.txt" | head -1)"
actual="$(shasum -a 256 "$tmp/Perch.dmg" | awk '{print $1}')"
[ "$expected" = "$actual" ] || die "Checksum mismatch. The download was discarded."

# /Applications is writable by admins; fall back to the user's own folder so
# this never needs sudo.
dest="/Applications"
[ -w "$dest" ] || dest="$HOME/Applications"
mkdir -p "$dest"

say "Installing to $dest"
mkdir -p "$tmp/mnt"
hdiutil attach "$tmp/Perch.dmg" -nobrowse -noautoopen -quiet -mountpoint "$tmp/mnt" \
|| die "Could not open the disk image."
[ -d "$tmp/mnt/Perch.app" ] || die "The disk image did not contain Perch."

if pgrep -f "Perch.app/Contents/MacOS/Perch" >/dev/null 2>&1; then
say "Quitting the running copy"
osascript -e 'quit app "Perch"' 2>/dev/null || true
sleep 1
fi

rm -rf "$dest/Perch.app"
# ditto rather than cp: it preserves the signature's extended attributes.
ditto "$tmp/mnt/Perch.app" "$dest/Perch.app" || die "Could not copy Perch into $dest."

say "Clearing the quarantine flag"
xattr -dr com.apple.quarantine "$dest/Perch.app" 2>/dev/null || true

say "Starting Perch"
open "$dest/Perch.app"

cat <<NOTE

Perch $version is installed in $dest.

Grant Accessibility access when asked — window tiling, the switcher and the
cleaning modes need it: System Settings -> Privacy & Security -> Accessibility.

Press Control-Option-Space to open the panel.
Updates from here install themselves: press Check in the panel footer.
NOTE
Loading