diff --git a/Scripts/lab/remote.sh b/Scripts/lab/remote.sh index 4877e1b1b..49377d82d 100755 --- a/Scripts/lab/remote.sh +++ b/Scripts/lab/remote.sh @@ -1,6 +1,20 @@ #!/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" @@ -65,10 +79,19 @@ configure_tart_path() { 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 + 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 @@ -123,23 +146,36 @@ provider_capacity() { 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 - df -Pk /System/Volumes/Data | awk 'NR == 2 {print $4}' + # 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 + 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 internal free space" + [[ "$free_kib" == <-> ]] || die "could not determine free space for $path" minimum_kib=$((minimum_gib * 1024 * 1024)) - print -u2 "disk_reserve\tfree_gib=$((free_kib / 1024 / 1024))\tminimum_gib=$minimum_gib" + # 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" + print -u2 "disk_reserve_busy\tfree_gib=$((free_kib / 1024 / 1024))\tminimum_gib=$minimum_gib\tpath=$path" return 75 fi } @@ -483,13 +519,13 @@ warmup_desktop() { 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")" \ + --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")" --parallels-user keypathqa \ + --parallels-template "$(base_for "$macos" "$lane" 1)" --parallels-user keypathqa \ --parallels-work-root /Users/keypathqa/crabbox --ssh-port 22 \ --slug "$slug" --ttl 2h fi @@ -628,7 +664,7 @@ write_provisional_lease_manifest() { print "slug\t$slug" print "macos\t$macos" print "test_lane\t$lane" - print "base_name\t$(base_for "$macos" "$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" @@ -731,7 +767,7 @@ create_lease() { print "slug\t$slug" print "macos\t$macos" print "test_lane\t$lane" - print "base_name\t$(base_for "$macos" "$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" diff --git a/Scripts/lab/tests/keypath-lab-tests.sh b/Scripts/lab/tests/keypath-lab-tests.sh index 9c630a063..5a63501f7 100755 --- a/Scripts/lab/tests/keypath-lab-tests.sh +++ b/Scripts/lab/tests/keypath-lab-tests.sh @@ -426,6 +426,7 @@ 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=$? @@ -565,6 +566,12 @@ 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" @@ -672,6 +679,7 @@ 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' @@ -693,6 +701,16 @@ 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"