Skip to content

Commit 491fb59

Browse files
committed
feat(macos): manage apps with Homebrew and background deferral, and cleanup legacy logic
1 parent dd17067 commit 491fb59

File tree

3 files changed

+33
-254
lines changed

3 files changed

+33
-254
lines changed

external.lock.json

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
11
{
2-
"wezterm": {
3-
"version": "20240203-110809-5046fc22",
4-
"url": "https://github.com/wezterm/wezterm/releases/download/20240203-110809-5046fc22/WezTerm-macos-20240203-110809-5046fc22.zip",
5-
"sha256": "e77388cad55f2e9da95a220a89206a6c58f865874a629b7c3ea3c162f5692224"
6-
},
7-
"zed": {
8-
"version": "v0.219.4",
9-
"aarch64": {
10-
"url": "https://github.com/zed-industries/zed/releases/download/v0.219.4/Zed-aarch64.dmg",
11-
"sha256": "2b99569832df36ed017c3e39fd48efdb065084705bd54cb5dd18351f4b2ff543"
12-
},
13-
"x86_64": {
14-
"url": "https://github.com/zed-industries/zed/releases/download/v0.219.4/Zed-x86_64.dmg",
15-
"sha256": "55c7fbf87b2957d9aeaedf10dbbd7876374dfb67a68590ce4615ae2ed2e0ad2a"
16-
}
17-
},
182
"iosevka-charon": {
193
"version": "pre-b3162d9da3c6c56995cbc764ff8d6fe025b6af9b",
204
"url": "https://github.com/jul-sh/iosevka-charon/releases/download/pre-b3162d9da3c6c56995cbc764ff8d6fe025b6af9b/iosevka-charon.zip",
215
"sha256": "d5edb8cf50f9fe3f1ceef08f27498e070cc77deef92c0e52756fc2d399367dc6"
22-
},
23-
"clipkitty": {
24-
"version": "commit-f6e5972d755276c4a4566b92eba8c17611017481",
25-
"url": "https://github.com/jul-sh/clipkitty/releases/download/commit-f6e5972d755276c4a4566b92eba8c17611017481/ClipKitty.dmg",
26-
"sha256": "c4f7966ca948b5ee3ed1b9712f201403a04602d431e10b6a57d3698e88113382"
276
}
287
}

macos/setup.sh

Lines changed: 29 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -8,147 +8,37 @@ cd "$(dirname "$0")/.."
88

99
die() { echo "error: $1" >&2; exit 1; }
1010

