feat(e2e): scripts that build the golden image of the macOS guest and its programs

build-golden.sh makes the golden Tart image from a vanilla one of Cirrus Labs: it provisions the
guest over SSH (provision.sh: the agent starts at login from a Terminal window, no state of earlier
runs is left, Terminal does not reopen old windows), stops for the one step that cannot be
scripted (a person allows Screen Recording and Accessibility for Terminal, once, and the grants live
in every clone), checks that the agent answers, and shuts the guest down from inside, so that
the last writes are on the disk the clones copy. build-bins.sh builds the programs under test for
the host and points the libiconv of the nix store at the system's, as the nix package does.
This commit is contained in:
LGUG2Z committed 2026-11-06 14:40:50 -08:00
1 parent 77e43d1602
commit fa2742a75d
3 files changed
+156

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
# Builds the programs the macOS guest runs and puts them in BIN_DIR, ready to upload:
#
# build-bins.sh BIN_DIR [cargo args, e.g. --release]
#
# komorebi, komorebic, the test window and the agent, built for the host (the guest is the same
# architecture). A build in the dev shell of the flake links the libiconv of the nix store, which the
# guest does not have, so the library is pointed at the system's, as the nix package does, and the
# programs are signed again, as the system asks of a changed one.
set -eu
bin_dir=${1:?usage: build-bins.sh BIN_DIR [cargo args]}
shift
root=$(cd "$(dirname "$0")/../../.." && pwd)
cd "$root"
# the default dev shell builds for Windows: this is for the host
unset CARGO_BUILD_TARGET
profile=debug
case " $* " in *" --release "*) profile=release ;; esac
COMMIT_HASH=${COMMIT_HASH:-$(git rev-parse HEAD 2>/dev/null || echo dev)} \
cargo build --locked -p komorebi -p komorebic -p komorebi-testwin -p komorebi-e2e-agent "$@"
mkdir -p "$bin_dir"
for bin in komorebi komorebic komorebi-testwin komorebi-e2e-agent; do
rm -f "$bin_dir/$bin"
cp "target/$profile/$bin" "$bin_dir/$bin"
chmod u+w "$bin_dir/$bin"
for lib in $(otool -L "$bin_dir/$bin" | grep -o '/nix/store/[^ ]*' || true); do
install_name_tool -change "$lib" "/usr/lib/$(basename "$lib" | sed 's/\.[0-9.]*dylib/.dylib/')" "$bin_dir/$bin"
done
codesign -f -s - "$bin_dir/$bin" 2>/dev/null
done
git rev-parse HEAD > "$bin_dir/commit" 2>/dev/null || true
echo "build-bins: $bin_dir"
+80
View File
@@ -0,0 +1,80 @@
#!/bin/sh
# Builds the golden image of the macOS guest from a vanilla Tart image of Cirrus Labs.
#
# build-golden.sh BIN_DIR [GOLDEN [VANILLA]]
#
# BIN_DIR is where `build-bins.sh` put komorebi-e2e-agent. GOLDEN is the name of the image to make
# (komorebi-e2e-golden), VANILLA the image to start from (the latest macOS of Cirrus Labs).
#
# One step cannot be scripted, and is the reason for a golden image: macOS asks a person to allow
# Screen Recording and Accessibility, once, for the program the agent runs under (Terminal). The
# grants live in the image and survive every clone of it. The script stops for it.
#
# It needs `tart` (KOMOREBI_E2E_TART or the path) and `sshpass` (the vanilla images log in with
# admin/admin), and about 30 GB of disk.
set -eu
bin_dir=${1:?usage: build-golden.sh BIN_DIR [GOLDEN [VANILLA]]}
golden=${2:-komorebi-e2e-golden}
vanilla=${3:-ghcr.io/cirruslabs/macos-tahoe-vanilla:latest}
tart=${KOMOREBI_E2E_TART:-tart}
here=$(cd "$(dirname "$0")" && pwd)
if "$tart" list | grep -q " $golden "; then
echo "build-golden: $golden exists; delete it first (tart delete $golden)" >&2
exit 1
fi
"$tart" clone "$vanilla" "$golden"
# the display of every guest is this size, which the scenarios and the work area rely on
"$tart" set "$golden" --display 1280x800 --cpu 4 --memory 8192
# a window, because a person has to use it
"$tart" run "$golden" > "${TMPDIR:-/tmp}/$golden-run.log" 2>&1 &
run=$!
ip=$("$tart" ip "$golden" --wait 120)
echo "build-golden: $golden is at $ip"
# a throwaway guest: its key is new every time, and the password is the one of the vanilla image
guest() {
sshpass -p admin ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR "admin@$ip" "$@"
}
copy_to_guest() {
sshpass -p admin scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR "$@"
}
i=0
until guest true 2>/dev/null; do
i=$((i + 1)); [ "$i" -lt 60 ] || { echo "build-golden: no ssh" >&2; exit 1; }
sleep 2
done
guest 'mkdir -p ~/e2e/bin'
copy_to_guest "$here/provision.sh" "$bin_dir/komorebi-e2e-agent" "admin@$ip:/Users/admin/e2e/bin/"
guest 'sh ~/e2e/bin/provision.sh && rm ~/e2e/bin/provision.sh && chmod +x ~/e2e/bin/komorebi-e2e-agent'
cat <<'MSG'
build-golden: now, in the window of the guest:
1. open Terminal and run: ~/e2e/agent.command
2. allow Screen Recording and Accessibility for Terminal when asked
(System Settings, Privacy & Security; the password of the account is admin)
3. when the agent runs without the prompts coming back, press Enter here.
MSG
read -r _
# the agent has to answer, or what was granted is not what a clone will run
python3 - "$ip" <<'PY'
import json, socket, sys
s = socket.create_connection((sys.argv[1], 7878), timeout=10)
f = s.makefile("rwb")
f.write(b'{"id": 1, "verb": "displays"}\n'); f.flush()
print("build-golden: the agent answers:", json.loads(f.readline()).get("ok") is not None)
PY
# a clean shutdown from inside, so that everything written is on the disk the clones copy: a
# `tart stop` of a running guest loses the last writes
guest 'sync; echo admin | sudo -S shutdown -h now' || true
wait "$run" || true
echo "build-golden: done; $golden is stopped and ready"
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# Runs in the guest, as the logged-in user, to make it what a scenario clones: the agent starts at
# login from a Terminal window (which holds the grants, so the agent and the daemon it starts are
# attributed to it), nothing from earlier runs is left, and Terminal does not reopen old windows.
set -eu
pkill -x komorebi || true
pkill -f komorebi-e2e-agent || true
pkill -f komorebi-testwin || true
killall Terminal 2>/dev/null || true
sleep 1
rm -rf "$HOME/Library/Saved Application State/com.apple.Terminal.savedState" \
"$HOME/Library/Application Support/komorebi"
defaults write com.apple.Terminal NSQuitAlwaysKeepsWindows -bool false
defaults write -g ApplePersistenceIgnoreState -bool true
mkdir -p "$HOME/e2e/bin" "$HOME/e2e/tmp" "$HOME/Library/LaunchAgents"
# what the launch agent opens: a Terminal window that runs the agent
cat > "$HOME/e2e/agent.command" <<COMMAND
#!/bin/sh
exec \$HOME/e2e/bin/komorebi-e2e-agent
COMMAND
chmod +x "$HOME/e2e/agent.command"
cat > "$HOME/Library/LaunchAgents/dev.komorebi.e2e-agent.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>dev.komorebi.e2e-agent</string>
<key>ProgramArguments</key><array>
<string>/usr/bin/open</string><string>-a</string><string>Terminal</string>
<string>$HOME/e2e/agent.command</string>
</array>
<key>RunAtLoad</key><true/>
<key>LimitLoadToSessionType</key><string>Aqua</string>
</dict></plist>
PLIST
plutil -lint "$HOME/Library/LaunchAgents/dev.komorebi.e2e-agent.plist"