5614a56ccd
prd-number-check / require-numbered-prds (pull_request) Successful in 11s
test / image-input-builds (pull_request) Successful in 44s
test / unit (pull_request) Successful in 57s
test / integration-docker (pull_request) Failing after 3m4s
test / coverage (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Failing after 13m12s
`test` passes. `test-ready` got as far as the service start and failed:
Launching container-apiserver...
Error: failed to get a response from apiserver: invalidState: "unauthorized request"
which is the limitation this script's own header predicted — "may need a full
launchd user session (`launchctl asuser`)".
`container system start` registers com.apple.container.apiserver as a per-USER
launchd agent and then talks to it over XPC. `launchctl list` confirms the
shape: the apiserver and every container-network/runtime job are agents in the
invoking user's domain. Under plain `sudo -u` the caller stays in root's
bootstrap namespace, so the lookup crosses domains and the apiserver rejects it
as unauthorized — the agent launched fine, the client just could not reach it.
run_as_user now enters the target user's domain with `launchctl asuser <uid>`,
which is also what a real user gets from Terminal, so it is the more faithful
way to run everything here rather than a special case for the service start.
A never-GUI-logged-in account may have no bootstrappable domain at all, so
availability is probed once and cached, and everything falls back to plain
`sudo -u` when it is missing. Without that fallback a missing domain would
break `test` — which passes today and does not need a session — for a reason
unrelated to what it tests. The rig covers that path explicitly.
If the start still fails, the error now names the cause and the way out
(log into the account once to get it a domain) instead of just relaying the
CLI's message.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEfZZhakx13bxTfXcZCoS5
513 lines
22 KiB
Bash
Executable File
513 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Clean-install test harness for the macOS (Apple `container`) path.
|
|
#
|
|
# Exercises install.sh the way a brand-new user would, inside a throwaway
|
|
# macOS account you create and delete from the CLI. install.sh's entire
|
|
# footprint is user-home-local — the pipx venv under ~/.local, or the private
|
|
# venv at ~/.bot-bottle/venv plus a ~/.local/bin symlink, and the ~/.bot-bottle
|
|
# config dir. It writes no shell-profile PATH line, and never installs the
|
|
# backend (see
|
|
# the header of install.sh), so deleting the user is a complete,
|
|
# deterministic reset of everything the installer touched. The Apple
|
|
# `container` runtime is a HOST prerequisite installed once and kept;
|
|
# `deep-reset` is the rare escape hatch that also removes it.
|
|
#
|
|
# Why a throwaway user and not a disposable VM: bot-bottle's default macOS
|
|
# backend is Apple `container`, which runs each container in its own
|
|
# Virtualization.framework microVM. Running that backend inside a macOS
|
|
# guest VM needs nested virtualization, which Apple gates to M3+ silicon.
|
|
# On M1/M2 a separate user account is the only way to get a clean $HOME
|
|
# while still reaching the real host backend. Full rationale in
|
|
# docs/research/testing-clean-install-on-macos.md.
|
|
#
|
|
# Usage:
|
|
# sudo ./scripts/macos-install-test.sh test # up -> run -> status -> down
|
|
# sudo ./scripts/macos-install-test.sh test-ready # ... with prereqs satisfied
|
|
# sudo ./scripts/macos-install-test.sh up # create the throwaway user
|
|
# sudo ./scripts/macos-install-test.sh run # run install.sh (+doctor) as it
|
|
# sudo ./scripts/macos-install-test.sh prereqs # start the per-user backend svc
|
|
# ./scripts/macos-install-test.sh status # user present? backend ready?
|
|
# sudo ./scripts/macos-install-test.sh down # delete user + home (the reset)
|
|
# sudo ./scripts/macos-install-test.sh deep-reset # ALSO uninstall host `container`
|
|
#
|
|
# TWO TEST VARIANTS, because "does the installer work" and "can a new user
|
|
# actually run a bottle" are different questions with different answers:
|
|
#
|
|
# test A macOS system WITHOUT the prerequisites set up for this user —
|
|
# the default state of any brand-new account, since the Apple
|
|
# `container` service is per-user. Asserts the INSTALL is sound
|
|
# (entry point present, doctor runs without crashing, python and
|
|
# config check out). Backend readiness is reported, not required:
|
|
# install.sh does not provide a backend and cannot regress one,
|
|
# so failing on it would make this permanently red.
|
|
#
|
|
# test-ready A macOS system WITH the prerequisites satisfied — it runs
|
|
# `container system start` for the throwaway user first, then
|
|
# demands doctor go fully green, backend included. This is the
|
|
# end-to-end claim: a new user can actually run a bottle.
|
|
#
|
|
# Both refuse to start if the account already exists (a reused home is not a
|
|
# clean install), and both tear the account down on the way out however they
|
|
# exit, so a failed run never leaves an orphan behind. install.sh itself exits
|
|
# 0 when doctor reports unmet prerequisites, so either variant is a stricter
|
|
# gate than running the installer by hand.
|
|
#
|
|
# Config via env:
|
|
# BB_TEST_USER account short name (default: bbtest)
|
|
# BB_TEST_FULLNAME account full name (default: "bot-bottle install test")
|
|
# BB_TEST_ADMIN 1=admin (reach container svc), 0=standard (default: 1)
|
|
# BB_TEST_INSTALL_URL curl this install.sh instead of piping the local checkout
|
|
# BB_TEST_KEEP 1=`test` skips its teardown, to poke at a failure
|
|
# BB_TEST_REQUIRE_BACKEND 1=`test` also fails when no backend is ready
|
|
# BB_TEST_REPO_URL https repo the throwaway user clones (default: this one)
|
|
# BOT_BOTTLE_INSTALL_SPEC passed through to install.sh (pip / git spec)
|
|
#
|
|
# Notes:
|
|
# * Run from a normally-booted admin session. Grant Terminal *Full Disk
|
|
# Access* (System Settings -> Privacy & Security) or `down` half-fails
|
|
# with error -14120 and leaves an orphaned account.
|
|
# * `sysadminctl` always exits 0 even on failure, so `up`/`down` verify
|
|
# the result with `dscl` and fail loudly on a mismatch.
|
|
# * The account is created without a password: `run` drives it headlessly
|
|
# via `sudo -u`, which never needs the target's password. The account
|
|
# cannot GUI-login, which this harness does not require.
|
|
# * `run` covers the installer + `bot-bottle doctor`. Actually launching a
|
|
# bottle from the throwaway user may need a full launchd user session
|
|
# (`launchctl asuser`); on M1/M2 the backend can't run under nested virt
|
|
# anyway, so this harness stops at install + doctor.
|
|
|
|
set -euo pipefail
|
|
|
|
USER_NAME="${BB_TEST_USER:-bbtest}"
|
|
FULL_NAME="${BB_TEST_FULLNAME:-bot-bottle install test}"
|
|
ADMIN="${BB_TEST_ADMIN:-1}"
|
|
# https, not the ssh `origin`: the throwaway user has no key of ours.
|
|
BB_TEST_REPO_URL="${BB_TEST_REPO_URL:-https://gitea.dideric.is/didericis/bot-bottle.git}"
|
|
|
|
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
_REPO_ROOT="$(cd "$_SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Set by `test`, which chains the steps itself and so suppresses the
|
|
# "here's the next command to run" hints the individual steps print.
|
|
IN_TEST=0
|
|
|
|
# --- guards ----------------------------------------------------------
|
|
require_macos() {
|
|
[ "$(uname -s)" = "Darwin" ] \
|
|
|| { echo "error: this harness is macOS-only (uname is $(uname -s))" >&2; exit 1; }
|
|
}
|
|
|
|
require_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "error: '$1' needs root; re-run under sudo" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
user_exists() { dscl . -read "/Users/$USER_NAME" >/dev/null 2>&1; }
|
|
|
|
user_uid() {
|
|
dscl . -read "/Users/$USER_NAME" UniqueID 2>/dev/null | awk '{print $2}'
|
|
}
|
|
|
|
# Whether we can enter the target user's launchd domain.
|
|
#
|
|
# This matters more than it sounds. `container system start` registers
|
|
# com.apple.container.apiserver as a per-USER launchd agent and then talks to
|
|
# it over XPC. Under plain `sudo -u` the caller is still in root's bootstrap
|
|
# namespace, so the lookup crosses domains and the apiserver answers
|
|
# invalidState: "unauthorized request"
|
|
# even though it launched fine. `launchctl asuser <uid>` puts the whole
|
|
# invocation in that user's domain, which is also what a real user gets from
|
|
# Terminal — so it is the more faithful way to run *everything* here, not just
|
|
# the service start. Falls back to plain sudo when unavailable, since a
|
|
# never-logged-in account may have no bootstrappable domain at all.
|
|
_SESSION_OK=""
|
|
user_session_available() {
|
|
if [ -z "$_SESSION_OK" ]; then
|
|
local uid
|
|
uid="$(user_uid)"
|
|
if [ -n "$uid" ] && launchctl asuser "$uid" true >/dev/null 2>&1; then
|
|
_SESSION_OK=1
|
|
else
|
|
_SESSION_OK=0
|
|
fi
|
|
fi
|
|
[ "$_SESSION_OK" = "1" ]
|
|
}
|
|
|
|
# Run a shell snippet as the throwaway user in a fresh login shell.
|
|
#
|
|
# The snippet goes in on stdin, never as an argument. `sudo -i` joins its argv
|
|
# into a single string and hands that to the login shell's -c, so quoting in an
|
|
# argument is not preserved and a newline ends the command outright ("sh: -c:
|
|
# line 1: syntax error: unexpected end of file"). Feeding `sh -s` from stdin
|
|
# sidesteps the joining entirely and lets snippets be multi-line.
|
|
run_as_user() {
|
|
if user_session_available; then
|
|
printf '%s\n' "$1" | launchctl asuser "$(user_uid)" sudo -u "$USER_NAME" -i sh -s
|
|
else
|
|
printf '%s\n' "$1" | sudo -u "$USER_NAME" -i sh -s
|
|
fi
|
|
}
|
|
|
|
# `bot-bottle doctor` as the throwaway user. Non-zero when no entry point was
|
|
# installed at all, or when doctor itself is unhappy.
|
|
#
|
|
# Deliberately does NOT require `bot-bottle` on PATH: install.sh prints the
|
|
# PATH line rather than editing a shell profile, so on a fresh account the
|
|
# entry point is installed and working but not on PATH. Demanding PATH here
|
|
# would fail every run for a reason the installer intends.
|
|
#
|
|
# Doctor's own exit code conflates two unrelated things: whether the install
|
|
# works, and whether this user has a backend ready to run a bottle. Those need
|
|
# different verdicts here. install.sh explicitly does not install a backend,
|
|
# and the Apple `container` service is per-USER — its state lives in
|
|
# ~/Library/Application Support/com.apple.container — so a brand-new account
|
|
# never has one running, no matter how correct the install is. Failing on that
|
|
# would leave `test` permanently red for something the installer cannot fix
|
|
# and cannot regress. So: assert the install, report the backend.
|
|
# BB_TEST_REQUIRE_BACKEND=1 makes backend readiness fatal too.
|
|
doctor_as_user() {
|
|
local out rc=0 bad=0
|
|
out="$(mktemp "${TMPDIR:-/tmp}/bb-doctor.XXXXXX")"
|
|
# shellcheck disable=SC2016 # $HOME/$bb must expand in the *target* user's
|
|
# shell, not in this one — that's the whole point of the single quotes.
|
|
run_as_user '
|
|
for bb in bot-bottle "$HOME/.local/bin/bot-bottle" "$HOME/.bot-bottle/venv/bin/bot-bottle"; do
|
|
if command -v "$bb" >/dev/null 2>&1; then
|
|
case "$bb" in
|
|
bot-bottle) : ;;
|
|
*) echo " (not on PATH — running $bb directly, as install.sh advises)" ;;
|
|
esac
|
|
exec "$bb" doctor
|
|
fi
|
|
done
|
|
echo " no bot-bottle entry point found for this user" >&2
|
|
exit 1
|
|
' >"$out" 2>&1 || rc=$?
|
|
cat "$out"
|
|
|
|
# An unhandled exception is always an install/product defect, never an
|
|
# environment fact — this is exactly how the PermissionError on `ip` showed
|
|
# up, and doctor's non-zero exit alone would not have distinguished it.
|
|
if grep -q 'Traceback (most recent call last)' "$out"; then
|
|
echo " doctor crashed (traceback above) — a defect, not a missing prerequisite" >&2
|
|
bad=1
|
|
fi
|
|
grep -qE '^ok: +python:' "$out" \
|
|
|| { echo " doctor never reported a usable python" >&2; bad=1; }
|
|
grep -qE '^ok: +config:' "$out" \
|
|
|| { echo " doctor never reported a usable config dir" >&2; bad=1; }
|
|
if [ "$rc" -ne 0 ] && ! grep -qE '^(fail|warn): +backend' "$out"; then
|
|
echo " doctor failed for something other than backend readiness" >&2
|
|
bad=1
|
|
fi
|
|
|
|
local backend_ready=1
|
|
grep -qE '^fail: +backend' "$out" && backend_ready=0
|
|
rm -f "$out"
|
|
|
|
[ "$bad" -eq 0 ] || return 1
|
|
if [ "$backend_ready" -eq 0 ]; then
|
|
if [ "${BB_TEST_REQUIRE_BACKEND:-0}" = "1" ]; then
|
|
echo " no backend is ready and BB_TEST_REQUIRE_BACKEND=1" >&2
|
|
return 1
|
|
fi
|
|
echo " note: install is sound; no backend ready for this user."
|
|
echo " Expected on a fresh account — the Apple 'container' service is"
|
|
echo " per-user and needs one 'container system start'. Set"
|
|
echo " BB_TEST_REQUIRE_BACKEND=1 to treat this as a failure."
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# --- commands --------------------------------------------------------
|
|
cmd_up() {
|
|
require_macos
|
|
require_root up
|
|
if user_exists; then
|
|
echo "$USER_NAME already exists; nothing to do (run 'down' first to reset)"
|
|
return 0
|
|
fi
|
|
local admin_flag=()
|
|
[ "$ADMIN" = "1" ] && admin_flag=(-admin)
|
|
# No -password: the account is only ever driven headlessly via `sudo -u`,
|
|
# which doesn't need one. sysadminctl warns about FileVault here; that's
|
|
# irrelevant to a headless test account.
|
|
sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" "${admin_flag[@]}" || true
|
|
# sysadminctl exits 0 regardless of outcome, so confirm the account landed.
|
|
user_exists || { echo "error: failed to create $USER_NAME" >&2; return 1; }
|
|
if [ "$IN_TEST" = 1 ]; then
|
|
echo "created $USER_NAME (admin=$ADMIN)"
|
|
else
|
|
echo "created $USER_NAME (admin=$ADMIN). Install into it with: sudo $0 run"
|
|
fi
|
|
}
|
|
|
|
# The package spec to install. install.sh's own default is the repo's DEFAULT
|
|
# BRANCH, which would mean piping this checkout's installer into the throwaway
|
|
# user and then installing main's code — verifying the PR's install.sh against
|
|
# a package that doesn't contain the PR. Pin to the branch under test instead.
|
|
#
|
|
# The branch has to be pushed: the throwaway user clones over https and cannot
|
|
# read this checkout (mode-700 home, and no SSH key for the ssh remote), so
|
|
# what it gets is whatever the remote has. Say so when that differs from here.
|
|
resolve_install_spec() {
|
|
if [ -n "${BOT_BOTTLE_INSTALL_SPEC:-}" ]; then
|
|
echo "$BOT_BOTTLE_INSTALL_SPEC"
|
|
return 0
|
|
fi
|
|
local branch
|
|
branch="$(git -C "$_REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" || branch=""
|
|
if [ -z "$branch" ] || [ "$branch" = "HEAD" ]; then
|
|
echo "note: not on a named branch; installing the repo default" >&2
|
|
echo "git+${BB_TEST_REPO_URL}"
|
|
return 0
|
|
fi
|
|
if [ -n "$(git -C "$_REPO_ROOT" status --porcelain 2>/dev/null)" ]; then
|
|
echo "warning: working tree is dirty — the throwaway user installs" >&2
|
|
echo " $branch as pushed, not what is on disk here." >&2
|
|
elif ! git -C "$_REPO_ROOT" diff --quiet "@{upstream}" 2>/dev/null; then
|
|
echo "warning: $branch differs from its upstream — push first, or the" >&2
|
|
echo " throwaway user installs stale code." >&2
|
|
fi
|
|
echo "git+${BB_TEST_REPO_URL}@${branch}"
|
|
}
|
|
|
|
cmd_run() {
|
|
require_macos
|
|
require_root run
|
|
user_exists || { echo "error: $USER_NAME does not exist; run 'sudo $0 up' first" >&2; return 1; }
|
|
|
|
local spec
|
|
spec="$(resolve_install_spec)"
|
|
|
|
echo "== installing bot-bottle as $USER_NAME =="
|
|
echo "== spec: $spec =="
|
|
if [ -n "${BB_TEST_INSTALL_URL:-}" ]; then
|
|
run_as_user "BOT_BOTTLE_INSTALL_SPEC='$spec' curl -fsSL '$BB_TEST_INSTALL_URL' | sh"
|
|
else
|
|
# Test THIS checkout's install.sh, not the published one, so a PR is
|
|
# verifiable before it lands. Feed it in on stdin rather than staging a
|
|
# copy somewhere the throwaway user can read: the redirect is opened by
|
|
# root before sudo drops privileges, so the tester's mode-700 home is a
|
|
# non-issue, there's no temp file to leak if the run is interrupted, and
|
|
# `sh -s` is the same shape as the documented `curl … | sh` install.
|
|
#
|
|
# The spec is prepended to the stream as an export rather than passed
|
|
# as an argument, for the same argv-joining reason as run_as_user.
|
|
{
|
|
printf "export BOT_BOTTLE_INSTALL_SPEC='%s'\n" "$spec"
|
|
cat "$_REPO_ROOT/install.sh"
|
|
} | sudo -u "$USER_NAME" -i sh -s
|
|
fi
|
|
[ "$IN_TEST" = 1 ] \
|
|
|| echo "== install.sh runs 'doctor' itself; re-check anytime with: $0 status =="
|
|
}
|
|
|
|
# Informational, with one teeth-bearing case: when it can actually reach
|
|
# doctor (root, account present) its exit status is doctor's, so `test` and
|
|
# any other caller can use it as the post-install assertion.
|
|
cmd_status() {
|
|
require_macos
|
|
local rc=0
|
|
if user_exists; then
|
|
echo "user: $USER_NAME present"
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
echo "doctor (as $USER_NAME):"
|
|
doctor_as_user || rc=1
|
|
else
|
|
echo " (re-run under sudo to run 'bot-bottle doctor' as $USER_NAME)"
|
|
fi
|
|
else
|
|
echo "user: $USER_NAME absent"
|
|
fi
|
|
if command -v container >/dev/null 2>&1; then
|
|
echo "backend: apple 'container' present ($(container --version 2>/dev/null | head -1))"
|
|
else
|
|
echo "backend: apple 'container' NOT on PATH (host prerequisite; install once)"
|
|
fi
|
|
return "$rc"
|
|
}
|
|
|
|
cmd_down() {
|
|
require_macos
|
|
require_root down
|
|
if ! user_exists; then
|
|
echo "$USER_NAME not present; nothing to remove"
|
|
return 0
|
|
fi
|
|
# A plain -deleteUser removes the home dir, which is the whole reset.
|
|
# -secure is a no-op on modern macOS (secure erase of the home folder
|
|
# was removed in Sierra), so it buys nothing here.
|
|
sysadminctl -deleteUser "$USER_NAME" || true
|
|
if user_exists; then
|
|
echo "error: $USER_NAME still present after delete." >&2
|
|
echo " - grant Terminal Full Disk Access (System Settings > Privacy & Security), or" >&2
|
|
echo " - it may hold the last Secure Token (won't happen while another admin exists)" >&2
|
|
return 1
|
|
fi
|
|
echo "removed $USER_NAME and its home — install surface is clean."
|
|
}
|
|
|
|
# Satisfy the throwaway user's backend prerequisites.
|
|
#
|
|
# `bot-bottle backend setup --backend=macos-container` deliberately does NOT do
|
|
# this: it only checks, then tells you to run `container system start` yourself
|
|
# (see backend/macos_container/setup.py — "no privileged host setup required").
|
|
# So the harness runs the command the product asks for, as the user who needs
|
|
# it, which is the whole prerequisite for the macOS backend given the CLI is
|
|
# already installed host-wide.
|
|
cmd_prereqs() {
|
|
require_macos
|
|
require_root prereqs
|
|
user_exists || { echo "error: $USER_NAME does not exist" >&2; return 1; }
|
|
command -v container >/dev/null 2>&1 || {
|
|
echo "error: the Apple 'container' CLI is not on PATH. It is a HOST" >&2
|
|
echo " prerequisite this harness does not install; see the header." >&2
|
|
return 1
|
|
}
|
|
echo "starting the per-user Apple 'container' service for $USER_NAME"
|
|
user_session_available || {
|
|
echo "warning: no launchd session for $USER_NAME; the service start is" >&2
|
|
echo " likely to fail with 'unauthorized request'. See user_session_available." >&2
|
|
}
|
|
run_as_user 'container system start' || {
|
|
echo "error: 'container system start' failed for $USER_NAME" >&2
|
|
echo " If this said invalidState/\"unauthorized request\", the CLI could not" >&2
|
|
echo " reach its per-user apiserver over XPC. A never-GUI-logged-in account" >&2
|
|
echo " may not have a usable launchd domain; logging into $USER_NAME once" >&2
|
|
echo " (fast user switching) gives it one." >&2
|
|
return 1
|
|
}
|
|
run_as_user 'container system status' || {
|
|
echo "error: the service is still not running for $USER_NAME" >&2
|
|
return 1
|
|
}
|
|
}
|
|
|
|
# Teardown half of the test cycles, installed as an EXIT trap the moment the
|
|
# account exists so a failure — or a Ctrl-C — still leaves the machine clean.
|
|
_test_teardown() {
|
|
local rc=$?
|
|
trap - EXIT INT TERM
|
|
if [ "${BB_TEST_KEEP:-0}" = "1" ]; then
|
|
echo
|
|
echo "== [$_STEPS/$_STEPS] down: SKIPPED (BB_TEST_KEEP=1) =="
|
|
echo " $USER_NAME is still around; remove it with: sudo $0 down"
|
|
exit "$rc"
|
|
fi
|
|
echo
|
|
echo "== [$_STEPS/$_STEPS] down =="
|
|
cmd_down || rc=1
|
|
if [ "$rc" -eq 0 ]; then
|
|
echo
|
|
echo "PASS: $_PASS_CLAIM"
|
|
else
|
|
echo
|
|
echo "FAIL: see above (the throwaway account was torn down regardless)." >&2
|
|
fi
|
|
exit "$rc"
|
|
}
|
|
|
|
# The two variants differ only in whether the backend prerequisites get
|
|
# satisfied before doctor runs, which is exactly the question each one asks:
|
|
#
|
|
# test a brand-new user on a machine where nothing has been set up for
|
|
# them. Asserts the INSTALL is sound; backend readiness is
|
|
# reported, not required, because install.sh does not provide a
|
|
# backend and cannot regress one.
|
|
# test-ready the same user with the documented prerequisites satisfied.
|
|
# Asserts doctor goes fully green, backend included — the
|
|
# end-to-end "a new user can actually run a bottle" claim.
|
|
_test_cycle() {
|
|
local with_prereqs="$1"
|
|
require_macos
|
|
require_root test
|
|
# A pre-existing account means a pre-existing home, which is the one thing
|
|
# this harness exists to rule out. Don't silently test a dirty install.
|
|
if user_exists; then
|
|
echo "error: $USER_NAME already exists, so this would not be a clean install." >&2
|
|
echo " reset first: sudo $0 down" >&2
|
|
return 1
|
|
fi
|
|
IN_TEST=1
|
|
|
|
echo "== [1/$_STEPS] up =="
|
|
cmd_up
|
|
trap _test_teardown EXIT INT TERM
|
|
|
|
echo
|
|
echo "== [2/$_STEPS] run =="
|
|
cmd_run
|
|
|
|
if [ "$with_prereqs" = 1 ]; then
|
|
echo
|
|
echo "== [3/$_STEPS] prereqs =="
|
|
cmd_prereqs || {
|
|
echo "error: could not satisfy the backend prerequisites (see above)." >&2
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
echo
|
|
echo "== [$((_STEPS - 1))/$_STEPS] status =="
|
|
# install.sh exits 0 even when doctor reports unmet prerequisites, so the
|
|
# install succeeding is not the verdict — this is.
|
|
cmd_status || {
|
|
echo "error: doctor is unhappy for a freshly installed user (see above)." >&2
|
|
echo " re-run with BB_TEST_KEEP=1 to keep $USER_NAME around and dig in." >&2
|
|
return 1
|
|
}
|
|
}
|
|
|
|
cmd_test() {
|
|
_STEPS=4
|
|
_PASS_CLAIM="a brand-new user can install bot-bottle; the install is sound."
|
|
_test_cycle 0
|
|
}
|
|
|
|
cmd_test_ready() {
|
|
_STEPS=5
|
|
_PASS_CLAIM="a brand-new user with the prerequisites met passes doctor outright."
|
|
# With the prerequisites satisfied there is no excuse for an unready
|
|
# backend, so this variant demands the thing `test` only reports.
|
|
BB_TEST_REQUIRE_BACKEND=1
|
|
_test_cycle 1
|
|
}
|
|
|
|
cmd_deep_reset() {
|
|
require_macos
|
|
require_root deep-reset
|
|
# Remove the user first (idempotent), then the HOST-level container
|
|
# runtime that a user deletion leaves behind under /usr/local + launchd.
|
|
cmd_down || true
|
|
if command -v container >/dev/null 2>&1; then
|
|
# The service can run in more than one launchd context (the invoking
|
|
# user's and root's), so stop both, best-effort.
|
|
[ -n "${SUDO_USER:-}" ] && sudo -u "$SUDO_USER" container system stop 2>/dev/null || true
|
|
container system stop 2>/dev/null || true
|
|
if [ -x /usr/local/bin/uninstall-container.sh ]; then
|
|
/usr/local/bin/uninstall-container.sh -d || true
|
|
echo "uninstalled the host Apple 'container' runtime"
|
|
else
|
|
echo "note: /usr/local/bin/uninstall-container.sh not found; runtime left as-is" >&2
|
|
fi
|
|
else
|
|
echo "no 'container' runtime on PATH; nothing further to remove"
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
test) cmd_test ;;
|
|
test-ready) cmd_test_ready ;;
|
|
up) cmd_up ;;
|
|
run) cmd_run ;;
|
|
prereqs) cmd_prereqs ;;
|
|
status) cmd_status ;;
|
|
down) cmd_down ;;
|
|
deep-reset) cmd_deep_reset ;;
|
|
*) echo "usage: $0 {test|test-ready|up|run|prereqs|status|down|deep-reset}" >&2 ; exit 2 ;;
|
|
esac
|