11-
check_app_updates() {
12-
local lockfile="./external.lock.json"
13-
14-
local locked_wezterm
15-
locked_wezterm=$(jq -r '.wezterm.version' "$lockfile")
16-
local latest_wezterm
17-
latest_wezterm=$(curl -fsSL "https://api.github.com/repos/wez/wezterm/releases/latest" 2>/dev/null | jq -r '.tag_name' || echo "$locked_wezterm")
18-
if [[ "$latest_wezterm" != "$locked_wezterm" ]]; then
19-
echo "warning: WezTerm $latest_wezterm available (locked: $locked_wezterm). Run ./update.sh to update."
20-
fi
21-
22-
local locked_zed
23-
locked_zed=$(jq -r '.zed.version' "$lockfile")
24-
local latest_zed
25-
latest_zed=$(curl -fsSL "https://api.github.com/repos/zed-industries/zed/releases/latest" 2>/dev/null | jq -r '.tag_name' || echo "$locked_zed")
26-
if [[ "$latest_zed" != "$locked_zed" ]]; then
27-
echo "warning: Zed $latest_zed available (locked: $locked_zed). Run ./update.sh to update."
28-
fi
29-
}
30-
31-
finalize_app_install() {
32-
local source_bundle="$1" app_name="$2"
33-
echo " Finalizing $app_name installation..."
34-
sudo rm -rf "/Applications/${app_name}.app"
35-
sudo mv "$source_bundle" "/Applications/${app_name}.app"
36-
sudo xattr -dr com.apple.quarantine "/Applications/${app_name}.app" 2>/dev/null || true
37-
}
38-
39-
install_app_logic() {
40-
local new_bundle="$1" app_name="$2"
41-
42-
if pgrep -x "$app_name" >/dev/null; then
43-
local staging_dir="$HOME/.local/share/clipkitty/update_staging"
44-
mkdir -p "$staging_dir"
45-
local staged_bundle="${staging_dir}/${app_name}.app"
46-
47-
echo " $app_name is currently running. Update scheduled for when it closes."
48-
rm -rf "$staged_bundle"
49-
mv "$new_bundle" "$staged_bundle"
50-
51-
# Spawn background waiter
52-
(
53-
while pgrep -x "$app_name" >/dev/null; do
54-
# Keep sudo session alive (non-interactive refresh)
55-
sudo -n -v 2>/dev/null || true
56-
sleep 60
57-
done
58-
finalize_app_install "$staged_bundle" "$app_name"
59-
) & disown
60-
else
61-
finalize_app_install "$new_bundle" "$app_name"
62-
fi
63-
}
64-
65-
install_app_from_zip() {
66-
local url="$1" expected_sha256="$2" app_name="$3"
67-
local tmp_dir zip_path actual_sha256
68-
69-
tmp_dir="$(mktemp -d)"
70-
zip_path="${tmp_dir}/app.zip"
71-
72-
curl -fsSL "$url" -o "$zip_path"
73-
actual_sha256=$(shasum -a 256 "$zip_path" | awk '{print $1}')
74-
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
75-
rm -rf "$tmp_dir"
76-
die "SHA256 mismatch for $app_name: expected $expected_sha256, got $actual_sha256"
77-
fi
78-
79-
unzip -q "$zip_path" -d "$tmp_dir"
80-
local app_bundle
81-
app_bundle=$(find "$tmp_dir" -name "*.app" -type d | head -1)
82-
if [[ -z "$app_bundle" ]]; then
83-
echo "Contents of extracted zip:"
84-
ls -la "$tmp_dir"
85-
rm -rf "$tmp_dir"
86-
die "No .app bundle found in $app_name zip file"
87-
fi
88-
89-
install_app_logic "$app_bundle" "$app_name"
90-
91-
rm -rf "$tmp_dir"
92-
}
93-
94-
install_app_from_dmg() {
95-
local url="$1" expected_sha256="$2" app_name="$3"
96-
local tmp_dir dmg_path actual_sha256 mount_point
97-
98-
tmp_dir="$(mktemp -d)"
99-
dmg_path="${tmp_dir}/app.dmg"
100-
101-
curl -fsSL "$url" -o "$dmg_path"
102-
actual_sha256=$(shasum -a 256 "$dmg_path" | awk '{print $1}')
103-
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
104-
rm -rf "$tmp_dir"
105-
die "SHA256 mismatch for $app_name: expected $expected_sha256, got $actual_sha256"
106-
fi
107-
108-
mount_point=$(hdiutil attach -nobrowse -readonly "$dmg_path" 2>/dev/null | grep -o '/Volumes/.*' | head -1)
109-
local app_bundle
110-
app_bundle=$(find "$mount_point" -maxdepth 1 -name "*.app" | head -1)
111-
112-
# Copy from read-only mount to a writable temp location before handing off to logic
113-
local writable_bundle="${tmp_dir}/staged_${app_name}.app"
114-
cp -R "$app_bundle" "$writable_bundle"
115-
hdiutil detach "$mount_point" -quiet
116-
117-
install_app_logic "$writable_bundle" "$app_name"
118-
119-
rm -rf "$tmp_dir"
120-
}
121-
122-
install_clipkitty() {
123-
local lockfile="./external.lock.json"
124-
local url sha256
125-
url=$(jq -r '.clipkitty.url' "$lockfile")
126-
sha256=$(jq -r '.clipkitty.sha256' "$lockfile")
127-
echo " Installing ClipKitty..."
128-
install_app_from_dmg "$url" "$sha256" "ClipKitty"
129-
}
130-
13111
install_desktop_apps() {
13212
echo "Installing desktop apps..."
133-
check_app_updates
134-
135-
local arch
136-
arch=$(uname -m)
137-
[[ "$arch" == "arm64" ]] && arch="aarch64"
138-
139-
local wezterm_url wezterm_sha256
140-
wezterm_url=$(jq -r '.wezterm.url' "./external.lock.json")
141-
wezterm_sha256=$(jq -r '.wezterm.sha256' "./external.lock.json")
142-
echo " Installing WezTerm..."
143-
install_app_from_zip "$wezterm_url" "$wezterm_sha256" "WezTerm"
144-
145-
local zed_url zed_sha256
146-
zed_url=$(jq -r ".zed.${arch}.url" "./external.lock.json")
147-
zed_sha256=$(jq -r ".zed.${arch}.sha256" "./external.lock.json")
148-
echo " Installing Zed..."
149-
install_app_from_dmg "$zed_url" "$zed_sha256" "Zed"
150-
151-
install_clipkitty
13+
echo "Ensuring desktop apps are managed by Homebrew..."
14+
local casks=("wezterm" "zed" "jul-sh/clipkitty/clipkitty")
15+
for cask in "${casks[@]}"; do
16+
local base_name="${cask##*/}"
17+
# Check if installed
18+
if ! brew list --cask | grep -q "^${base_name}$"; then
19+
echo "Installing $cask..."
20+
brew install --cask "$cask"
21+
else
22+
# Check if outdated
23+
if brew outdated --cask --quiet "$cask" >/dev/null 2>&1; then
24+
if pgrep -ix "$base_name" >/dev/null; then
25+
echo " $base_name is currently running. Scheduling update for when it closes..."
26+
(
27+
while pgrep -ix "$base_name" >/dev/null; do
28+
sleep 60
29+
done
30+
echo " $base_name closed. Starting Homebrew update..."
31+
brew upgrade --cask "$cask"
32+
) & disown
33+
else
34+
echo "Updating $base_name..."
35+
brew upgrade --cask "$cask" || true
36+
fi
37+
else
38+
echo " $base_name is up to date."
39+
fi
40+
fi
41+
done
15242
}
15343

