Nix PATH preservation, amfidont boot, and preflight stability (#196)

* fix: preserve caller PATH through Nix zshenv reset in cfw scripts

Nix darwin's /etc/zshenv resets PATH on every zsh subprocess,
discarding the Makefile's carefully constructed PATH (which includes
.venv/bin and /opt/homebrew/bin). This caused 'Missing Python deps'
and ldid PKCS12_parse errors during cfw_install.

Pass the Makefile PATH through _VPHONE_PATH env var (which zshenv
won't touch), and restore it at the top of each cfw_install script.

* fix(cfw_install_dev): add python resolver, use glob for vphoned sources

- Add _resolve_python3() matching cfw_install.sh so the venv python
  is used instead of Nix system python (which lacks capstone/keystone).
- Replace hardcoded VPHONED_SRCS list with glob pattern to auto-pick
  up new .m files (was missing 5 files: accessibility, apps, clipboard,
  settings, url — causing linker errors).

* fix: amfidont uses bundle binary CDHash and .build path

make boot launches the bundle binary (.build/vphone-cli.app/Contents/
MacOS/vphone-cli), not the release binary. amfidont's --path must
cover the .app bundle location.

- amfidont_allow_vphone depends on bundle (not build)
- start_amfidont_for_vphone.sh extracts CDHash from bundle binary
- --path points to .build/ so amfidont covers .app bundle contents

* fix(preflight): prevent run_capture errexit on non-zero return

zsh set -e is global scope — set -e inside run_capture then
return 137 triggers errexit and kills the script before reaching
the assert-bootable check. Use '|| rc=$?' instead to capture
the exit code without modifying errexit state.
This commit is contained in:
Xin Huang
2026-03-11 22:51:45 -07:00
committed by GitHub
parent 06e12c94a1
commit 08c9cb78ee
6 changed files with 57 additions and 43 deletions

View File

@@ -189,7 +189,7 @@ vm_new:
CPU="$(CPU)" MEMORY="$(MEMORY)" \
zsh $(SCRIPTS)/vm_create.sh --dir $(VM_DIR) --disk-size $(DISK_SIZE)
amfidont_allow_vphone: build
amfidont_allow_vphone: bundle
zsh $(SCRIPTS)/start_amfidont_for_vphone.sh
boot_host_preflight: build
@@ -280,10 +280,10 @@ ramdisk_send:
.PHONY: cfw_install cfw_install_dev cfw_install_jb
cfw_install:
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
cfw_install_dev:
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_dev.sh" .
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_dev.sh" .
cfw_install_jb:
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .

View File

@@ -51,10 +51,8 @@ run_capture() {
shift
local log_file="${TMP_DIR}/${label}.log"
set +e
"$@" >"$log_file" 2>&1
local rc=$?
set -e
local rc=0
"$@" >"$log_file" 2>&1 || rc=$?
(( QUIET == 0 )) && echo "[${label}] exit=${rc}"
if (( QUIET == 0 )) && [[ -s "$log_file" ]]; then

View File

@@ -17,6 +17,9 @@
# Usage: make cfw_install
set -euo pipefail
# ── Restore caller's PATH — Nix /etc/zshenv resets PATH on zsh startup ─
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
VM_DIR="${1:-.}"
SCRIPT_DIR="${0:a:h}"
CFW_SKIP_HALT="${CFW_SKIP_HALT:-0}"

View File

@@ -17,6 +17,9 @@
# Usage: make cfw_install_dev
set -euo pipefail
# ── Restore caller's PATH — Nix /etc/zshenv resets PATH on zsh startup ─
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
VM_DIR="${1:-.}"
SCRIPT_DIR="${0:a:h}"
CFW_SKIP_HALT="${CFW_SKIP_HALT:-0}"
@@ -24,6 +27,17 @@ CFW_SKIP_HALT="${CFW_SKIP_HALT:-0}"
# Resolve absolute paths
VM_DIR="$(cd "$VM_DIR" && pwd)"
# ── Python resolver — prefer project venv over whatever is in PATH ─
_resolve_python3() {
local venv_py="${SCRIPT_DIR:h}/.venv/bin/python3"
if [[ -x "$venv_py" ]]; then
echo "$venv_py"
else
command -v python3 || true
fi
}
PYTHON3="$(_resolve_python3)"
# ── Configuration ───────────────────────────────────────────────
CFW_INPUT="cfw_input"
CFW_ARCHIVE="cfw_input.tar.zst"
@@ -177,9 +191,12 @@ apply_dev_overlay() {
check_prereqs() {
command -v ipsw >/dev/null 2>&1 || die "'ipsw' not found. Install: brew install blacktop/tap/ipsw"
command -v aea >/dev/null 2>&1 || die "'aea' not found (requires macOS 12+)"
command -v python3 >/dev/null 2>&1 || die "python3 not found"
python3 -c "import capstone, keystone" 2>/dev/null ||
die "Missing Python deps. Install: pip install capstone keystone-engine"
[[ -x "$PYTHON3" ]] || die "python3 not found (tried: $PYTHON3). Run: make setup_venv"
echo "[*] Python: $PYTHON3 ($("$PYTHON3" --version 2>&1))"
local py_err
py_err="$("$PYTHON3" -c "import capstone, keystone" 2>&1)" || {
die "Missing Python deps (using $PYTHON3).\n Error: ${py_err}\n Fix: source ${SCRIPT_DIR:h}/.venv/bin/activate && pip install capstone keystone-engine\n Or: make setup_venv"
}
}
# ── Cleanup trap (unmount DMGs on error) ───────────────────────
@@ -210,7 +227,7 @@ mkdir -p "$TEMP_DIR"
# ── Parse Cryptex paths from BuildManifest ─────────────────────
echo ""
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..."
CRYPTEX_PATHS=$(python3 "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist")
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist")
CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
echo " SystemOS: $CRYPTEX_SYSOS"
@@ -270,7 +287,7 @@ fi
scp_from "/mnt1/sbin/launchd.bak" "$TEMP_DIR/launchd"
python3 "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-jetsam "$TEMP_DIR/launchd"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-jetsam "$TEMP_DIR/launchd"
ldid_sign "$TEMP_DIR/launchd"
scp_to "$TEMP_DIR/launchd" "/mnt1/sbin/launchd"
ssh_cmd "/bin/chmod 0755 /mnt1/sbin/launchd"
@@ -348,7 +365,7 @@ if ! remote_file_exists "/mnt1/usr/libexec/seputil.bak"; then
fi
scp_from "/mnt1/usr/libexec/seputil.bak" "$TEMP_DIR/seputil"
python3 "$SCRIPT_DIR/patchers/cfw.py" patch-seputil "$TEMP_DIR/seputil"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-seputil "$TEMP_DIR/seputil"
ldid_sign "$TEMP_DIR/seputil" "com.apple.seputil"
scp_to "$TEMP_DIR/seputil" "/mnt1/usr/libexec/seputil"
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/seputil"
@@ -404,7 +421,7 @@ if ! remote_file_exists "/mnt1/usr/libexec/launchd_cache_loader.bak"; then
fi
scp_from "/mnt1/usr/libexec/launchd_cache_loader.bak" "$TEMP_DIR/launchd_cache_loader"
python3 "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-cache-loader "$TEMP_DIR/launchd_cache_loader"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-cache-loader "$TEMP_DIR/launchd_cache_loader"
ldid_sign "$TEMP_DIR/launchd_cache_loader" "com.apple.launchd_cache_loader"
scp_to "$TEMP_DIR/launchd_cache_loader" "/mnt1/usr/libexec/launchd_cache_loader"
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/launchd_cache_loader"
@@ -422,7 +439,7 @@ if ! remote_file_exists "/mnt1/usr/libexec/mobileactivationd.bak"; then
fi
scp_from "/mnt1/usr/libexec/mobileactivationd.bak" "$TEMP_DIR/mobileactivationd"
python3 "$SCRIPT_DIR/patchers/cfw.py" patch-mobileactivationd "$TEMP_DIR/mobileactivationd"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-mobileactivationd "$TEMP_DIR/mobileactivationd"
ldid_sign "$TEMP_DIR/mobileactivationd"
scp_to "$TEMP_DIR/mobileactivationd" "/mnt1/usr/libexec/mobileactivationd"
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/mobileactivationd"
@@ -436,17 +453,7 @@ echo "[7/7] Installing LaunchDaemons..."
# Install vphoned (vsock HID injector daemon)
VPHONED_SRC="$SCRIPT_DIR/vphoned"
VPHONED_BIN="$VPHONED_SRC/vphoned"
VPHONED_SRCS=(
"$VPHONED_SRC/unarchive.m"
"$VPHONED_SRC/vphoned.m"
"$VPHONED_SRC/vphoned_install.m"
"$VPHONED_SRC/vphoned_protocol.m"
"$VPHONED_SRC/vphoned_hid.m"
"$VPHONED_SRC/vphoned_devmode.m"
"$VPHONED_SRC/vphoned_location.m"
"$VPHONED_SRC/vphoned_files.m"
"$VPHONED_SRC/vphoned_keychain.m"
)
VPHONED_SRCS=("$VPHONED_SRC"/*.m)
needs_vphoned_build=0
if [[ ! -f "$VPHONED_BIN" ]]; then
needs_vphoned_build=1
@@ -495,7 +502,7 @@ fi
scp_from "/mnt1/System/Library/xpc/launchd.plist.bak" "$TEMP_DIR/launchd.plist"
cp "$VPHONED_SRC/vphoned.plist" "$INPUT_DIR/jb/LaunchDaemons/"
python3 "$SCRIPT_DIR/patchers/cfw.py" inject-daemons "$TEMP_DIR/launchd.plist" "$INPUT_DIR/jb/LaunchDaemons"
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" inject-daemons "$TEMP_DIR/launchd.plist" "$INPUT_DIR/jb/LaunchDaemons"
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"

View File

@@ -12,6 +12,8 @@
# Usage: make cfw_install_jb
set -euo pipefail
# ── Restore caller's PATH — Nix /etc/zshenv resets PATH on zsh startup ─
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
VM_DIR="${1:-.}"
SCRIPT_DIR="${0:a:h}"

View File

@@ -2,15 +2,15 @@
# start_amfidont_for_vphone.sh — Start amfidont for the current vphone build.
#
# This is the README "Option 2" host workaround packaged for this repo:
# - computes the signed release binary CDHash
# - uses the URL-encoded project path form observed by AMFIPathValidator
# - computes the signed bundle binary CDHash (what `make boot` actually launches)
# - uses the .build path so amfidont covers binaries inside the .app bundle
# - starts amfidont in daemon mode so signed vphone-cli launches are allowlisted
set -euo pipefail
SCRIPT_DIR="${0:A:h}"
PROJECT_ROOT="${SCRIPT_DIR:h}"
RELEASE_BIN="${PROJECT_ROOT}/.build/release/vphone-cli"
BUNDLE_BIN="${PROJECT_ROOT}/.build/vphone-cli.app/Contents/MacOS/vphone-cli"
AMFIDONT_BIN="${HOME}/Library/Python/3.9/bin/amfidont"
[[ -x "$AMFIDONT_BIN" ]] || {
@@ -19,29 +19,33 @@ AMFIDONT_BIN="${HOME}/Library/Python/3.9/bin/amfidont"
exit 1
}
[[ -x "$RELEASE_BIN" ]] || {
echo "Missing release binary: $RELEASE_BIN" >&2
echo "Run 'make build' first." >&2
[[ -x "$BUNDLE_BIN" ]] || {
echo "Missing bundle binary: $BUNDLE_BIN" >&2
echo "Run 'make bundle' first." >&2
exit 1
}
CDHASH="$(
codesign -dv --verbose=4 "$RELEASE_BIN" 2>&1 \
codesign -dv --verbose=4 "$BUNDLE_BIN" 2>&1 \
| sed -n 's/^CDHash=//p' \
| head -n1
)"
[[ -n "$CDHASH" ]] || {
echo "Failed to extract CDHash for $RELEASE_BIN" >&2
echo "Failed to extract CDHash for $BUNDLE_BIN" >&2
exit 1
}
ENCODED_PROJECT_ROOT="${PROJECT_ROOT// /%20}"
# amfidont --path must cover the actual binary location inside the .app
AMFI_PATH="${PROJECT_ROOT}/.build"
ENCODED_AMFI_PATH="${AMFI_PATH// /%20}"
echo "[*] Project root: $PROJECT_ROOT"
echo "[*] Encoded AMFI path: $ENCODED_PROJECT_ROOT"
echo "[*] Release CDHash: $CDHASH"
echo "[*] AMFI path: $AMFI_PATH"
echo "[*] Bundle CDHash: $CDHASH"
exec sudo "$AMFIDONT_BIN" daemon \
--path "$ENCODED_PROJECT_ROOT" \
--cdhash "$CDHASH" \
--verbose
sudo env PYTHONPATH="/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python" \
/usr/bin/python3 "$AMFIDONT_BIN" daemon \
--path "$ENCODED_AMFI_PATH" \
--cdhash "$CDHASH" \
--verbose \
>/dev/null 2>&1