Nucleic-Session: 4FCF4F8B-A7C3-42F6-BE13-1979080F61C7 Co-authored-by: Nucleic <[email protected]>
387 lines
24 KiB
Bash
Executable File
387 lines
24 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Turn a freshly-installed macOS guest into Nucleic's GOLDEN base image.
|
|
#
|
|
# ./provision-macos-guest.sh [/path/to/nucleic-id_ed25519.pub]
|
|
#
|
|
# THIS RUNS INSIDE THE VM, not on the host. It's the second half of the "install + provision locally"
|
|
# base flow — scripts/build-macos-base.sh installs a clean macOS and drops you at Setup Assistant;
|
|
# after you create the `agent` account by hand, you copy THIS script (plus Nucleic's host PUBLIC key)
|
|
# into the guest and run it as `agent`. It:
|
|
#
|
|
# 1. Enables Remote Login (sshd) so Nucleic can reach the guest as agent@<ip>.
|
|
# 2. Authorizes Nucleic's host public key in ~agent/.ssh/authorized_keys.
|
|
# 3. Installs the Xcode Command Line Tools (the minimum toolchain) and documents the FULL-Xcode
|
|
# path via `xcodes` (full Xcode + simulators need an Apple ID / manual download).
|
|
# 4. Installs Homebrew + node, python, git, and common dev tools.
|
|
# 5. CRITICAL: appends the toolchain PATH to /etc/zshenv so NON-login, non-interactive SSH shells
|
|
# (how the engine runs commands) can see node/npm/python/xcodebuild. .zprofile/.zshrc are NOT
|
|
# sourced by those shells; /etc/zshenv always is.
|
|
# 6. Warms the iOS simulator.
|
|
# 7. COMPUTER USE (GUI automation): installs cliclick at /usr/local/bin, enables auto-login for the
|
|
# agent (so a headless boot has an Aqua session), and pre-grants Screen Recording + Accessibility
|
|
# in the SYSTEM TCC.db so screencapture/cliclick work unattended (needs SIP off — see Phase 7).
|
|
# 8. Shuts the guest DOWN cleanly so the base can be cloned.
|
|
#
|
|
# Run it as the `agent` user (it will `sudo` for the system-level bits). It's idempotent — safe to
|
|
# re-run. Some steps (Remote Login, first-launch) may prompt or need Full Disk Access for Terminal.
|
|
|
|
set -euo pipefail
|
|
|
|
# The account Nucleic logs in as. Must match MacVMSettings.defaultSSHUser; also the account you
|
|
# created in Setup Assistant. Provision under this user even if invoked via sudo.
|
|
AGENT_USER="agent"
|
|
AGENT_HOME="$(/usr/bin/dscl . -read "/Users/$AGENT_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')"
|
|
AGENT_HOME="${AGENT_HOME:-/Users/$AGENT_USER}"
|
|
|
|
# Nucleic's host PUBLIC key to authorize. Prefer the script argument; else a copy dropped next to the
|
|
# script or into the agent home; else the shared workspace (virtiofs) if the host staged it there.
|
|
PUBKEY_ARG="${1:-}"
|
|
SHARED_WORKSPACE="/Volumes/My Shared Files/workspace"
|
|
|
|
echo "▸ Provisioning this macOS guest as Nucleic's golden base (user: $AGENT_USER, home: $AGENT_HOME)"
|
|
|
|
# ── Phase 0: sanity ──────────────────────────────────────────────────────────────────────────────
|
|
if [ "$(uname -s)" != "Darwin" ]; then
|
|
echo "✗ this script runs INSIDE a macOS guest, not on Linux/other." >&2
|
|
exit 1
|
|
fi
|
|
if ! /usr/bin/dscl . -read "/Users/$AGENT_USER" >/dev/null 2>&1; then
|
|
echo "✗ account '$AGENT_USER' does not exist. Create it in Setup Assistant first (short name: $AGENT_USER)." >&2
|
|
exit 1
|
|
fi
|
|
# We sudo repeatedly; prompt once up front so the rest flows.
|
|
echo "▸ This uses sudo for system settings — you may be prompted for the $AGENT_USER password."
|
|
sudo -v
|
|
|
|
# ── Phase 1: enable Remote Login (sshd) ──────────────────────────────────────────────────────────
|
|
# Nucleic reaches the guest over SSH; without this the base is unreachable. On recent macOS,
|
|
# `systemsetup -setremotelogin on` may require the caller (Terminal) to have Full Disk Access and can
|
|
# prompt interactively — if it fails, enable it by hand in System Settings ▸ General ▸ Sharing.
|
|
echo "▸ [1/8] Enabling Remote Login (sshd) …"
|
|
if sudo systemsetup -setremotelogin on 2>/dev/null; then
|
|
echo " ✓ Remote Login on."
|
|
else
|
|
echo " ⚠ Could not toggle Remote Login via systemsetup (needs Full Disk Access for Terminal, or a"
|
|
echo " GUI prompt). Enable it manually: System Settings ▸ General ▸ Sharing ▸ Remote Login." >&2
|
|
fi
|
|
|
|
# ── Phase 2: authorize Nucleic's host public key ─────────────────────────────────────────────────
|
|
echo "▸ [2/8] Installing Nucleic's host public key into $AGENT_HOME/.ssh/authorized_keys …"
|
|
PUBKEY_SRC=""
|
|
if [ -n "$PUBKEY_ARG" ] && [ -f "$PUBKEY_ARG" ]; then
|
|
PUBKEY_SRC="$PUBKEY_ARG"
|
|
else
|
|
# Look in a few obvious places the operator may have staged the key.
|
|
for cand in \
|
|
"$(cd "$(dirname "$0")" && pwd)/id_ed25519.pub" \
|
|
"$(cd "$(dirname "$0")" && pwd)/nucleic-id_ed25519.pub" \
|
|
"$AGENT_HOME/id_ed25519.pub" \
|
|
"$AGENT_HOME/nucleic-id_ed25519.pub" \
|
|
"$SHARED_WORKSPACE/id_ed25519.pub"; do
|
|
[ -f "$cand" ] && { PUBKEY_SRC="$cand"; break; }
|
|
done
|
|
fi
|
|
|
|
if [ -z "$PUBKEY_SRC" ]; then
|
|
echo "✗ No Nucleic public key found. Pass it as the first argument, e.g." >&2
|
|
echo " ./provision-macos-guest.sh /path/to/id_ed25519.pub" >&2
|
|
echo " The host prints its path (…/Nucleic/macvms/ssh/id_ed25519.pub) in build-macos-base.sh." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Basic shape check so we don't authorize garbage.
|
|
if ! grep -Eq '^(ssh-ed25519|ssh-rsa|ecdsa-|sk-) ' "$PUBKEY_SRC"; then
|
|
echo "✗ $PUBKEY_SRC doesn't look like an OpenSSH public key." >&2
|
|
exit 1
|
|
fi
|
|
|
|
SSH_DIR="$AGENT_HOME/.ssh"
|
|
AUTH_KEYS="$SSH_DIR/authorized_keys"
|
|
mkdir -p "$SSH_DIR"
|
|
touch "$AUTH_KEYS"
|
|
# Idempotent: only append if this exact key isn't already authorized.
|
|
KEY_LINE="$(tr -d '\r' < "$PUBKEY_SRC")"
|
|
if grep -qxF "$KEY_LINE" "$AUTH_KEYS"; then
|
|
echo " ✓ key already authorized."
|
|
else
|
|
printf '%s\n' "$KEY_LINE" >> "$AUTH_KEYS"
|
|
echo " ✓ key appended."
|
|
fi
|
|
# Ownership + perms sshd insists on (StrictModes): 700 dir, 600 file, owned by agent.
|
|
chmod 700 "$SSH_DIR"
|
|
chmod 600 "$AUTH_KEYS"
|
|
sudo chown -R "$AGENT_USER":staff "$SSH_DIR"
|
|
|
|
# ── Phase 3: Xcode Command Line Tools (the minimum toolchain) ────────────────────────────────────
|
|
# The CLT give us clang, git, and (crucially) `xcodebuild`/`xcrun`/`simctl`. FULL Xcode + iOS
|
|
# simulators are a separate, larger, Apple-ID-gated download — automated below via `xcodes` but left
|
|
# for the operator to trigger, since it needs credentials.
|
|
echo "▸ [3/8] Installing the Xcode Command Line Tools …"
|
|
if xcode-select -p >/dev/null 2>&1; then
|
|
echo " ✓ CLT already installed at $(xcode-select -p)."
|
|
else
|
|
# `xcode-select --install` shows a GUI installer; `softwareupdate` can do it headless on newer OSes.
|
|
# Use the softwareupdate "label" trick so this can complete without a click when possible.
|
|
touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress 2>/dev/null || true
|
|
CLT_LABEL="$(softwareupdate -l 2>/dev/null \
|
|
| grep -E 'Command Line Tools' | tail -1 | sed -E 's/^[* ]*Label: //')" || true
|
|
if [ -n "${CLT_LABEL:-}" ]; then
|
|
echo " installing via softwareupdate: $CLT_LABEL"
|
|
sudo softwareupdate -i "$CLT_LABEL" --verbose || xcode-select --install || true
|
|
else
|
|
echo " triggering the GUI CLT installer (complete it in the VM window if it appears) …"
|
|
xcode-select --install || true
|
|
fi
|
|
rm -f /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress 2>/dev/null || true
|
|
fi
|
|
|
|
# Accept license + run first-launch for whatever developer tools are selected. Harmless if CLT-only.
|
|
sudo xcodebuild -runFirstLaunch 2>/dev/null || true
|
|
sudo xcodebuild -license accept 2>/dev/null || true
|
|
|
|
# ── Phase 4: Homebrew + dev tools ────────────────────────────────────────────────────────────────
|
|
echo "▸ [4/8] Installing Homebrew + dev tools …"
|
|
# Apple silicon Homebrew lives in /opt/homebrew. Install non-interactively if absent.
|
|
if [ -x /opt/homebrew/bin/brew ]; then
|
|
BREW=/opt/homebrew/bin/brew
|
|
elif [ -x /usr/local/bin/brew ]; then
|
|
BREW=/usr/local/bin/brew
|
|
else
|
|
echo " installing Homebrew (non-interactive) …"
|
|
NONINTERACTIVE=1 /bin/bash -c \
|
|
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
BREW=/opt/homebrew/bin/brew
|
|
[ -x "$BREW" ] || BREW=/usr/local/bin/brew
|
|
fi
|
|
eval "$("$BREW" shellenv)"
|
|
|
|
echo " installing packages via brew …"
|
|
# node → node/npm/npx; python → python3/pip3; git + common CLI dev tools. `brew install` is idempotent.
|
|
"$BREW" install node python git jq ripgrep coreutils || true
|
|
# Tooling for the FULL Xcode path (documented below): `xcodes` + `aria2` for fast, resumable downloads.
|
|
"$BREW" install xcodesorg/made/xcodes aria2 2>/dev/null || "$BREW" install xcodes aria2 || true
|
|
|
|
cat <<'EOF'
|
|
── FULL Xcode + iOS simulators (optional, needs an Apple ID) ─────────────────────────────────
|
|
The CLT above are the MINIMUM. To build/run iOS apps in this guest you need full Xcode + a
|
|
simulator runtime. That download is Apple-ID-gated, so it can't be fully unattended here. With
|
|
`xcodes` + `aria2` now installed, run (inside the guest, as agent):
|
|
xcodes install --latest # prompts for your Apple ID; installs & selects Xcode
|
|
sudo xcodebuild -runFirstLaunch # installs bundled components
|
|
xcodes runtimes --include-unreleased | grep iOS
|
|
xcodes runtimes install "iOS 18.0" # or whatever simulator runtime you need
|
|
──────────────────────────────────────────────────────────────────────────────────────────────
|
|
EOF
|
|
|
|
# ── Phase 5: CRITICAL — toolchain PATH into /etc/zshenv ──────────────────────────────────────────
|
|
# Nucleic runs commands over SSH via the guest's default shell (zsh) in NON-login, non-interactive
|
|
# mode. Those shells source ONLY /etc/zshenv (not /etc/zprofile, ~/.zprofile, or ~/.zshrc), so PATH
|
|
# additions MUST live in /etc/zshenv or the engine won't find node/npm/python/xcodebuild. This is the
|
|
# single most load-bearing step in provisioning — get it wrong and remote builds fail with "command
|
|
# not found" despite the tools being installed.
|
|
echo "▸ [5/8] Appending toolchain PATH to /etc/zshenv (for non-login SSH shells) …"
|
|
BREW_PREFIX="$("$BREW" --prefix)" # /opt/homebrew (arm64) or /usr/local
|
|
ZSHENV=/etc/zshenv
|
|
MARKER="# >>> nucleic macvm toolchain PATH >>>"
|
|
END_MARKER="# <<< nucleic macvm toolchain PATH <<<"
|
|
# Idempotent: strip any existing Nucleic block first, then append a fresh one — so re-runs never
|
|
# duplicate the block or leave a stale PATH. awk matches the markers as FIXED strings (index()),
|
|
# avoiding all the regex-escaping hazards of doing this with sed.
|
|
if [ -f "$ZSHENV" ] && grep -qF "$MARKER" "$ZSHENV"; then
|
|
echo " ✓ /etc/zshenv already has the Nucleic PATH block (refreshing it)."
|
|
TMP_ZSHENV="$(mktemp)"
|
|
awk -v start="$MARKER" -v end="$END_MARKER" '
|
|
index($0, start) { skip = 1 }
|
|
!skip { print }
|
|
index($0, end) { skip = 0 }
|
|
' "$ZSHENV" > "$TMP_ZSHENV"
|
|
sudo cp "$TMP_ZSHENV" "$ZSHENV"
|
|
rm -f "$TMP_ZSHENV"
|
|
fi
|
|
sudo tee -a "$ZSHENV" >/dev/null <<EOF
|
|
$MARKER
|
|
# Added by scripts/provision-macos-guest.sh so Nucleic's non-login SSH zsh shells find the toolchain.
|
|
export PATH="$BREW_PREFIX/bin:$BREW_PREFIX/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:\$PATH"
|
|
$END_MARKER
|
|
EOF
|
|
echo " ✓ PATH written to $ZSHENV (prefix: $BREW_PREFIX)."
|
|
|
|
# ── Phase 6: warm the simulator + shut down clean ────────────────────────────────────────────────
|
|
# Pre-accept / warm the iOS simulator first-launch so the first real agent turn isn't slowed by it.
|
|
# Harmless (and skipped) if only the CLT are installed and no runtimes exist yet.
|
|
echo "▸ [6/8] Warming the iOS simulator (best effort) …"
|
|
xcrun simctl list >/dev/null 2>&1 && echo " ✓ simctl responsive." || echo " (simctl not available yet — full Xcode/runtimes not installed.)"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════════════════════════
|
|
# ── Phase 7: COMPUTER USE (GUI automation) ────────────────────────────────────────────────────────
|
|
# ══════════════════════════════════════════════════════════════════════════════════════════════════
|
|
# This phase makes the base drivable by Nucleic's `mac_vm_computer` MCP tool, which SSHes in and
|
|
# dispatches screen/input commands into the console user's Aqua GUI session:
|
|
#
|
|
# launchctl asuser $(id -u) <ABSOLUTE-PATH> …
|
|
#
|
|
# using screencapture for screenshots (base64'd back), cliclick for mouse/keyboard, and `open -a` for
|
|
# apps. Three things must be true in the guest for that to work; this phase sets up all three,
|
|
# IDEMPOTENTLY:
|
|
#
|
|
# (a) cliclick lives at the CANONICAL path /usr/local/bin/cliclick (the engine calls it there, and
|
|
# TCC attributes Accessibility to that exact path);
|
|
# (b) `agent` AUTO-LOGS-IN, so a headless boot has an active Aqua session (screencapture and
|
|
# cliclick have no GUI session to talk to otherwise);
|
|
# (c) Screen Recording + Accessibility are PRE-GRANTED in the SYSTEM TCC.db — the only unattended
|
|
# way to do which is SIP-off + a direct sqlite3 write (see the honest caveats below).
|
|
#
|
|
# See docs/MACOS_VM.md §14 (Computer use) for the full picture and the SIP/TCC reality.
|
|
echo "▸ [7/8] Setting up computer use (cliclick + auto-login + TCC pre-grant) …"
|
|
|
|
# ── 7a: cliclick at the canonical /usr/local/bin/cliclick ─────────────────────────────────────────
|
|
# Homebrew on Apple silicon installs to /opt/homebrew/bin, but the Swift engine calls cliclick at the
|
|
# EXACT path /usr/local/bin/cliclick, and TCC must attribute Accessibility to that exact path. So we
|
|
# `brew install` it, then COPY it (NOT a symlink — a copy preserves the code signature and gives a
|
|
# real file at the TCC-granted path; a symlink's target path can confuse TCC attribution).
|
|
echo " ▸ [7a] Installing cliclick and copying it to /usr/local/bin/cliclick …"
|
|
"$BREW" install cliclick 2>/dev/null || "$BREW" install cliclick || true
|
|
CLICLICK_SRC="$("$BREW" --prefix)/bin/cliclick"
|
|
if [ -x "$CLICLICK_SRC" ]; then
|
|
sudo mkdir -p /usr/local/bin
|
|
sudo cp "$CLICLICK_SRC" /usr/local/bin/cliclick
|
|
if /usr/local/bin/cliclick p >/dev/null 2>&1; then
|
|
echo " ✓ /usr/local/bin/cliclick installed (from $CLICLICK_SRC) and responsive."
|
|
else
|
|
echo " ⚠ /usr/local/bin/cliclick exists but 'cliclick p' failed — it may need Accessibility (7c)." >&2
|
|
fi
|
|
else
|
|
echo " ✗ brew did not produce cliclick at $CLICLICK_SRC — install it by hand, then re-run." >&2
|
|
fi
|
|
|
|
# ── 7b: auto-login for agent (so a headless boot has an Aqua session) ─────────────────────────────
|
|
# screencapture and cliclick need a LIVE console GUI (Aqua) session. A headless clone that boots to
|
|
# the login window has none, so screenshots come back black and clicks no-op. Auto-login lands `agent`
|
|
# straight into the desktop on every boot.
|
|
#
|
|
# ⚠ FileVault MUST be OFF for this to work — auto-login is fundamentally incompatible with FileVault
|
|
# (FV requires the password at pre-boot to unlock the disk, which defeats auto-login). The base is
|
|
# a disposable clone, not a device holding secrets, so FileVault-off is the right trade here.
|
|
#
|
|
# The password comes from $NUCLEIC_AGENT_PW, else the 2nd script arg ($2). We do NOT hardcode it.
|
|
echo " ▸ [7b] Enabling auto-login for '$AGENT_USER' …"
|
|
AGENT_PW="${NUCLEIC_AGENT_PW:-${2:-}}"
|
|
if [ -z "$AGENT_PW" ]; then
|
|
echo " ⚠ No agent password provided (set \$NUCLEIC_AGENT_PW or pass it as the 2nd arg) — SKIPPING"
|
|
echo " auto-login. Without it, a headless boot has no Aqua session and computer use won't work."
|
|
echo " Re-run as: ./provision-macos-guest.sh /path/to/id_ed25519.pub 'AGENT_PASSWORD'" >&2
|
|
else
|
|
if sudo sysadminctl -autologin set -userName "$AGENT_USER" -password "$AGENT_PW" 2>/dev/null; then
|
|
echo " ✓ auto-login set for $AGENT_USER."
|
|
sudo sysadminctl -autologin status 2>&1 | sed 's/^/ /' || true
|
|
else
|
|
echo " ⚠ sysadminctl could not set auto-login (is FileVault ON? it must be OFF)." >&2
|
|
fi
|
|
fi
|
|
|
|
# ── 7c: pre-grant TCC (Screen Recording + Accessibility) ──────────────────────────────────────────
|
|
# This is the hard part, and we're honest about it. The two grants we need:
|
|
#
|
|
# • kTCCServiceScreenCapture for /usr/sbin/screencapture (so `screencapture -x` isn't black)
|
|
# • kTCCServiceAccessibility for /usr/local/bin/cliclick (so cliclick can post events)
|
|
#
|
|
# Both live in the SYSTEM TCC db at /Library/Application Support/com.apple.TCC/TCC.db (NOT the per-user
|
|
# ~/Library one — Screen Recording & Accessibility are system-scoped).
|
|
#
|
|
# HONEST REALITY about unattended granting:
|
|
# • `tccutil` CANNOT grant — it only RESETs. There is no supported CLI to *add* a TCC grant.
|
|
# • PPPC/MDM profiles can pre-approve Accessibility, but can NEVER silently grant Screen Recording
|
|
# (Apple deliberately forces a manual user click for kTCCServiceScreenCapture — a PPPC payload for
|
|
# it only PRE-CONFIGURES the toggle, it does not turn it on).
|
|
# • The ONLY unattended path is: SIP DISABLED in the guest, then a direct sqlite3 INSERT into the
|
|
# system TCC.db. SIP protects that db from writes; with SIP on, the INSERT is denied.
|
|
#
|
|
# And `csrutil disable` is a ONE-TIME MANUAL step: it must run from recoveryOS, which has NO SSH and
|
|
# no scripting hook — a human boots the BASE VM into recoveryOS once and runs `csrutil disable`. So
|
|
# below we DETECT whether SIP is off; if it's on we print exactly what to do and SKIP gracefully
|
|
# (never failing the whole provisioner over it).
|
|
echo " ▸ [7c] Pre-granting TCC (Screen Recording + Accessibility) in the system TCC.db …"
|
|
|
|
SIP_OFF=0
|
|
if csrutil status 2>/dev/null | grep -qiE 'disabled|System Integrity Protection status: disabled'; then
|
|
SIP_OFF=1
|
|
fi
|
|
|
|
if [ "$SIP_OFF" -ne 1 ]; then
|
|
echo " ⚠ SIP is ENABLED — cannot write the system TCC.db, so computer-use TCC grants are SKIPPED."
|
|
echo " This is a ONE-TIME MANUAL step (recoveryOS has no SSH, so it can't be scripted):"
|
|
echo " 1. Fully shut down this guest."
|
|
echo " 2. Boot the BASE VM into recoveryOS (hold the power button / Options at boot in the"
|
|
echo " VZ window), open Terminal, and run: csrutil disable"
|
|
echo " 3. Reboot back into macOS and RE-RUN this provisioner — it will then grant TCC here."
|
|
echo " (Screen Recording in particular has NO other unattended grant path — PPPC/MDM can't do"
|
|
echo " it, and tccutil can only reset, never add.)" >&2
|
|
else
|
|
echo " ✓ SIP is disabled — writing TCC grants directly into the system TCC.db."
|
|
|
|
TCC_DB="/Library/Application Support/com.apple.TCC/TCC.db"
|
|
|
|
# grant_tcc <service> <abs-binary-path>: build the binary's code-signing designated requirement,
|
|
# compile it to a csreq blob, and INSERT (or REPLACE) a fully NAMED-COLUMN row allowing that
|
|
# service for that client path. We ALWAYS name columns because macOS keeps adding columns to
|
|
# `access` across releases (26 added several) — a positional INSERT breaks on the next OS. The
|
|
# csreq is OS-build-specific, so it's (re)generated here per build rather than hardcoded.
|
|
#
|
|
# client_type = 1 → the client is identified by ABSOLUTE PATH (0 would be a bundle id)
|
|
# auth_value = 2 → ALLOWED (0 denied, 1 unknown, 2 allowed, 3 limited)
|
|
# auth_reason = 4 → system set / MDM-style (a plausible provenance for a pre-grant)
|
|
# auth_version= 1 → current row schema version
|
|
# flags = 0
|
|
grant_tcc() { # $1=service $2=abs-binary-path
|
|
local req hexreq
|
|
# Extract the designated requirement; on some platform binaries this yields nothing usable.
|
|
req=$(codesign -d -r- "$2" 2>&1 | sed -n 's/^designated => //p')
|
|
if [ -n "$req" ] && echo "$req" | csreq -r- -b /tmp/csreq.bin 2>/dev/null; then
|
|
hexreq="X'$(xxd -p /tmp/csreq.bin | tr -d '\n')'"
|
|
echo " • $1 ← $2 (with code requirement)"
|
|
else
|
|
# FALLBACK: /usr/sbin/screencapture is a platform binary whose `codesign -d -r-` can come back
|
|
# empty/unusable. A NULL csreq still allows by PATH on many builds — TCC matches client_type=1
|
|
# rows on the client path alone when csreq is NULL. Less strict, but it unblocks the grant.
|
|
hexreq="NULL"
|
|
echo " • $1 ← $2 (csreq NULL fallback — no usable designated requirement)"
|
|
fi
|
|
sudo sqlite3 "$TCC_DB" \
|
|
"INSERT OR REPLACE INTO access
|
|
(service,client,client_type,auth_value,auth_reason,auth_version,csreq,policy_id,indirect_object_identifier_type,indirect_object_identifier,indirect_object_code_identity,flags,last_modified)
|
|
VALUES('$1','$2',1,2,4,1,$hexreq,NULL,0,'UNUSED',NULL,0,strftime('%s','now'));"
|
|
}
|
|
|
|
grant_tcc kTCCServiceScreenCapture /usr/sbin/screencapture
|
|
grant_tcc kTCCServiceAccessibility /usr/local/bin/cliclick
|
|
|
|
# Bounce tccd so it reloads the db and the fresh grants take effect without a reboot.
|
|
sudo killall tccd 2>/dev/null || true
|
|
rm -f /tmp/csreq.bin 2>/dev/null || true
|
|
|
|
echo " ✓ TCC grants written. Current computer-use rows:"
|
|
sudo sqlite3 "$TCC_DB" \
|
|
"SELECT service,client,auth_value FROM access WHERE service IN ('kTCCServiceScreenCapture','kTCCServiceAccessibility');" \
|
|
2>/dev/null | sed 's/^/ /' || true
|
|
fi
|
|
|
|
echo ""
|
|
echo " ── Computer use needs ALL THREE of the above (cliclick + auto-login + TCC) ─────────────────"
|
|
echo " If screenshots come back BLACK or clicks NO-OP after this base is live, check, in the guest:"
|
|
echo " • SIP is disabled: csrutil status"
|
|
echo " • TCC rows exist & allowed: sudo sqlite3 \"/Library/Application Support/com.apple.TCC/TCC.db\" \\"
|
|
echo " \"SELECT service,client,auth_value FROM access;\""
|
|
echo " • agent is auto-logged-in: an Aqua/desktop session is active (not the login window)"
|
|
echo " ──────────────────────────────────────────────────────────────────────────────────────────────"
|
|
|
|
echo ""
|
|
echo "────────────────────────────────────────────────────────────────────────────────────────────────"
|
|
echo "✓ Provisioning complete. This guest is now Nucleic's golden base."
|
|
echo " Shutting DOWN cleanly so the host can clone the base bundle (a running/dirty guest can't be)."
|
|
echo " After it powers off, verify from the host with the default macvm-spike round-trip."
|
|
echo "────────────────────────────────────────────────────────────────────────────────────────────────"
|
|
echo "▸ [8/8] Shutting down …"
|
|
# `shutdown -h now` requires root; run last so nothing after it matters.
|
|
sudo shutdown -h now
|