diff --git a/AGENTS.md b/AGENTS.md index a29ca8cdc..faa49fc53 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,12 +99,18 @@ Notes: ### VM Lab Concurrency (mandatory admission path) -- All disposable KeyPath VM work must go through `Scripts/lab/keypath-lab` from - current `master`. Do not create installer-test VMs with raw `crabbox`, `tart`, - `prlctl`, `keypath15`, `keypath26`, or `keypath27` commands; those bypass the - shared provider admission contract. -- Before requesting a VM, run `Scripts/lab/keypath-lab list`. Create it with the - explicit commit, signed installer, lane, and desktop requirement documented in +- The lab now lives in its own repository and serves several projects. All + disposable KeyPath VM work must go through the installed `vm-lab` CLI, which + takes the tenant first: `vm-lab keypath `. Do not create + installer-test VMs with raw `crabbox`, `tart`, `prlctl`, `keypath15`, + `keypath26`, or `keypath27` commands; those bypass the shared provider + admission contract. +- KeyPath declares itself to the lab in `.vm-lab.tsv` at the repository root: + the artifact its installer must contain, the command that admits a lane, and + the managed-policy generator and verifier. The lab knows nothing about KeyPath + beyond that file, so a change there is how KeyPath changes lab behaviour. +- Before requesting a VM, run `vm-lab keypath list`. Create it with the explicit + commit, signed installer, lane, and desktop requirement documented in `docs/testing/remote-installer-lab.md`. - Provider capacity is centrally enforced on the mini: one Tart lease and two Parallels leases by default. `create` exits 75 with `capacity_busy` and the @@ -112,8 +118,11 @@ Notes: failure: continue non-VM work or retry after the owning lease is destroyed or expires. Never stop, adopt, or mutate another agent's lease. - A successful lease manifest is the reservation. Always collect artifacts and - call `Scripts/lab/keypath-lab destroy ` when finished; do not leave a VM + call `vm-lab keypath destroy ` when finished; do not leave a VM running for another agent to infer ownership. +- What stays in `Scripts/lab` is KeyPath's own: scenarios, MDM policy, the + dashboards, and `host-disk-reserve`, which CI calls directly so that CI does + not depend on the lab being installed on the runner. ### PR Workflow & Git Safety diff --git a/Scripts/lab/desktop-bootstrap b/Scripts/lab/desktop-bootstrap deleted file mode 100755 index 032957a09..000000000 --- a/Scripts/lab/desktop-bootstrap +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/zsh -set -euo pipefail - -die() { print -u2 "desktop-bootstrap: $*"; exit 1; } - -usage() { - print -u2 "Usage: Scripts/lab/desktop-bootstrap --output DIR [--install-tools]" - exit 2 -} - -output= -install_tools=0 -while [[ $# -gt 0 ]]; do - case "$1" in - --output) [[ -n ${2:-} ]] || usage; output=$2; shift 2 ;; - --install-tools) install_tools=1; shift ;; - *) usage ;; - esac -done -[[ -n "$output" && "$output" != -* ]] || usage -mkdir -p "$output" - -console_user=$(stat -f '%Su' /dev/console) -[[ "$console_user" != root && "$console_user" != loginwindow ]] || die "no logged-in console user" -[[ "$console_user" == "$USER" ]] || die "command user $USER does not own console session $console_user" - -if (( install_tools )); then - command -v brew >/dev/null || die "Homebrew is required to install desktop tools" - command -v python3 >/dev/null || brew install python - command -v peekaboo >/dev/null || brew install steipete/tap/peekaboo - command -v mcporter >/dev/null || brew install steipete/tap/mcporter -fi - -python_bin=$(command -v python3 || true) -[[ -x "$python_bin" ]] || die "Python 3 is required" -peekaboo_bin=$(command -v peekaboo || true) -mcporter_bin=$(command -v mcporter || true) -[[ -x "$peekaboo_bin" && -x "$mcporter_bin" ]] || die "Peekaboo 3 and mcporter are required" -[[ "$("$peekaboo_bin" --version | head -1)" == Peekaboo\ 3.* ]] || die "Peekaboo 3 is required" - -"$peekaboo_bin" permissions status --json > "$output/peekaboo-permissions-before.json" || true -# Ensure there is a normal shareable window. A bare Finder desktop has no -# eligible window, so it cannot trigger or verify capture consent. -open -a "System Settings" -sleep 2 -# The first capture can display macOS's private-picker bypass consent sheet. -"$peekaboo_bin" see --app "System Settings" --json > "$output/initial-desktop.json" || true -"$peekaboo_bin" see --app UserNotificationCenter --json > "$output/user-notification-center.json" || true - -consent=$("$python_bin" - "$output/user-notification-center.json" <<'PY' -import json, sys -try: - data = json.load(open(sys.argv[1])).get("data", {}) -except Exception: - raise SystemExit(1) -for element in data.get("ui_elements", []): - if element.get("is_actionable") and element.get("label") == "Allow": - print(data["snapshot_id"], element["id"], sep="\t") - break -PY -) || true - -if [[ -n "$consent" ]]; then - snapshot=${consent%%$'\t'*} - element=${consent#*$'\t'} - "$peekaboo_bin" click --on "$element" --snapshot "$snapshot" --no-auto-focus --json \ - > "$output/peekaboo-consent-click.json" -fi - -"$peekaboo_bin" see --app "System Settings" --json > "$output/desktop-ready.json" -"$peekaboo_bin" permissions status --json > "$output/peekaboo-permissions-after.json" - -"$python_bin" - "$output/bootstrap.json" "$console_user" "$("$peekaboo_bin" --version | head -1)" <<'PY' -import json, platform, sys -path, user, version = sys.argv[1:] -with open(path, "w") as f: - json.dump({ - "status": "ready", - "consoleUser": user, - "macOS": platform.mac_ver()[0], - "peekaboo": version, - }, f, indent=2) - f.write("\n") -PY -cat "$output/bootstrap.json" diff --git a/Scripts/lab/keypath-lab b/Scripts/lab/keypath-lab deleted file mode 100755 index 6b7b6acca..000000000 --- a/Scripts/lab/keypath-lab +++ /dev/null @@ -1,303 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR=$(cd "$(dirname "$0")" >/dev/null && pwd -P) -REPO_ROOT=$(cd "$SCRIPT_DIR/../.." >/dev/null && pwd -P) -REMOTE_SCRIPT="$SCRIPT_DIR/remote.sh" -DEFAULT_HOST="clawd@keypath-lab-mini" - -usage() { - cat >&2 <<'EOF' -Usage: Scripts/lab/keypath-lab [--host HOST] [options] - -Commands: - preflight - create --macos 15|26|27 --lane managed-functional|unmanaged-ui --commit SHA --installer PATH [--ttl 2h] [--desktop] [--tart-usb-passthrough] - install-app LEASE_ID - console-login LEASE_ID - reset-guest-password LEASE_ID - secure-console-submit LEASE_ID - rfb-pointer-probe LEASE_ID --x X --y Y - desktop-bootstrap LEASE_ID [--install-tools] - nameplate LEASE_ID enable|show|hide|status - protected-click LEASE_ID --app APP --window TITLE (--x X --y Y | --ax-x X --ax-y Y) [--after-window TITLE] [--count 1|2] - desktop-type LEASE_ID --text TEXT - secure-dialog-input LEASE_ID --app APP --field LABEL [--submit BUTTON] [--already-focused] - resume-managed-policy LEASE_ID - run LEASE_ID -- COMMAND [ARG...] - status LEASE_ID - list - artifacts LEASE_ID - scenario LEASE_ID NAME - destroy LEASE_ID - cleanup [--dry-run] - -The default host is clawd@keypath-lab-mini. Set KEYPATH_LAB_HOST or pass ---host to override it. Creation always requires an explicit full commit SHA and -an installer artifact. No command embeds or prompts for credentials. - -VM creation is centrally admitted per provider. Exit 75 with `capacity_busy` -means another owned lease has the available slot; do not bypass this controller, -stop that lease, or adopt it. Continue non-VM work or retry after its owner -destroys it or its TTL expires. See docs/testing/remote-installer-lab.md. -EOF - exit 2 -} - -die() { - echo "keypath-lab: $*" >&2 - exit 1 -} - -host=${KEYPATH_LAB_HOST:-$DEFAULT_HOST} -if [[ ${1:-} == "--host" ]]; then - [[ -n ${2:-} ]] || usage - host=$2 - shift 2 -fi - -command=${1:-} -[[ -n $command ]] || usage -shift - -remote() { - local remote_command="/bin/zsh -s --" argument quoted - for argument in "$@"; do - printf -v quoted '%q' "$argument" - remote_command="$remote_command $quoted" - done - ssh -o BatchMode=yes "$host" "$remote_command" < "$REMOTE_SCRIPT" -} - -require_lease_id() { - [[ $1 =~ ^[A-Za-z0-9._-]+$ ]] || die "invalid lease id: $1" -} - -case "$command" in - preflight) - [[ $# -eq 0 ]] || usage - remote preflight - ;; - create) - macos= - lane= - commit= - installer= - ttl=2h - desktop=0 - tart_usb_passthrough=0 - while [[ $# -gt 0 ]]; do - case "$1" in - --macos) macos=${2:-}; shift 2 ;; - --lane) lane=${2:-}; shift 2 ;; - --commit) commit=${2:-}; shift 2 ;; - --installer) installer=${2:-}; shift 2 ;; - --ttl) ttl=${2:-}; shift 2 ;; - --desktop) desktop=1; shift ;; - --tart-usb-passthrough) tart_usb_passthrough=1; shift ;; - *) usage ;; - esac - done - [[ $macos == "15" || $macos == "26" || $macos == "27" ]] || die "--macos must be 15, 26, or 27" - [[ $lane == "managed-functional" || $lane == "unmanaged-ui" ]] || die "--lane must be managed-functional or unmanaged-ui" - [[ ! ($macos == "27" && $lane == "managed-functional") ]] || die "managed-functional is not yet supported on macOS 27" - [[ $tart_usb_passthrough == "0" || $macos == "15" ]] || die "--tart-usb-passthrough requires --macos 15" - [[ $commit =~ ^[0-9a-fA-F]{40}$ ]] || die "--commit must be a full 40-character SHA" - [[ -f $installer ]] || die "installer not found: $installer" - resolved=$(git -C "$REPO_ROOT" rev-parse --verify "$commit^{commit}") - [[ $resolved == "$commit" ]] || die "commit does not resolve exactly: $commit" - - installer_sha=$(shasum -a 256 "$installer" | awk '{print $1}') - archive_key="${commit}-${installer_sha}" - temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/keypath-lab.XXXXXX") - trap 'rm -rf "$temp_dir"' EXIT - mkdir -p "$temp_dir/repo/.keypath-lab/installer" - git -C "$REPO_ROOT" archive "$commit" | tar -x -C "$temp_dir/repo" - installer_name=$(basename "$installer") - [[ $installer_name =~ ^[A-Za-z0-9._-]+$ ]] || die "installer filename must contain only letters, numbers, dots, underscores, and dashes" - cp "$installer" "$temp_dir/repo/.keypath-lab/installer/$installer_name" - if [[ $lane == "managed-functional" ]]; then - mkdir -p "$temp_dir/installer-extract" - /usr/bin/ditto -x -k "$installer" "$temp_dir/installer-extract" - [[ -d $temp_dir/installer-extract/KeyPath.app ]] || die "managed installer must contain KeyPath.app at its root" - "$SCRIPT_DIR/mdm/generate-keypath-profiles" \ - --app "$temp_dir/installer-extract/KeyPath.app" \ - --output "$temp_dir/repo/.keypath-lab/managed-policy" >/dev/null - fi - cat > "$temp_dir/repo/.keypath-lab/source.tsv" </dev/null -} - -is_running() { - /usr/bin/pgrep -x Nameplate >/dev/null 2>&1 -} - -wait_for_state() { - local expected=$1 attempt - for attempt in {1..40}; do - if [[ "$expected" == running ]] && is_running; then return 0; fi - if [[ "$expected" == stopped ]] && ! is_running; then return 0; fi - sleep 0.25 - done - die "Nameplate did not become $expected" -} - -show_nameplate() { - [[ "$(installed_version || true)" == "$NAMEPLATE_VERSION" ]] || die "pinned Nameplate $NAMEPLATE_VERSION is not installed" - /usr/bin/open -gj "$app_path" - wait_for_state running - print_metadata - print "nameplate_state\tvisible" -} - -hide_nameplate() { - if is_running; then - /usr/bin/pkill -x Nameplate || true - wait_for_state stopped - fi - print_metadata - print "nameplate_state\thidden" -} - -status_nameplate() { - local version=not-installed state=hidden - version=$(installed_version || print not-installed) - is_running && state=visible - print "nameplate_version\t$version" - print "nameplate_sha256\t$([[ "$version" == "$NAMEPLATE_VERSION" ]] && print "$NAMEPLATE_SHA256" || print unknown)" - print "nameplate_state\t$state" -} - -enable_nameplate() { - [[ $# -eq 4 ]] || usage - local macos=$1 lane=$2 provider=$3 lease=$4 color name location temp archive extracted_app actual_sha - valid_component "$macos" - valid_component "$lane" - valid_component "$provider" - valid_component "$lease" - case "$macos" in - 15) color="#1D9E75" ;; - 26) color="#7F77DD" ;; - 27) color="#E24B30" ;; - *) die "unsupported macOS lane: $macos" ;; - esac - - temp=$(mktemp -d "${TMPDIR:-/tmp}/keypath-nameplate.XXXXXXXX") - NAMEPLATE_TEMP=$temp - trap '[[ -n "${NAMEPLATE_TEMP:-}" ]] && rm -rf "$NAMEPLATE_TEMP"' EXIT - archive="$temp/Nameplate.zip" - /usr/bin/curl --fail --location --silent --show-error "$NAMEPLATE_URL" --output "$archive" - actual_sha=$(/usr/bin/shasum -a 256 "$archive" | /usr/bin/awk '{print $1}') - [[ "$actual_sha" == "$NAMEPLATE_SHA256" ]] || die "Nameplate checksum mismatch" - /usr/bin/ditto -x -k "$archive" "$temp/extracted" - extracted_app=$(find "$temp/extracted" -maxdepth 2 -type d -name Nameplate.app -print -quit) - [[ -n "$extracted_app" ]] || die "Nameplate.app missing from pinned archive" - /usr/bin/codesign --verify --deep --strict "$extracted_app" - /usr/sbin/spctl --assess --type execute "$extracted_app" - - if is_running; then - /usr/bin/pkill -x Nameplate || true - wait_for_state stopped - fi - mkdir -p "$HOME/Applications" - [[ ! -e "$app_path" || ( -d "$app_path" && ! -L "$app_path" ) ]] || die "unsafe Nameplate destination" - rm -rf "$app_path" - /usr/bin/ditto "$extracted_app" "$app_path" - /usr/bin/codesign --verify --deep --strict "$app_path" - /usr/sbin/spctl --assess --type execute "$app_path" - - name="KeyPath macOS $macos · $lane" - location="$provider · $lease · DISPOSABLE" - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" customName -string "$name" - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" colorHex -string "$color" - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" glyph -string "KP" - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" customLocation -string "$location" - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" useFleetFile -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" hasCompletedFirstRun -bool true - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" overlaysEnabled -bool true - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" visibilityModeRaw -string always - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" frameEnabled -bool true - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" frameThickness -float 4 - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" tagEnabled -bool true - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" tagShowsGlyph -bool true - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" watermarkEnabled -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" splashEnabled -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" splashOnWake -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" splashOnUnlock -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" splashOnDisplayChange -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" launchAtLogin -bool false - /usr/bin/defaults write "$NAMEPLATE_BUNDLE_ID" autoUpdateEnabled -bool false - - show_nameplate -} - -action=${1:-} -shift || true -case "$action" in - metadata) [[ $# -eq 0 ]] || usage; print_metadata ;; - enable) assert_console_user; enable_nameplate "$@" ;; - show) [[ $# -eq 0 ]] || usage; assert_console_user; show_nameplate ;; - hide) [[ $# -eq 0 ]] || usage; assert_console_user; hide_nameplate ;; - status) [[ $# -eq 0 ]] || usage; assert_console_user; status_nameplate ;; - *) usage ;; -esac diff --git a/Scripts/lab/peekaboo-ui b/Scripts/lab/peekaboo-ui deleted file mode 100755 index ac341b90d..000000000 --- a/Scripts/lab/peekaboo-ui +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/zsh -set -euo pipefail - -die() { print -u2 "peekaboo-ui: $*"; exit 1; } - -usage() { - cat >&2 <<'EOF' -Usage: Scripts/lab/peekaboo-ui [options] - -Commands: - preflight - snapshot --app APP --output FILE - click --app APP --query QUERY --output FILE [--foreground] - dialogs --app APP --output FILE - file --app APP --path PATH --output FILE [--select BUTTON] - screenshot --app APP --output FILE [--retina] - -This command runs inside a desktop-enabled disposable guest. Every operation -writes Peekaboo's JSON result to FILE. It never accepts credentials. A -successful click proves delivery only; protected System Settings controls must -also be checked with an independent system/runtime postcondition. -EOF - exit 2 -} - -require_value() { - [[ -n "${2:-}" ]] || die "$1 requires a value" -} - -require_output_path() { - local output=$1 parent - [[ -n "$output" && "$output" != -* ]] || die "invalid output path" - parent=${output:h} - mkdir -p "$parent" -} - -peekaboo_bin=${PEEKABOO_BIN:-$(command -v peekaboo || true)} - -command=${1:-} -[[ -n "$command" ]] || usage -shift - -if [[ "$command" == preflight ]]; then - [[ $# -eq 0 ]] || usage - [[ -x "$peekaboo_bin" ]] || die "Peekaboo 3.x is required (brew install steipete/tap/peekaboo)" - version=$($peekaboo_bin --version 2>/dev/null | head -1) - [[ "$version" =~ '^Peekaboo[[:space:]]+3\.' ]] || die "Peekaboo 3.x is required; found: ${version:-unknown}" - $peekaboo_bin permissions status --json - exit -fi - -[[ -x "$peekaboo_bin" ]] || die "Peekaboo 3.x is required (brew install steipete/tap/peekaboo)" - -app= -output= -query= -file_path= -select_button=Open -foreground=0 -retina=0 - -while [[ $# -gt 0 ]]; do - case "$1" in - --app) require_value "$1" "${2:-}"; app=$2; shift 2 ;; - --output) require_value "$1" "${2:-}"; output=$2; shift 2 ;; - --query) require_value "$1" "${2:-}"; query=$2; shift 2 ;; - --path) require_value "$1" "${2:-}"; file_path=$2; shift 2 ;; - --select) require_value "$1" "${2:-}"; select_button=$2; shift 2 ;; - --foreground) foreground=1; shift ;; - --retina) retina=1; shift ;; - *) usage ;; - esac -done - -[[ -n "$app" ]] || die "--app is required" -[[ -n "$output" ]] || die "--output is required" -require_output_path "$output" - -case "$command" in - snapshot) - $peekaboo_bin see --app "$app" --json > "$output" - ;; - click) - [[ -n "$query" ]] || die "--query is required" - args=(click "$query" --app "$app" --json) - (( foreground )) && args+=(--foreground) - $peekaboo_bin "${args[@]}" > "$output" - ;; - dialogs) - $peekaboo_bin dialog list --app "$app" --json > "$output" - ;; - file) - [[ -n "$file_path" && "$file_path" == /* ]] || die "--path must be absolute" - file_directory=${file_path:h} - file_name=${file_path:t} - [[ -n "$file_name" ]] || die "--path must identify a file" - $peekaboo_bin dialog file --app "$app" --path "$file_directory" --name "$file_name" --select "$select_button" --json > "$output" - ;; - screenshot) - args=(image --app "$app" --mode window --path "$output" --json) - (( retina )) && args+=(--retina) - result="${output}.json" - $peekaboo_bin "${args[@]}" > "$result" - ;; - *) usage ;; -esac diff --git a/Scripts/lab/permission-drag b/Scripts/lab/permission-drag deleted file mode 100755 index bc40b346b..000000000 --- a/Scripts/lab/permission-drag +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/zsh -set -euo pipefail - -die() { print -u2 "permission-drag: $*"; exit 1; } - -usage() { - print -u2 "Usage: Scripts/lab/permission-drag --path FILE --target-identifier IDENTIFIER --output FILE" - exit 2 -} - -peekaboo=${PEEKABOO_BIN:-/opt/homebrew/bin/peekaboo} -open_bin=${OPEN_BIN:-/usr/bin/open} -osascript=${OSASCRIPT_BIN:-/usr/bin/osascript} -python_bin=$(command -v python3 || true) -file_path= -target_identifier= -output= - -while [[ $# -gt 0 ]]; do - case "$1" in - --path) file_path=${2:-}; shift 2 ;; - --target-identifier) target_identifier=${2:-}; shift 2 ;; - --output) output=${2:-}; shift 2 ;; - *) usage ;; - esac -done - -[[ "$file_path" == /* && -f "$file_path" ]] || die "--path must identify an existing absolute file" -[[ -n "$target_identifier" ]] || die "--target-identifier is required" -[[ -n "$output" && "$output" != -* ]] || die "--output is required" -[[ -x "$python_bin" ]] || die "Python 3 is required" -mkdir -p "${output:h}" - -tmp=$(mktemp -d /tmp/keypath-permission-drag.XXXXXX) -finder_geometry= -settings_geometry= - -window_geometry() { - "$osascript" -l JavaScript -e 'function run(argv) { var p = Application("System Events").processes.byName(argv[0]); if (!p.exists() || p.windows().length === 0) throw new Error("window unavailable"); var w = p.windows[0]; return w.position().concat(w.size()).join(","); }' "$1" -} - -restore_window() { - [[ -n "$2" ]] || return 0 - "$osascript" -l JavaScript -e 'function run(argv) { var values = argv[1].split(",").map(Number); if (values.length !== 4 || values.some(function (value) { return !Number.isFinite(value); })) throw new Error("invalid geometry"); var p = Application("System Events").processes.byName(argv[0]); if (!p.exists() || p.windows().length === 0) return; var w = p.windows[0]; w.position = values.slice(0, 2); w.size = values.slice(2, 4); }' "$1" "$2" >/dev/null 2>&1 || true -} - -cleanup() { - restore_window Finder "$finder_geometry" - restore_window "System Settings" "$settings_geometry" - rm -rf "$tmp" -} -trap cleanup EXIT -name=${file_path:t} - -"$open_bin" -R "$file_path" -sleep ${KEYPATH_PERMISSION_DRAG_REVEAL_SECONDS:-2} -finder_geometry=$(window_geometry Finder) || die "could not capture Finder window geometry" -settings_geometry=$(window_geometry "System Settings") || die "could not capture System Settings window geometry" -[[ "$finder_geometry" =~ '^-?[0-9]+,-?[0-9]+,[0-9]+,[0-9]+$' ]] || die "Finder returned invalid window geometry" -[[ "$settings_geometry" =~ '^-?[0-9]+,-?[0-9]+,[0-9]+,[0-9]+$' ]] || die "System Settings returned invalid window geometry" -"$osascript" -e 'tell application "System Events" to tell process "Finder" to tell window 1 to set {position, size} to {{0, 40}, {500, 700}}' >/dev/null -"$osascript" -e 'tell application "System Events" to tell process "System Settings" to tell window 1 to set {position, size} to {{500, 40}, {524, 700}}' >/dev/null -sleep ${KEYPATH_PERMISSION_DRAG_SETTLE_SECONDS:-1} - -"$peekaboo" see --app Finder --json > "$tmp/source.json" -"$peekaboo" see --app "System Settings" --json > "$tmp/target.json" - -coords=$("$python_bin" - "$tmp/source.json" "$tmp/target.json" "$name" "$target_identifier" <<'PY' -import json, sys - -source = json.load(open(sys.argv[1])).get("data", {}).get("ui_elements", []) -target = json.load(open(sys.argv[2])).get("data", {}).get("ui_elements", []) -source_name, target_identifier = sys.argv[3], sys.argv[4] - -def center(element): - bounds = element.get("bounds") or {} - return round(bounds["x"] + bounds["width"] / 2), round(bounds["y"] + bounds["height"] / 2) - -source_element = next((e for e in source if e.get("label") == source_name and e.get("is_actionable")), None) -target_element = next((e for e in target if e.get("identifier") == target_identifier), None) -if source_element is None or target_element is None: - raise SystemExit(1) -sx, sy = center(source_element) -tx, ty = center(target_element) -print(f"{sx},{sy} {tx},{ty}") -PY -) || die "could not resolve source or permission-list target" - -source_coords=${coords%% *} -target_coords=${coords##* } -"$peekaboo" drag --from-coords "$source_coords" --to-coords "$target_coords" --duration 1500 --steps 30 --profile linear --json > "$output" -sleep ${KEYPATH_PERMISSION_DRAG_SETTLE_SECONDS:-1} - -# Delivery is not success. Require either the expected authorization sheet or -# the newly-added permission row before returning. -if "$osascript" -l JavaScript -e 'var p=Application("System Events").processes.byName("System Settings"); function all(e){var o=[];try{var x=e.uiElements();for(var i=0;i/dev/null | grep -q secure; then - print "permission_drag\tauthorization-required" - exit 0 -fi - -"$peekaboo" see --app "System Settings" --json > "$tmp/after.json" -"$python_bin" - "$tmp/after.json" "${name}_Title" <<'PY' || die "drag delivery had no authorization sheet or permission-row postcondition" -import json, sys -elements = json.load(open(sys.argv[1])).get("data", {}).get("ui_elements", []) -raise SystemExit(0 if any(e.get("identifier") == sys.argv[2] for e in elements) else 1) -PY -print "permission_drag\tadded" diff --git a/Scripts/lab/remote.sh b/Scripts/lab/remote.sh deleted file mode 100755 index 49377d82d..000000000 --- a/Scripts/lab/remote.sh +++ /dev/null @@ -1,1676 +0,0 @@ -#!/bin/zsh -set -euo pipefail - -# This script runs over a non-interactive SSH shell, which does not source the -# login profile and therefore does not have Homebrew on PATH. That matters -# because the repository configures core.hookspath, and those hooks call -# git-lfs. A missing git-lfs makes the hook exit non-zero, and `set -e` then -# aborts mid-command with no diagnostic: `create` died after staging its -# archive, reporting only exit 2. Set the environment this script needs rather -# than depending on how it was launched. -for candidate in /opt/homebrew/bin /usr/local/bin; do - if [[ -d "$candidate" && ":$PATH:" != *":$candidate:"* ]]; then - PATH="$candidate:$PATH" - fi -done -export PATH - -PRODUCTION_ROOT="/Volumes/KeyPath Lab/CrabBox" -OWNER="keypath-installer-lab-v1" -NAMEPLATE_VERSION="0.2.5" -NAMEPLATE_SHA256="96d1b6c58167b4a8f3713a61a7e216f8a24c2adad36c9027db974f852d543a3d" - -if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - LAB_ROOT="${KEYPATH_LAB_TEST_ROOT:?KEYPATH_LAB_TEST_ROOT is required in test mode}" - LAUNCHER_15="${KEYPATH_LAB_LAUNCHER_15:?test launcher 15 is required}" - LAUNCHER_26="${KEYPATH_LAB_LAUNCHER_26:?test launcher 26 is required}" - LAUNCHER_27="${KEYPATH_LAB_LAUNCHER_27:?test launcher 27 is required}" - CRABBOX="${KEYPATH_LAB_CRABBOX:?test CrabBox is required}" - TART="${KEYPATH_LAB_TART:?test Tart is required}" - GUEST_SSH="${KEYPATH_LAB_GUEST_SSH:?test guest SSH is required}" -else - LAB_ROOT="$PRODUCTION_ROOT" - LAUNCHER_15="$LAB_ROOT/keypath15" - LAUNCHER_26="$LAB_ROOT/keypath26" - LAUNCHER_27="$LAB_ROOT/keypath27" - CRABBOX="$LAB_ROOT/SharedTools/bin/crabbox" - TART="${KEYPATH_LAB_TART:-$LAB_ROOT/CompatTools/bin/tart}" - GUEST_SSH="${KEYPATH_LAB_GUEST_SSH:-/usr/bin/ssh}" -fi - -TART_USB_TOOL_ROOT="${KEYPATH_LAB_TART_USB_TOOL_ROOT:-$LAB_ROOT/CompatTools/KeyPathUSB}" - -STATE_ROOT="$LAB_ROOT/KeyPathInstallerLab" -ARCHIVES="$STATE_ROOT/archives" -LEASES="$STATE_ROOT/leases" -ARTIFACTS="$STATE_ROOT/artifacts" -LOGS="$STATE_ROOT/logs" -OPERATIONS="$STATE_ROOT/operations" -HELD_ADMISSION_LOCK= -HELD_ADMISSION_OWNER= -PENDING_ADMISSION_OWNER= - -die() { print -u2 "keypath-lab(remote): $*"; exit 1; } -now_epoch() { date +%s; } -utc_now() { date -u +%Y-%m-%dT%H:%M:%SZ; } - -valid_id() { - [[ "$1" =~ '^[A-Za-z0-9._-]+$' ]] || die "invalid identifier: $1" -} - -launcher_for() { - case "$1" in - 15) print -r -- "$LAUNCHER_15" ;; - 26) print -r -- "$LAUNCHER_26" ;; - 27) print -r -- "$LAUNCHER_27" ;; - *) die "unsupported macOS lane: $1" ;; - esac -} - -provider_for() { - case "$1" in 15) print tart ;; 26|27) print parallels ;; *) die "unsupported macOS lane: $1" ;; esac -} - -configure_tart_path() { - local usb_prefix= - if [[ "${CRABBOX_TART_USB_PASSTHROUGH:-false}" == "true" ]]; then - usb_prefix="$TART_USB_TOOL_ROOT/bin:" - fi - export PATH="${usb_prefix}$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" -} - -# Desktop leases need the provisioned desktop base, which already carries the -# console session, Python, and the approved Peekaboo Lab Host. Without this the -# --desktop flag still produced a plain base, so desktop-bootstrap failed on a -# missing Homebrew and semantic UI automation could never run. Managed lanes are -# excluded: their base carries MDM enrollment that the desktop base does not, so -# a managed desktop lease keeps the managed base and gains desktop capability -# from the launcher instead. -base_for() { - local macos=$1 lane=$2 desktop=${3:-0} - if [[ "$macos" == "15" ]]; then - [[ "$lane" == "managed-functional" ]] && print keypath-macos-15-managed || print ghcr.io/cirruslabs/macos-sequoia-base:latest - elif [[ ("$macos" == "26" || "$macos" == "27") && "$desktop" == "1" && "$lane" != "managed-functional" ]]; then - print "keypath-macos-$macos-desktop" - else - [[ "$lane" == "managed-functional" ]] && print "keypath-macos-$macos-managed" || print "keypath-macos-$macos" - fi -} - -manifest_path() { print -r -- "$LEASES/$1/manifest.tsv"; } - -field() { - local manifest=$1 key=$2 - awk -F '\t' -v key="$key" '$1 == key {sub(/^[^\t]*\t/, ""); print; exit}' "$manifest" -} - -set_field() { - local manifest=$1 key=$2 value=$3 temp="${manifest}.tmp.$$" - awk -F '\t' -v key="$key" -v value="$value" 'BEGIN {OFS="\t"} $1 == key {$0=key OFS value; found=1} {print} END {if (!found) print key, value}' "$manifest" > "$temp" - mv "$temp" "$manifest" -} - -owned_manifest() { - local lease=$1 manifest - valid_id "$lease" - manifest=$(manifest_path "$lease") - [[ -f "$manifest" ]] || die "lease is not owned by this interface: $lease" - [[ "$(field "$manifest" owner)" == "$OWNER" ]] || die "ownership marker mismatch for lease: $lease" - [[ "$(field "$manifest" lease_id)" == "$lease" ]] || die "lease manifest id mismatch: $lease" - print -r -- "$manifest" -} - -duration_seconds() { - local value=$1 number unit - if [[ "$value" == <-> ]]; then print "$value"; return; fi - number=${value[1,-2]} - unit=${value[-1]} - [[ "$number" == <-> ]] || die "invalid duration: $value" - case "$unit" in - m) print $((number * 60)) ;; - h) print $((number * 3600)) ;; - d) print $((number * 86400)) ;; - *) die "invalid duration: $value" ;; - esac -} - -ensure_roots() { - mkdir -p "$ARCHIVES" "$LEASES" "$ARTIFACTS" "$LOGS" "$OPERATIONS" -} - -provider_capacity() { - case "$1" in - tart) print "${KEYPATH_LAB_CAPACITY_TART:-1}" ;; - parallels) print "${KEYPATH_LAB_CAPACITY_PARALLELS:-2}" ;; - *) die "unsupported provider capacity key: $1" ;; - esac -} - -# The path whose free space gates provisioning. This must be the filesystem the -# provider actually writes clones to, which is not necessarily the boot disk: -# Parallels stores clones under its configured VM folder, which on this host is -# an external volume. Measuring the boot disk while clones land elsewhere let a -# full clone pass admission and then exhaust the volume it was really using. -disk_reserve_path() { - print -r -- "${KEYPATH_LAB_DISK_RESERVE_PATH:-/System/Volumes/Data}" -} - -host_free_kib() { - if [[ -n "${KEYPATH_LAB_TEST_FREE_KIB:-}" ]]; then - print -r -- "$KEYPATH_LAB_TEST_FREE_KIB" - else - # Absolute paths: this guard must not depend on PATH being well-formed. - /bin/df -Pk "$(disk_reserve_path)" | /usr/bin/awk 'NR == 2 {print $4}' - fi -} - -assert_internal_disk_reserve() { - local minimum_gib=${KEYPATH_LAB_MIN_FREE_DISK_GIB:-100} free_kib minimum_kib path - [[ "$minimum_gib" == <-> && "$minimum_gib" -gt 0 ]] || die "invalid disk reserve: $minimum_gib GiB" - path=$(disk_reserve_path) - free_kib=$(host_free_kib) - [[ "$free_kib" == <-> ]] || die "could not determine free space for $path" - minimum_kib=$((minimum_gib * 1024 * 1024)) - # Report the measured path so a guard pointed at the wrong filesystem is - # visible in the log rather than silently passing. - print -u2 "disk_reserve\tfree_gib=$((free_kib / 1024 / 1024))\tminimum_gib=$minimum_gib\tpath=$path" - if (( free_kib < minimum_kib )); then - print -u2 "disk_reserve_busy\tfree_gib=$((free_kib / 1024 / 1024))\tminimum_gib=$minimum_gib\tpath=$path" - return 75 - fi -} - -acquire_admission_lock() { - local provider=$1 attempt=0 owner owner_pid stale lock_age lock_mtime lock="$STATE_ROOT/provider-admission-$provider.lock" - local owner_record="$STATE_ROOT/.provider-admission-$provider.owner.$$" - local max_attempts=${KEYPATH_LAB_ADMISSION_WAIT_ATTEMPTS:-3000} - local incomplete_grace=${KEYPATH_LAB_INCOMPLETE_LOCK_GRACE_SECONDS:-5} - [[ "$max_attempts" == <-> && "$max_attempts" -gt 0 ]] || die "invalid admission wait attempts: $max_attempts" - [[ "$incomplete_grace" == <-> ]] || die "invalid incomplete lock grace: $incomplete_grace" - PENDING_ADMISSION_OWNER="$owner_record" - { - print "pid\t$$" - print "provider\t$provider" - print "created_at\t$(utc_now)" - } > "$owner_record" - while ((attempt < max_attempts)); do - if ln "$owner_record" "$lock" 2>/dev/null; then - PENDING_ADMISSION_OWNER= - HELD_ADMISSION_LOCK="$lock" - HELD_ADMISSION_OWNER="$owner_record" - return - fi - if [[ -d "$lock" ]]; then - # Recover directory locks created by the initial implementation of this protocol. - owner_pid=$(field "$lock/owner.tsv" pid 2>/dev/null || true) - lock_mtime=$(stat -f %m "$lock" 2>/dev/null || stat -c %Y "$lock" 2>/dev/null || print 0) - lock_age=$(( $(now_epoch) - lock_mtime )) - else - owner_pid=$(field "$lock" pid 2>/dev/null || true) - lock_age=0 - fi - if { [[ "$owner_pid" == <-> ]] && ! kill -0 "$owner_pid" 2>/dev/null; } || - { [[ -d "$lock" && -z "$owner_pid" && "$lock_age" -ge "$incomplete_grace" ]]; }; then - stale="$STATE_ROOT/provider-admission-$provider.stale.$$" - if mv "$lock" "$stale" 2>/dev/null; then - rm -rf "$stale" - continue - fi - fi - ((attempt += 1)) - sleep 0.1 - done - rm -f "$owner_record" - PENDING_ADMISSION_OWNER= - if [[ -d "$lock" ]]; then - owner=$(cat "$lock/owner.tsv" 2>/dev/null || print unavailable) - else - owner=$(cat "$lock" 2>/dev/null || print unavailable) - fi - print -u2 "admission_lock_busy" - print -u2 -- "$owner" - return 75 -} - -release_admission_lock() { - [[ -n "$PENDING_ADMISSION_OWNER" ]] && rm -f "$PENDING_ADMISSION_OWNER" - PENDING_ADMISSION_OWNER= - if [[ -n "$HELD_ADMISSION_LOCK" && -n "$HELD_ADMISSION_OWNER" && - -f "$HELD_ADMISSION_LOCK" && "$HELD_ADMISSION_LOCK" -ef "$HELD_ADMISSION_OWNER" ]]; then - rm -f "$HELD_ADMISSION_LOCK" - fi - [[ -n "$HELD_ADMISSION_OWNER" ]] && rm -f "$HELD_ADMISSION_OWNER" - HELD_ADMISSION_LOCK= - HELD_ADMISSION_OWNER= -} - -release_admission_lock_and_exit() { - local exit_code=$1 - trap - EXIT INT TERM HUP - release_admission_lock || true - exit "$exit_code" -} - -assert_provider_capacity() { - local provider=$1 capacity active=0 manifest lease expires cleanup lease_status commit macos lane slug - capacity=$(provider_capacity "$provider") - [[ "$capacity" == <-> && "$capacity" -gt 0 ]] || die "invalid $provider capacity: $capacity" - for manifest in "$LEASES"/*/manifest.tsv(N); do - [[ "$(field "$manifest" owner)" == "$OWNER" ]] || continue - [[ "$(field "$manifest" provider)" == "$provider" ]] || continue - cleanup=$(field "$manifest" cleanup_status) - lease_status=$(field "$manifest" status) - expires=$(field "$manifest" expires_epoch) - [[ "$cleanup" != complete && "$lease_status" != destroyed && "$expires" == <-> && "$expires" -gt "$(now_epoch)" ]] || continue - lease=$(field "$manifest" lease_id) - commit=$(field "$manifest" keypath_commit) - macos=$(field "$manifest" macos) - lane=$(field "$manifest" test_lane) - slug=$(field "$manifest" slug) - ((active += 1)) - print -u2 "active_lease\t$lease\tprovider=$provider\tmacos=$macos\tlane=${lane:-legacy}\tstatus=$lease_status\texpires_epoch=$expires\tcommit=$commit\tslug=$slug" - done - if ((active >= capacity)); then - print -u2 "capacity_busy\tprovider=$provider\tactive=$active\tlimit=$capacity" - return 75 - fi -} - -managed_identity_scope_for() { - local macos=$1 lane=$2 base_name enrollment_id - [[ "$lane" == managed-functional ]] || { print none; return 0; } - if [[ "$macos" == "15" ]]; then - print unique-clone - return 0 - fi - base_name=$(base_for "$macos" "$lane") - enrollment_id=$(managed_enrollment_id_for "$base_name") - print -r -- "shared:$enrollment_id" -} - -assert_managed_identity_available() { - local macos=$1 lane=$2 requested_scope manifest cleanup lease_status expires - local existing_lease existing_scope existing_macos - [[ "$lane" == managed-functional ]] || return 0 - requested_scope=$(managed_identity_scope_for "$macos" "$lane") - [[ "$requested_scope" == unique-clone ]] && return 0 - for manifest in "$LEASES"/*/manifest.tsv(N); do - [[ "$(field "$manifest" owner)" == "$OWNER" ]] || continue - [[ "$(field "$manifest" test_lane)" == managed-functional ]] || continue - cleanup=$(field "$manifest" cleanup_status) - lease_status=$(field "$manifest" status) - expires=$(field "$manifest" expires_epoch) - [[ "$cleanup" != complete && "$lease_status" != destroyed && "$expires" == <-> && "$expires" -gt "$(now_epoch)" ]] || continue - existing_macos=$(field "$manifest" macos) - if [[ -z "$existing_macos" ]]; then - existing_scope=legacy-unknown - else - existing_scope=$(managed_identity_scope_for "$existing_macos" managed-functional) - fi - [[ "$existing_scope" == "$requested_scope" || "$existing_scope" == legacy-unknown ]] || continue - existing_lease=$(field "$manifest" lease_id) - print -u2 "managed_identity_busy\tactive_lease=$existing_lease\tscope=$requested_scope\tstatus=$lease_status\texpires_epoch=$expires" - return 75 - done -} - -record_command() { - local lease=$1 result=$2; shift 2 - local command_text - command_text=$(printf '%q ' "$@") - print -r -- "$(utc_now)\t$result\t$command_text" >> "$LEASES/$lease/commands.tsv" -} - -prepare_worktree() { - local repo=$1 changes - [[ "$repo" == "$OPERATIONS"/*/repo ]] || die "unsafe lease worktree path" - [[ -d "$repo/.git" ]] || die "lease checkout is not a Git worktree" - changes=$(git -C "$repo" status --porcelain --untracked-files=all -- . \ - ':(exclude).crabbox/logs/**' \ - ':(exclude).crabbox/captures/**' \ - ':(exclude).crabbox/runs/**') - [[ -z "$changes" ]] || die "refusing to sync a changing checkout" -} - -managed_enrollment_id_for() { - local base_name=$1 identity_file enrollment_id - valid_id "$base_name" - identity_file="$STATE_ROOT/managed-identities/$base_name.enrollment-id" - [[ -f "$identity_file" && ! -L "$identity_file" ]] || die "managed enrollment identity is unavailable for base: $base_name" - enrollment_id=$(<"$identity_file") - [[ "$enrollment_id" =~ '^[A-Fa-f0-9-]{36}$' ]] || die "invalid managed enrollment identity for base: $base_name" - print -r -- "$enrollment_id" -} - -approve_peekaboo_capture() { - local lease=$1 manifest macos resource key ip prompt_command prompt_coords attempt - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "15" ]] || die "Peekaboo capture approval currently supports only the Tart macOS 15 lane" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Za-z0-9._-]+$' && "$resource" != "unknown" ]] || die "invalid Tart resource id" - - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - export PATH="$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - ip=$($TART ip "$resource") - [[ "$ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Tart returned an invalid guest address" - prompt_command=$'/usr/bin/osascript -l JavaScript -e \'\nfunction run() {\n var matches = Application("System Events").processes.whose({name: "NotificationCenter"})();\n if (matches.length === 0 || matches[0].windows().length === 0) return "";\n var window = matches[0].windows[0];\n try {\n var size = window.size();\n if (window.subrole() === "AXSystemDialog" && size[0] === 1024 && size[1] === 768) return "512,399";\n } catch (_) {}\n return "";\n}\'' - prompt_coords= - for attempt in {1..20}; do - prompt_coords=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" \ - "/bin/zsh -lc $(printf %q "$prompt_command")") - [[ -n "$prompt_coords" ]] && break - sleep "${KEYPATH_LAB_CAPTURE_APPROVAL_POLL_SECONDS:-0.2}" - done - if [[ -z "$prompt_coords" ]]; then - print "peekaboo_capture_approval\talready-approved" - return 0 - fi - [[ "$prompt_coords" =~ '^[0-9]+,[0-9]+$' ]] || die "Peekaboo capture approval prompt coordinates are invalid" - "$CRABBOX" desktop click --provider tart --target macos --id "$resource" \ - --x "${prompt_coords%,*}" --y "${prompt_coords#*,}" >/dev/null - sleep "${KEYPATH_LAB_CAPTURE_APPROVAL_SETTLE_SECONDS:-5}" - record_command "$lease" passed approve-peekaboo-capture - print "peekaboo_capture_approval\tpassed" -} - -rehydrate_managed_clone() { - local lease=$1 manifest macos base_name base_enrollment_id enrollment_id repo provider_resource profile_dir guest_policy guest_repo parallels_cli evidence filename launcher copy_command verify_command identity_output - local enrollment_ready enrollment_record enrollment_status attempt - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - base_name=$(field "$manifest" base_name) - base_enrollment_id=$(managed_enrollment_id_for "$base_name") - repo=$(field "$manifest" worktree) - provider_resource=$(field "$manifest" provider_resource) - profile_dir="$repo/.keypath-lab/managed-policy" - guest_policy=/Library/KeyPathLab/managed-policy - guest_repo="/Users/$([[ "$macos" == "15" ]] && print admin || print keypathqa)/crabbox/$lease/repo" - evidence="$ARTIFACTS/$lease/managed-policy" - for filename in keypath-pppc.mobileconfig keypath-system-extension.mobileconfig keypath-service-management.mobileconfig manifest.json; do - [[ -f "$profile_dir/$filename" && ! -L "$profile_dir/$filename" ]] || die "managed policy archive is missing: $filename" - done - if [[ "$macos" == "15" ]]; then - launcher=$(launcher_for "$macos") - identity_output=$(cd "$repo" && "$launcher" run "$lease" -- /bin/zsh -lc \ - "/usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice -a | /usr/bin/plutil -extract 0.IOPlatformUUID raw -") - enrollment_id=$(print -r -- "$identity_output" | sed -nE '/^[A-Fa-f0-9-]{36}$/p' | tail -1) - [[ "$enrollment_id" =~ '^[A-Fa-f0-9-]{36}$' ]] || die "managed clone hardware identity is unavailable" - [[ "$enrollment_id" != "$base_enrollment_id" ]] || die "managed clone retained the base hardware identity; Tart random serial personalization is required" - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - enrollment_ready=0 - enrollment_record="$HOME/Library/Application Support/KeyPathLabMDM/state/nanomdm/dbkv/enrollments" - enrollment_status=$(cd "$repo" && "$launcher" run "$lease" -- /usr/bin/profiles status -type enrollment 2>/dev/null || true) - if print -r -- "$enrollment_status" | grep -Fq 'MDM enrollment: Yes' && - find "$enrollment_record" -type f -name "$enrollment_id.type" -print -quit 2>/dev/null | grep -q .; then - enrollment_ready=1 - print "managed_clone_enrollment\talready-enrolled" - else - desktop_bootstrap "$lease" 1 - run_command "$lease" /bin/zsh Scripts/lab/mdm/enroll-clone-ui - approve_peekaboo_capture "$lease" - protected_click "$lease" "System Settings" "__ANY__" "Device Management" native 249 226 1 - sleep "${KEYPATH_LAB_PROFILE_LIST_SETTLE_SECONDS:-20}" - protected_click "$lease" "System Settings" "Device Management" "Device Management" native 600 216 2 - protected_click "$lease" "System Settings" "Device Management" "Device Management" native 329 610 - secure_dialog_input "$lease" SecurityAgent AXSecureTextField Enroll 0 - for attempt in {1..150}; do - enrollment_status=$(cd "$repo" && "$launcher" run "$lease" -- /usr/bin/profiles status -type enrollment 2>/dev/null || true) - if print -r -- "$enrollment_status" | grep -Fq 'MDM enrollment: Yes' && - find "$enrollment_record" -type f -name "$enrollment_id.type" -print -quit 2>/dev/null | grep -q .; then - enrollment_ready=1 - break - fi - sleep "${KEYPATH_LAB_MANAGED_ENROLLMENT_POLL_SECONDS:-0.2}" - done - ((enrollment_ready == 1)) && print "managed_clone_enrollment\tuser-approved" - fi - ((enrollment_ready == 1)) || die "managed clone did not establish its unique NanoMDM enrollment" - fi - copy_command="setopt errexit nounset pipefail; mkdir -p '$guest_policy';" - for filename in keypath-pppc.mobileconfig keypath-system-extension.mobileconfig keypath-service-management.mobileconfig manifest.json; do - copy_command+=" /usr/bin/install -m 444 '$guest_repo/.keypath-lab/managed-policy/$filename' '$guest_policy/$filename';" - done - (cd "$repo" && "$launcher" run "$lease" -- /bin/zsh -lc "sudo -n /bin/zsh -lc $(printf %q "$copy_command")") - else - enrollment_id=$base_enrollment_id - [[ "$provider_resource" =~ '^[A-Fa-f0-9-]{36}$' ]] || die "invalid managed Parallels resource id" - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - "$parallels_cli" exec "$provider_resource" /bin/mkdir -p "$guest_policy" - for filename in keypath-pppc.mobileconfig keypath-system-extension.mobileconfig keypath-service-management.mobileconfig manifest.json; do - "$parallels_cli" exec "$provider_resource" /usr/bin/tee "$guest_policy/$filename" < "$profile_dir/$filename" >/dev/null - done - "$parallels_cli" exec "$provider_resource" /bin/chmod 444 \ - "$guest_policy/keypath-pppc.mobileconfig" \ - "$guest_policy/keypath-system-extension.mobileconfig" \ - "$guest_policy/keypath-service-management.mobileconfig" \ - "$guest_policy/manifest.json" - fi - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" && "${KEYPATH_LAB_TEST_PUBLISH_MANAGED:-0}" != "1" ]]; then - mkdir -p "$evidence" - cp "$profile_dir/manifest.json" "$evidence/manifest.json" - print "enrollment_id\t$enrollment_id" - else - "$repo/Scripts/lab/mdm/publish-managed-profiles" \ - --profile-dir "$profile_dir" \ - --evidence-dir "$evidence" \ - --enrollment-id "$enrollment_id" || return $? - fi - if [[ "$macos" == "15" ]]; then - verify_command="'$guest_repo/Scripts/lab/mdm/verify-lane' managed-functional --manifest '$guest_policy/manifest.json'" - (cd "$repo" && "$launcher" run "$lease" -- /bin/zsh -lc "sudo -n /bin/zsh -lc $(printf %q "$verify_command")") - else - "$parallels_cli" exec "$provider_resource" \ - "$guest_repo/Scripts/lab/mdm/verify-lane" managed-functional \ - --manifest "$guest_policy/manifest.json" - fi - print "managed_policy_rehydration\tpassed" -} - -resume_managed_policy() { - local lease=$1 manifest lane result - manifest=$(owned_manifest "$lease") - lane=$(field "$manifest" test_lane) - [[ "$lane" == managed-functional ]] || die "managed policy resume requires a managed-functional lease" - - set +e - rehydrate_managed_clone "$lease" > "$LOGS/$lease/managed-policy.log" 2>&1 - result=$? - set -e - set_field "$manifest" managed_policy_result "$result" - set_field "$manifest" managed_policy_at "$(utc_now)" - cat "$LOGS/$lease/managed-policy.log" - if ((result != 0)); then - set_field "$manifest" status managed-policy-failed - return "$result" - fi - set_field "$manifest" status ready - record_command "$lease" passed managed-policy-rehydration - print "managed_policy_resume\tpassed" -} - -run_with_download() { - local macos=$1 lease=$2 remote_file=$3 local_file=$4; shift 4 - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - "$CRABBOX" run --provider "$(provider_for "$macos")" --target macos --id "$lease" \ - --stop-after never --download "$remote_file=$local_file" -- "$@" - elif [[ "$macos" == "15" ]]; then - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - export PATH="$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - "$CRABBOX" run --provider tart --target macos --id "$lease" \ - --tart-user admin --ssh-port 22 --stop-after never \ - --download "$remote_file=$local_file" -- "$@" - else - export PATH="$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - "$CRABBOX" run --provider parallels --target macos --id "$lease" \ - --parallels-user keypathqa --parallels-work-root /Users/keypathqa/crabbox \ - --ssh-port 22 --stop-after never --download "$remote_file=$local_file" -- "$@" - fi -} - -warmup_desktop() { - local macos=$1 lane=$2 slug=$3 - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - "$CRABBOX" warmup --provider "$(provider_for "$macos")" --target macos --desktop --slug "$slug" --ttl 2h - elif [[ "$macos" == "15" ]]; then - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - configure_tart_path - "$CRABBOX" warmup --provider tart --target macos --desktop \ - --tart-image "$(base_for "$macos" "$lane" 1)" \ - --tart-user admin --tart-cpu 4 --tart-memory 8192 --tart-random-serial --ssh-port 22 \ - --slug "$slug" --ttl 2h - else - export PATH="$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - "$CRABBOX" warmup --provider parallels --target macos --desktop \ - --parallels-template "$(base_for "$macos" "$lane" 1)" --parallels-user keypathqa \ - --parallels-work-root /Users/keypathqa/crabbox --ssh-port 22 \ - --slug "$slug" --ttl 2h - fi -} - -warmup_lease() { - local macos=$1 lane=$2 slug=$3 desktop=$4 - if [[ "$desktop" == "1" ]]; then - warmup_desktop "$macos" "$lane" "$slug" - elif [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - "$(launcher_for "$macos")" warmup "$slug" - elif [[ "$macos" == "15" ]]; then - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - configure_tart_path - "$CRABBOX" warmup --provider tart --target macos \ - --tart-image "$(base_for "$macos" "$lane")" \ - --tart-user admin --tart-cpu 4 --tart-memory 8192 --tart-random-serial --ssh-port 22 \ - --slug "$slug" --ttl 2h - else - export PATH="$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - "$CRABBOX" warmup --provider parallels --target macos \ - --parallels-template "$(base_for "$macos" "$lane")" \ - --parallels-user keypathqa --parallels-work-root /Users/keypathqa/crabbox \ - --ssh-port 22 --slug "$slug" --ttl 2h - fi -} - -preflight() { - local mount_point - [[ "$LAB_ROOT" == "$PRODUCTION_ROOT" || "${KEYPATH_LAB_TESTING:-0}" == "1" ]] || die "unsafe lab root" - [[ -d "$LAB_ROOT" ]] || die "lab root is not mounted: $LAB_ROOT" - [[ -x "$LAUNCHER_15" && -x "$LAUNCHER_26" && -x "$LAUNCHER_27" && -x "$CRABBOX" ]] || die "lab launchers or CrabBox are unavailable" - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - mount_point=$(df -P "$LAB_ROOT" | awk 'NR == 2 {for (i=6; i<=NF; i++) printf "%s%s", (i == 6 ? "" : " "), $i; print ""}') - [[ "$mount_point" == "/Volumes/KeyPath Lab" ]] || die "lab root is not on the expected external volume" - fi - ensure_roots - "$LAUNCHER_15" doctor - "$LAUNCHER_26" doctor - "$LAUNCHER_27" doctor - print "host_os\t$(sw_vers -productVersion 2>/dev/null || print unknown)" - print "host_build\t$(sw_vers -buildVersion 2>/dev/null || print unknown)" - print "lab_root\t$LAB_ROOT" - print "capacity_tart\t$(provider_capacity tart)" - print "capacity_parallels\t$(provider_capacity parallels)" - print "disk_reserve_minimum_gib\t${KEYPATH_LAB_MIN_FREE_DISK_GIB:-100}" - print "disk_reserve_free_gib\t$(( $(host_free_kib) / 1024 / 1024 ))" - print "safety\tdisposable-owned-leases-only" -} - -prepare_upload() { - valid_id "$1" - [[ "$1" =~ '^[0-9a-f]{40}-[0-9a-f]{64}$' ]] || die "invalid archive key" - mktemp "/tmp/keypath-lab.XXXXXXXX" -} - -install_archive() { - local source=$1 key=$2 commit=$3 installer_sha=$4 installer_name=$5 - [[ "$source" =~ '^/tmp/keypath-lab\.[A-Za-z0-9]+$' ]] || die "invalid upload ticket" - [[ -f "$source" && ! -L "$source" && -O "$source" ]] || die "upload ticket is not an owned regular file" - valid_id "$key" - [[ "$commit" =~ '^[0-9a-f]{40}$' ]] || die "invalid commit SHA" - [[ "$installer_sha" =~ '^[0-9a-f]{64}$' ]] || die "invalid installer checksum" - [[ "$installer_name" =~ '^[A-Za-z0-9._-]+$' ]] || die "invalid installer name" - ensure_roots - local destination="$ARCHIVES/$key" staging="$ARCHIVES/.staging-$key-$$" lock="$ARCHIVES/.lock-$key" attempt - if [[ -f "$destination/ready.tsv" ]]; then - [[ "$(field "$destination/ready.tsv" owner)" == "$OWNER" ]] || die "archive ownership mismatch" - [[ "$(field "$destination/ready.tsv" keypath_commit)" == "$commit" ]] || die "archive commit mismatch" - [[ "$(field "$destination/ready.tsv" installer_sha256)" == "$installer_sha" ]] || die "archive installer checksum mismatch" - rm -f "$source" - print "archive\treused\t$key" - return - fi - mkdir -p "$staging" - tar -xzf "$source" -C "$staging" - rm -f "$source" - [[ -d "$staging/repo" && ! -e "$staging/repo/.git" ]] || die "uploaded payload must contain exported content without Git state" - local actual_sha - actual_sha=$(shasum -a 256 "$staging/repo/.keypath-lab/installer/$installer_name" | awk '{print $1}') - [[ "$actual_sha" == "$installer_sha" ]] || die "installer checksum mismatch" - git -C "$staging/repo" init -q - git -C "$staging/repo" config user.name "KeyPath Lab" - git -C "$staging/repo" config user.email "keypath-lab@localhost" - git -C "$staging/repo" add -A - GIT_AUTHOR_DATE=2000-01-01T00:00:00Z GIT_COMMITTER_DATE=2000-01-01T00:00:00Z git -C "$staging/repo" commit -q -m "KeyPath lab archive $commit" - [[ -z "$(git -C "$staging/repo" status --porcelain)" ]] || die "archive checkout is dirty" - { - print "owner\t$OWNER" - print "keypath_commit\t$commit" - print "installer_sha256\t$installer_sha" - print "installer_name\t$installer_name" - print "created_at\t$(utc_now)" - } > "$staging/ready.tsv" - if ! mkdir "$lock" 2>/dev/null; then - rm -rf "$staging" - for attempt in {1..100}; do - if [[ -f "$destination/ready.tsv" ]]; then - [[ "$(field "$destination/ready.tsv" owner)" == "$OWNER" ]] || die "archive ownership mismatch after concurrent publish" - [[ "$(field "$destination/ready.tsv" keypath_commit)" == "$commit" ]] || die "archive commit mismatch after concurrent publish" - [[ "$(field "$destination/ready.tsv" installer_sha256)" == "$installer_sha" ]] || die "archive checksum mismatch after concurrent publish" - print "archive\treused\t$key" - return - fi - sleep 0.1 - done - die "timed out waiting for concurrent archive publish: $key" - fi - if [[ -f "$destination/ready.tsv" ]]; then - rm -rf "$staging" - rmdir "$lock" - print "archive\treused\t$key" - return - fi - if [[ -e "$destination" ]]; then - rm -rf "$staging" - rmdir "$lock" - die "archive destination exists without a ready marker: $key" - fi - mv "$staging" "$destination" - rmdir "$lock" - print "archive\tcreated\t$key" -} - -write_provisional_lease_manifest() { - local lease=$1 slug=$2 macos=$3 lane=$4 provider=$5 archive_key=$6 commit=$7 installer_sha=$8 installer_name=$9 repo=${10} created=${11} expires=${12} desktop=${13} - local manifest identity_scope - valid_id "$lease" - identity_scope=$(managed_identity_scope_for "$macos" "$lane") - mkdir -p "$LEASES/$lease" "$LOGS/$lease" "$ARTIFACTS/$lease" - manifest=$(manifest_path "$lease") - [[ -e "$manifest" ]] && return - { - print "owner\t$OWNER" - print "lease_id\t$lease" - print "slug\t$slug" - print "macos\t$macos" - print "test_lane\t$lane" - print "base_name\t$(base_for "$macos" "$lane" "$desktop")" - print "managed_identity_scope\t$identity_scope" - print "provider\t$provider" - print "archive_key\t$archive_key" - print "keypath_commit\t$commit" - print "installer_sha256\t$installer_sha" - print "installer_name\t$installer_name" - print "worktree\t$repo" - print "created_epoch\t$created" - print "created_at\t$(utc_now)" - print "expires_epoch\t$expires" - print "status\tprovisioning" - print "cleanup_status\tpending" - print "desktop_enabled\t$([[ "$desktop" == "1" ]] && print true || print false)" - print "provider_resource\tunknown" - } > "$manifest" -} - -lease_candidate_from_line() { - print -r -- "$1" | awk ' - $1 == "leased" && $2 ~ /^cbx_[A-Za-z0-9]+$/ {print $2; exit} - $0 ~ /^cbx_[A-Za-z0-9]+$/ {print; exit} - ' -} - -create_lease() { - local macos=$1 lane=$2 archive_key=$3 commit=$4 installer_sha=$5 installer_name=$6 ttl=$7 desktop=$8 tart_usb_passthrough=${9:-0} - local launcher provider archive repo slug output lease created expires manifest guest_output product build operation ttl_seconds - local provider_resource create_status candidate_file create_log exit_code managed_policy_exit identity_scope - launcher=$(launcher_for "$macos") - provider=$(provider_for "$macos") - [[ "$lane" == "managed-functional" || "$lane" == "unmanaged-ui" ]] || die "invalid test lane: $lane" - [[ ! ("$macos" == "27" && "$lane" == "managed-functional") ]] || die "managed-functional is not yet supported on macOS 27" - [[ "$tart_usb_passthrough" == "0" || "$tart_usb_passthrough" == "1" ]] || die "invalid Tart USB passthrough setting" - [[ "$tart_usb_passthrough" == "0" || "$macos" == "15" ]] || die "Tart USB passthrough requires macOS 15" - if [[ "$tart_usb_passthrough" == "1" ]]; then - [[ -x "$TART_USB_TOOL_ROOT/bin/crabbox-usb" ]] || die "USB-enabled CrabBox is unavailable" - [[ -x "$TART_USB_TOOL_ROOT/tart-usb.app/Contents/MacOS/tart" ]] || die "signed USB-enabled Tart is unavailable" - CRABBOX="$TART_USB_TOOL_ROOT/bin/crabbox-usb" - export CRABBOX_TART_USB_PASSTHROUGH=true - export PATH="$TART_USB_TOOL_ROOT/bin:$PATH" - fi - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" && "$macos" == "15" && "$lane" == "managed-functional" ]]; then - desktop=1 - fi - valid_id "$archive_key" - archive="$ARCHIVES/$archive_key" - [[ -f "$archive/ready.tsv" && -d "$archive/repo/.git" ]] || die "prepared archive not found: $archive_key" - ttl_seconds=$(duration_seconds "$ttl") - (( ttl_seconds > 0 && ttl_seconds <= 7200 )) || die "TTL must be between 1 second and 2 hours" - trap 'release_admission_lock' EXIT - trap 'release_admission_lock_and_exit 130' INT - trap 'release_admission_lock_and_exit 143' TERM - trap 'release_admission_lock_and_exit 129' HUP - acquire_admission_lock "$provider" || { - exit_code=$? - trap - EXIT INT TERM HUP - return "$exit_code" - } - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" && -n "${KEYPATH_LAB_TEST_PAUSE_AFTER_ADMISSION_LOCK:-}" ]]; then - sleep "$KEYPATH_LAB_TEST_PAUSE_AFTER_ADMISSION_LOCK" - fi - assert_provider_capacity "$provider" || return $? - assert_managed_identity_available "$macos" "$lane" || return $? - assert_internal_disk_reserve || return $? - created=$(now_epoch) - expires=$((created + ttl_seconds)) - slug="keypath${macos}-$(print -r -- "$commit" | cut -c1-8)-$(date -u +%Y%m%d%H%M%S)-$$" - operation="$OPERATIONS/$slug" - mkdir -p "$operation" - git clone -q --local --no-hardlinks "$archive/repo" "$operation/repo" - repo="$operation/repo" - prepare_worktree "$repo" - create_log="$operation/create.log" - candidate_file="$operation/lease-candidate.tsv" - : > "$create_log" - : > "$candidate_file" - set +e - (cd "$repo" && warmup_lease "$macos" "$lane" "$slug" "$desktop" 2>&1) | while IFS= read -r line || [[ -n "$line" ]]; do - print -r -- "$line" - print -r -- "$line" >> "$create_log" - candidate=$(lease_candidate_from_line "$line") - if [[ -n "$candidate" ]]; then - print -r -- "$candidate" > "$candidate_file" - write_provisional_lease_manifest "$candidate" "$slug" "$macos" "$lane" "$provider" "$archive_key" "$commit" "$installer_sha" "$installer_name" "$repo" "$created" "$expires" "$desktop" - fi - done - create_status=${pipestatus[1]} - set -e - output=$(<"$create_log") - lease=$(<"$candidate_file") - [[ -n "$lease" ]] || die "CrabBox did not report a lease id; inspect provider inventory before cleanup" - valid_id "$lease" - provider_resource=$(print -r -- "$output" | sed -nE 's/.* (vm|instance)=([^ ]+).*/\2/p' | tail -1) - mkdir -p "$LEASES/$lease" "$LOGS/$lease" "$ARTIFACTS/$lease" - manifest=$(manifest_path "$lease") - identity_scope=$(managed_identity_scope_for "$macos" "$lane") - { - print "owner\t$OWNER" - print "lease_id\t$lease" - print "slug\t$slug" - print "macos\t$macos" - print "test_lane\t$lane" - print "base_name\t$(base_for "$macos" "$lane" "$desktop")" - print "managed_identity_scope\t$identity_scope" - print "provider\t$provider" - print "archive_key\t$archive_key" - print "keypath_commit\t$commit" - print "installer_sha256\t$installer_sha" - print "installer_name\t$installer_name" - print "worktree\t$repo" - print "created_epoch\t$created" - print "created_at\t$(utc_now)" - print "expires_epoch\t$expires" - print "status\tcreated" - print "cleanup_status\tpending" - print "desktop_enabled\t$([[ "$desktop" == "1" ]] && print true || print false)" - print "tart_usb_passthrough\t$([[ "$tart_usb_passthrough" == "1" ]] && print true || print false)" - print "provider_resource\t${provider_resource:-unknown}" - } > "$manifest" - if (( create_status != 0 )); then - set_field "$manifest" status provisioning-failed - set_field "$manifest" provision_result "$create_status" - release_admission_lock - trap - EXIT INT TERM HUP - return "$create_status" - fi - print -r -- "$output" > "$LOGS/$lease/create.log" - guest_output=$(cd "$repo" && "$launcher" run "$lease" -- /bin/zsh -lc 'printf "product=%s\n" "$(sw_vers -productVersion)"; printf "build=%s\n" "$(sw_vers -buildVersion)"' 2>&1) || { - record_command "$lease" failed sw_vers - set_field "$manifest" status verification-failed - print -r -- "$guest_output" > "$LOGS/$lease/guest-version.log" - die "lease created but guest verification failed: $lease" - } - print -r -- "$guest_output" > "$LOGS/$lease/guest-version.log" - product=$(print -r -- "$guest_output" | sed -n 's/^product=//p' | tail -1) - build=$(print -r -- "$guest_output" | sed -n 's/^build=//p' | tail -1) - set_field "$manifest" macos_product_version "${product:-unknown}" - set_field "$manifest" macos_build "${build:-unknown}" - if [[ "$lane" == managed-functional ]]; then - set +e - rehydrate_managed_clone "$lease" > "$LOGS/$lease/managed-policy.log" 2>&1 - managed_policy_exit=$? - set -e - set_field "$manifest" managed_policy_result "$managed_policy_exit" - set_field "$manifest" managed_policy_at "$(utc_now)" - cat "$LOGS/$lease/managed-policy.log" - if ((managed_policy_exit != 0)); then - set_field "$manifest" status managed-policy-failed - release_admission_lock - trap - EXIT INT TERM HUP - return "$managed_policy_exit" - fi - record_command "$lease" passed managed-policy-rehydration - fi - set_field "$manifest" status ready - record_command "$lease" passed sw_vers - release_admission_lock - trap - EXIT INT TERM HUP - print "lease_id\t$lease" - print "manifest\t$manifest" -} - -install_app() { - local lease=$1 manifest macos lane repo installer_name provider_resource guest_repo command exit_code admission_command - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - lane=$(field "$manifest" test_lane) - repo=$(field "$manifest" worktree) - installer_name=$(field "$manifest" installer_name) - provider_resource=$(field "$manifest" provider_resource) - prepare_worktree "$repo" - guest_repo="/Users/$([[ "$macos" == "15" ]] && print admin || print keypathqa)/crabbox/$lease/repo" - admission_command="cd '$guest_repo'; Scripts/lab/mdm/verify-lane '$lane'" - if [[ "$lane" == "managed-functional" ]]; then - admission_command+=" --manifest /Library/KeyPathLab/managed-policy/manifest.json" - fi - command="setopt errexit nounset pipefail; $admission_command; rm -rf /tmp/keypath-install; mkdir -p /tmp/keypath-install; ditto -x -k '$guest_repo/.keypath-lab/installer/$installer_name' /tmp/keypath-install; cd '$guest_repo'; if [[ '$lane' == managed-functional ]]; then Scripts/lab/mdm/verify-artifact-policy --app /tmp/keypath-install/KeyPath.app --manifest /Library/KeyPathLab/managed-policy/manifest.json; fi; rm -rf /Applications/KeyPath.app; ditto /tmp/keypath-install/KeyPath.app /Applications/KeyPath.app" - set +e - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - print "admission $lane" >> "$LOGS/$lease/install-app.log" - print "install-app $macos $lease $provider_resource" >> "$LOGS/$lease/install-app.log" - exit_code=0 - elif [[ "$macos" == "15" ]]; then - (cd "$repo" && "$(launcher_for "$macos")" run "$lease" -- /bin/zsh -lc "sudo -n /bin/zsh -lc $(printf %q "$command")") > "$LOGS/$lease/install-app.log" 2>&1 - exit_code=$? - else - [[ "$provider_resource" =~ '^[A-Fa-f0-9-]+$' && "$provider_resource" != "unknown" ]] || die "invalid Parallels resource id" - "/Applications/Parallels Desktop.app/Contents/MacOS/prlctl" exec "$provider_resource" /bin/zsh -lc "$command" > "$LOGS/$lease/install-app.log" 2>&1 - exit_code=$? - fi - set -e - set_field "$manifest" install_app_result "$exit_code" - set_field "$manifest" admission_result "$exit_code" - set_field "$manifest" install_app_at "$(utc_now)" - cat "$LOGS/$lease/install-app.log" - return "$exit_code" -} - -run_command() { - local lease=$1; shift - local manifest macos launcher repo log exit_code - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - launcher=$(launcher_for "$macos") - repo=$(field "$manifest" worktree) - prepare_worktree "$repo" - log="$LOGS/$lease/run-$(date -u +%Y%m%dT%H%M%SZ).log" - set +e - (cd "$repo" && "$launcher" run "$lease" -- "$@") 2>&1 | tee "$log" - exit_code=${pipestatus[1]} - set -e - if (( exit_code == 0 )); then record_command "$lease" passed "$@"; else record_command "$lease" "failed:$exit_code" "$@"; fi - set_field "$manifest" last_result "$exit_code" - set_field "$manifest" last_run_at "$(utc_now)" - return "$exit_code" -} - -secure_dialog_input() { - local lease=$1 app=$2 field_label=$3 submit_button=$4 already_focused=$5 - local manifest macos resource key ip secret_file guest_command exit_code - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "15" ]] || die "secure dialog input currently supports only the Tart macOS 15 lane" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "secure dialog input requires a desktop-enabled lease" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Za-z0-9._-]+$' && "$resource" != "unknown" ]] || die "invalid Tart resource id" - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - key="${KEYPATH_LAB_TEST_SSH_KEY:?test SSH key is required}" - secret_file="${KEYPATH_LAB_TEST_SECRET_FILE:?test secret file is required}" - else - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - secret_file=$(mktemp "$STATE_ROOT/.secure-input.XXXXXXXX") - chmod 600 "$secret_file" - typeset -g KEYPATH_LAB_SECURE_TEMP="$secret_file" - trap '[[ -z ${KEYPATH_LAB_SECURE_TEMP:-} ]] || rm -f "$KEYPATH_LAB_SECURE_TEMP"' EXIT - /opt/homebrew/bin/sops -d "$HOME/dotfiles/secrets.env" | awk -F= '$1 == "KEYPATH_TART_ADMIN_PASSWORD" {sub(/^[^=]*=/, ""); printf "%s", $0; found=1} END {if (!found) exit 1}' > "$secret_file" || die "KEYPATH_TART_ADMIN_PASSWORD is unavailable" - fi - [[ -s "$secret_file" ]] || die "secure input secret is empty" - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - ip=$($TART ip "$resource") - [[ "$ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Tart returned an invalid guest address" - - # Peekaboo's MCP type response contains the typed value. Suppress both output - # streams for that command so the secret cannot enter controller logs. - local refresh_command field_command click_command submit_command submit_label_quoted button_geometry_command postcondition_command - local -a refresh_args focus_args click_args submit_args button_geometry_args postcondition_args - guest_command='set -euo pipefail; command -v /opt/homebrew/bin/peekaboo >/dev/null; command -v /opt/homebrew/bin/mcporter >/dev/null; ' - if [[ "$field_label" == "AXSecureTextField" ]]; then - [[ -n "$submit_button" ]] || die "AXSecureTextField requires a submit button for postcondition verification" - [[ "$already_focused" == "0" ]] || die "AXSecureTextField does not use --already-focused" - focus_args=(/usr/bin/osascript -l JavaScript -e 'function descendants(element) { var result = []; try { var children = element.uiElements(); for (var i = 0; i < children.length; i++) { result.push(children[i]); result = result.concat(descendants(children[i])); } } catch (_) {} return result; } function run(argv) { var appName = argv[1]; var secret = $.NSString.stringWithContentsOfFileEncodingError(argv[0], $.NSUTF8StringEncoding, null).js.replace(/\r?\n$/, ""); var process = Application("System Events").processes.byName(appName); var field = descendants(process.windows[0]).find(function (element) { try { return element.subrole() === "AXSecureTextField"; } catch (_) { return false; } }); if (!field) throw new Error("secure text field not found"); field.value = secret; }') - printf -v field_command '%q ' "${focus_args[@]}" - guest_command+='IFS= read -r secret_value || [[ -n "$secret_value" ]]; secret_path=$(/usr/bin/mktemp /tmp/keypath-secure-input.XXXXXX); /bin/chmod 600 "$secret_path"; trap '\''rm -f "$secret_path"'\'' EXIT; printf '\''%s'\'' "$secret_value" > "$secret_path"; unset secret_value; ' - guest_command+="$field_command \"\$secret_path\" $(printf %q "$app") >/dev/null; rm -f \"\$secret_path\"; trap - EXIT" - elif [[ "$already_focused" == "0" ]]; then - refresh_args=(/opt/homebrew/bin/peekaboo see --app "$app" --json) - printf -v refresh_command '%q ' "${refresh_args[@]}" - guest_command+="$refresh_command >/dev/null || exit 40; " - # Peekaboo 3 accepts the semantic target as the positional click argument. - # --query belongs to Scripts/lab/peekaboo-ui and is not a Peekaboo option. - click_args=(/opt/homebrew/bin/peekaboo click "$field_label" --app "$app" --foreground --json) - printf -v click_command '%q ' "${click_args[@]}" - guest_command+="$click_command >/dev/null || exit 41; " - elif [[ -n "$submit_button" ]]; then - die "--already-focused cannot be combined with a submit button" - fi - if [[ "$field_label" != "AXSecureTextField" ]]; then - guest_command+='PEEKABOO_VISUALIZER_MASK_TYPED_TEXT=true /opt/homebrew/bin/mcporter call --stdio '\''peekaboo mcp serve --bridge-socket "$HOME/Library/Application Support/Peekaboo/daemon.sock"'\'' --env PEEKABOO_VISUALIZER_MASK_TYPED_TEXT=true type text=@/dev/stdin clear=true --output json --timeout 20000 >/dev/null 2>&1 || exit 42' - fi - if [[ -n "$submit_button" ]]; then - if [[ "$field_label" == "AXSecureTextField" ]]; then - button_geometry_args=(/usr/bin/osascript -l JavaScript -e 'function descendants(element) { var result = []; try { var children = element.uiElements(); for (var i = 0; i < children.length; i++) { result.push(children[i]); result = result.concat(descendants(children[i])); } } catch (_) {} return result; } function run(argv) { var process = Application("System Events").processes.byName(argv[0]); var label = argv[1]; var button = descendants(process.windows[0]).find(function (element) { try { return element.role() === "AXButton" && (element.name() === label || element.description() === label); } catch (_) { return false; } }); if (!button) throw new Error("submit button not found"); var position = button.position(); var size = button.size(); return Math.round(position[0] + size[0] / 2) + "," + Math.round(position[1] + size[1] / 2); }' "$app" "$submit_button") - printf -v button_geometry_command '%q ' "${button_geometry_args[@]}" - guest_command+="; button_coords=\$( $button_geometry_command ); [[ \"\$button_coords\" =~ '^-?[0-9]+,-?[0-9]+$' ]] || exit 78; /opt/homebrew/bin/peekaboo click --coords \"\$button_coords\" --global-coords --foreground --input-strategy synthOnly --json >/dev/null 2>&1 || true" - else - submit_args=(/opt/homebrew/bin/peekaboo click "$submit_button" --app "$app" --foreground --json) - printf -v submit_command '%q ' "${submit_args[@]}" - printf -v submit_label_quoted '%q' "$submit_button" - guest_command+="; $refresh_command >/tmp/keypath-secure-submit.json || exit 44; if ! $submit_command >/dev/null; then $refresh_command >/tmp/keypath-secure-submit.json || exit 43; /usr/bin/env python3 -c 'import json,sys; elements=json.load(open(sys.argv[1])).get(\"data\",{}).get(\"ui_elements\",[]); raise SystemExit(1 if any(e.get(\"label\")==sys.argv[2] for e in elements) else 0)' /tmp/keypath-secure-submit.json $submit_label_quoted || exit 43; fi" - guest_command+="; for attempt in {1..150}; do $refresh_command >/tmp/keypath-secure-postcondition.json || exit 44; /usr/bin/env python3 -c 'import json,sys; elements=json.load(open(sys.argv[1])).get(\"data\",{}).get(\"ui_elements\",[]); labels={e.get(\"label\") for e in elements}; raise SystemExit(0 if sys.argv[2] not in labels and sys.argv[3] not in labels else 1)' /tmp/keypath-secure-postcondition.json $(printf %q "$field_label") $submit_label_quoted && break; sleep 0.1; done; /usr/bin/env python3 -c 'import json,sys; elements=json.load(open(sys.argv[1])).get(\"data\",{}).get(\"ui_elements\",[]); labels={e.get(\"label\") for e in elements}; raise SystemExit(0 if sys.argv[2] not in labels and sys.argv[3] not in labels else 79)' /tmp/keypath-secure-postcondition.json $(printf %q "$field_label") $submit_label_quoted" - fi - fi - if [[ "$field_label" == "AXSecureTextField" ]]; then - postcondition_args=(/usr/bin/osascript -l JavaScript -e 'function descendants(element) { var result = []; try { var children = element.uiElements(); for (var i = 0; i < children.length; i++) { result.push(children[i]); result = result.concat(descendants(children[i])); } } catch (_) {} return result; } function run(argv) { var processes = Application("System Events").processes.whose({name: argv[0]})(); if (processes.length === 0 || processes[0].windows().length === 0) return "closed"; var open = descendants(processes[0].windows[0]).some(function (element) { try { return element.subrole() === "AXSecureTextField"; } catch (_) { return false; } }); return open ? "open" : "closed"; }' "$app") - printf -v postcondition_command '%q ' "${postcondition_args[@]}" - guest_command+="; for attempt in {1..150}; do [[ \$( $postcondition_command ) == closed ]] && exit 0; sleep 0.1; done; exit 77" - fi - set +e - "$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" "/bin/zsh -lc $(printf %q "$guest_command")" < "$secret_file" - exit_code=$? - set -e - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - rm -f "$secret_file" - KEYPATH_LAB_SECURE_TEMP= - fi - if (( exit_code == 0 )); then - record_command "$lease" passed secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - print "secure_dialog_input\tpassed" - elif (( exit_code == 40 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed while refreshing the dialog snapshot" - elif (( exit_code == 41 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed while focusing the field" - elif (( exit_code == 42 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed while streaming masked input" - elif (( exit_code == 43 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed while submitting the dialog" - elif (( exit_code == 44 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed while refreshing the submitted dialog" - elif (( exit_code == 77 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input was submitted but the SecurityAgent sheet did not close" - elif (( exit_code == 78 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input could not resolve valid SecurityAgent button geometry" - elif (( exit_code == 79 )); then - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input was submitted but the authentication sheet did not close" - else - record_command "$lease" "failed:$exit_code" secure-dialog-input --app "$app" --field "$field_label" ${submit_button:+--submit "$submit_button"} - die "secure dialog input failed" - fi -} - -protected_click() { - local lease=$1 app=$2 expected_before=$3 expected_after=$4 coordinate_space=$5 x=$6 y=$7 count=${8:-1} - local manifest macos resource key ip before after guest_command geometry_command geometry - local native_width native_height logical_width logical_height scale_x scale_y - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "15" ]] || die "protected click currently supports only the Tart macOS 15 lane" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "protected click requires a desktop-enabled lease" - [[ "$x" == <-> && "$y" == <-> ]] || die "protected click coordinates must be non-negative integers" - [[ "$count" == "1" || "$count" == "2" ]] || die "protected click count must be 1 or 2" - [[ "$coordinate_space" == "native" || "$coordinate_space" == "ax" ]] || die "invalid protected click coordinate space" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Za-z0-9._-]+$' && "$resource" != "unknown" ]] || die "invalid Tart resource id" - - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - before=${KEYPATH_LAB_TEST_WINDOW_BEFORE:-$expected_before} - else - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - export PATH="$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - ip=$($TART ip "$resource") - [[ "$ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Tart returned an invalid guest address" - guest_command=$'/usr/bin/osascript -l JavaScript -e \'\nfunction run(argv) {\n var matches = Application("System Events").processes.whose({name: argv[0]})();\n if (matches.length === 0 || matches[0].windows().length === 0) return "";\n return matches[0].windows[0].name() || "__UNTITLED__";\n}\' -- '$(printf %q "$app") - before=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" "/bin/zsh -lc $(printf %q "$guest_command")") - fi - [[ "$expected_before" == "__ANY__" && -n "$before" ]] || [[ "$before" == "$expected_before" ]] || { - record_command "$lease" failed protected-click --app "$app" --window "$expected_before" --x "$x" --y "$y" - die "protected click precondition failed: expected window '$expected_before', found '${before:-unknown}'" - } - - if [[ "$coordinate_space" == "ax" ]]; then - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - geometry=${KEYPATH_LAB_TEST_DISPLAY_GEOMETRY:-'2048 1536 1024 768'} - else - geometry_command='/opt/homebrew/bin/peekaboo list windows --app '$(printf %q "$app")' --json | /usr/bin/env python3 -c '\''import json,re,sys; data=json.load(sys.stdin).get("data",{}); windows=data.get("windows",data if isinstance(data,list) else []); names=[w.get("screenName","") for w in windows if isinstance(w,dict)]; m=next((re.search(r"([0-9]+)×([0-9]+)",n) for n in names if re.search(r"([0-9]+)×([0-9]+)",n)),None); print(f"{m.group(1)} {m.group(2)}" if m else "",end="")'\''; printf " "; /usr/bin/osascript -l JavaScript -e '\''ObjC.import("AppKit"); var s=$.NSScreen.mainScreen.frame.size; s.width+" "+s.height'\''' - geometry=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" "/bin/zsh -lc $(printf %q "$geometry_command")") - fi - IFS=' ' read -r native_width native_height logical_width logical_height <<< "$geometry" - [[ "$native_width" == <-> && "$native_height" == <-> && "$logical_width" == <-> && "$logical_height" == <-> && "$logical_width" -gt 0 && "$logical_height" -gt 0 ]] || die "protected click could not measure display geometry" - (( native_width % logical_width == 0 && native_height % logical_height == 0 )) || die "protected click measured a non-integral display scale" - scale_x=$((native_width / logical_width)) - scale_y=$((native_height / logical_height)) - (( scale_x == scale_y && scale_x > 0 )) || die "protected click measured inconsistent display scales" - x=$((x * scale_x)) - y=$((y * scale_y)) - fi - - if [[ "$count" == "2" ]]; then - "$CRABBOX" desktop click --provider tart --target macos --id "$resource" --x "$x" --y "$y" --count 2 >/dev/null - else - "$CRABBOX" desktop click --provider tart --target macos --id "$resource" --x "$x" --y "$y" >/dev/null - fi - sleep "${KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS:-1}" - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - after=${KEYPATH_LAB_TEST_WINDOW_AFTER:-$expected_after} - else - after=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" "/bin/zsh -lc $(printf %q "$guest_command")") - fi - if [[ "$after" != "$expected_after" && "$expected_before" == "__ANY__" ]]; then - sleep "${KEYPATH_LAB_INITIAL_SETTINGS_RETRY_SECONDS:-5}" - "$CRABBOX" desktop click --provider tart --target macos --id "$resource" --x "$x" --y "$y" >/dev/null - sleep "${KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS:-1}" - after=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -i "$key" "admin@$ip" "/bin/zsh -lc $(printf %q "$guest_command")") - fi - [[ "$after" == "$expected_after" ]] || { - record_command "$lease" failed protected-click --app "$app" --window "$expected_before" --after-window "$expected_after" --x "$x" --y "$y" - die "protected click postcondition failed: expected window '$expected_after', found '${after:-unknown}'" - } - record_command "$lease" passed protected-click --app "$app" --window "$expected_before" --after-window "$expected_after" --x "$x" --y "$y" - print "protected_click\tpassed" - print "window_before\t$before" - print "window_after\t$after" - print "coordinate_space\t$coordinate_space" - print "click_count\t$count" - if [[ "$coordinate_space" == "ax" ]]; then - print "display_scale\t$scale_x" - fi -} - -desktop_type() { - local lease=$1 text=$2 manifest macos resource - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "15" ]] || die "desktop type currently supports only the Tart macOS 15 lane" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "desktop type requires a desktop-enabled lease" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Za-z0-9._-]+$' && "$resource" != "unknown" ]] || die "invalid Tart resource id" - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - export PATH="$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - "$CRABBOX" desktop type --provider tart --target macos --id "$resource" --text "$text" - record_command "$lease" passed desktop-type --bytes "${#text}" -} - -print_status() { - local lease=$1 manifest macos launcher - manifest=$(owned_manifest "$lease") - cat "$manifest" - macos=$(field "$manifest" macos) - launcher=$(launcher_for "$macos") - print "provider_inventory_begin" - "$launcher" list || true - print "provider_inventory_end" -} - -list_leases() { - ensure_roots - print "lease_id\tmacos\ttest_lane\tbase_name\tprovider\tstatus\texpires_epoch\tcommit\tcleanup" - local manifest lease - for manifest in "$LEASES"/*/manifest.tsv(N); do - [[ "$(field "$manifest" owner)" == "$OWNER" ]] || continue - lease=$(field "$manifest" lease_id) - print "$lease\t$(field "$manifest" macos)\t$(field "$manifest" test_lane)\t$(field "$manifest" base_name)\t$(field "$manifest" provider)\t$(field "$manifest" status)\t$(field "$manifest" expires_epoch)\t$(field "$manifest" keypath_commit)\t$(field "$manifest" cleanup_status)" - done -} - -collect_artifacts() { - local lease=$1 manifest output exit_code macos repo archive provider_resource parallels_cli - local parallels_resource_pattern='^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$' - local nameplate_restore=0 nameplate_hide_status=not-needed nameplate_restore_status=not-needed - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - repo=$(field "$manifest" worktree) - prepare_worktree "$repo" - output="$ARTIFACTS/$lease/$(date -u +%Y%m%dT%H%M%SZ)" - mkdir -p "$output" - cp "$manifest" "$output/manifest.tsv" - cp "$LEASES/$lease/commands.tsv" "$output/commands.tsv" 2>/dev/null || true - cp -R "$LOGS/$lease" "$output/controller-logs" - if [[ -d "$repo/.crabbox/captures" ]]; then - cp -R "$repo/.crabbox/captures" "$output/controller-crabbox-captures" - fi - if [[ "$(field "$manifest" nameplate_state)" == visible ]]; then - set +e - (nameplate_control "$lease" hide) > "$output/nameplate-hide.log" 2>&1 - nameplate_hide_status=$? - set -e - if (( nameplate_hide_status == 0 )); then - nameplate_restore=1 - fi - fi - archive="$output/scenario-output.tar.gz" - set +e - (cd "$repo" && run_with_download "$macos" "$lease" ".keypath-lab/scenario-output.tar.gz" "$archive" \ - /bin/zsh -lc 'set -e; out=.keypath-lab/scenario-output/controller-capture; mkdir -p "$out/logs"; sw_vers > "$out/sw-vers.txt"; date -u +%Y-%m-%dT%H:%M:%SZ > "$out/captured-at.txt"; cp -R "$HOME/Library/Logs/KeyPath/." "$out/logs/" 2>/dev/null || true; /Applications/KeyPath.app/Contents/MacOS/keypath-cli system inspect --json > "$out/system-inspect.json" 2>/dev/null || true; tar -czf .keypath-lab/scenario-output.tar.gz -C .keypath-lab scenario-output') > "$output/download.log" 2>&1 - exit_code=$? - set -e - if (( exit_code == 0 )); then - tar -xzf "$archive" -C "$output" - fi - if [[ "$(field "$manifest" desktop_enabled)" == "true" && "$nameplate_hide_status" != "0" && "$nameplate_hide_status" != not-needed ]]; then - screenshot_exit=unavailable:nameplate-hide-failed - set_field "$manifest" screenshot_status "$screenshot_exit" - elif [[ "$(field "$manifest" desktop_enabled)" == "true" ]]; then - set +e - if [[ "$macos" == "15" ]]; then - if [[ "${USER:-}" == "clawd" ]]; then export TART_HOME="$LAB_ROOT/TartHome-clawd"; else export TART_HOME="$LAB_ROOT/TartHome"; fi - export PATH="$LAB_ROOT/CompatTools/bin:$LAB_ROOT/SharedTools/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin" - (cd "$repo" && "$CRABBOX" screenshot --provider tart --target macos --id "$lease" --output "$output/screenshot.png") >> "$output/download.log" 2>&1 - else - provider_resource=$(field "$manifest" provider_resource) - [[ "$provider_resource" =~ $parallels_resource_pattern ]] || die "invalid Parallels resource id" - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - "$parallels_cli" capture "$provider_resource" --file "$output/screenshot.png" >> "$output/download.log" 2>&1 - fi - screenshot_exit=$? - set -e - set_field "$manifest" screenshot_status "$screenshot_exit" - else - screenshot_exit=unavailable:lease-not-created-with-desktop - set_field "$manifest" screenshot_status "$screenshot_exit" - fi - if (( nameplate_restore )); then - set +e - (nameplate_control "$lease" show) > "$output/nameplate-restore.log" 2>&1 - nameplate_restore_status=$? - set -e - fi - set_field "$manifest" artifacts_status "$exit_code" - set_field "$manifest" artifacts_last_collected_at "$(utc_now)" - set_field "$manifest" nameplate_artifact_hide_status "$nameplate_hide_status" - set_field "$manifest" nameplate_artifact_restore_status "$nameplate_restore_status" - cp "$manifest" "$output/manifest.tsv" - cp "$LEASES/$lease/commands.tsv" "$output/commands.tsv" 2>/dev/null || true - print "artifact_dir\t$output" - print "download_status\t$exit_code" - print "screenshot_status\t$screenshot_exit" - print "nameplate_hide_status\t$nameplate_hide_status" - print "nameplate_restore_status\t$nameplate_restore_status" - return "$exit_code" -} - -scenario() { - local lease=$1 name=$2 manifest repo scenario_script lane - manifest=$(owned_manifest "$lease") - repo=$(field "$manifest" worktree) - lane=$(field "$manifest" test_lane) - prepare_worktree "$repo" - scenario_script="Scripts/lab/scenarios/installer-scenario" - [[ -x "$repo/$scenario_script" ]] || die "scenario runner missing from archived commit" - run_command "$lease" "/bin/zsh" "$scenario_script" "$name" "$lane" -} - -desktop_bootstrap() { - local lease=$1 install_tools=$2 manifest macos repo output guest_output command - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "desktop bootstrap requires a desktop-enabled lease" - repo=$(field "$manifest" worktree) - prepare_worktree "$repo" - output=".keypath-lab/scenario-output/bootstrap" - command=(/bin/zsh Scripts/lab/desktop-bootstrap --output "$output") - [[ "$install_tools" == "1" ]] && command+=(--install-tools) - run_command "$lease" "${command[@]}" - set_field "$manifest" desktop_bootstrap_at "$(utc_now)" - set_field "$manifest" desktop_bootstrap_status passed -} - -console_login() { - local lease=$1 manifest macos resource parallels_cli secret_file exit_code console_user attempt guest_command autologin_status guest_control_ready configure_stage - local guest_ip key known_hosts known_hosts_option fifo status_file configure_pid fifo_ready stream_exit credential_timeout - local secret_leak - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "27" ]] || die "console login currently supports only the macOS 27 Parallels lane" - [[ "$(field "$manifest" provider)" == "parallels" ]] || die "console login requires a Parallels lease" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "console login requires a desktop-enabled lease" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$' ]] || die "invalid Parallels resource id" - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - if ! "$parallels_cli" status "$resource" 2>/dev/null | grep -q running; then - "$parallels_cli" start "$resource" > "$LOGS/$lease/console-login-start.log" 2>&1 || die "failed to start the disposable Parallels guest" - fi - guest_control_ready=0 - for attempt in {1..90}; do - if "$parallels_cli" exec "$resource" /usr/bin/true >/dev/null 2>&1; then - guest_control_ready=1 - break - fi - sleep "${KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS:-2}" - done - (( guest_control_ready == 1 )) || die "Parallels guest control did not become ready" - - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - secret_file="${KEYPATH_LAB_TEST_SECRET_FILE:?test secret file is required}" - key="${KEYPATH_LAB_TEST_SSH_KEY:?test SSH key is required}" - known_hosts="${KEYPATH_LAB_TEST_KNOWN_HOSTS:-/dev/null}" - guest_ip=192.0.2.27 - else - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - known_hosts="$HOME/Library/Application Support/crabbox/testboxes/$lease/known_hosts" - [[ -f "$known_hosts" && ! -L "$known_hosts" && -O "$known_hosts" ]] || die "owned CrabBox known-hosts file not found for lease" - guest_ip=$("$parallels_cli" list -i -f -j "$resource" | python3 -c 'import json,sys; rows=json.load(sys.stdin); addresses=rows[0].get("Network",{}).get("ipAddresses",[]) if rows else []; print(next((item.get("ip","") for item in addresses if item.get("type")=="ipv4"),""),end="")') - [[ "$guest_ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Parallels returned an invalid guest address" - secret_file=$(mktemp "$STATE_ROOT/.console-login.XXXXXXXX") - chmod 600 "$secret_file" - typeset -g KEYPATH_LAB_SECURE_TEMP="$secret_file" - trap '[[ -z ${KEYPATH_LAB_SECURE_TEMP:-} ]] || rm -f "$KEYPATH_LAB_SECURE_TEMP"' EXIT - /opt/homebrew/bin/sops -d "$HOME/dotfiles/secrets.env" | awk -F= '$1 == "KEYPATH_LAB_GUEST_PASSWORD" {sub(/^[^=]*=/, ""); printf "%s", $0; found=1} END {if (!found) exit 1}' > "$secret_file" || die "KEYPATH_LAB_GUEST_PASSWORD is unavailable" - fi - [[ -s "$secret_file" ]] || die "console login secret is empty" - # OpenSSH parses -o values a second time, so spaces in this path must remain - # escaped even though the complete option is already one shell argument. - known_hosts_option=${known_hosts// /\\ } - credential_timeout=${KEYPATH_LAB_CONSOLE_CREDENTIAL_TIMEOUT_SECONDS:-30} - [[ "$credential_timeout" == <-> && "$credential_timeout" -gt 0 ]] || die "console login credential timeout must be a positive integer" - - # Configure automatic login inside the disposable clone through Parallels' - # root guest-control channel. prlctl exec does not forward stdin, so the root - # process creates a lease-specific FIFO and the existing lease-owned SSH - # channel streams the password into it. The controller's short-lived, - # owner-only temp file is removed before the reboot; the value never appears - # in controller process arguments or logs. Inside the disposable guest, - # dscl and sysadminctl have no noninteractive stdin form and briefly receive - # the value in argv. Do not run process-argument capture during this action. - # sysadminctl's protected prompt cannot be driven through prlctl because - # guest-control does not forward stdin. Expand the FIFO-fed value only inside - # the isolated guest process; the controller command contains the variable - # reference, never its value, and both guest command output streams stay - # confined to the leak-checked controller log. - fifo="/tmp/keypath-console-login-$lease-$$.fifo" - status_file="/tmp/keypath-console-login-$lease-$$.status" - guest_command="set -euo pipefail; fifo=$(printf %q "$fifo"); status_file=$(printf %q "$status_file"); rm -f \"\$fifo\" \"\$status_file\"; print -r -- started > \"\$status_file\"; /usr/bin/mkfifo \"\$fifo\"; /usr/sbin/chown keypathqa:staff \"\$fifo\"; /bin/chmod 600 \"\$fifo\"; trap 'rm -f \"\$fifo\"' EXIT; exec 3<> \"\$fifo\"; KEYPATH_GUEST_PASSWORD=; IFS= read -r -t $credential_timeout -u 3 KEYPATH_GUEST_PASSWORD || [[ -n \"\$KEYPATH_GUEST_PASSWORD\" ]]; print -r -- credential-received >> \"\$status_file\"; /usr/bin/dscl . -authonly keypathqa \"\$KEYPATH_GUEST_PASSWORD\" || exit 91; print -r -- credential-valid >> \"\$status_file\"; set +e; /usr/sbin/sysadminctl -autologin set -userName keypathqa -password \"\$KEYPATH_GUEST_PASSWORD\"; sysadmin_result=\$?; set -e; autologin_status=\$(/usr/sbin/sysadminctl -autologin status 2>&1 || true); if [[ \"\$autologin_status\" != *'Automatic login is ON'* && \"\$autologin_status\" != *'Automatic login user: keypathqa'* ]]; then kcpassword_tmp=\$(/usr/bin/mktemp /etc/kcpassword.XXXXXXXX); trap 'rm -f \"\$fifo\" \"\$kcpassword_tmp\"' EXIT; printf %s \"\$KEYPATH_GUEST_PASSWORD\" | /usr/bin/perl -e 'binmode STDIN; binmode STDOUT; local \$/; my \$password = ; my @key = (0x7d,0x89,0x52,0x23,0xd2,0xbc,0xdd,0xea,0xa3,0xb9,0x1f); \$password .= chr(0); \$password .= chr(0) while length(\$password) % 12; print pack(\"C*\", map { ord(substr(\$password, \$_, 1)) ^ \$key[\$_ % @key] } 0 .. length(\$password)-1);' > \"\$kcpassword_tmp\"; /usr/sbin/chown root:wheel \"\$kcpassword_tmp\"; /bin/chmod 600 \"\$kcpassword_tmp\"; /bin/mv -f \"\$kcpassword_tmp\" /etc/kcpassword; /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser -string keypathqa; print -r -- method:kcpassword-fallback >> \"\$status_file\"; fi; /bin/mkdir -p /var/db/crabbox; printf '%s\n' \"\$KEYPATH_GUEST_PASSWORD\" > /var/db/crabbox/vnc.password; /usr/sbin/chown root:wheel /var/db/crabbox/vnc.password; /bin/chmod 600 /var/db/crabbox/vnc.password; print -r -- rfb-credential:aligned >> \"\$status_file\"; unset KEYPATH_GUEST_PASSWORD; print -r -- sysadminctl-exit:\$sysadmin_result >> \"\$status_file\"; exit 0" - set +e - "$parallels_cli" exec "$resource" /bin/zsh -lc "$(printf %q "$guest_command")" \ - > "$LOGS/$lease/console-login-configure.log" 2>&1 & - configure_pid=$! - fifo_ready=0 - for attempt in {1..300}; do - if "$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" \ - "/bin/test -p $(printf %q "$fifo")" /dev/null 2>&1; then - fifo_ready=1 - break - fi - sleep 0.1 - done - if (( fifo_ready == 1 )); then - # The decrypted dotenv value intentionally has no trailing newline. Frame - # it as one record so the guest's `read` completes immediately instead of - # waiting indefinitely after receiving a partial line from the FIFO. - { /bin/cat "$secret_file"; printf '\n'; } | \ - /usr/bin/perl -e 'my $timeout = shift; alarm $timeout; exec @ARGV or exit 127' "$credential_timeout" \ - "$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" \ - "/bin/zsh -c $(printf %q "/bin/cat > $(printf %q "$fifo")")" >/dev/null 2>&1 - stream_exit=$? - (( stream_exit == 0 )) || kill "$configure_pid" 2>/dev/null || true - else - stream_exit=76 - kill "$configure_pid" 2>/dev/null || true - fi - wait "$configure_pid" - exit_code=$? - "$parallels_cli" exec "$resource" /bin/cat "$status_file" >> "$LOGS/$lease/console-login-configure.log" 2>&1 || true - "$parallels_cli" exec "$resource" /bin/rm -f "$status_file" >/dev/null 2>&1 || true - configure_stage=$(< "$LOGS/$lease/console-login-configure.log") - (( stream_exit == 0 )) || exit_code=$stream_exit - secret_leak=0 - if grep -Fq -f "$secret_file" "$LOGS/$lease/console-login-configure.log"; then - : > "$LOGS/$lease/console-login-configure.log" - secret_leak=1 - exit_code=90 - fi - set -e - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - rm -f "$secret_file" - KEYPATH_LAB_SECURE_TEMP= - trap - EXIT - fi - if (( exit_code != 0 )); then - if (( secret_leak == 1 )); then - set_field "$manifest" console_login_status credential-leak-detected - record_command "$lease" failed:credential-leak console-login - die "guest credential disclosure was detected and redacted from the controller log" - fi - if (( stream_exit != 0 )); then - set_field "$manifest" console_login_status "credential-stream-failed:$stream_exit" - record_command "$lease" "failed:$stream_exit" console-login - die "failed to stream the guest credential into the disposable guest" - fi - if [[ "$configure_stage" != *credential-valid* ]]; then - set_field "$manifest" console_login_status credential-mismatch - record_command "$lease" failed:credential-mismatch console-login - die "KEYPATH_LAB_GUEST_PASSWORD does not authenticate the keypathqa guest account" - fi - set_field "$manifest" console_login_status "configure-failed:$exit_code" - record_command "$lease" "failed:$exit_code" console-login - die "failed to configure automatic login in the disposable guest" - fi - autologin_status=$("$parallels_cli" exec "$resource" /usr/sbin/sysadminctl -autologin status 2>&1 || true) - if [[ "$autologin_status" != *"Automatic login is ON"* && "$autologin_status" != *"Automatic login user: keypathqa"* ]]; then - set_field "$manifest" console_login_status "configure-postcondition-failed" - record_command "$lease" failed console-login - die "automatic login remained disabled after configuration" - fi - - # A reboot is required because the source base is intentionally captured at - # loginwindow. Use the provider-owned restart so the clone cannot be stranded - # stopped when its guest-control connection closes during shutdown. - "$parallels_cli" restart "$resource" \ - > "$LOGS/$lease/console-login-reboot.log" 2>&1 || die "failed to restart the disposable Parallels guest" - console_user= - for attempt in {1..90}; do - sleep "${KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS:-2}" - console_user=$("$parallels_cli" exec "$resource" /usr/bin/stat -f %Su /dev/console 2>/dev/null | tail -1 || true) - [[ "$console_user" == "keypathqa" ]] && break - done - if [[ "$console_user" != "keypathqa" ]]; then - set_field "$manifest" console_login_status "postcondition-failed" - record_command "$lease" failed console-login - die "automatic login did not establish the keypathqa console session" - fi - - set_field "$manifest" console_login_status passed - set_field "$manifest" console_login_at "$(utc_now)" - record_command "$lease" passed console-login - print "console_login\tpassed" - print "console_user\t$console_user" -} - -secure_console_submit() { - local lease=$1 manifest macos resource parallels_cli secret_file - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "26" || "$macos" == "27" ]] || die "secure console submit requires a macOS 26 or 27 Parallels lane" - [[ "$(field "$manifest" provider)" == "parallels" ]] || die "secure console submit requires a Parallels lease" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "secure console submit requires a desktop-enabled lease" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$' ]] || die "invalid Parallels resource id" - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - secret_file="${KEYPATH_LAB_TEST_SECRET_FILE:?test secret file is required}" - else - secret_file=$(mktemp "$STATE_ROOT/.secure-console.XXXXXXXX") - chmod 600 "$secret_file" - typeset -g KEYPATH_LAB_SECURE_TEMP="$secret_file" - trap '[[ -z ${KEYPATH_LAB_SECURE_TEMP:-} ]] || rm -f "$KEYPATH_LAB_SECURE_TEMP"' EXIT - /opt/homebrew/bin/sops -d "$HOME/dotfiles/secrets.env" | awk -F= '$1 == "KEYPATH_LAB_GUEST_PASSWORD" {sub(/^[^=]*=/, ""); printf "%s", $0; found=1} END {if (!found) exit 1}' > "$secret_file" || die "KEYPATH_LAB_GUEST_PASSWORD is unavailable" - fi - [[ -s "$secret_file" ]] || die "secure console secret is empty" - - # Convert the credential to Parallels key codes in a pipe. The plaintext is - # read only from the owner-only temp file and never enters argv, logs, the - # guest pasteboard, or an artifact. Keep the accepted alphabet deliberately - # narrow; expanding it requires an explicit key-map review. - python3 -c 'import json,sys -codes={"a":38,"b":56,"c":54,"d":40,"e":26,"f":41,"g":42,"h":43,"i":31,"j":44,"k":45,"l":46,"m":58,"n":57,"o":32,"p":33,"q":24,"r":27,"s":39,"t":28,"u":30,"v":55,"w":25,"x":53,"y":29,"z":52,"1":10,"2":11,"3":12,"4":13,"5":14,"6":15,"7":16,"8":17,"9":18,"0":19,"-":20} -value=open(sys.argv[1],"r",encoding="utf-8").read() -if not value or any(ch not in codes for ch in value): raise SystemExit(64) -for ch in value: print(json.dumps([{"key":codes[ch]}],separators=(",",":")))' "$secret_file" 3<&- | \ - while IFS= read -r key_event <&3; do - printf '%s\n' "$key_event" | "$parallels_cli" send-key-event "$resource" --json >/dev/null || exit 1 - sleep "${KEYPATH_LAB_SECURE_CONSOLE_KEY_DELAY_SECONDS:-0.2}" - done 3<&0 || die "failed to deliver the guest credential through Parallels key events" - sleep "${KEYPATH_LAB_SECURE_CONSOLE_SETTLE_SECONDS:-0.25}" - if [[ "$macos" == "26" ]]; then - # SecurityAgent accepts password text from the Parallels console but - # ignores synthetic application clicks. Leave the protected Enroll action - # to a real user click. - : - else - "$parallels_cli" send-key-event "$resource" --key 36 >/dev/null - fi - - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - rm -f "$secret_file" - KEYPATH_LAB_SECURE_TEMP= - trap - EXIT - fi - record_command "$lease" passed secure-console-submit - print "secure_console_submit\tpassed" - print "credential_transport\tparallels-key-events" -} - -reset_guest_password() { - local lease=$1 manifest resource parallels_cli secret_file key known_hosts known_hosts_option guest_ip - local fifo account_file guest_command reset_pid fifo_ready attempt stream_exit reset_exit enrollment_account - manifest=$(owned_manifest "$lease") - [[ "$(field "$manifest" provider)" == "parallels" ]] || die "guest password reset requires a Parallels lease" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "guest password reset requires a desktop-enabled lease" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$' ]] || die "invalid Parallels resource id" - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - known_hosts="$HOME/Library/Application Support/crabbox/testboxes/$lease/known_hosts" - [[ -f "$known_hosts" && ! -L "$known_hosts" && -O "$known_hosts" ]] || die "owned CrabBox known-hosts file not found for lease" - known_hosts_option=${known_hosts// /\\ } - guest_ip=$("$parallels_cli" list -i -f -j "$resource" | python3 -c 'import json,sys; rows=json.load(sys.stdin); addresses=rows[0].get("Network",{}).get("ipAddresses",[]) if rows else []; print(next((item.get("ip","") for item in addresses if item.get("type")=="ipv4"),""),end="")') - [[ "$guest_ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Parallels returned an invalid guest address" - - secret_file=$(mktemp "$STATE_ROOT/.guest-password-reset.XXXXXXXX") - chmod 600 "$secret_file" - typeset -g KEYPATH_LAB_SECURE_TEMP="$secret_file" - trap '[[ -z ${KEYPATH_LAB_SECURE_TEMP:-} ]] || rm -f "$KEYPATH_LAB_SECURE_TEMP"' EXIT - /opt/homebrew/bin/sops -d "$HOME/dotfiles/secrets.env" | awk -F= '$1 == "KEYPATH_LAB_GUEST_PASSWORD" {sub(/^[^=]*=/, ""); printf "%s", $0; found=1} END {if (!found) exit 1}' > "$secret_file" || die "KEYPATH_LAB_GUEST_PASSWORD is unavailable" - [[ -s "$secret_file" ]] || die "guest password reset secret is empty" - - fifo="/tmp/keypath-password-reset-$lease-$$.fifo" - account_file="/tmp/keypath-password-reset-$lease-$$.account" - guest_command="set -euo pipefail; fifo=$(printf %q "$fifo"); account_file=$(printf %q "$account_file"); rm -f \"\$fifo\" \"\$account_file\"; /usr/bin/mkfifo \"\$fifo\"; /usr/sbin/chown keypathqa:staff \"\$fifo\"; /bin/chmod 600 \"\$fifo\"; trap 'rm -f \"\$fifo\"' EXIT; password=; IFS= read -r password < \"\$fifo\"; if /usr/sbin/sysadminctl -resetPasswordFor keypathqa -newPassword \"\$password\" >/dev/null 2>&1 && /usr/bin/dscl . -authonly keypathqa \"\$password\"; then account=keypathqa; else /usr/sbin/sysadminctl -deleteUser keypathmdm >/dev/null 2>&1 || true; /usr/sbin/sysadminctl -addUser keypathmdm -fullName 'KeyPath MDM' -password \"\$password\" -admin >/dev/null; /usr/bin/dscl . -authonly keypathmdm \"\$password\"; /usr/sbin/dseditgroup -o checkmember -m keypathmdm admin | /usr/bin/grep -q 'yes'; account=keypathmdm; fi; printf '%s\n' \"\$account\" > \"\$account_file\"; /bin/chmod 600 \"\$account_file\"; unset password" - set +e - "$parallels_cli" exec "$resource" /bin/zsh -lc "$(printf %q "$guest_command")" >/dev/null 2>&1 & - reset_pid=$! - fifo_ready=0 - for attempt in {1..300}; do - if "$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" \ - "/bin/test -p $(printf %q "$fifo")" /dev/null 2>&1; then - fifo_ready=1 - break - fi - sleep 0.1 - done - if (( fifo_ready == 1 )); then - { /bin/cat "$secret_file"; printf '\n'; } | \ - "$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" \ - "/bin/zsh -c $(printf %q "/bin/cat > $(printf %q "$fifo")")" >/dev/null 2>&1 - stream_exit=$? - else - stream_exit=76 - kill "$reset_pid" 2>/dev/null || true - fi - wait "$reset_pid" - reset_exit=$? - set -e - "$parallels_cli" exec "$resource" /bin/rm -f "$fifo" >/dev/null 2>&1 || true - enrollment_account=$("$parallels_cli" exec "$resource" /bin/cat "$account_file" 2>/dev/null | tail -1 || true) - "$parallels_cli" exec "$resource" /bin/rm -f "$account_file" >/dev/null 2>&1 || true - rm -f "$secret_file" - KEYPATH_LAB_SECURE_TEMP= - trap - EXIT - (( stream_exit == 0 && reset_exit == 0 )) || die "failed to reset or provision and verify a disposable enrollment administrator" - [[ "$enrollment_account" == "keypathqa" || "$enrollment_account" == "keypathmdm" ]] || die "disposable enrollment administrator result was invalid" - - record_command "$lease" passed reset-guest-password - print "guest_password_reset\tpassed" - print "credential_verification\tpassed" - print "enrollment_account\t$enrollment_account" -} - -rfb_pointer_probe() { - local lease=$1 x=$2 y=$3 manifest macos resource parallels_cli key known_hosts known_hosts_option guest_ip cursor_command before after - manifest=$(owned_manifest "$lease") - macos=$(field "$manifest" macos) - [[ "$macos" == "26" || "$macos" == "27" ]] || die "RFB pointer probe currently supports only the macOS 26 and 27 Parallels lanes" - [[ "$(field "$manifest" provider)" == "parallels" ]] || die "RFB pointer probe requires a Parallels lease" - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "RFB pointer probe requires a desktop-enabled lease" - if [[ "$macos" == "27" ]]; then - [[ "$(field "$manifest" console_login_status)" == "passed" ]] || die "RFB pointer probe requires a verified console login on macOS 27" - fi - [[ "$x" == <-> && "$y" == <-> ]] || die "RFB pointer coordinates must be non-negative integers" - resource=$(field "$manifest" provider_resource) - [[ "$resource" =~ '^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$' ]] || die "invalid Parallels resource id" - - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - before=${KEYPATH_LAB_TEST_CURSOR_BEFORE:-"10 10"} - key=${KEYPATH_LAB_TEST_SSH_KEY:?test SSH key is required} - else - parallels_cli=${KEYPATH_LAB_PRLCTL:-"/Applications/Parallels Desktop.app/Contents/MacOS/prlctl"} - [[ -x "$parallels_cli" ]] || die "Parallels CLI is unavailable" - key="$HOME/Library/Application Support/crabbox/testboxes/$lease/id_ed25519" - [[ -f "$key" && ! -L "$key" && -O "$key" ]] || die "owned CrabBox SSH key not found for lease" - known_hosts="$HOME/Library/Application Support/crabbox/testboxes/$lease/known_hosts" - [[ -f "$known_hosts" && ! -L "$known_hosts" && -O "$known_hosts" ]] || die "owned CrabBox known-hosts file not found for lease" - known_hosts_option=${known_hosts// /\\ } - guest_ip=$("$parallels_cli" list -i -f -j "$resource" | python3 -c 'import json,sys; rows=json.load(sys.stdin); addresses=rows[0].get("Network",{}).get("ipAddresses",[]) if rows else []; print(next((item.get("ip","") for item in addresses if item.get("type")=="ipv4"),""),end="")') - [[ "$guest_ip" =~ '^[0-9A-Fa-f:.]+$' ]] || die "Parallels returned an invalid guest address" - cursor_command='/usr/bin/osascript -l JavaScript -e '\''ObjC.import("CoreGraphics"); p=$.CGEventGetLocation($.CGEventCreate(null)); p.x+" "+p.y'\''' - before=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" "/bin/zsh -lc $(printf %q "$cursor_command")") - fi - [[ "$before" =~ '^[0-9]+(\.[0-9]+)? [0-9]+(\.[0-9]+)?$' ]] || die "RFB pointer probe could not read the initial guest cursor location" - - if [[ "${KEYPATH_LAB_TESTING:-0}" != "1" ]]; then - export PATH="/Applications/Parallels Desktop.app/Contents/MacOS:$PATH" - fi - CRABBOX_PARALLELS_USER=keypathqa CRABBOX_SSH_USER=keypathqa CRABBOX_SSH_PORT=22 CRABBOX_SSH_KEY="$key" \ - "$CRABBOX" desktop click --provider parallels --target macos --id "$lease" --x "$x" --y "$y" >/dev/null - sleep "${KEYPATH_LAB_RFB_POINTER_SETTLE_SECONDS:-1}" - if [[ "${KEYPATH_LAB_TESTING:-0}" == "1" ]]; then - after=${KEYPATH_LAB_TEST_CURSOR_AFTER:-"$x $y"} - else - after=$("$GUEST_SSH" -o BatchMode=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$known_hosts_option" -i "$key" "keypathqa@$guest_ip" "/bin/zsh -lc $(printf %q "$cursor_command")") - fi - [[ "$after" =~ '^[0-9]+(\.[0-9]+)? [0-9]+(\.[0-9]+)?$' ]] || die "RFB pointer probe could not read the resulting guest cursor location" - if [[ "$after" == "$before" ]]; then - record_command "$lease" failed rfb-pointer-probe --x "$x" --y "$y" - die "CrabBox acknowledged the RFB click but the guest cursor did not move" - fi - record_command "$lease" passed rfb-pointer-probe --x "$x" --y "$y" - print "rfb_pointer_probe\tpassed" - print "cursor_before\t$before" - print "cursor_after\t$after" -} - -nameplate_control() { - local lease=$1 nameplate_action=$2 manifest macos lane provider repo script output version checksum state - manifest=$(owned_manifest "$lease") - [[ "$(field "$manifest" desktop_enabled)" == "true" ]] || die "Nameplate requires a desktop-enabled lease" - repo=$(field "$manifest" worktree) - prepare_worktree "$repo" - script="Scripts/lab/nameplate-instrumentation" - [[ -x "$repo/$script" ]] || die "Nameplate instrumentation is missing from the archived commit" - if [[ "$nameplate_action" != enable ]]; then - [[ "$(field "$manifest" nameplate_version)" == "$NAMEPLATE_VERSION" ]] || die "Nameplate is not enabled for lease: $lease" - fi - macos=$(field "$manifest" macos) - lane=$(field "$manifest" test_lane) - provider=$(field "$manifest" provider) - case "$nameplate_action" in - enable) output=$(run_command "$lease" /bin/zsh "$script" enable "$macos" "$lane" "$provider" "$lease") ;; - show|hide|status) output=$(run_command "$lease" /bin/zsh "$script" "$nameplate_action") ;; - *) die "invalid Nameplate action: $nameplate_action" ;; - esac - print -r -- "$output" - version=$(printf '%s\n' "$output" | awk -F '\t' '$1 == "nameplate_version" {print $2; exit}') - checksum=$(printf '%s\n' "$output" | awk -F '\t' '$1 == "nameplate_sha256" {print $2; exit}') - state=$(printf '%s\n' "$output" | awk -F '\t' '$1 == "nameplate_state" {print $2; exit}') - [[ "$version" == "$NAMEPLATE_VERSION" ]] || die "guest reported unexpected Nameplate version: ${version:-missing}" - [[ "$checksum" == "$NAMEPLATE_SHA256" ]] || die "guest reported unexpected Nameplate checksum" - [[ "$state" == visible || "$state" == hidden ]] || die "guest reported invalid Nameplate state: ${state:-missing}" - if [[ "$nameplate_action" != status ]]; then - set_field "$manifest" nameplate_version "$version" - set_field "$manifest" nameplate_sha256 "$checksum" - set_field "$manifest" nameplate_state "$state" - set_field "$manifest" nameplate_last_changed_at "$(utc_now)" - fi -} - -destroy_lease() { - local lease=$1 manifest macos launcher exit_code repo inventory inventory_exit provider_resource - manifest=$(owned_manifest "$lease") - [[ "$(field "$manifest" cleanup_status)" != "complete" ]] || { print "already_clean\t$lease"; return; } - macos=$(field "$manifest" macos) - launcher=$(launcher_for "$macos") - repo=$(field "$manifest" worktree) - prepare_worktree "$repo" - mkdir -p "$LOGS/$lease" - set +e - (cd "$repo" && "$launcher" stop "$lease") > "$LOGS/$lease/destroy.log" 2>&1 - exit_code=$? - set -e - set_field "$manifest" cleanup_attempted_at "$(utc_now)" - set_field "$manifest" cleanup_result "$exit_code" - inventory= - inventory_exit=1 - provider_resource=$(field "$manifest" provider_resource) - if (( exit_code != 0 )); then - set +e - inventory=$("$launcher" list 2>> "$LOGS/$lease/destroy.log") - inventory_exit=$? - set -e - print -r -- "$inventory" >> "$LOGS/$lease/destroy.log" - fi - if (( exit_code == 0 )) || { - (( inventory_exit == 0 )) && - ! print -r -- "$inventory" | grep -Eo 'cbx_[A-Za-z0-9]+' | grep -Fxq "$lease" && - { [[ "$provider_resource" == "unknown" ]] || ! print -r -- "$inventory" | grep -Fq "$provider_resource"; } - }; then - set_field "$manifest" cleanup_status complete - set_field "$manifest" status destroyed - exit_code=0 - else - set_field "$manifest" cleanup_status failed - set_field "$manifest" status cleanup-failed - fi - cat "$LOGS/$lease/destroy.log" - return "$exit_code" -} - -cleanup_expired() { - local dry_run=${1:-} current manifest lease expires cleanup - [[ -z "$dry_run" || "$dry_run" == "--dry-run" ]] || die "invalid cleanup option" - current=$(now_epoch) - for manifest in "$LEASES"/*/manifest.tsv(N); do - [[ "$(field "$manifest" owner)" == "$OWNER" ]] || continue - lease=$(field "$manifest" lease_id) - expires=$(field "$manifest" expires_epoch) - cleanup=$(field "$manifest" cleanup_status) - [[ "$expires" == <-> && "$expires" -le "$current" && "$cleanup" != "complete" ]] || continue - if [[ "$dry_run" == "--dry-run" ]]; then - print "would_destroy\t$lease" - else - destroy_lease "$lease" || true - fi - done -} - -action=${1:-} -shift || true -case "$action" in - preflight) [[ $# -eq 0 ]] || die "preflight takes no arguments"; preflight ;; - prepare-upload) [[ $# -eq 1 ]] || die "prepare-upload requires archive key"; prepare_upload "$1" ;; - install-archive) [[ $# -eq 5 ]] || die "install-archive requires ticket, key, commit, checksum, and name"; install_archive "$@" ;; - create) [[ $# -eq 8 || $# -eq 9 ]] || die "create requires macOS, test lane, archive, commit, checksum, name, ttl, desktop, and optional Tart USB passthrough"; create_lease "$@" ;; - install-app) [[ $# -eq 1 ]] || die "install-app requires lease"; install_app "$1" ;; - secure-dialog-input) [[ $# -eq 5 ]] || die "secure-dialog-input requires lease, app, field, optional submit value, and focus mode"; secure_dialog_input "$@" ;; - resume-managed-policy) [[ $# -eq 1 ]] || die "resume-managed-policy requires a lease"; resume_managed_policy "$1" ;; - protected-click) [[ $# -eq 7 || $# -eq 8 ]] || die "protected-click requires lease, app, before window, after window, coordinate space, x, y, and optional count"; protected_click "$@" ;; - desktop-type) [[ $# -eq 2 ]] || die "desktop-type requires lease and text"; desktop_type "$@" ;; - run) [[ $# -ge 2 ]] || die "run requires lease and command"; run_command "$@" ;; - status) [[ $# -eq 1 ]] || die "status requires lease"; print_status "$1" ;; - list) [[ $# -eq 0 ]] || die "list takes no arguments"; list_leases ;; - artifacts) [[ $# -eq 1 ]] || die "artifacts requires lease"; collect_artifacts "$1" ;; - scenario) [[ $# -eq 2 ]] || die "scenario requires lease and name"; scenario "$1" "$2" ;; - desktop-bootstrap) [[ $# -eq 2 ]] || die "desktop-bootstrap requires lease and install-tools flag"; desktop_bootstrap "$@" ;; - console-login) [[ $# -eq 1 ]] || die "console-login requires lease"; console_login "$1" ;; - reset-guest-password) [[ $# -eq 1 ]] || die "reset-guest-password requires lease"; reset_guest_password "$1" ;; - secure-console-submit) [[ $# -eq 1 ]] || die "secure-console-submit requires lease"; secure_console_submit "$1" ;; - rfb-pointer-probe) [[ $# -eq 3 ]] || die "rfb-pointer-probe requires lease, x, and y"; rfb_pointer_probe "$@" ;; - nameplate) [[ $# -eq 2 ]] || die "nameplate requires lease and action"; nameplate_control "$@" ;; - destroy) [[ $# -eq 1 ]] || die "destroy requires lease"; destroy_lease "$1" ;; - cleanup) cleanup_expired "${1:-}" ;; - *) die "unknown action: $action" ;; -esac diff --git a/Scripts/lab/scenarios/installer-scenario b/Scripts/lab/scenarios/installer-scenario index 467e21760..d9b0ef37c 100755 --- a/Scripts/lab/scenarios/installer-scenario +++ b/Scripts/lab/scenarios/installer-scenario @@ -3,8 +3,8 @@ set -euo pipefail name=${1:-} lane=${2:-} -installer_dir="$PWD/.keypath-lab/installer" -artifact_dir="$PWD/.keypath-lab/scenario-output/$name" +installer_dir="$PWD/.vm-lab/installer" +artifact_dir="$PWD/.vm-lab/scenario-output/$name" mkdir -p "$artifact_dir" result_tool="$PWD/Scripts/lab/scenario-result" @@ -90,7 +90,7 @@ case "$name" in mkdir -p "$artifact_dir/logs" cp -R "$HOME/Library/Logs/KeyPath/." "$artifact_dir/logs/" 2>/dev/null || true "$installed_cli" system inspect --json > "$artifact_dir/system-inspect.json" 2>/dev/null || true - print "Scenario artifacts written to $artifact_dir; follow with keypath-lab artifacts ." + print "Scenario artifacts written to $artifact_dir; follow with vm-lab keypath artifacts ." ;; macos-27-regression) require_installed_cli diff --git a/Scripts/lab/tests/keypath-lab-tests.sh b/Scripts/lab/tests/keypath-lab-tests.sh deleted file mode 100755 index 5a63501f7..000000000 --- a/Scripts/lab/tests/keypath-lab-tests.sh +++ /dev/null @@ -1,886 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR=$(cd "$(dirname "$0")" >/dev/null && pwd -P) -LAB_DIR=$(cd "$SCRIPT_DIR/.." >/dev/null && pwd -P) -REMOTE="$LAB_DIR/remote.sh" -TMP=$(mktemp -d "${TMPDIR:-/tmp}/keypath-lab-tests.XXXXXX") -trap 'rm -rf "$TMP"' EXIT - -ROOT="$TMP/CrabBox" -mkdir -p "$ROOT/bin" "$ROOT/KeyPathInstallerLab/archives" -CALLS="$TMP/calls.log" - -cat > "$ROOT/bin/launcher15" <> "$CALLS" -case "\$1" in - doctor) echo doctor-15 ;; - warmup) echo "\${KEYPATH_LAB_TEST_LEASE_15:-cbx_test15}" ;; - run) - if [[ "\$*" == *"nameplate-instrumentation"* ]]; then - [[ -f "$ROOT/fail-nameplate-hide" && " \$* " == *" hide "* ]] && exit 9 - [[ -f "$ROOT/fail-nameplate-show" && " \$* " == *" show "* ]] && exit 10 - echo $'nameplate_version\t0.2.5' - echo $'nameplate_sha256\t96d1b6c58167b4a8f3713a61a7e216f8a24c2adad36c9027db974f852d543a3d' - [[ " \$* " == *" hide "* ]] && echo $'nameplate_state\thidden' || echo $'nameplate_state\tvisible' - elif [[ "\$*" == *"IOPlatformUUID"* ]]; then - [[ "\$*" == *"cbx_test15b"* ]] && echo 15151515-1515-1515-1515-151515151517 || echo 15151515-1515-1515-1515-151515151516 - else - echo product=15.7.7; echo build=24G720 - fi ;; - stop) - echo "stop-15 \$2" >> "$CALLS" - [[ \${KEYPATH_LAB_TEST_TART_STOP_MISSING:-0} == 1 ]] && { echo "tart lease not found: \$2" >&2; exit 4; } - exit 0 - ;; - list) - [[ \${KEYPATH_LAB_TEST_TART_STOP_MISSING:-0} == 1 ]] || echo cbx_test15 - exit 0 - ;; -esac -EOF -cat > "$ROOT/bin/launcher26" <> "$CALLS" ;; - list) echo cbx_test26 ;; -esac -EOF -cat > "$ROOT/bin/launcher27" <> "$CALLS" ;; - list) echo cbx_test27 ;; -esac -EOF -cat > "$ROOT/bin/crabbox" <> "$CALLS" -if [[ \$1 == warmup ]]; then - if [[ " \$* " == *" --provider tart "* ]]; then - echo 'leased cbx_stale instance=stale-resource' - echo 'diagnostic previous=cbx_unrelated' - printf 'leased cbx_desktop15 instance=test-resource' - elif [[ " \$* " == *"keypath27-"* ]]; then - printf 'leased cbx_desktop27 vm=11111111-1111-1111-1111-111111111111' - else - printf 'leased cbx_desktop26 vm=00000000-0000-0000-0000-000000000000' - fi - [[ \${KEYPATH_LAB_TEST_WARMUP_FAIL:-0} == 1 ]] && exit 9 - exit 0 -fi -if [[ \$1 == screenshot ]]; then - while [[ \$# -gt 0 ]]; do - if [[ \$1 == --output ]]; then mkdir -p "\$(dirname "\$2")"; echo png > "\$2"; break; fi - shift - done - exit 0 -fi -while [[ \$# -gt 0 ]]; do - if [[ \$1 == --download ]]; then - target=\${2#*=} - mkdir -p "\$(dirname "\$target")" - fixture="\$(mktemp -d)" - mkdir -p "\$fixture/scenario-output/controller-capture" - echo test > "\$fixture/scenario-output/controller-capture/sw-vers.txt" - tar -czf "\$target" -C "\$fixture" scenario-output - rm -rf "\$fixture" - break - fi - shift -done -exit 0 -EOF -chmod +x "$ROOT/bin/launcher15" "$ROOT/bin/launcher26" "$ROOT/bin/launcher27" "$ROOT/bin/crabbox" - -echo 192.0.2.15 > "$TMP/tart-ip" -cat > "$ROOT/bin/tart" < "$ROOT/bin/guest-ssh" < "$TMP/guest-ssh-args" -if [[ " \$* " == *" /bin/test -p /tmp/keypath-console-login-"* ]]; then - cat >/dev/null -else - if [[ \${KEYPATH_LAB_TEST_STREAM_FAIL:-0} == 1 ]]; then - exit 17 - fi - if [[ \${KEYPATH_LAB_TEST_STREAM_HANG:-0} == 1 ]]; then - sleep 10 - exit 0 - fi - cat > "$TMP/guest-ssh-stdin" -fi -EOF -chmod +x "$ROOT/bin/tart" "$ROOT/bin/guest-ssh" -mkdir -p "$ROOT/CompatTools/KeyPathUSB/bin" "$ROOT/CompatTools/KeyPathUSB/tart-usb.app/Contents/MacOS" -cp "$ROOT/bin/crabbox" "$ROOT/CompatTools/KeyPathUSB/bin/crabbox-usb" -cp "$ROOT/bin/tart" "$ROOT/CompatTools/KeyPathUSB/tart-usb.app/Contents/MacOS/tart" -cat > "$ROOT/bin/prlctl" <> "$CALLS" -if [[ \$1 == exec && \$3 == /usr/bin/true && \${KEYPATH_LAB_TEST_GUEST_CONTROL_FAIL:-0} == 1 ]]; then - exit 1 -fi -if [[ \$1 == exec && " \$* " == *sysadminctl*autologin*set* ]]; then - touch "$TMP/console-fifo-ready" - if [[ \${KEYPATH_LAB_TEST_SECRET_LEAK:-0} == 1 ]]; then - echo fixture-password-that-must-not-leak - exit 0 - fi - for _ in {1..100}; do - if [[ -f "$TMP/guest-ssh-stdin" ]]; then - if [[ \${KEYPATH_LAB_TEST_CONSOLE_AUTH_FAIL:-0} == 1 ]]; then - echo started - exit 9 - fi - exit 0 - fi - sleep 0.01 - done - exit 9 -fi -if [[ \$1 == exec && " \$* " == *" /usr/bin/test -p /tmp/keypath-console-login-"* ]]; then - [[ -f "$TMP/console-fifo-ready" ]] -fi -if [[ \$1 == send-key-event && " \$* " == *" --json "* ]]; then - cat >> "$TMP/secure-console-key-events.jsonl" -fi -if [[ \$1 == exec && " \$* " == *" /usr/sbin/sysadminctl -autologin status "* ]]; then - echo 'Automatic login is ON.' -fi -if [[ \$1 == exec && " \$* " == *" /usr/bin/stat -f %Su /dev/console "* ]]; then - if [[ \${KEYPATH_LAB_TEST_CONSOLE_USER_FAIL:-0} == 1 ]]; then - echo loginwindow - else - echo keypathqa - fi -fi -if [[ \$1 == status ]]; then - echo running -fi -if [[ \$1 == capture && \$3 == --file ]]; then - mkdir -p "\$(dirname "\$4")" - echo png > "\$4" -fi -EOF -chmod +x "$ROOT/bin/prlctl" -echo test-private-key > "$TMP/id_ed25519" -printf 'fixture-password-that-must-not-leak' > "$TMP/secure-input" -grep -Fq 'exec 3<> \"\$fifo\"; KEYPATH_GUEST_PASSWORD=; IFS= read -r -t $credential_timeout -u 3 KEYPATH_GUEST_PASSWORD || [[ -n \"\$KEYPATH_GUEST_PASSWORD\" ]]' "$REMOTE" -grep -Fq 'IFS= read -r secret_value || [[ -n "$secret_value" ]]' "$REMOTE" -grep -Fq 'managed_clone_enrollment\talready-enrolled' "$REMOTE" -grep -Fq 'window.subrole() === "AXSystemDialog"' "$REMOTE" -grep -Fq 'usb_prefix="$TART_USB_TOOL_ROOT/bin:"' "$REMOTE" -if grep -Fq 'peekaboo see --app "System Settings"' "$REMOTE"; then - echo "capture prompt guard must not create a new capture request" >&2 - exit 1 -fi -grep -q 'resume-managed-policy)' "$LAB_DIR/keypath-lab" -grep -Fq '/usr/bin/mktemp /etc/kcpassword.XXXXXXXX' "$REMOTE" -grep -Fq "Automatic login user: keypathqa" "$REMOTE" - -/bin/bash -n "$LAB_DIR/../qa-macos-27-regression.sh" -/bin/zsh -n "$LAB_DIR/desktop-bootstrap" -/bin/zsh -n "$LAB_DIR/mdm/enroll-clone-ui" -/bin/zsh -n "$LAB_DIR/nameplate-instrumentation" -/bin/zsh -n "$LAB_DIR/scenarios/kanata-vhid-two-clients" -grep -q 'macos-27-regression)' "$LAB_DIR/scenarios/installer-scenario" - -run_remote() { - KEYPATH_LAB_TESTING=1 \ - KEYPATH_LAB_TEST_ROOT="$ROOT" \ - KEYPATH_LAB_LAUNCHER_15="$ROOT/bin/launcher15" \ - KEYPATH_LAB_LAUNCHER_26="$ROOT/bin/launcher26" \ - KEYPATH_LAB_LAUNCHER_27="$ROOT/bin/launcher27" \ - KEYPATH_LAB_CRABBOX="$ROOT/bin/crabbox" \ - KEYPATH_LAB_TART="$ROOT/bin/tart" \ - KEYPATH_LAB_GUEST_SSH="$ROOT/bin/guest-ssh" \ - KEYPATH_LAB_PRLCTL="$ROOT/bin/prlctl" \ - KEYPATH_LAB_TEST_SSH_KEY="$TMP/id_ed25519" \ - KEYPATH_LAB_TEST_SECRET_FILE="$TMP/secure-input" \ - KEYPATH_LAB_TEST_CONSOLE_AUTH_FAIL="${KEYPATH_LAB_TEST_CONSOLE_AUTH_FAIL:-0}" \ - KEYPATH_LAB_TEST_STREAM_FAIL="${KEYPATH_LAB_TEST_STREAM_FAIL:-0}" \ - KEYPATH_LAB_TEST_STREAM_HANG="${KEYPATH_LAB_TEST_STREAM_HANG:-0}" \ - KEYPATH_LAB_TEST_GUEST_CONTROL_FAIL="${KEYPATH_LAB_TEST_GUEST_CONTROL_FAIL:-0}" \ - KEYPATH_LAB_TEST_CONSOLE_USER_FAIL="${KEYPATH_LAB_TEST_CONSOLE_USER_FAIL:-0}" \ - KEYPATH_LAB_TEST_SECRET_LEAK="${KEYPATH_LAB_TEST_SECRET_LEAK:-0}" \ - KEYPATH_LAB_TEST_TART_STOP_MISSING="${KEYPATH_LAB_TEST_TART_STOP_MISSING:-0}" \ - KEYPATH_LAB_TEST_CURSOR_BEFORE="${KEYPATH_LAB_TEST_CURSOR_BEFORE:-10 10}" \ - KEYPATH_LAB_TEST_CURSOR_AFTER="${KEYPATH_LAB_TEST_CURSOR_AFTER:-160 120}" \ - KEYPATH_LAB_TEST_LEASE_15="${KEYPATH_LAB_TEST_LEASE_15:-cbx_test15}" \ - KEYPATH_LAB_CAPACITY_TART="${KEYPATH_LAB_CAPACITY_TART:-1}" \ - KEYPATH_LAB_CAPACITY_PARALLELS="${KEYPATH_LAB_CAPACITY_PARALLELS:-2}" \ - /bin/zsh "$REMOTE" "$@" -} - -assert_contains() { - [[ $1 == *"$2"* ]] || { echo "expected '$2' in: $1" >&2; exit 1; } -} - -nameplate_metadata=$(/bin/zsh "$LAB_DIR/nameplate-instrumentation" metadata) -assert_contains "$nameplate_metadata" $'nameplate_version\t0.2.5' -assert_contains "$nameplate_metadata" $'nameplate_sha256\t96d1b6c58167b4a8f3713a61a7e216f8a24c2adad36c9027db974f852d543a3d' -grep -q 'NAMEPLATE_VERSION="0.2.5"' "$LAB_DIR/remote.sh" -grep -q 'NAMEPLATE_SHA256="96d1b6c58167b4a8f3713a61a7e216f8a24c2adad36c9027db974f852d543a3d"' "$LAB_DIR/remote.sh" -if grep -q 'launchAtLogin -bool true' "$LAB_DIR/nameplate-instrumentation"; then - echo "Nameplate instrumentation enabled launch at login" >&2 - exit 1 -fi -grep -q 'useFleetFile -bool false' "$LAB_DIR/nameplate-instrumentation" -grep -q 'hasCompletedFirstRun -bool true' "$LAB_DIR/nameplate-instrumentation" -[[ $(grep -c '/usr/bin/pkill -x Nameplate || true' "$LAB_DIR/nameplate-instrumentation") -eq 2 ]] - -preflight=$(run_remote preflight) -assert_contains "$preflight" doctor-15 -assert_contains "$preflight" doctor-26 -assert_contains "$preflight" doctor-27 -assert_contains "$preflight" $'disk_reserve_minimum_gib\t100' - -ticket_one=$(run_remote prepare-upload "$(printf 'a%.0s' {1..40})-$(printf 'b%.0s' {1..64})") -ticket_two=$(run_remote prepare-upload "$(printf 'a%.0s' {1..40})-$(printf 'b%.0s' {1..64})") -[[ "$ticket_one" == /tmp/keypath-lab.* && "$ticket_two" == /tmp/keypath-lab.* ]] -[[ "$ticket_one" != "$ticket_two" && -f "$ticket_one" && -f "$ticket_two" ]] -rm -f "$ticket_one" "$ticket_two" - -publish_commit=$(printf 'c%.0s' {1..40}) -publish_checksum=$(shasum -a 256 "$LAB_DIR/scenarios/installer-scenario" | awk '{print $1}') -publish_key="$publish_commit-$publish_checksum" -mkdir -p "$TMP/upload/repo/.keypath-lab/installer" -cp "$LAB_DIR/scenarios/installer-scenario" "$TMP/upload/repo/.keypath-lab/installer/installer.zip" -for pass in 1 2; do - ticket=$(run_remote prepare-upload "$publish_key") - tar -czf "$ticket" -C "$TMP/upload" repo - published=$(run_remote install-archive "$ticket" "$publish_key" "$publish_commit" "$publish_checksum" installer.zip) - if [[ $pass == 1 ]]; then assert_contains "$published" $'archive\tcreated'; else assert_contains "$published" $'archive\treused'; fi -done -if find "$ROOT/KeyPathInstallerLab/archives" -maxdepth 1 -name ".staging-$publish_key-*" | grep -q .; then - echo "archive publish left a staging directory" >&2 - exit 1 -fi - -archive_key="$(printf 'a%.0s' {1..40})-$(printf 'b%.0s' {1..64})" -repo="$ROOT/KeyPathInstallerLab/archives/$archive_key/repo" -mkdir -p "$repo/.keypath-lab/installer" "$repo/Scripts/lab/scenarios" "$repo/Scripts/lab/mdm" -cp "$LAB_DIR/scenarios/installer-scenario" "$repo/Scripts/lab/scenarios/installer-scenario" -cp "$LAB_DIR/nameplate-instrumentation" "$repo/Scripts/lab/nameplate-instrumentation" -cat > "$repo/Scripts/lab/mdm/publish-managed-profiles" <<'EOF' -#!/bin/bash -if [[ ${KEYPATH_LAB_TEST_PUBLISH_FAIL:-0} == 1 ]]; then - echo 'fixture managed profile publication failed' >&2 - exit 42 -fi -printf 'enrollment_id\t%s\n' "${@: -1}" -EOF -mkdir -p "$repo/.keypath-lab/managed-policy" -for profile in keypath-pppc.mobileconfig keypath-system-extension.mobileconfig keypath-service-management.mobileconfig; do - printf '\n' > "$repo/.keypath-lab/managed-policy/$profile" -done -printf '{"lane":"managed-functional"}\n' > "$repo/.keypath-lab/managed-policy/manifest.json" -chmod +x "$repo/Scripts/lab/scenarios/installer-scenario" -chmod +x "$repo/Scripts/lab/nameplate-instrumentation" -chmod +x "$repo/Scripts/lab/mdm/publish-managed-profiles" -echo installer > "$repo/.keypath-lab/installer/KeyPath.zip" -mkdir -p "$ROOT/KeyPathInstallerLab/managed-identities" -printf '%s\n' '15151515-1515-1515-1515-151515151515' \ - > "$ROOT/KeyPathInstallerLab/managed-identities/keypath-macos-15-managed.enrollment-id" -printf '%s\n' '26262626-2626-2626-2626-262626262626' \ - > "$ROOT/KeyPathInstallerLab/managed-identities/keypath-macos-26-managed.enrollment-id" -git -C "$repo" init -q -git -C "$repo" config user.name test -git -C "$repo" config user.email test@example.com -git -C "$repo" add -A -git -C "$repo" commit -qm fixture -cat > "$ROOT/KeyPathInstallerLab/archives/$archive_key/ready.tsv" <&1) -disk_exit=$? -set -e -[[ $disk_exit -eq 75 ]] || { echo "expected disk reserve admission to exit 75, got $disk_exit" >&2; exit 1; } -assert_contains "$disk_output" $'disk_reserve_busy\tfree_gib=99\tminimum_gib=100' - -create=$(run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$create" $'lease_id\tcbx_test15' -manifest="$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -operation_repo="$ROOT/KeyPathInstallerLab/operations/$(grep $'^slug\t' "$manifest" | cut -f2)/repo" -grep -q $'owner\tkeypath-installer-lab-v1' "$manifest" -grep -q $'macos_build\t24G720' "$manifest" -grep -q $'test_lane\tunmanaged-ui' "$manifest" -grep -q $'base_name\tghcr.io/cirruslabs/macos-sequoia-base:latest' "$manifest" -archive_object=$(find "$repo/.git/objects" -type f -print -quit) -object_relative_path=${archive_object#"$repo/.git/objects/"} -operation_object="$operation_repo/.git/objects/$object_relative_path" -[[ -f "$operation_object" ]] -if [[ $(stat -f %i "$archive_object") == "$(stat -f %i "$operation_object")" ]]; then - echo "operation repository must not hard-link archive Git objects" >&2 - exit 1 -fi - -set +e -capacity_output=$(run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 2>&1) -capacity_exit=$? -set -e -[[ $capacity_exit -eq 75 ]] || { echo "expected Tart capacity admission to exit 75, got $capacity_exit" >&2; exit 1; } -assert_contains "$capacity_output" $'capacity_busy\tprovider=tart\tactive=1\tlimit=1' -assert_contains "$capacity_output" $'active_lease\tcbx_test15' - -set +e -capacity_output=$(run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 2>&1) -capacity_exit=$? -set -e -[[ $capacity_exit -eq 75 ]] || { echo "expected Tart capacity admission to exit 75, got $capacity_exit" >&2; exit 1; } -assert_contains "$capacity_output" $'capacity_busy\tprovider=tart\tactive=1\tlimit=1' -assert_contains "$capacity_output" $'active_lease\tcbx_test15' - -run_remote run cbx_test15 echo hello >/dev/null -grep -q 'echo hello' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/commands.tsv" -run_remote scenario cbx_test15 clean-install >/dev/null -grep -q 'installer-scenario clean-install' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/commands.tsv" -run_remote install-app cbx_test15 >/dev/null -grep -q 'install-app 15 cbx_test15' "$ROOT/KeyPathInstallerLab/logs/cbx_test15/install-app.log" - -artifacts=$(run_remote artifacts cbx_test15) -assert_contains "$artifacts" $'download_status\t0' -assert_contains "$artifacts" $'screenshot_status\tunavailable:lease-not-created-with-desktop' -grep -q -- '--download' "$CALLS" -grep -q 'crabbox run --provider tart --target macos --id cbx_test15 --stop-after never --download' "$CALLS" -if grep -q 'crabbox cp' "$CALLS"; then - echo "artifact collection used unsupported crabbox cp" >&2 - exit 1 -fi -[[ -f "$(find "$ROOT/KeyPathInstallerLab/artifacts/cbx_test15" -path '*/scenario-output/controller-capture/sw-vers.txt' -print -quit)" ]] -if run_remote nameplate cbx_test15 enable >/dev/null 2>&1; then - echo "Nameplate accepted a non-desktop lease" >&2 - exit 1 -fi - -mkdir -p "$operation_repo/.crabbox/captures" -echo failure-evidence > "$operation_repo/.crabbox/captures/failure.tar.gz" -run_remote run cbx_test15 echo generated-output-is-safe >/dev/null -artifacts_with_capture=$(run_remote artifacts cbx_test15) -capture_dir=$(printf '%s\n' "$artifacts_with_capture" | awk -F '\t' '$1 == "artifact_dir" {print $2}') -[[ -f "$capture_dir/controller-crabbox-captures/failure.tar.gz" ]] - -touch "$operation_repo/changing-file" -if run_remote run cbx_test15 echo unsafe >/dev/null 2>&1; then - echo "run accepted a changing checkout" >&2 - exit 1 -fi -rm "$operation_repo/changing-file" - -mkdir -p "$ROOT/KeyPathInstallerLab/leases/not-owned" -printf 'owner\tother\nlease_id\tnot-owned\n' > "$ROOT/KeyPathInstallerLab/leases/not-owned/manifest.tsv" -if run_remote destroy not-owned >/dev/null 2>&1; then - echo "destroy accepted an unowned lease" >&2 - exit 1 -fi - -run_remote destroy cbx_test15 >/dev/null -grep -q 'stop-15 cbx_test15' "$CALLS" -grep -q $'cleanup_status\tcomplete' "$manifest" - -usb_create=$(run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 1) -assert_contains "$usb_create" $'lease_id\tcbx_test15' -usb_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -grep -q $'tart_usb_passthrough\ttrue' "$usb_manifest" -run_remote destroy cbx_test15 >/dev/null - -printf 'pid\t%s\nprovider\ttart\n' "$$" > "$ROOT/KeyPathInstallerLab/provider-admission-tart.lock" -parallel_provider_create=$(run_remote create 26 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$parallel_provider_create" $'lease_id\tcbx_test26' -run_remote destroy cbx_test26 >/dev/null - -managed_create=$(run_remote create 26 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$managed_create" $'managed_policy_rehydration\tpassed' -assert_contains "$managed_create" $'enrollment_id\t26262626-2626-2626-2626-262626262626' -assert_contains "$managed_create" $'lease_id\tcbx_test26' -managed_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_test26/manifest.tsv" -grep -q $'managed_policy_result\t0' "$managed_manifest" -grep -q $'managed_identity_scope\tshared:26262626-2626-2626-2626-262626262626' "$managed_manifest" -grep -q $'managed-policy-rehydration' "$ROOT/KeyPathInstallerLab/leases/cbx_test26/commands.tsv" -grep -q $'base_name\tkeypath-macos-26-managed' "$managed_manifest" -set +e -managed_busy_output=$(run_remote create 26 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 2>&1) -managed_busy_exit=$? -set -e -[[ $managed_busy_exit -eq 75 ]] || { echo "expected managed identity contention to exit 75, got $managed_busy_exit" >&2; exit 1; } -assert_contains "$managed_busy_output" $'managed_identity_busy\tactive_lease=cbx_test26' -assert_contains "$managed_busy_output" $'scope=shared:26262626-2626-2626-2626-262626262626' -run_remote destroy cbx_test26 >/dev/null - -set +e -lock_output=$(KEYPATH_LAB_ADMISSION_WAIT_ATTEMPTS=1 run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 2>&1) -lock_exit=$? -set -e -[[ $lock_exit -eq 75 ]] || { echo "expected admission-lock contention to exit 75, got $lock_exit" >&2; exit 1; } -assert_contains "$lock_output" admission_lock_busy - -env \ - KEYPATH_LAB_TESTING=1 \ - KEYPATH_LAB_TEST_ROOT="$ROOT" \ - KEYPATH_LAB_LAUNCHER_15="$ROOT/bin/launcher15" \ - KEYPATH_LAB_LAUNCHER_26="$ROOT/bin/launcher26" \ - KEYPATH_LAB_LAUNCHER_27="$ROOT/bin/launcher27" \ - KEYPATH_LAB_CRABBOX="$ROOT/bin/crabbox" \ - KEYPATH_LAB_TART="$ROOT/bin/tart" \ - KEYPATH_LAB_GUEST_SSH="$ROOT/bin/guest-ssh" \ - KEYPATH_LAB_TEST_SSH_KEY="$TMP/id_ed25519" \ - KEYPATH_LAB_TEST_SECRET_FILE="$TMP/secure-input" \ - KEYPATH_LAB_ADMISSION_WAIT_ATTEMPTS=3000 \ - /bin/zsh "$REMOTE" create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 >"$TMP/interrupted-wait.log" 2>&1 & -interrupted_wait_pid=$! -for _ in {1..100}; do - compgen -G "$ROOT/KeyPathInstallerLab/.provider-admission-tart.owner.*" >/dev/null && break - sleep 0.05 -done -compgen -G "$ROOT/KeyPathInstallerLab/.provider-admission-tart.owner.*" >/dev/null || { echo "contending create did not write pending owner record" >&2; exit 1; } -kill -TERM "$interrupted_wait_pid" -set +e -wait "$interrupted_wait_pid" -interrupted_wait_exit=$? -set -e -[[ $interrupted_wait_exit -eq 143 ]] || { cat "$TMP/interrupted-wait.log" >&2; echo "expected interrupted lock wait to exit 143, got $interrupted_wait_exit" >&2; exit 1; } -if compgen -G "$ROOT/KeyPathInstallerLab/.provider-admission-tart.owner.*" >/dev/null; then - echo "interrupted lock wait left a pending owner record" >&2 - exit 1 -fi -rm -rf "$ROOT/KeyPathInstallerLab/provider-admission-tart.lock" - -managed15_create=$(run_remote create 15 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$managed15_create" $'managed_policy_rehydration\tpassed' -assert_contains "$managed15_create" $'enrollment_id\t15151515-1515-1515-1515-151515151516' -assert_contains "$managed15_create" $'lease_id\tcbx_test15' -grep -q $'managed_identity_scope\tunique-clone' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -grep -F 'launcher15 run cbx_test15' "$CALLS" | grep -Fq '/usr/bin/install' -grep -F 'launcher15 run cbx_test15' "$CALLS" | grep -Fq '/Users/admin/crabbox/cbx_test15/repo/.keypath-lab/managed-policy/keypath-pppc.mobileconfig' -grep -F 'launcher15 run cbx_test15' "$CALLS" | grep -Fq '/Users/admin/crabbox/cbx_test15/repo/Scripts/lab/mdm/verify-lane' -grep -F 'launcher15 run cbx_test15' "$CALLS" | grep -Fq '/Library/KeyPathLab/managed-policy/manifest.json' -managed15_second=$(KEYPATH_LAB_CAPACITY_TART=2 KEYPATH_LAB_TEST_LEASE_15=cbx_test15b \ - run_remote create 15 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$managed15_second" $'managed_policy_rehydration\tpassed' -assert_contains "$managed15_second" $'enrollment_id\t15151515-1515-1515-1515-151515151517' -assert_contains "$managed15_second" $'lease_id\tcbx_test15b' -grep -q $'managed_identity_scope\tunique-clone' "$ROOT/KeyPathInstallerLab/leases/cbx_test15b/manifest.tsv" -run_remote destroy cbx_test15b >/dev/null -run_remote destroy cbx_test15 >/dev/null -sed -i '' $'s/^status\t.*/status\tready/; s/^cleanup_status\t.*/cleanup_status\tpending/' \ - "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -KEYPATH_LAB_TEST_TART_STOP_MISSING=1 run_remote destroy cbx_test15 >/dev/null -grep -q $'status\tdestroyed' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -grep -q $'cleanup_status\tcomplete' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" - -set +e -managed_publish_failure=$(KEYPATH_LAB_TEST_PUBLISH_MANAGED=1 KEYPATH_LAB_TEST_PUBLISH_FAIL=1 \ - run_remote create 15 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 2>&1) -managed_publish_exit=$? -set -e -[[ $managed_publish_exit -eq 42 ]] || { - echo "expected managed publication failure to exit 42, got $managed_publish_exit" >&2 - exit 1 -} -assert_contains "$managed_publish_failure" 'fixture managed profile publication failed' -if [[ "$managed_publish_failure" == *$'managed_policy_rehydration\tpassed'* ]]; then - echo "managed publication failure continued into a false-positive verification" >&2 - exit 1 -fi -grep -q $'managed_policy_result\t42' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -grep -q $'status\tmanaged-policy-failed' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -managed_resume=$(KEYPATH_LAB_TEST_PUBLISH_MANAGED=1 run_remote resume-managed-policy cbx_test15) -assert_contains "$managed_resume" $'managed_policy_rehydration\tpassed' -assert_contains "$managed_resume" $'managed_policy_resume\tpassed' -grep -q $'managed_policy_result\t0' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -grep -q $'status\tready' "$ROOT/KeyPathInstallerLab/leases/cbx_test15/manifest.tsv" -run_remote destroy cbx_test15 >/dev/null - -env \ - KEYPATH_LAB_TESTING=1 \ - KEYPATH_LAB_TEST_ROOT="$ROOT" \ - KEYPATH_LAB_LAUNCHER_15="$ROOT/bin/launcher15" \ - KEYPATH_LAB_LAUNCHER_26="$ROOT/bin/launcher26" \ - KEYPATH_LAB_LAUNCHER_27="$ROOT/bin/launcher27" \ - KEYPATH_LAB_CRABBOX="$ROOT/bin/crabbox" \ - KEYPATH_LAB_TART="$ROOT/bin/tart" \ - KEYPATH_LAB_GUEST_SSH="$ROOT/bin/guest-ssh" \ - KEYPATH_LAB_TEST_SSH_KEY="$TMP/id_ed25519" \ - KEYPATH_LAB_TEST_SECRET_FILE="$TMP/secure-input" \ - KEYPATH_LAB_TEST_PAUSE_AFTER_ADMISSION_LOCK=1 \ - /bin/zsh "$REMOTE" create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0 >"$TMP/interrupted-create.log" 2>&1 & -interrupted_pid=$! -for _ in {1..100}; do - grep -q $'^pid\t' "$ROOT/KeyPathInstallerLab/provider-admission-tart.lock" 2>/dev/null && break - sleep 0.05 -done -[[ -f "$ROOT/KeyPathInstallerLab/provider-admission-tart.lock" ]] || { echo "interrupted create never acquired admission lock" >&2; exit 1; } -kill -TERM "$interrupted_pid" -set +e -wait "$interrupted_pid" -interrupted_exit=$? -set -e -[[ $interrupted_exit -eq 143 ]] || { cat "$TMP/interrupted-create.log" >&2; echo "expected interrupted create to exit 143, got $interrupted_exit" >&2; exit 1; } -[[ ! -e "$ROOT/KeyPathInstallerLab/provider-admission-tart.lock" ]] || { echo "interrupted create left admission lock" >&2; exit 1; } - -printf 'pid\t99999999\nprovider\tparallels\n' > "$ROOT/KeyPathInstallerLab/provider-admission-parallels.lock" -create26=$(run_remote create 26 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$create26" $'lease_id\tcbx_test26' -[[ ! -e "$ROOT/KeyPathInstallerLab/provider-admission-parallels.lock" ]] || { echo "stale Parallels admission lock was not reclaimed" >&2; exit 1; } -artifacts26=$(run_remote artifacts cbx_test26) -assert_contains "$artifacts26" $'download_status\t0' -grep -q 'crabbox run --provider parallels --target macos --id cbx_test26 --stop-after never --download' "$CALLS" -run_remote destroy cbx_test26 >/dev/null - -create27=$(run_remote create 27 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 0) -assert_contains "$create27" $'lease_id\tcbx_test27' -artifacts27=$(run_remote artifacts cbx_test27) -assert_contains "$artifacts27" $'download_status\t0' -grep -q 'crabbox run --provider parallels --target macos --id cbx_test27 --stop-after never --download' "$CALLS" -run_remote destroy cbx_test27 >/dev/null -grep -q 'stop-27 cbx_test27' "$CALLS" - -desktop27_create=$(run_remote create 27 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 1) -assert_contains "$desktop27_create" $'lease_id\tcbx_desktop27' -# A desktop lease must land on the provisioned desktop base. Asserting only the -# lease id let the base silently regress to the plain template, which has no -# Homebrew, no console session, and no approved Peekaboo Lab Host. The recorded -# base_name is the assertable signal here: in test mode warmup_desktop calls the -# stub launcher without --parallels-template, so the calls log cannot show it. -grep -q $'base_name\tkeypath-macos-27-desktop' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -test_known_hosts="$TMP/known hosts/known_hosts" -mkdir -p "$(dirname "$test_known_hosts")" -touch "$test_known_hosts" -console_login=$(KEYPATH_LAB_TEST_KNOWN_HOSTS="$test_known_hosts" KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27) -assert_contains "$console_login" $'console_login\tpassed' -assert_contains "$console_login" $'console_user\tkeypathqa' -[[ "$(cat "$TMP/guest-ssh-stdin")" == "$(cat "$TMP/secure-input")" ]] || { echo "console login streamed the wrong credential" >&2; exit 1; } -{ cat "$TMP/secure-input"; printf '\n'; } | cmp -s - "$TMP/guest-ssh-stdin" || { echo "console login did not frame the credential as one FIFO record" >&2; exit 1; } -escaped_test_known_hosts=${test_known_hosts// /\\ } -grep -Fq "UserKnownHostsFile=$escaped_test_known_hosts" "$TMP/guest-ssh-args" -secure_console_submit=$(KEYPATH_LAB_SECURE_CONSOLE_KEY_DELAY_SECONDS=0 KEYPATH_LAB_SECURE_CONSOLE_SETTLE_SECONDS=0 run_remote secure-console-submit cbx_desktop27) -assert_contains "$secure_console_submit" $'secure_console_submit\tpassed' -assert_contains "$secure_console_submit" $'credential_transport\tparallels-key-events' -python3 -c 'import json,sys -codes={"a":38,"b":56,"c":54,"d":40,"e":26,"f":41,"g":42,"h":43,"i":31,"j":44,"k":45,"l":46,"m":58,"n":57,"o":32,"p":33,"q":24,"r":27,"s":39,"t":28,"u":30,"v":55,"w":25,"x":53,"y":29,"z":52,"1":10,"2":11,"3":12,"4":13,"5":14,"6":15,"7":16,"8":17,"9":18,"0":19,"-":20} -events=[json.loads(line) for line in open(sys.argv[1]) if line.strip()] -expected=[[{"key":codes[ch]}] for ch in open(sys.argv[2]).read()] -assert events == expected' "$TMP/secure-console-key-events.jsonl" "$TMP/secure-input" -! grep -Fq 'fixture-password-that-must-not-leak' "$TMP/secure-console-key-events.jsonl" -grep -q 'prlctl send-key-event 11111111-1111-1111-1111-111111111111 --key 36' "$CALLS" -if grep -R -F 'fixture-password-that-must-not-leak' "$ROOT/KeyPathInstallerLab" "$CALLS" "$TMP/guest-ssh-args"; then - echo "secure console submit leaked its secret into controller logs or arguments" >&2 - exit 1 -fi -cp "$TMP/secure-input" "$TMP/secure-input.valid" -printf 'Unsupported-credential' > "$TMP/secure-input" -secure_console_calls_before=$(wc -l < "$CALLS") -set +e -secure_console_rejected=$(KEYPATH_LAB_SECURE_CONSOLE_KEY_DELAY_SECONDS=0 KEYPATH_LAB_SECURE_CONSOLE_SETTLE_SECONDS=0 run_remote secure-console-submit cbx_desktop27 2>&1) -secure_console_rejected_status=$? -set -e -mv "$TMP/secure-input.valid" "$TMP/secure-input" -[[ $secure_console_rejected_status -ne 0 ]] || { echo "unsupported secure console credential unexpectedly passed" >&2; exit 1; } -assert_contains "$secure_console_rejected" 'failed to deliver the guest credential through Parallels key events' -if tail -n "+$((secure_console_calls_before + 1))" "$CALLS" | grep -q 'send-key-event'; then - echo "unsupported secure console credential sent a key event" >&2 - exit 1 -fi -grep -q 'prlctl exec 11111111-1111-1111-1111-111111111111 /bin/zsh -lc' "$CALLS" -grep -q 'sysadminctl.*autologin.*set.*userName.*keypathqa.*password' "$CALLS" -grep -q 'prlctl restart 11111111-1111-1111-1111-111111111111' "$CALLS" -grep -q $'console_login_status\tpassed' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -rfb_probe=$(KEYPATH_LAB_RFB_POINTER_SETTLE_SECONDS=0 run_remote rfb-pointer-probe cbx_desktop27 160 120) -assert_contains "$rfb_probe" $'rfb_pointer_probe\tpassed' -assert_contains "$rfb_probe" $'cursor_before\t10 10' -assert_contains "$rfb_probe" $'cursor_after\t160 120' -grep -q 'crabbox desktop click --provider parallels --target macos --id cbx_desktop27 --x 160 --y 120' "$CALLS" -set +e -rfb_probe_undelivered=$(KEYPATH_LAB_TEST_CURSOR_AFTER='10 10' KEYPATH_LAB_RFB_POINTER_SETTLE_SECONDS=0 run_remote rfb-pointer-probe cbx_desktop27 160 120 2>&1) -rfb_probe_undelivered_status=$? -set -e -[[ $rfb_probe_undelivered_status -ne 0 ]] -assert_contains "$rfb_probe_undelivered" 'CrabBox acknowledged the RFB click but the guest cursor did not move' -set +e -console_login_stream_failure=$(KEYPATH_LAB_TEST_STREAM_FAIL=1 KEYPATH_LAB_CONSOLE_CREDENTIAL_TIMEOUT_SECONDS=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_stream_failure_status=$? -set -e -[[ $console_login_stream_failure_status -ne 0 ]] -assert_contains "$console_login_stream_failure" 'failed to stream the guest credential into the disposable guest' -grep -q $'console_login_status\tcredential-stream-failed:17' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -SECONDS=0 -set +e -console_login_stream_hang=$(KEYPATH_LAB_TEST_STREAM_HANG=1 KEYPATH_LAB_CONSOLE_CREDENTIAL_TIMEOUT_SECONDS=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_stream_hang_status=$? -set -e -[[ $console_login_stream_hang_status -ne 0 ]] -[[ $SECONDS -le 4 ]] -assert_contains "$console_login_stream_hang" 'failed to stream the guest credential into the disposable guest' -set +e -console_login_guest_control_failure=$(KEYPATH_LAB_TEST_GUEST_CONTROL_FAIL=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_guest_control_failure_status=$? -set -e -[[ $console_login_guest_control_failure_status -ne 0 ]] -assert_contains "$console_login_guest_control_failure" 'Parallels guest control did not become ready' -set +e -console_login_console_user_failure=$(KEYPATH_LAB_TEST_CONSOLE_USER_FAIL=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_console_user_failure_status=$? -set -e -[[ $console_login_console_user_failure_status -ne 0 ]] -assert_contains "$console_login_console_user_failure" 'automatic login did not establish the keypathqa console session' -grep -q $'console_login_status\tpostcondition-failed' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -set +e -console_login_leak=$(KEYPATH_LAB_TEST_SECRET_LEAK=1 KEYPATH_LAB_CONSOLE_CREDENTIAL_TIMEOUT_SECONDS=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_leak_status=$? -set -e -[[ $console_login_leak_status -ne 0 ]] -assert_contains "$console_login_leak" 'guest credential disclosure was detected and redacted from the controller log' -grep -q $'console_login_status\tcredential-leak-detected' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -if grep -R -F 'fixture-password-that-must-not-leak' "$ROOT/KeyPathInstallerLab"; then - echo "console login retained a disclosed credential after redaction" >&2 - exit 1 -fi -if grep -R -F 'fixture-password-that-must-not-leak' "$ROOT/KeyPathInstallerLab" "$CALLS"; then - echo "console login leaked its secret into controller logs or arguments" >&2 - exit 1 -fi -set +e -console_login_bad_credential=$(KEYPATH_LAB_TEST_CONSOLE_AUTH_FAIL=1 KEYPATH_LAB_CONSOLE_LOGIN_POLL_SECONDS=0 run_remote console-login cbx_desktop27 2>&1) -console_login_bad_credential_status=$? -set -e -[[ $console_login_bad_credential_status -ne 0 ]] -assert_contains "$console_login_bad_credential" 'KEYPATH_LAB_GUEST_PASSWORD does not authenticate the keypathqa guest account' -grep -q $'console_login_status\tcredential-mismatch' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop27/manifest.tsv" -run_remote destroy cbx_desktop27 >/dev/null - -desktop26_create=$(run_remote create 26 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 1) -assert_contains "$desktop26_create" $'lease_id\tcbx_desktop26' -grep -q $'base_name\tkeypath-macos-26-desktop' "$ROOT/KeyPathInstallerLab/leases/cbx_desktop26/manifest.tsv" -rfb_probe26=$(KEYPATH_LAB_TEST_SSH_KEY="$TMP/test-ssh-key" KEYPATH_LAB_TEST_CURSOR_BEFORE='10 10' KEYPATH_LAB_TEST_CURSOR_AFTER='170 130' KEYPATH_LAB_RFB_POINTER_SETTLE_SECONDS=0 run_remote rfb-pointer-probe cbx_desktop26 170 130) -assert_contains "$rfb_probe26" $'rfb_pointer_probe\tpassed' -assert_contains "$rfb_probe26" $'cursor_before\t10 10' -assert_contains "$rfb_probe26" $'cursor_after\t170 130' -grep -q 'crabbox desktop click --provider parallels --target macos --id cbx_desktop26 --x 170 --y 130' "$CALLS" -desktop26_artifacts=$(run_remote artifacts cbx_desktop26) -assert_contains "$desktop26_artifacts" $'screenshot_status\t0' -grep -q 'prlctl capture 00000000-0000-0000-0000-000000000000 --file' "$CALLS" -desktop26_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_desktop26/manifest.tsv" -awk -F '\t' 'BEGIN {OFS="\t"} $1 == "provider_resource" {$2="-option-like-id"} {print}' "$desktop26_manifest" > "$desktop26_manifest.tmp" -mv "$desktop26_manifest.tmp" "$desktop26_manifest" -prlctl_calls_before=$(grep -c '^prlctl capture ' "$CALLS") -set +e -invalid_resource_output=$(run_remote artifacts cbx_desktop26 2>&1) -invalid_resource_exit=$? -set -e -[[ $invalid_resource_exit -eq 1 ]] -assert_contains "$invalid_resource_output" 'invalid Parallels resource id' -[[ $(grep -c '^prlctl capture ' "$CALLS") -eq $prlctl_calls_before ]] -run_remote destroy cbx_desktop26 >/dev/null - -# A managed lease that also asks for a desktop must keep the managed base: only -# that base carries MDM enrollment. Desktop capability comes from the launcher, -# not from swapping to the unenrolled desktop base. -managed_desktop_create=$(run_remote create 26 managed-functional "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 1) -assert_contains "$managed_desktop_create" $'lease_id\tcbx_desktop26' -managed_desktop_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_desktop26/manifest.tsv" -grep -q $'base_name\tkeypath-macos-26-managed' "$managed_desktop_manifest" -grep -q $'desktop_enabled\ttrue' "$managed_desktop_manifest" -run_remote destroy cbx_desktop26 >/dev/null - -desktop_create=$(run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 1) -assert_contains "$desktop_create" $'lease_id\tcbx_desktop15' -grep -q $'status\tprovisioning' "$ROOT/KeyPathInstallerLab/leases/cbx_stale/manifest.tsv" -run_remote destroy cbx_stale >/dev/null -desktop_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_desktop15/manifest.tsv" -grep -q $'desktop_enabled\ttrue' "$desktop_manifest" -nameplate_enable=$(run_remote nameplate cbx_desktop15 enable) -assert_contains "$nameplate_enable" $'nameplate_state\tvisible' -grep -q $'nameplate_version\t0.2.5' "$desktop_manifest" -grep -q $'nameplate_sha256\t96d1b6c58167b4a8f3713a61a7e216f8a24c2adad36c9027db974f852d543a3d' "$desktop_manifest" -grep -q $'nameplate_state\tvisible' "$desktop_manifest" -nameplate_status=$(run_remote nameplate cbx_desktop15 status) -assert_contains "$nameplate_status" $'nameplate_state\tvisible' -nameplate_hide=$(run_remote nameplate cbx_desktop15 hide) -assert_contains "$nameplate_hide" $'nameplate_state\thidden' -nameplate_show=$(run_remote nameplate cbx_desktop15 show) -assert_contains "$nameplate_show" $'nameplate_state\tvisible' -desktop_artifacts=$(run_remote artifacts cbx_desktop15) -assert_contains "$desktop_artifacts" $'screenshot_status\t0' -assert_contains "$desktop_artifacts" $'nameplate_hide_status\t0' -assert_contains "$desktop_artifacts" $'nameplate_restore_status\t0' -desktop_artifact_dir=$(printf '%s\n' "$desktop_artifacts" | awk -F '\t' '$1 == "artifact_dir" {print $2}') -[[ -f "$desktop_artifact_dir/screenshot.png" ]] -[[ -f "$desktop_artifact_dir/nameplate-hide.log" && -f "$desktop_artifact_dir/nameplate-restore.log" ]] -grep -q $'nameplate_state\tvisible' "$desktop_manifest" -touch "$ROOT/fail-nameplate-hide" -failed_hide_artifacts=$(run_remote artifacts cbx_desktop15) -rm "$ROOT/fail-nameplate-hide" -assert_contains "$failed_hide_artifacts" $'download_status\t0' -assert_contains "$failed_hide_artifacts" $'screenshot_status\tunavailable:nameplate-hide-failed' -assert_contains "$failed_hide_artifacts" $'nameplate_hide_status\t1' -assert_contains "$failed_hide_artifacts" $'nameplate_restore_status\tnot-needed' -failed_hide_artifact_dir=$(printf '%s\n' "$failed_hide_artifacts" | awk -F '\t' '$1 == "artifact_dir" {print $2}') -[[ -f "$failed_hide_artifact_dir/nameplate-hide.log" && ! -f "$failed_hide_artifact_dir/screenshot.png" ]] -grep -q 'guest reported unexpected Nameplate version: missing' "$failed_hide_artifact_dir/nameplate-hide.log" -touch "$ROOT/fail-nameplate-show" -failed_restore_artifacts=$(run_remote artifacts cbx_desktop15) -rm "$ROOT/fail-nameplate-show" -assert_contains "$failed_restore_artifacts" $'download_status\t0' -assert_contains "$failed_restore_artifacts" $'screenshot_status\t0' -assert_contains "$failed_restore_artifacts" $'nameplate_hide_status\t0' -assert_contains "$failed_restore_artifacts" $'nameplate_restore_status\t1' -failed_restore_artifact_dir=$(printf '%s\n' "$failed_restore_artifacts" | awk -F '\t' '$1 == "artifact_dir" {print $2}') -[[ -f "$failed_restore_artifact_dir/nameplate-hide.log" && -f "$failed_restore_artifact_dir/nameplate-restore.log" ]] -grep -q 'guest reported unexpected Nameplate version: missing' "$failed_restore_artifact_dir/nameplate-restore.log" -grep -q $'nameplate_state\thidden' "$desktop_manifest" -secure_result=$(run_remote secure-dialog-input cbx_desktop15 'System Settings' Password 'Modify Settings' 0) -assert_contains "$secure_result" $'secure_dialog_input\tpassed' -grep -q 'admin@192.0.2.15' "$TMP/guest-ssh-args" -grep -q 'mcporter' "$TMP/guest-ssh-args" -grep -q 'text=@/dev/stdin' "$TMP/guest-ssh-args" -grep -q 'dev/null' "$TMP/guest-ssh-args" -grep -q 'peekaboo.*click.*Password.*--app.*System.*Settings' "$TMP/guest-ssh-args" -grep -q 'peekaboo.*click.*Modify.*Settings.*--app.*System.*Settings' "$TMP/guest-ssh-args" -grep -q 'keypath-secure-postcondition' "$TMP/guest-ssh-args" -grep -q 'keypath-secure-postcondition.json.*79' "$TMP/guest-ssh-args" -if grep -q -- '--query' "$TMP/guest-ssh-args"; then - echo "secure dialog input passed the adapter-only --query option to Peekaboo" >&2 - exit 1 -fi -if grep -q 'click--app' "$TMP/guest-ssh-args"; then - echo "secure dialog input collapsed adjacent guest arguments" >&2 - exit 1 -fi -cmp -s "$TMP/secure-input" "$TMP/guest-ssh-stdin" -if grep -R -F 'fixture-password-that-must-not-leak' "$ROOT/KeyPathInstallerLab" "$TMP/guest-ssh-args"; then - echo "secure dialog input leaked its secret into logs or arguments" >&2 - exit 1 -fi -secure_agent_result=$(run_remote secure-dialog-input cbx_desktop15 SecurityAgent AXSecureTextField Allow 0) -assert_contains "$secure_agent_result" $'secure_dialog_input\tpassed' -grep -q 'keypath-secure-input' "$TMP/guest-ssh-args" -grep -q 'button.*position.*size' "$TMP/guest-ssh-args" -grep -q 'peekaboo.*click.*--coords.*button_coords.*--global-coords' "$TMP/guest-ssh-args" -grep -q -- '--foreground.*--input-strategy.*synthOnly' "$TMP/guest-ssh-args" -grep -q 'SecurityAgent.*closed' "$TMP/guest-ssh-args" -if grep -q '/usr/bin/sudo\|pbcopy\|the\\ clipboard' "$TMP/guest-ssh-args"; then - echo "SecurityAgent secure input used an unsafe password path" >&2 - exit 1 -fi -secure_settings_result=$(run_remote secure-dialog-input cbx_desktop15 'System Settings' AXSecureTextField 'Modify Settings' 0) -assert_contains "$secure_settings_result" $'secure_dialog_input\tpassed' -grep -q 'processes.byName.*appName' "$TMP/guest-ssh-args" -grep -q 'System.*Settings.*Modify.*Settings' "$TMP/guest-ssh-args" -grep -q 'AXSecureTextField' "$TMP/guest-ssh-args" -grep -q 'return.*open.*closed' "$TMP/guest-ssh-args" -secure_focused_result=$(run_remote secure-dialog-input cbx_desktop15 SecurityAgent Password '' 1) -assert_contains "$secure_focused_result" $'secure_dialog_input\tpassed' -if grep -q 'peekaboo.*see\|peekaboo.*click' "$TMP/guest-ssh-args"; then - echo "already-focused secure input attempted inaccessible AX discovery" >&2 - exit 1 -fi -protected_result=$(KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS=0 run_remote protected-click cbx_desktop15 'System Settings' Accessibility Accessibility native 402 247) -assert_contains "$protected_result" $'protected_click\tpassed' -assert_contains "$protected_result" $'click_count\t1' -grep -q 'crabbox desktop click --provider tart --target macos --id test-resource --x 402 --y 247' "$CALLS" -protected_double_result=$(KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS=0 run_remote protected-click cbx_desktop15 'System Settings' Accessibility Accessibility native 402 247 2) -assert_contains "$protected_double_result" $'click_count\t2' -grep -q 'crabbox desktop click --provider tart --target macos --id test-resource --x 402 --y 247 --count 2' "$CALLS" -protected_any_result=$(KEYPATH_LAB_TEST_WINDOW_BEFORE=General KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS=0 run_remote protected-click cbx_desktop15 'System Settings' __ANY__ Accessibility native 402 247) -assert_contains "$protected_any_result" $'window_before\tGeneral' -set +e -protected_wrong_page=$(KEYPATH_LAB_TEST_WINDOW_AFTER=Network KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS=0 run_remote protected-click cbx_desktop15 'System Settings' Accessibility Accessibility native 402 247 2>&1) -protected_wrong_page_exit=$? -set -e -[[ $protected_wrong_page_exit -ne 0 ]] || { echo "protected click accepted the wrong destination page" >&2; exit 1; } -assert_contains "$protected_wrong_page" "protected click postcondition failed" -protected_ax_result=$(KEYPATH_LAB_PROTECTED_CLICK_SETTLE_SECONDS=0 run_remote protected-click cbx_desktop15 'System Settings' Accessibility Accessibility ax 402 247) -assert_contains "$protected_ax_result" $'display_scale\t2' -grep -q 'crabbox desktop click --provider tart --target macos --id test-resource --x 804 --y 494' "$CALLS" -run_remote desktop-type cbx_desktop15 q >/dev/null -grep -q 'crabbox desktop type --provider tart --target macos --id test-resource --text q' "$CALLS" -run_remote destroy cbx_desktop15 >/dev/null - -set +e -KEYPATH_LAB_TEST_WARMUP_FAIL=1 run_remote create 15 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 2h 1 >/dev/null 2>&1 -warmup_fail_exit=$? -set -e -[[ $warmup_fail_exit -ne 0 ]] || { echo "warmup failure fixture unexpectedly succeeded" >&2; exit 1; } -failed_manifest="$ROOT/KeyPathInstallerLab/leases/cbx_desktop15/manifest.tsv" -grep -q $'status\tprovisioning-failed' "$failed_manifest" -grep -q $'provision_result\t9' "$failed_manifest" - -if run_remote create 26 unmanaged-ui "$archive_key" "$commit" "$checksum" KeyPath.zip 3h 0 >/dev/null 2>&1; then - echo "create accepted a TTL longer than the launchers support" >&2 - exit 1 -fi - -cp -R "$ROOT/KeyPathInstallerLab/leases/cbx_test15" "$ROOT/KeyPathInstallerLab/leases/cbx_expired" -expired="$ROOT/KeyPathInstallerLab/leases/cbx_expired/manifest.tsv" -sed -i '' 's/cbx_test15/cbx_expired/g; s/^expires_epoch.*/expires_epoch\t1/; s/^cleanup_status.*/cleanup_status\tpending/' "$expired" -dry_run=$(run_remote cleanup --dry-run) -assert_contains "$dry_run" $'would_destroy\tcbx_expired' -if grep -q 'stop-15 cbx_expired' "$CALLS"; then - echo "dry-run destroyed a lease" >&2 - exit 1 -fi -run_remote cleanup >/dev/null -grep -q 'stop-15 cbx_expired' "$CALLS" - -touch "$ROOT/base-image-must-survive" -run_remote cleanup >/dev/null -[[ -f "$ROOT/base-image-must-survive" ]] - -mkdir -p "$TMP/fake-bin" -cat > "$TMP/fake-bin/ssh" < "$TMP/ssh-args" -cat >/dev/null -echo controller-preflight -EOF -chmod +x "$TMP/fake-bin/ssh" -controller=$(PATH="$TMP/fake-bin:$PATH" KEYPATH_LAB_HOST=tester@test-host "$LAB_DIR/keypath-lab" preflight) -assert_contains "$controller" controller-preflight -grep -q 'tester@test-host' "$TMP/ssh-args" - -echo fake-installer > "$TMP/KeyPath.zip" -if PATH="$TMP/fake-bin:$PATH" "$LAB_DIR/keypath-lab" create --macos 15 --lane unmanaged-ui --commit abc --installer "$TMP/KeyPath.zip" >/dev/null 2>&1; then - echo "controller accepted a non-explicit commit SHA" >&2 - exit 1 -fi -if PATH="$TMP/fake-bin:$PATH" "$LAB_DIR/keypath-lab" create --macos 15 --commit "$(printf 'a%.0s' {1..40})" --installer "$TMP/KeyPath.zip" >/dev/null 2>&1; then - echo "create accepted a request without an explicit test lane" >&2 - exit 1 -fi -if PATH="$TMP/fake-bin:$PATH" "$LAB_DIR/keypath-lab" create --macos 27 --lane managed-functional --commit "$(printf 'a%.0s' {1..40})" --installer "$TMP/KeyPath.zip" >/dev/null 2>&1; then - echo "create accepted the unsupported macOS 27 managed lane" >&2 - exit 1 -fi - -"$LAB_DIR/tests/peekaboo-ui-tests.sh" - -echo "keypath-lab shell tests passed" diff --git a/Scripts/lab/tests/peekaboo-ui-tests.sh b/Scripts/lab/tests/peekaboo-ui-tests.sh deleted file mode 100755 index 1ad33b050..000000000 --- a/Scripts/lab/tests/peekaboo-ui-tests.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash -set -euo pipefail - -SCRIPT_DIR=$(cd "$(dirname "$0")" >/dev/null && pwd -P) -LAB_DIR=$(cd "$SCRIPT_DIR/.." >/dev/null && pwd -P) -TMP=$(mktemp -d "${TMPDIR:-/tmp}/peekaboo-ui-tests.XXXXXX") -trap 'rm -rf "$TMP"' EXIT - -FAKE="$TMP/peekaboo" -cat > "$FAKE" <<'EOF' -#!/bin/bash -set -euo pipefail -printf '%s\n' "$*" >> "$PEEKABOO_CALLS" -if [[ ${1:-} == --version ]]; then - echo "${PEEKABOO_TEST_VERSION:-Peekaboo 3.9.0}" -else - printf '{"success":true}\n' -fi -EOF -chmod +x "$FAKE" -export PEEKABOO_BIN="$FAKE" -export PEEKABOO_CALLS="$TMP/calls" - -"$LAB_DIR/peekaboo-ui" preflight > "$TMP/preflight.json" -grep -q 'permissions status --json' "$PEEKABOO_CALLS" - -for unsupported in 'Peekaboo 2.3.0' 'Peekaboo 13.0.0'; do - if PEEKABOO_TEST_VERSION="$unsupported" "$LAB_DIR/peekaboo-ui" preflight >/dev/null 2>&1; then - echo "expected unsupported version to fail: $unsupported" >&2 - exit 1 - fi -done - -"$LAB_DIR/peekaboo-ui" snapshot --app 'System Settings' --output "$TMP/out/snapshot.json" -grep -q 'see --app System Settings --json' "$PEEKABOO_CALLS" -grep -q '"success":true' "$TMP/out/snapshot.json" - -"$LAB_DIR/peekaboo-ui" click --app KeyPath --query 'Get Started' --foreground --output "$TMP/out/click.json" -grep -q 'click Get Started --app KeyPath --json --foreground' "$PEEKABOO_CALLS" - -"$LAB_DIR/peekaboo-ui" dialogs --app 'System Settings' --output "$TMP/out/dialogs.json" -grep -q 'dialog list --app System Settings --json' "$PEEKABOO_CALLS" - -"$LAB_DIR/peekaboo-ui" file --app 'System Settings' --path /Applications/KeyPath.app --select Open --output "$TMP/out/file.json" -grep -q 'dialog file --app System Settings --path /Applications --name KeyPath.app --select Open --json' "$PEEKABOO_CALLS" - -"$LAB_DIR/peekaboo-ui" screenshot --app KeyPath --retina --output "$TMP/out/keypath.png" -grep -q 'image --app KeyPath --mode window --path .*keypath.png --json --retina' "$PEEKABOO_CALLS" -grep -q '"success":true' "$TMP/out/keypath.png.json" - -if "$LAB_DIR/peekaboo-ui" file --app KeyPath --path relative --output "$TMP/out/bad.json" >/dev/null 2>&1; then - echo 'expected relative file path to fail' >&2 - exit 1 -fi - -if "$LAB_DIR/peekaboo-ui" click --app KeyPath --output "$TMP/out/bad.json" >/dev/null 2>&1; then - echo 'expected missing query to fail' >&2 - exit 1 -fi - -DRAG_PEEKABOO="$TMP/drag-peekaboo" -cat > "$DRAG_PEEKABOO" <<'EOF' -#!/bin/bash -set -euo pipefail -printf '%s\n' "$*" >> "$PEEKABOO_CALLS" -if [[ ${1:-} == see && "$*" == *"--app Finder"* ]]; then - printf '{"data":{"ui_elements":[{"label":"kanata-launcher","is_actionable":true,"bounds":{"x":10,"y":20,"width":40,"height":60}}]}}\n' -elif [[ ${1:-} == see ]]; then - printf '{"data":{"ui_elements":[{"identifier":"KeyPath_Title","bounds":{"x":600,"y":100,"width":100,"height":20}}]}}\n' -else - printf '{"success":true}\n' -fi -EOF -chmod +x "$DRAG_PEEKABOO" - -FAKE_OPEN="$TMP/open" -printf '#!/bin/bash\nprintf "%%s\\n" "$*" >> "$OPEN_CALLS"\n' > "$FAKE_OPEN" -chmod +x "$FAKE_OPEN" -export OPEN_CALLS="$TMP/open-calls" - -FAKE_OSASCRIPT="$TMP/osascript" -cat > "$FAKE_OSASCRIPT" <<'EOF' -#!/bin/bash -printf '%s\n' "$*" >> "$OSASCRIPT_CALLS" -if [[ "$*" == *'return w.position().concat(w.size()).join'* ]]; then - [[ "${*: -1}" == Finder ]] && echo '80,90,640,480' || echo '700,100,800,600' - exit 0 -fi -[[ "$*" == *'AXSecureTextField'* ]] && echo secure -exit 0 -EOF -chmod +x "$FAKE_OSASCRIPT" -export OSASCRIPT_CALLS="$TMP/osascript-calls" - -touch "$TMP/kanata-launcher" -KEYPATH_PERMISSION_DRAG_REVEAL_SECONDS=0 KEYPATH_PERMISSION_DRAG_SETTLE_SECONDS=0 \ - PEEKABOO_BIN="$DRAG_PEEKABOO" OPEN_BIN="$FAKE_OPEN" OSASCRIPT_BIN="$FAKE_OSASCRIPT" \ - "$LAB_DIR/permission-drag" --path "$TMP/kanata-launcher" --target-identifier KeyPath_Title --output "$TMP/out/drag.json" > "$TMP/drag-result" -grep -q $'permission_drag\tauthorization-required' "$TMP/drag-result" -grep -q -- '-R .*kanata-launcher' "$OPEN_CALLS" -grep -q 'drag --from-coords 30,50 --to-coords 650,110 --duration 1500 --steps 30 --profile linear --json' "$PEEKABOO_CALLS" -grep -q 'Finder 80,90,640,480' "$OSASCRIPT_CALLS" -grep -q 'System Settings 700,100,800,600' "$OSASCRIPT_CALLS" - -if grep -q '/usr/bin/python3' "$LAB_DIR/permission-drag" "$LAB_DIR/remote.sh"; then - echo 'desktop QA helper bypassed the provisioned Python 3 on PATH' >&2 - exit 1 -fi - -echo 'peekaboo-ui shell tests passed' diff --git a/docs/testing/installer-gui-automation-capabilities.md b/docs/testing/installer-gui-automation-capabilities.md index 3c870425c..8b6c818f5 100644 --- a/docs/testing/installer-gui-automation-capabilities.md +++ b/docs/testing/installer-gui-automation-capabilities.md @@ -100,13 +100,18 @@ Create a desktop lease and use the typed guest adapter for evidence-producing semantic operations: ```bash -Scripts/lab/keypath-lab create \ +vm-lab keypath create \ --macos 15 --lane unmanaged-ui --commit "$SHA" --installer dist/KeyPath.zip --desktop -Scripts/lab/keypath-lab nameplate "$LEASE" enable +vm-lab keypath nameplate "$LEASE" enable -Scripts/lab/keypath-lab run "$LEASE" -- \ - Scripts/lab/peekaboo-ui snapshot \ +> `peekaboo-ui` moved to the vm-lab repository and is no longer shipped into +> the guest with this repo. The desktop base provides `peekaboo` itself at +> `/usr/local/bin/peekaboo`; call it directly, or see vm-lab's +> `docs/ui-automation.md` for the targeting contract. + +vm-lab keypath run "$LEASE" -- \ + peekaboo-ui snapshot \ --app 'System Settings' \ --output .keypath-lab/scenario-output/approvals/system-settings.json ``` @@ -121,7 +126,7 @@ automatically. Nameplate launch-at-login remains disabled so its own For a password sheet on the Tart lane: ```bash -Scripts/lab/keypath-lab secure-dialog-input "$LEASE" \ +vm-lab keypath secure-dialog-input "$LEASE" \ --app 'System Settings' \ --field Password \ --submit 'Modify Settings' diff --git a/docs/testing/managed-vm-lanes.md b/docs/testing/managed-vm-lanes.md index 780514c59..f4b3a9c0e 100644 --- a/docs/testing/managed-vm-lanes.md +++ b/docs/testing/managed-vm-lanes.md @@ -85,7 +85,7 @@ The harness verifies its lane automatically before installing KeyPath: Create every lease with an explicit lane: ```bash -Scripts/lab/keypath-lab create --macos 26 --lane managed-functional \ +vm-lab keypath create --macos 26 --lane managed-functional \ --commit "$COMMIT" --installer dist/KeyPath.zip ``` diff --git a/docs/testing/remote-installer-lab.md b/docs/testing/remote-installer-lab.md index 53194e767..e7336d1c2 100644 --- a/docs/testing/remote-installer-lab.md +++ b/docs/testing/remote-installer-lab.md @@ -1,6 +1,6 @@ # Remote KeyPath Installer Lab -`Scripts/lab/keypath-lab` is the supported controller for disposable installer +`vm-lab keypath` is the supported controller for disposable installer testing on the existing Mac mini lab. It defaults to `clawd@keypath-lab-mini`; use `--host` or `KEYPATH_LAB_HOST` for another SSH alias that exposes the same lab contract. @@ -42,7 +42,7 @@ the same 100 GiB threshold is not met. Check the non-mutating host/provider contract: ```bash -Scripts/lab/keypath-lab preflight +vm-lab keypath preflight ``` ## Concurrent agents and provider admission @@ -89,7 +89,7 @@ the completed archive. ```bash SHA=$(git rev-parse HEAD) -Scripts/lab/keypath-lab create \ +vm-lab keypath create \ --macos 27 \ --lane unmanaged-ui \ --commit "$SHA" \ @@ -97,17 +97,17 @@ Scripts/lab/keypath-lab create \ --ttl 2h \ --desktop -Scripts/lab/keypath-lab list -Scripts/lab/keypath-lab status cbx_example -Scripts/lab/keypath-lab install-app cbx_example -Scripts/lab/keypath-lab nameplate cbx_example enable -Scripts/lab/keypath-lab run cbx_example -- sw_vers -Scripts/lab/keypath-lab secure-dialog-input cbx_example \ +vm-lab keypath list +vm-lab keypath status cbx_example +vm-lab keypath install-app cbx_example +vm-lab keypath nameplate cbx_example enable +vm-lab keypath run cbx_example -- sw_vers +vm-lab keypath secure-dialog-input cbx_example \ --app 'System Settings' --field Password --submit 'Modify Settings' -Scripts/lab/keypath-lab artifacts cbx_example -Scripts/lab/keypath-lab destroy cbx_example -Scripts/lab/keypath-lab cleanup --dry-run -Scripts/lab/keypath-lab cleanup +vm-lab keypath artifacts cbx_example +vm-lab keypath destroy cbx_example +vm-lab keypath cleanup --dry-run +vm-lab keypath cleanup ``` Every manifest records the source commit, macOS product version and build, @@ -134,9 +134,14 @@ records an explicit unavailable status otherwise. the guest is ready for semantic UI automation. Before a scenario relies on System Settings, admit the base through these postconditions: +> `peekaboo-ui` moved to the vm-lab repository and is no longer shipped into +> the guest with this repo. The desktop base provides `peekaboo` itself at +> `/usr/local/bin/peekaboo`; call it directly, or see vm-lab's +> `docs/ui-automation.md` for the targeting contract. + - a real console user is logged in (not merely an SSH account); - the guest has a Python runtime for the scenario drivers; and -- `Scripts/lab/peekaboo-ui preflight` succeeds for that console session. +- `peekaboo-ui preflight` succeeds for that console session. If any condition is absent, record an `environment-precondition-failure`, collect artifacts, and destroy the lease. Do not fall back to raw provider @@ -150,7 +155,7 @@ running `desktop-bootstrap`, establish its disposable clone's console session through the owned controller: ```bash -Scripts/lab/keypath-lab console-login cbx_example +vm-lab keypath console-login cbx_example ``` This command is macOS-27/Parallels-only. It streams @@ -195,7 +200,7 @@ After console login, verify that the RFB path delivers input instead of merely acknowledging the protocol write: ```bash -Scripts/lab/keypath-lab rfb-pointer-probe cbx_example --x 160 --y 120 +vm-lab keypath rfb-pointer-probe cbx_example --x 160 --y 120 ``` The probe reads the guest cursor location, sends one CrabBox RFB click, and @@ -214,8 +219,8 @@ focused, submit the encrypted guest credential without exposing it to argv, logs, screenshots, or the pasteboard: ```bash -Scripts/lab/keypath-lab secure-console-submit cbx_example -Scripts/lab/keypath-lab rfb-pointer-probe cbx_example --x 160 --y 120 +vm-lab keypath secure-console-submit cbx_example +vm-lab keypath rfb-pointer-probe cbx_example --x 160 --y 120 ``` `secure-console-submit` emits one Parallels key event at a time with a bounded @@ -239,10 +244,10 @@ This is base provisioning, not a per-scenario setup step. Nameplate can label an owned desktop lease without modifying its base image: ```bash -Scripts/lab/keypath-lab nameplate cbx_example enable -Scripts/lab/keypath-lab nameplate cbx_example status -Scripts/lab/keypath-lab nameplate cbx_example hide -Scripts/lab/keypath-lab nameplate cbx_example show +vm-lab keypath nameplate cbx_example enable +vm-lab keypath nameplate cbx_example status +vm-lab keypath nameplate cbx_example hide +vm-lab keypath nameplate cbx_example show ``` `enable` is accepted only for a desktop-enabled lease. It downloads the pinned @@ -271,7 +276,7 @@ For the current capability matrix, security boundaries, and agent handoff sequence, see [`installer-gui-automation-capabilities.md`](installer-gui-automation-capabilities.md). -Desktop guests can use `Scripts/lab/peekaboo-ui` to discover and operate +Desktop guests can use `peekaboo-ui` to discover and operate KeyPath and System Settings without framebuffer coordinate assumptions. The adapter provides typed commands for snapshots, semantic clicks, dialog inspection, file selection, and Retina screenshots. Every command writes JSON @@ -280,13 +285,13 @@ evidence alongside the scenario output so it is included by `artifacts`. ```bash OUT=.keypath-lab/scenario-output/approvals/peekaboo -Scripts/lab/keypath-lab run cbx_example -- \ - Scripts/lab/peekaboo-ui preflight -Scripts/lab/keypath-lab run cbx_example -- \ - Scripts/lab/peekaboo-ui snapshot \ +vm-lab keypath run cbx_example -- \ + peekaboo-ui preflight +vm-lab keypath run cbx_example -- \ + peekaboo-ui snapshot \ --app 'System Settings' --output "$OUT/input-monitoring.json" -Scripts/lab/keypath-lab run cbx_example -- \ - Scripts/lab/permission-drag \ +vm-lab keypath run cbx_example -- \ + permission-drag \ --path /Applications/KeyPath.app/Contents/Library/KeyPath/kanata-launcher \ --target-identifier KeyPath_Title \ --output "$OUT/permission-drag.json" @@ -340,7 +345,7 @@ On macOS 15 authentication sheets, use an explicit submit selector when the button is available: ```bash -Scripts/lab/keypath-lab secure-dialog-input cbx_example \ +vm-lab keypath secure-dialog-input cbx_example \ --app 'System Settings' --field Password --submit 'Modify Settings' ``` @@ -358,7 +363,7 @@ proves that its password field is already focused, use the constrained focused mode without a submit selector: ```bash -Scripts/lab/keypath-lab secure-dialog-input cbx_example \ +vm-lab keypath secure-dialog-input cbx_example \ --app SecurityAgent --field Password --already-focused ``` @@ -382,7 +387,7 @@ For a protected control that requires native RFB delivery, use the lease-owned guard instead of invoking CrabBox directly: ```bash -Scripts/lab/keypath-lab protected-click cbx_example \ +vm-lab keypath protected-click cbx_example \ --app 'System Settings' \ --window Accessibility \ --ax-x 402 --ax-y 247 @@ -402,7 +407,7 @@ For ordinary text or single-key input on a macOS 15 desktop lease, use the lease-owned VNC path rather than calling CrabBox directly: ```bash -Scripts/lab/keypath-lab desktop-type cbx_example --text q +vm-lab keypath desktop-type cbx_example --text q ``` The controller verifies ownership, desktop capability, OS lane, and the exact @@ -424,19 +429,19 @@ guest password. Run a named scenario after creating a lease: ```bash -Scripts/lab/keypath-lab scenario cbx_example clean-install -Scripts/lab/keypath-lab scenario cbx_example approvals -Scripts/lab/keypath-lab scenario cbx_example helper-daemon-health -Scripts/lab/keypath-lab scenario cbx_example launch -Scripts/lab/keypath-lab scenario cbx_example repair-reinstall -Scripts/lab/keypath-lab scenario cbx_example reboot-persistence-before +vm-lab keypath scenario cbx_example clean-install +vm-lab keypath scenario cbx_example approvals +vm-lab keypath scenario cbx_example helper-daemon-health +vm-lab keypath scenario cbx_example launch +vm-lab keypath scenario cbx_example repair-reinstall +vm-lab keypath scenario cbx_example reboot-persistence-before # Reboot the disposable guest through the approved lab workflow. -Scripts/lab/keypath-lab scenario cbx_example reboot-persistence-after -Scripts/lab/keypath-lab scenario cbx_example uninstall -Scripts/lab/keypath-lab scenario cbx_example cancellation-failure -Scripts/lab/keypath-lab scenario cbx_example artifact-capture -Scripts/lab/keypath-lab scenario cbx_example macos-27-regression -Scripts/lab/keypath-lab artifacts cbx_example +vm-lab keypath scenario cbx_example reboot-persistence-after +vm-lab keypath scenario cbx_example uninstall +vm-lab keypath scenario cbx_example cancellation-failure +vm-lab keypath scenario cbx_example artifact-capture +vm-lab keypath scenario cbx_example macos-27-regression +vm-lab keypath artifacts cbx_example ``` The scenario set covers clean installation, every macOS approval gate, @@ -461,8 +466,8 @@ For a disposable macOS 27 desktop lease, run the same capture after installing the app: ```bash -Scripts/lab/keypath-lab scenario cbx_example macos-27-regression -Scripts/lab/keypath-lab artifacts cbx_example +vm-lab keypath scenario cbx_example macos-27-regression +vm-lab keypath artifacts cbx_example ``` The command records the exact OS build, canonical CLI system snapshot,