15444
build_spotlight_scripts() {

update.sh

Lines changed: 4 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,17 @@
11
#!/usr/bin/env bash
22
#
3-
# Updates flake.lock and apps.lock.json to latest versions.
3+
# Updates flake.lock and external.lock.json to latest versions.
44
#
55
set -euo pipefail
66
cd "$(dirname "${BASH_SOURCE[0]}")"
77

88
LOCKFILE="external.lock.json"
99

10-
get_arch() {
11-
case "$(uname -m)" in
12-
x86_64) echo "x86_64" ;;
13-
aarch64|arm64) echo "aarch64" ;;
14-
*) echo "unknown" ;;
15-
esac
16-
}
17-
1810
update_flake() {
1911
echo "Updating flake.lock..."
2012
nix flake update --flake ./nix
2113
}
2214

23-
fetch_wezterm_latest() {
24-
local api_url="https://api.github.com/repos/wez/wezterm/releases/latest"
25-
local tmp_json
26-
tmp_json=$(mktemp)
27-
28-
curl -fsSL "$api_url" -o "$tmp_json"
29-
30-
local version
31-
version=$(jq -r '.tag_name' "$tmp_json")
32-
33-
local url="https://github.com/wezterm/wezterm/releases/download/${version}/WezTerm-macos-${version}.zip"
34-
35-
echo " Fetching WezTerm hash..." >&2
36-
local sha256
37-
sha256=$(curl -fsSL "${url}.sha256" | awk '{print $1}')
38-
39-
local result
40-
result=$(jq -n --arg v "$version" --arg u "$url" --arg s "$sha256" \
41-
'{version: $v, url: $u, sha256: $s}')
42-
rm "$tmp_json"
43-
echo "$result"
44-
}
45-
46-
fetch_zed_latest() {
47-
local api_url="https://api.github.com/repos/zed-industries/zed/releases/latest"
48-
local tmp_json
49-
tmp_json=$(mktemp)
50-
51-
curl -fsSL "$api_url" -o "$tmp_json"
52-
53-
local version
54-
version=$(jq -r '.tag_name' "$tmp_json")
55-
rm "$tmp_json"
56-
57-
local result='{"version":"'"$version"'"}'
58-
59-
for arch in aarch64 x86_64; do
60-
local url="https://github.com/zed-industries/zed/releases/download/${version}/Zed-${arch}.dmg"
61-
echo " Fetching Zed ${arch} hash..." >&2
62-
local sha256
63-
sha256=$(curl -fsSL "$url" | shasum -a 256 | awk '{print $1}')
64-
result=$(echo "$result" | jq --arg a "$arch" --arg u "$url" --arg s "$sha256" \
65-
'.[$a] = {url: $u, sha256: $s}')
66-
done
67-
68-
echo "$result"
69-
}
70-
7115
fetch_iosevka_charon_latest() {
7216
local api_url="https://api.github.com/repos/jul-sh/iosevka-charon/releases"
7317
local tmp_json
@@ -90,49 +34,15 @@ fetch_iosevka_charon_latest() {
9034
'{version: $v, url: $u, sha256: $s}'
9135
}
9236

93-
fetch_clipkitty_latest() {
94-
local api_url="https://api.github.com/repos/jul-sh/clipkitty/releases/latest"
95-
local tmp_json
96-
tmp_json=$(mktemp)
97-
98-
curl -fsSL "$api_url" -o "$tmp_json"
99-
100-
local version
101-
version=$(jq -r '.tag_name' "$tmp_json")
102-
103-
local url
104-
url=$(jq -r '.assets[] | select(.name == "ClipKitty.dmg") | .browser_download_url' "$tmp_json")
105-
rm "$tmp_json"
106-
107-
echo " Fetching ClipKitty hash..." >&2
108-
local sha256
109-
sha256=$(curl -fsSL "$url" | shasum -a 256 | awk '{print $1}')
110-
111-
jq -n --arg v "$version" --arg u "$url" --arg s "$sha256" \
112-
'{version: $v, url: $u, sha256: $s}'
113-
}
114-
11537
update_apps() {
116-
echo "Updating external.lock.json..."
117-
118-
echo "Fetching WezTerm latest..."
119-
local wezterm
120-
wezterm=$(fetch_wezterm_latest)
121-
122-
echo "Fetching Zed latest..."
123-
local zed
124-
zed=$(fetch_zed_latest)
38+
echo "Updating $LOCKFILE..."
12539

12640
echo "Fetching iosevka-charon latest..."
12741
local iosevka_charon
12842
iosevka_charon=$(fetch_iosevka_charon_latest)
12943

130-
echo "Fetching ClipKitty latest..."
131-
local clipkitty
132-
clipkitty=$(fetch_clipkitty_latest)
133-
134-
jq -n --argjson w "$wezterm" --argjson z "$zed" --argjson i "$iosevka_charon" --argjson c "$clipkitty" \
135-
'{wezterm: $w, zed: $z, "iosevka-charon": $i, clipkitty: $c}' > "$LOCKFILE"
44+
jq -n --argjson i "$iosevka_charon" \
45+
'{"iosevka-charon": $i}' > "$LOCKFILE"
13646

13747
echo "Updated $LOCKFILE"
13848
}

0 commit comments

Comments
 (0)