mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-05 17:14:28 +00:00
Rework JB finalization: drop dropbear, auto-bootstrap on first boot (#141)
* fix: build
* fix: remove [trusted=yes] from Havoc apt source
The inline [trusted=yes] option can cause issues with Sileo's
source parser. The apt-get calls already use AllowUnauthenticated
flags, making it redundant.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: main actor crash in VPhoneControl + IPA extraction failures
VPhoneControl: pending request handlers are @MainActor-isolated closures
but were called from DispatchQueue.global() in the read loop and timeout
handler, causing dispatch_assert_queue_fail crashes. Wrap all
pending.handler() calls in DispatchQueue.main.async.
unarchive: the recent ARCHIVE_EXTRACT_SECURE_* hardening (ef02d50) broke
IPA extraction on iOS because:
- SECURE_NOABSOLUTEPATHS: we set absolute output paths on entries
- SECURE_SYMLINKS: iOS system paths (/var, /tmp) are symlinks
- archive_write_header failures were silently swallowed due to if/else if
structure, making extraction report success with no files extracted
Fix by keeping only SECURE_NODOTDOT, resolving symlinks in extraction
path, fixing header error handling, removing unnecessary ACL/FFLAGS
flags, and surfacing libarchive errors in the install response.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* remove dropbear SSH daemon from guest
Drop all dropbear setup: LaunchDaemon plist injection, host key
generation, daemon deployment, and SSH availability messages.
Guest communication is handled by vphoned over vsock.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* feat: liblaunch compat stub + automatic JB first-boot setup
liblaunch_compat.dylib: stub exporting _launch_active_user_switch
(missing from PCC VM's libSystem.B.dylib) so procursus binaries
like launchctl can load. Deployed to /cores/, loaded via
DYLD_INSERT_LIBRARIES in LaunchDaemon environment and JB profile.
vphone_jb_setup.sh: first-boot script replacing the SSH-based
cfw_install_jb_post.sh. Runs as a LaunchDaemon on first normal
boot and performs all JB finalization: /var/jb symlink,
prep_bootstrap, markers, Sileo, apt setup, TrollStore Lite.
Idempotent with done marker. Logs to /var/log/vphone_jb_setup.log.
Removes the cfw_install_jb_finalize make target and the entire
SSH/iproxy/sshpass-based post-boot flow from setup_machine.sh.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* chore: update AGENTS.md firmware table, gitignore build artifacts
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: launchctl wrapper uses absolute path + timeout to prevent hangs
- Use absolute path to launchctl.real instead of relative dirname,
fixing "not found" when called via /var/jb/bin/launchctl symlink
- Add 5s timeout so launchctl doesn't hang when launchd is
unresponsive on PCC VMs — always exits 0 for dpkg postinst compat
- Symlink /var/jb/bin/launchctl -> /var/jb/usr/bin/launchctl so both
paths work (openssh postinst uses the /bin/ path)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: replace liblaunch_compat dylib stub with iosbinpack64 launchctl symlink
Procursus launchctl crashes on PCC VMs due to missing
_launch_active_user_switch symbol. Rather than a custom dylib stub,
simply symlink iosbinpack64's launchctl into /var/jb — it talks to
launchd fine and always exits 0, which is all dpkg scripts need.
- Remove liblaunch_compat.c, its build target, signing, and deployment
- Remove DYLD_INSERT_LIBRARIES from setup script and plist
- Replace launchctl wrapper with symlinks to /iosbinpack64/bin/launchctl
- Both /var/jb/usr/bin/launchctl and /var/jb/bin/launchctl are covered
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5921cba2ba
commit
32b73cd50b
@@ -319,6 +319,7 @@ __marimo__/
|
|||||||
TODO.md
|
TODO.md
|
||||||
/references/
|
/references/
|
||||||
scripts/vphoned/vphoned
|
scripts/vphoned/vphoned
|
||||||
|
/cfw_input/
|
||||||
sources/vphone-cli/VPhoneBuildInfo.swift
|
sources/vphone-cli/VPhoneBuildInfo.swift
|
||||||
setup_logs/
|
setup_logs/
|
||||||
/testing_results
|
/testing_results
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ For any changes applying new patches, also update research/0_binary_patch_compar
|
|||||||
| --------------- | :---------: | :-------: | ------------------------------------------------------------ |
|
| --------------- | :---------: | :-------: | ------------------------------------------------------------ |
|
||||||
| **Regular** | 51 patches | 10 phases | `fw_patch` + `cfw_install` |
|
| **Regular** | 51 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||||
| **Development** | 64 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
| **Development** | 64 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||||
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` + `cfw_install_jb_finalize` |
|
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||||
|
|
||||||
> `cfw_install_jb_finalize` requires booting into the full system, not the ramdisk.
|
> JB finalization (symlinks, Sileo, apt, TrollStore) runs automatically on first boot via `/cores/vphone_jb_setup.sh` LaunchDaemon. Monitor progress: `/var/log/vphone_jb_setup.log`.
|
||||||
|
|
||||||
See `research/` for detailed firmware pipeline, component origins, patch breakdowns, and boot flow documentation.
|
See `research/` for detailed firmware pipeline, component origins, patch breakdowns, and boot flow documentation.
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ help:
|
|||||||
@echo " make cfw_install Install CFW mods via SSH"
|
@echo " make cfw_install Install CFW mods via SSH"
|
||||||
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
||||||
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
||||||
@echo " make cfw_install_jb_finalize Finalize JB bootstrap on live device (symlinks/sileo/apt)"
|
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
||||||
|
|
||||||
@@ -244,7 +243,7 @@ ramdisk_send:
|
|||||||
# CFW
|
# CFW
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb cfw_install_jb_finalize
|
.PHONY: cfw_install cfw_install_dev cfw_install_jb
|
||||||
|
|
||||||
cfw_install:
|
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)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||||
@@ -254,6 +253,3 @@ cfw_install_dev:
|
|||||||
|
|
||||||
cfw_install_jb:
|
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)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||||
|
|
||||||
cfw_install_jb_finalize:
|
|
||||||
$(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") $(if $(SSH_PASS),SSH_PASS="$(SSH_PASS)") zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb_post.sh"
|
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
|||||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||||
|
|
||||||
# Send daemon plists (overwrite on re-run)
|
# Send daemon plists (overwrite on re-run)
|
||||||
for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
for plist in bash.plist trollvnc.plist rpcserver_ios.plist; do
|
||||||
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||||
done
|
done
|
||||||
@@ -518,7 +518,6 @@ rm -f "$TEMP_DIR/seputil" \
|
|||||||
echo ""
|
echo ""
|
||||||
echo "[+] CFW installation complete!"
|
echo "[+] CFW installation complete!"
|
||||||
echo " Reboot the device for changes to take effect."
|
echo " Reboot the device for changes to take effect."
|
||||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
|
||||||
|
|
||||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ cp "$TEMP_DIR/vphoned" "$VM_DIR/.vphoned.signed"
|
|||||||
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
echo " [+] vphoned installed (signed copy at .vphoned.signed)"
|
||||||
|
|
||||||
# Send daemon plists (overwrite on re-run)
|
# Send daemon plists (overwrite on re-run)
|
||||||
for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do
|
for plist in bash.plist trollvnc.plist rpcserver_ios.plist; do
|
||||||
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/"
|
||||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist"
|
||||||
done
|
done
|
||||||
@@ -517,7 +517,6 @@ rm -f "$TEMP_DIR/seputil" \
|
|||||||
echo ""
|
echo ""
|
||||||
echo "[+] CFW installation complete!"
|
echo "[+] CFW installation complete!"
|
||||||
echo " Reboot the device for changes to take effect."
|
echo " Reboot the device for changes to take effect."
|
||||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
|
||||||
|
|
||||||
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
if [[ "$CFW_SKIP_HALT" == "1" ]]; then
|
||||||
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
echo "[*] CFW_SKIP_HALT=1, skipping halt."
|
||||||
|
|||||||
@@ -277,6 +277,40 @@ if [[ -d "$BASEBIN_DIR" ]]; then
|
|||||||
echo " [+] BaseBin hooks deployed"
|
echo " [+] BaseBin hooks deployed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ═══════════ JB-5 DEPLOY FIRST-BOOT SETUP ══════════════════════
|
||||||
|
echo ""
|
||||||
|
echo "[JB-5] Deploying first-boot setup..."
|
||||||
|
|
||||||
|
# Deploy first-boot JB setup script + LaunchDaemon
|
||||||
|
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
||||||
|
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
||||||
|
if [[ -f "$SETUP_SCRIPT" ]]; then
|
||||||
|
scp_to "$SETUP_SCRIPT" "/mnt1/cores/vphone_jb_setup.sh"
|
||||||
|
ssh_cmd "/bin/chmod 0755 /mnt1/cores/vphone_jb_setup.sh"
|
||||||
|
echo " [+] vphone_jb_setup.sh -> /cores/"
|
||||||
|
fi
|
||||||
|
if [[ -f "$SETUP_PLIST" ]]; then
|
||||||
|
scp_to "$SETUP_PLIST" "/mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||||
|
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||||
|
|
||||||
|
# Inject into launchd.plist so launchd starts it at boot
|
||||||
|
echo " Injecting com.vphone.jb-setup into launchd.plist..."
|
||||||
|
scp_from "/mnt1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||||
|
python3 -c "
|
||||||
|
import plistlib, sys
|
||||||
|
with open(sys.argv[1], 'rb') as f:
|
||||||
|
target = plistlib.load(f)
|
||||||
|
with open(sys.argv[2], 'rb') as f:
|
||||||
|
daemon = plistlib.load(f)
|
||||||
|
target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone.jb-setup.plist'] = daemon
|
||||||
|
with open(sys.argv[1], 'wb') as f:
|
||||||
|
plistlib.dump(target, f, sort_keys=False)
|
||||||
|
" "$TEMP_DIR/launchd.plist" "$SETUP_PLIST"
|
||||||
|
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||||
|
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||||
|
echo " [+] com.vphone.jb-setup.plist injected into launchd.plist"
|
||||||
|
fi
|
||||||
|
|
||||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||||
echo ""
|
echo ""
|
||||||
echo "[*] Unmounting device filesystems..."
|
echo "[*] Unmounting device filesystems..."
|
||||||
@@ -291,6 +325,5 @@ rm -f "$TEMP_DIR/launchd" \
|
|||||||
echo ""
|
echo ""
|
||||||
echo "[+] CFW + JB installation complete!"
|
echo "[+] CFW + JB installation complete!"
|
||||||
echo " Reboot the device for changes to take effect."
|
echo " Reboot the device for changes to take effect."
|
||||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
|
||||||
|
|
||||||
ssh_cmd "/sbin/halt" || true
|
ssh_cmd "/sbin/halt" || true
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
# cfw_install_jb_post.sh — Finalize JB bootstrap on a normally-booted vphone.
|
|
||||||
#
|
|
||||||
# Runs after `cfw_install_jb` + first normal boot. Connects to the live device
|
|
||||||
# via SSH and sets up procursus symlinks, markers, Sileo, and apt packages.
|
|
||||||
#
|
|
||||||
# Every step is idempotent — safe to re-run at any point.
|
|
||||||
# All binary paths are discovered dynamically (no hardcoded /bin, /sbin, etc.).
|
|
||||||
#
|
|
||||||
# Usage: make cfw_install_jb_finalize [SSH_PORT=22222] [SSH_PASS=alpine]
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="${0:a:h}"
|
|
||||||
|
|
||||||
# ── Configuration ───────────────────────────────────────────────
|
|
||||||
SSH_PORT="${SSH_PORT:-22222}"
|
|
||||||
SSH_PASS="${SSH_PASS:-alpine}"
|
|
||||||
SSH_USER="root"
|
|
||||||
SSH_HOST="localhost"
|
|
||||||
SSH_RETRY="${SSH_RETRY:-3}"
|
|
||||||
SSHPASS_BIN=""
|
|
||||||
SSH_OPTS=(
|
|
||||||
-o StrictHostKeyChecking=no
|
|
||||||
-o UserKnownHostsFile=/dev/null
|
|
||||||
-o PreferredAuthentications=password
|
|
||||||
-o ConnectTimeout=30
|
|
||||||
-q
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── Helpers ─────────────────────────────────────────────────────
|
|
||||||
die() {
|
|
||||||
echo "[-] $*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_sshpass() {
|
|
||||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ssh_retry() {
|
|
||||||
local attempt rc label
|
|
||||||
label=${2:-cmd}
|
|
||||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
|
||||||
"$@" && return 0
|
|
||||||
rc=$?
|
|
||||||
[[ $rc -ne 255 ]] && return $rc
|
|
||||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
|
|
||||||
# Raw ssh — no PATH prefix
|
|
||||||
ssh_raw() {
|
|
||||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ssh with discovered PATH prepended
|
|
||||||
ssh_cmd() {
|
|
||||||
ssh_raw "$RENV $*"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Prerequisites ──────────────────────────────────────────────
|
|
||||||
command -v sshpass &>/dev/null || die "Missing sshpass. Run: make setup_tools"
|
|
||||||
SSHPASS_BIN="$(command -v sshpass)"
|
|
||||||
|
|
||||||
echo "[*] cfw_install_jb_post.sh — Finalizing JB bootstrap..."
|
|
||||||
echo " Target: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── Verify SSH connectivity ────────────────────────────────────
|
|
||||||
echo "[*] Checking SSH connectivity..."
|
|
||||||
ssh_raw "echo ready" >/dev/null 2>&1 || die "Cannot reach device on ${SSH_HOST}:${SSH_PORT}. Is the VM booted normally?"
|
|
||||||
echo "[+] Device reachable"
|
|
||||||
|
|
||||||
# ── Discover remote PATH ──────────────────────────────────────
|
|
||||||
# Uses only shell builtins (test -d, echo) — works with empty PATH.
|
|
||||||
echo "[*] Discovering remote binary directories..."
|
|
||||||
DISCOVERED_PATH=$(ssh_raw 'P=""; \
|
|
||||||
for d in \
|
|
||||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
|
||||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
|
||||||
/usr/bin /usr/sbin /bin /sbin; do \
|
|
||||||
[ -d "$d" ] && P="$P:$d"; \
|
|
||||||
done; \
|
|
||||||
echo "${P#:}"')
|
|
||||||
|
|
||||||
[[ -n "$DISCOVERED_PATH" ]] || die "Could not discover any binary directories on device"
|
|
||||||
echo " PATH=$DISCOVERED_PATH"
|
|
||||||
|
|
||||||
# This gets prepended to every ssh_cmd call
|
|
||||||
RENV="export PATH='$DISCOVERED_PATH' TERM='xterm-256color';"
|
|
||||||
|
|
||||||
# Quick sanity: verify we can run ls now
|
|
||||||
ssh_cmd "ls / >/dev/null" || die "PATH discovery succeeded but 'ls' still not found"
|
|
||||||
echo "[+] Remote environment ready"
|
|
||||||
|
|
||||||
# ═══════════ 1/6 SYMLINK /var/jb ══════════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[1/6] Creating /private/var/jb symlink..."
|
|
||||||
|
|
||||||
# Find 96-char boot manifest hash — use shell glob (no ls dependency)
|
|
||||||
BOOT_HASH=$(ssh_cmd 'for d in /private/preboot/*/; do \
|
|
||||||
b="${d%/}"; b="${b##*/}"; \
|
|
||||||
[ "${#b}" = 96 ] && echo "$b" && break; \
|
|
||||||
done')
|
|
||||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in /private/preboot"
|
|
||||||
echo " Boot manifest hash: $BOOT_HASH"
|
|
||||||
|
|
||||||
JB_TARGET="/private/preboot/$BOOT_HASH/jb-vphone/procursus"
|
|
||||||
ssh_cmd "test -d '$JB_TARGET'" || die "Procursus directory not found at $JB_TARGET. Run cfw_install_jb first."
|
|
||||||
|
|
||||||
CURRENT_LINK=$(ssh_cmd "readlink /private/var/jb 2>/dev/null || true")
|
|
||||||
if [[ "$CURRENT_LINK" == "$JB_TARGET" ]]; then
|
|
||||||
echo " [*] Symlink already correct, skipping"
|
|
||||||
else
|
|
||||||
ssh_cmd "ln -sf '$JB_TARGET' /private/var/jb"
|
|
||||||
echo " [+] /private/var/jb -> $JB_TARGET"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ═══════════ 2/6 FIX OWNERSHIP / PERMISSIONS ═════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[2/6] Fixing mobile Library ownership..."
|
|
||||||
|
|
||||||
ssh_cmd "mkdir -p /var/jb/var/mobile/Library/Preferences"
|
|
||||||
ssh_cmd "chown -R 501:501 /var/jb/var/mobile/Library"
|
|
||||||
ssh_cmd "chmod 0755 /var/jb/var/mobile/Library"
|
|
||||||
ssh_cmd "chown -R 501:501 /var/jb/var/mobile/Library/Preferences"
|
|
||||||
ssh_cmd "chmod 0755 /var/jb/var/mobile/Library/Preferences"
|
|
||||||
|
|
||||||
echo " [+] Ownership set"
|
|
||||||
|
|
||||||
# ═══════════ 3/6 RUN prep_bootstrap.sh ════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[3/6] Running prep_bootstrap.sh..."
|
|
||||||
|
|
||||||
if ssh_cmd "test -f /var/jb/prep_bootstrap.sh"; then
|
|
||||||
# Skip interactive password prompt (uses uialert GUI — not usable over SSH)
|
|
||||||
ssh_cmd "NO_PASSWORD_PROMPT=1 /var/jb/prep_bootstrap.sh"
|
|
||||||
echo " [+] prep_bootstrap.sh completed"
|
|
||||||
echo " [!] Terminal password was NOT set (automated mode)."
|
|
||||||
echo " To set it manually: ssh in and run: passwd"
|
|
||||||
else
|
|
||||||
echo " [*] prep_bootstrap.sh already ran (deleted itself), skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Re-discover PATH after prep_bootstrap.sh may have changed the login shell.
|
|
||||||
# The shell switch (chsh) can alter which profile scripts run on subsequent SSH
|
|
||||||
# sessions, so we must refresh RENV to ensure dpkg/apt/uicache are reachable.
|
|
||||||
echo "[*] Re-discovering remote PATH after bootstrap prep..."
|
|
||||||
DISCOVERED_PATH=$(ssh_raw 'P=""; \
|
|
||||||
for d in \
|
|
||||||
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
|
||||||
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
|
||||||
/usr/bin /usr/sbin /bin /sbin; do \
|
|
||||||
[ -d "$d" ] && P="$P:$d"; \
|
|
||||||
done; \
|
|
||||||
echo "${P#:}"')
|
|
||||||
RENV="export PATH='$DISCOVERED_PATH' TERM='xterm-256color';"
|
|
||||||
echo " PATH=$DISCOVERED_PATH"
|
|
||||||
|
|
||||||
# Fix interactive SSH environment.
|
|
||||||
# dropbear uses --shell /iosbinpack64/bin/bash and ignores /etc/passwd.
|
|
||||||
# /iosbinpack64/etc/profile spawns a non-login subshell that only reads ~/.bashrc.
|
|
||||||
# prep_bootstrap.sh's chsh has no effect because dropbear doesn't consult passwd.
|
|
||||||
# Create ~/.bashrc so the interactive subshell sources /var/jb/etc/profile (full PATH).
|
|
||||||
echo "[*] Setting up shell profile for interactive SSH..."
|
|
||||||
if ! ssh_cmd "test -f /var/root/.bashrc"; then
|
|
||||||
ssh_cmd "printf '%s\n' '# Source JB environment' '[ -r /var/jb/etc/profile ] && . /var/jb/etc/profile' > /var/root/.bashrc"
|
|
||||||
echo " [+] /var/root/.bashrc created"
|
|
||||||
else
|
|
||||||
echo " [*] /var/root/.bashrc already exists, skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ═══════════ 4/6 CREATE MARKER FILES ═════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[4/6] Creating marker files..."
|
|
||||||
|
|
||||||
for marker in .procursus_strapped .installed_dopamine; do
|
|
||||||
if ssh_cmd "test -f /var/jb/$marker"; then
|
|
||||||
echo " [*] $marker already exists, skipping"
|
|
||||||
else
|
|
||||||
ssh_cmd ": > /var/jb/$marker && chown 0:0 /var/jb/$marker && chmod 0644 /var/jb/$marker"
|
|
||||||
echo " [+] $marker created"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ═══════════ 5/6 INSTALL SILEO ══════════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[5/6] Installing Sileo..."
|
|
||||||
|
|
||||||
SILEO_DEB_PATH="/private/preboot/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
|
||||||
|
|
||||||
if ssh_cmd "dpkg -s org.coolstar.sileo >/dev/null 2>&1"; then
|
|
||||||
echo " [*] Sileo already installed, skipping"
|
|
||||||
else
|
|
||||||
ssh_cmd "test -f '$SILEO_DEB_PATH'" || die "Sileo deb not found at $SILEO_DEB_PATH. Was it uploaded by cfw_install_jb?"
|
|
||||||
ssh_cmd "dpkg -i '$SILEO_DEB_PATH'"
|
|
||||||
echo " [+] Sileo installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ssh_cmd "uicache -a 2>/dev/null || true"
|
|
||||||
echo " [+] uicache refreshed"
|
|
||||||
|
|
||||||
# ═══════════ 6/7 APT SETUP ═════════════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[6/7] Running apt setup..."
|
|
||||||
|
|
||||||
HAVOC_LIST="/var/jb/etc/apt/sources.list.d/havoc.list"
|
|
||||||
if ssh_cmd "test -d /etc/apt/sources.list.d && test ! -d /var/jb/etc/apt/sources.list.d"; then
|
|
||||||
HAVOC_LIST="/etc/apt/sources.list.d/havoc.list"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HAVOC_SOURCES="$(ssh_cmd "grep -RIl 'havoc.app' /etc/apt /var/jb/etc/apt 2>/dev/null || true")"
|
|
||||||
if [[ -n "$HAVOC_SOURCES" ]]; then
|
|
||||||
echo " [*] Havoc source already present:"
|
|
||||||
echo "$HAVOC_SOURCES" | sed 's/^/ - /'
|
|
||||||
|
|
||||||
OTHER_HAVOC_SOURCES="$(printf '%s\n' "$HAVOC_SOURCES" | grep -Fvx "$HAVOC_LIST" || true)"
|
|
||||||
if [[ -n "$OTHER_HAVOC_SOURCES" ]] && ssh_cmd "test -f '$HAVOC_LIST' && grep -q 'https://havoc.app/' '$HAVOC_LIST'"; then
|
|
||||||
ssh_cmd "rm -f '$HAVOC_LIST'"
|
|
||||||
echo " [+] Removed duplicate autogenerated Havoc source: $HAVOC_LIST"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ssh_cmd "mkdir -p '${HAVOC_LIST:h}'"
|
|
||||||
ssh_cmd "printf '%s\n' 'deb [trusted=yes] https://havoc.app/ ./' > '$HAVOC_LIST'"
|
|
||||||
echo " [+] Havoc source added: $HAVOC_LIST"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo " [*] Allowing unsigned third-party repos during automated apt refresh"
|
|
||||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o Acquire::AllowInsecureRepositories=true -o Acquire::AllowDowngradeToInsecureRepositories=true update -qq"
|
|
||||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true install -y -qq libkrw0-tfp0 2>/dev/null || true"
|
|
||||||
echo " [+] apt update + libkrw0-tfp0 done"
|
|
||||||
|
|
||||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true upgrade -y -qq 2>/dev/null || true"
|
|
||||||
echo " [+] apt upgrade done"
|
|
||||||
|
|
||||||
# ═══════════ 7/7 INSTALL TROLLSTORE LITE ═══════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[7/7] Installing TrollStore Lite..."
|
|
||||||
|
|
||||||
if ssh_cmd "dpkg -s com.opa334.trollstorelite >/dev/null 2>&1"; then
|
|
||||||
echo " [*] TrollStore Lite already installed, skipping"
|
|
||||||
else
|
|
||||||
ssh_cmd "DEBIAN_FRONTEND=noninteractive apt-get -o APT::Get::AllowUnauthenticated=true install -y -qq com.opa334.trollstorelite"
|
|
||||||
echo " [+] TrollStore Lite installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ssh_cmd "uicache -a 2>/dev/null || true"
|
|
||||||
echo " [+] uicache refreshed"
|
|
||||||
|
|
||||||
# ═══════════ DONE ═══════════════════════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "[+] JB finalization complete!"
|
|
||||||
echo " TrollStore Lite is installed automatically during finalization."
|
|
||||||
echo " Next: open Sileo on device, add source https://ellekit.space, install ElleKit"
|
|
||||||
echo " Then reboot the device for full JB environment."
|
|
||||||
@@ -24,7 +24,7 @@ Commands:
|
|||||||
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
||||||
|
|
||||||
inject-daemons <launchd.plist> <daemon_dir>
|
inject-daemons <launchd.plist> <daemon_dir>
|
||||||
Inject bash/dropbear/trollvnc into launchd.plist.
|
Inject bash/trollvnc into launchd.plist.
|
||||||
|
|
||||||
inject-dylib <binary> <dylib_path>
|
inject-dylib <binary> <dylib_path>
|
||||||
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Commands:
|
|||||||
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
Patch launchd jetsam panic guard to avoid initproc crash loop.
|
||||||
|
|
||||||
inject-daemons <launchd.plist> <daemon_dir>
|
inject-daemons <launchd.plist> <daemon_dir>
|
||||||
Inject bash/dropbear/trollvnc into launchd.plist.
|
Inject bash/trollvnc into launchd.plist.
|
||||||
|
|
||||||
inject-dylib <binary> <dylib_path>
|
inject-dylib <binary> <dylib_path>
|
||||||
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal).
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ def parse_cryptex_paths(manifest_path):
|
|||||||
|
|
||||||
|
|
||||||
def inject_daemons(plist_path, daemon_dir):
|
def inject_daemons(plist_path, daemon_dir):
|
||||||
"""Inject bash/dropbear/trollvnc entries into launchd.plist."""
|
"""Inject bash/trollvnc entries into launchd.plist."""
|
||||||
# Convert to XML first (macOS binary plist -> XML)
|
# Convert to XML first (macOS binary plist -> XML)
|
||||||
subprocess.run(["plutil", "-convert", "xml1", plist_path], capture_output=True)
|
subprocess.run(["plutil", "-convert", "xml1", plist_path], capture_output=True)
|
||||||
|
|
||||||
with open(plist_path, "rb") as f:
|
with open(plist_path, "rb") as f:
|
||||||
target = plistlib.load(f)
|
target = plistlib.load(f)
|
||||||
|
|
||||||
for name in ("bash", "dropbear", "trollvnc", "vphoned", "rpcserver_ios"):
|
for name in ("bash", "trollvnc", "vphoned", "rpcserver_ios"):
|
||||||
src = os.path.join(daemon_dir, f"{name}.plist")
|
src = os.path.join(daemon_dir, f"{name}.plist")
|
||||||
if not os.path.exists(src):
|
if not os.path.exists(src):
|
||||||
print(f" [!] Missing {src}, skipping")
|
print(f" [!] Missing {src}, skipping")
|
||||||
|
|||||||
@@ -486,11 +486,8 @@ send_first_boot_commands() {
|
|||||||
|
|
||||||
local commands=(
|
local commands=(
|
||||||
"export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games:/iosbinpack64/usr/local/sbin:/iosbinpack64/usr/local/bin:/iosbinpack64/usr/sbin:/iosbinpack64/usr/bin:/iosbinpack64/sbin:/iosbinpack64/bin'"
|
"export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games:/iosbinpack64/usr/local/sbin:/iosbinpack64/usr/local/bin:/iosbinpack64/usr/sbin:/iosbinpack64/usr/bin:/iosbinpack64/sbin:/iosbinpack64/bin'"
|
||||||
"mkdir -p /var/dropbear"
|
|
||||||
"cp /iosbinpack64/etc/profile /var/profile"
|
"cp /iosbinpack64/etc/profile /var/profile"
|
||||||
"cp /iosbinpack64/etc/motd /var/motd"
|
"cp /iosbinpack64/etc/motd /var/motd"
|
||||||
"dropbearkey -t rsa -f /var/dropbear/dropbear_rsa_host_key"
|
|
||||||
"dropbearkey -t ecdsa -f /var/dropbear/dropbear_ecdsa_host_key"
|
|
||||||
"shutdown -h now"
|
"shutdown -h now"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1033,53 +1030,9 @@ main() {
|
|||||||
if [[ "$JB_MODE" -eq 1 ]]; then
|
if [[ "$JB_MODE" -eq 1 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== JB Finalize ==="
|
echo "=== JB Finalize ==="
|
||||||
echo "[*] Booting VM normally for JB bootstrap finalization..."
|
echo "[*] JB finalization will run automatically on first normal boot"
|
||||||
|
echo " via /cores/vphone_jb_setup.sh (LaunchDaemon)."
|
||||||
check_vm_storage_locks
|
echo " Monitor progress via vphoned file browser: /var/log/vphone_jb_setup.log"
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
: > "$BOOT_LOG"
|
|
||||||
(make boot >"$BOOT_LOG" 2>&1) &
|
|
||||||
BOOT_PID=$!
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
if ! kill -0 "$BOOT_PID" 2>/dev/null; then
|
|
||||||
echo "[-] make boot exited early during JB finalize stage."
|
|
||||||
tail -n 40 "$BOOT_LOG" 2>/dev/null || true
|
|
||||||
die "JB finalize boot failed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
local jb_ssh_port jb_iproxy_pid jb_iproxy_log
|
|
||||||
local iproxy_bin="${PROJECT_ROOT}/.limd/bin/iproxy"
|
|
||||||
|
|
||||||
jb_ssh_port="$(pick_random_ssh_port)" \
|
|
||||||
|| die "Failed to allocate a random local SSH port for JB finalize"
|
|
||||||
|
|
||||||
jb_iproxy_log="${LOG_DIR}/iproxy_jb_${jb_ssh_port}.log"
|
|
||||||
: > "$jb_iproxy_log"
|
|
||||||
|
|
||||||
echo "[*] Waiting for device UDID=${DEVICE_UDID} on USBMux..."
|
|
||||||
wait_for_iproxy_target_udid
|
|
||||||
|
|
||||||
echo "[*] Starting iproxy ${jb_ssh_port} -> 22222 (target_udid=${IPROXY_TARGET_UDID})..."
|
|
||||||
("$iproxy_bin" -u "$IPROXY_TARGET_UDID" "$jb_ssh_port" 22222 >"$jb_iproxy_log" 2>&1) &
|
|
||||||
jb_iproxy_pid=$!
|
|
||||||
sleep 1
|
|
||||||
if ! kill -0 "$jb_iproxy_pid" 2>/dev/null; then
|
|
||||||
echo "[-] iproxy exited early. Log:"
|
|
||||||
tail -n 40 "$jb_iproxy_log" || true
|
|
||||||
die "iproxy for JB finalize failed to start."
|
|
||||||
fi
|
|
||||||
echo "[+] iproxy running (pid=$jb_iproxy_pid, log=$jb_iproxy_log)"
|
|
||||||
|
|
||||||
wait_for_device_ssh "$jb_ssh_port" 120
|
|
||||||
|
|
||||||
run_make "JB finalize" cfw_install_jb_finalize SSH_PORT="$jb_ssh_port"
|
|
||||||
|
|
||||||
halt_device_ssh "$jb_ssh_port"
|
|
||||||
stop_process_tree "$jb_iproxy_pid" 2>/dev/null || true
|
|
||||||
echo "[*] Waiting for VM shutdown..."
|
|
||||||
wait "$BOOT_PID" || true
|
|
||||||
BOOT_PID=""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.vphone.jb-setup</string>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/iosbinpack64/bin/bash</string>
|
||||||
|
<string>/cores/vphone_jb_setup.sh</string>
|
||||||
|
</array>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/var/log/vphone_jb_setup.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/var/log/vphone_jb_setup.log</string>
|
||||||
|
<key>UserName</key>
|
||||||
|
<string>root</string>
|
||||||
|
<key>EnablePressuredExit</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Executable
+198
@@ -0,0 +1,198 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# vphone_jb_setup.sh — First-boot JB finalization script.
|
||||||
|
#
|
||||||
|
# Deployed to /cores/ during cfw_install_jb.sh (ramdisk phase).
|
||||||
|
# Runs automatically via LaunchDaemon on first normal boot.
|
||||||
|
# Idempotent — safe to re-run on subsequent boots.
|
||||||
|
#
|
||||||
|
# Logs to /var/log/vphone_jb_setup.log for host-side monitoring
|
||||||
|
# via vphoned file browser.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
LOG="/var/log/vphone_jb_setup.log"
|
||||||
|
DONE_MARKER="/var/mobile/.vphone_jb_setup_done"
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG"; }
|
||||||
|
die() { log "FATAL: $*"; exit 1; }
|
||||||
|
|
||||||
|
# Redirect all output to log
|
||||||
|
exec > >(tee -a "$LOG") 2>&1
|
||||||
|
|
||||||
|
log "=== vphone_jb_setup.sh starting ==="
|
||||||
|
|
||||||
|
# ── Check done marker ────────────────────────────────────────
|
||||||
|
if [ -f "$DONE_MARKER" ]; then
|
||||||
|
log "Already completed (marker exists), exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Environment ──────────────────────────────────────────────
|
||||||
|
export TERM=xterm-256color
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Discover PATH dynamically
|
||||||
|
P=""
|
||||||
|
for d in \
|
||||||
|
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||||
|
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||||
|
/usr/bin /usr/sbin /bin /sbin; do
|
||||||
|
[ -d "$d" ] && P="$P:$d"
|
||||||
|
done
|
||||||
|
export PATH="${P#:}"
|
||||||
|
log "PATH=$PATH"
|
||||||
|
|
||||||
|
# ── Find boot manifest hash ─────────────────────────────────
|
||||||
|
BOOT_HASH=""
|
||||||
|
for d in /private/preboot/*/; do
|
||||||
|
b="${d%/}"; b="${b##*/}"
|
||||||
|
if [ "${#b}" = 96 ]; then
|
||||||
|
BOOT_HASH="$b"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -n "$BOOT_HASH" ] || die "Could not find 96-char boot manifest hash"
|
||||||
|
log "Boot hash: $BOOT_HASH"
|
||||||
|
|
||||||
|
JB_TARGET="/private/preboot/$BOOT_HASH/jb-vphone/procursus"
|
||||||
|
[ -d "$JB_TARGET" ] || die "Procursus not found at $JB_TARGET"
|
||||||
|
|
||||||
|
# ═══════════ 0/7 REPLACE LAUNCHCTL ═════════════════════════════
|
||||||
|
# Procursus launchctl crashes (missing _launch_active_user_switch symbol).
|
||||||
|
# iosbinpack64's launchctl talks to launchd fine and always exits 0,
|
||||||
|
# which is enough for dpkg postinst/prerm script compatibility.
|
||||||
|
log "[0/7] Linking iosbinpack64 launchctl into procursus..."
|
||||||
|
IOSBINPACK_LAUNCHCTL=""
|
||||||
|
for p in /iosbinpack64/bin/launchctl /iosbinpack64/usr/bin/launchctl; do
|
||||||
|
[ -f "$p" ] && IOSBINPACK_LAUNCHCTL="$p" && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$IOSBINPACK_LAUNCHCTL" ]; then
|
||||||
|
if [ -f "$JB_TARGET/usr/bin/launchctl" ] && [ ! -L "$JB_TARGET/usr/bin/launchctl" ] && [ ! -f "$JB_TARGET/usr/bin/launchctl.procursus" ]; then
|
||||||
|
mv "$JB_TARGET/usr/bin/launchctl" "$JB_TARGET/usr/bin/launchctl.procursus"
|
||||||
|
log " procursus original saved as launchctl.procursus"
|
||||||
|
fi
|
||||||
|
ln -sf "$IOSBINPACK_LAUNCHCTL" "$JB_TARGET/usr/bin/launchctl"
|
||||||
|
mkdir -p "$JB_TARGET/bin"
|
||||||
|
ln -sf "$IOSBINPACK_LAUNCHCTL" "$JB_TARGET/bin/launchctl"
|
||||||
|
log " linked usr/bin/launchctl + bin/launchctl -> $IOSBINPACK_LAUNCHCTL"
|
||||||
|
else
|
||||||
|
log " WARNING: iosbinpack64 launchctl not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═══════════ 1/7 SYMLINK /var/jb ═════════════════════════════
|
||||||
|
log "[1/7] Creating /private/var/jb symlink..."
|
||||||
|
CURRENT_LINK=$(readlink /private/var/jb 2>/dev/null || true)
|
||||||
|
if [ "$CURRENT_LINK" = "$JB_TARGET" ]; then
|
||||||
|
log " Symlink already correct"
|
||||||
|
else
|
||||||
|
ln -sf "$JB_TARGET" /private/var/jb
|
||||||
|
log " /var/jb -> $JB_TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═══════════ 2/7 FIX OWNERSHIP / PERMISSIONS ═════════════════
|
||||||
|
log "[2/7] Fixing mobile Library ownership..."
|
||||||
|
mkdir -p /var/jb/var/mobile/Library/Preferences
|
||||||
|
chown -R 501:501 /var/jb/var/mobile/Library
|
||||||
|
chmod 0755 /var/jb/var/mobile/Library
|
||||||
|
chown -R 501:501 /var/jb/var/mobile/Library/Preferences
|
||||||
|
chmod 0755 /var/jb/var/mobile/Library/Preferences
|
||||||
|
log " Ownership set"
|
||||||
|
|
||||||
|
# ═══════════ 3/7 RUN prep_bootstrap.sh ═══════════════════════
|
||||||
|
log "[3/7] Running prep_bootstrap.sh..."
|
||||||
|
if [ -f /var/jb/prep_bootstrap.sh ]; then
|
||||||
|
NO_PASSWORD_PROMPT=1 /var/jb/prep_bootstrap.sh || log " prep_bootstrap.sh exited with $?"
|
||||||
|
log " prep_bootstrap.sh completed"
|
||||||
|
else
|
||||||
|
log " prep_bootstrap.sh already ran (deleted itself), skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Re-discover PATH after prep_bootstrap
|
||||||
|
P=""
|
||||||
|
for d in \
|
||||||
|
/var/jb/usr/bin /var/jb/bin /var/jb/sbin /var/jb/usr/sbin \
|
||||||
|
/iosbinpack64/bin /iosbinpack64/usr/bin /iosbinpack64/sbin /iosbinpack64/usr/sbin \
|
||||||
|
/usr/bin /usr/sbin /bin /sbin; do
|
||||||
|
[ -d "$d" ] && P="$P:$d"
|
||||||
|
done
|
||||||
|
export PATH="${P#:}"
|
||||||
|
log " PATH=$PATH"
|
||||||
|
|
||||||
|
# ═══════════ 4/7 CREATE MARKER FILES ═════════════════════════
|
||||||
|
log "[4/7] Creating marker files..."
|
||||||
|
for marker in .procursus_strapped .installed_dopamine; do
|
||||||
|
if [ -f "/var/jb/$marker" ]; then
|
||||||
|
log " $marker already exists"
|
||||||
|
else
|
||||||
|
: > "/var/jb/$marker"
|
||||||
|
chown 0:0 "/var/jb/$marker"
|
||||||
|
chmod 0644 "/var/jb/$marker"
|
||||||
|
log " $marker created"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ═══════════ 5/7 INSTALL SILEO ═══════════════════════════════
|
||||||
|
log "[5/7] Installing Sileo..."
|
||||||
|
SILEO_DEB_PATH="/private/preboot/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||||
|
|
||||||
|
if dpkg -s org.coolstar.sileo >/dev/null 2>&1; then
|
||||||
|
log " Sileo already installed"
|
||||||
|
else
|
||||||
|
if [ -f "$SILEO_DEB_PATH" ]; then
|
||||||
|
dpkg -i "$SILEO_DEB_PATH" || log " dpkg -i sileo exited with $?"
|
||||||
|
log " Sileo installed"
|
||||||
|
else
|
||||||
|
log " WARNING: Sileo deb not found at $SILEO_DEB_PATH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
uicache -a 2>/dev/null || true
|
||||||
|
log " uicache refreshed"
|
||||||
|
|
||||||
|
# ═══════════ 6/7 APT SETUP ══════════════════════════════════
|
||||||
|
log "[6/7] Running apt setup..."
|
||||||
|
|
||||||
|
# Determine apt sources directory
|
||||||
|
HAVOC_LIST="/var/jb/etc/apt/sources.list.d/havoc.list"
|
||||||
|
if [ -d /etc/apt/sources.list.d ] && [ ! -d /var/jb/etc/apt/sources.list.d ]; then
|
||||||
|
HAVOC_LIST="/etc/apt/sources.list.d/havoc.list"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -rIl 'havoc.app' /etc/apt /var/jb/etc/apt 2>/dev/null | grep -q .; then
|
||||||
|
mkdir -p "$(dirname "$HAVOC_LIST")"
|
||||||
|
printf '%s\n' 'deb https://havoc.app/ ./' > "$HAVOC_LIST"
|
||||||
|
log " Havoc source added: $HAVOC_LIST"
|
||||||
|
else
|
||||||
|
log " Havoc source already present"
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt-get -o Acquire::AllowInsecureRepositories=true \
|
||||||
|
-o Acquire::AllowDowngradeToInsecureRepositories=true \
|
||||||
|
update -qq 2>&1 || log " apt update exited with $?"
|
||||||
|
log " apt update done"
|
||||||
|
|
||||||
|
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||||
|
install -y -qq libkrw0-tfp0 2>/dev/null || true
|
||||||
|
log " libkrw0-tfp0 done"
|
||||||
|
|
||||||
|
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||||
|
upgrade -y -qq 2>/dev/null || true
|
||||||
|
log " apt upgrade done"
|
||||||
|
|
||||||
|
# ═══════════ 7/7 INSTALL TROLLSTORE LITE ═════════════════════
|
||||||
|
log "[7/7] Installing TrollStore Lite..."
|
||||||
|
if dpkg -s com.opa334.trollstorelite >/dev/null 2>&1; then
|
||||||
|
log " TrollStore Lite already installed"
|
||||||
|
else
|
||||||
|
apt-get -o APT::Get::AllowUnauthenticated=true \
|
||||||
|
install -y -qq com.opa334.trollstorelite 2>&1 || log " TrollStore install exited with $?"
|
||||||
|
log " TrollStore Lite installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
uicache -a 2>/dev/null || true
|
||||||
|
log " uicache refreshed"
|
||||||
|
|
||||||
|
# ═══════════ DONE ════════════════════════════════════════════
|
||||||
|
: > "$DONE_MARKER"
|
||||||
|
log "=== vphone_jb_setup.sh complete ==="
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
extern int vp_extract_archive(NSString *archivePath, NSString *extractionPath);
|
extern int vp_extract_archive(NSString *archivePath, NSString *extractionPath, NSString **errorOutput);
|
||||||
|
|||||||
+32
-15
@@ -20,14 +20,15 @@ static int copy_data(struct archive *ar, struct archive *aw) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
int vp_extract_archive(NSString *archivePath, NSString *extractionPath, NSString **errorOutput) {
|
||||||
int flags = ARCHIVE_EXTRACT_TIME
|
int flags = ARCHIVE_EXTRACT_TIME
|
||||||
| ARCHIVE_EXTRACT_PERM
|
| ARCHIVE_EXTRACT_PERM
|
||||||
| ARCHIVE_EXTRACT_ACL
|
| ARCHIVE_EXTRACT_SECURE_NODOTDOT;
|
||||||
| ARCHIVE_EXTRACT_FFLAGS
|
|
||||||
| ARCHIVE_EXTRACT_SECURE_SYMLINKS
|
// Resolve symlinks in extractionPath (e.g. /tmp -> /private/tmp on iOS)
|
||||||
| ARCHIVE_EXTRACT_SECURE_NODOTDOT
|
// so ARCHIVE_EXTRACT_SECURE_SYMLINKS doesn't reject trusted system symlinks.
|
||||||
| ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;
|
NSString *resolvedPath = [extractionPath stringByResolvingSymlinksInPath];
|
||||||
|
NSLog(@"vphoned: extract %@ -> %@ (resolved: %@)", archivePath, extractionPath, resolvedPath);
|
||||||
|
|
||||||
struct archive *a = archive_read_new();
|
struct archive *a = archive_read_new();
|
||||||
archive_read_support_format_all(a);
|
archive_read_support_format_all(a);
|
||||||
@@ -39,6 +40,9 @@ int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
|||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
if (archive_read_open_filename(a, archivePath.fileSystemRepresentation, 10240) != ARCHIVE_OK) {
|
if (archive_read_open_filename(a, archivePath.fileSystemRepresentation, 10240) != ARCHIVE_OK) {
|
||||||
|
NSString *err = [NSString stringWithFormat:@"archive_read_open failed: %s", archive_error_string(a)];
|
||||||
|
NSLog(@"vphoned: %@", err);
|
||||||
|
if (errorOutput) *errorOutput = err;
|
||||||
ret = 1;
|
ret = 1;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
@@ -48,30 +52,43 @@ int vp_extract_archive(NSString *archivePath, NSString *extractionPath) {
|
|||||||
int r = archive_read_next_header(a, &entry);
|
int r = archive_read_next_header(a, &entry);
|
||||||
if (r == ARCHIVE_EOF) break;
|
if (r == ARCHIVE_EOF) break;
|
||||||
if (r < ARCHIVE_OK)
|
if (r < ARCHIVE_OK)
|
||||||
fprintf(stderr, "%s\n", archive_error_string(a));
|
NSLog(@"vphoned: archive_read_next_header: %s", archive_error_string(a));
|
||||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
if (r < ARCHIVE_WARN) {
|
||||||
|
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_read_next_header failed: %s", archive_error_string(a)];
|
||||||
|
ret = 1; goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
const char *entryPath = archive_entry_pathname(entry);
|
const char *entryPath = archive_entry_pathname(entry);
|
||||||
if (!entryPath) { ret = 1; goto cleanup; }
|
if (!entryPath) { ret = 1; goto cleanup; }
|
||||||
NSString *currentFile = [NSString stringWithUTF8String:entryPath];
|
NSString *currentFile = [NSString stringWithUTF8String:entryPath];
|
||||||
if (!currentFile) { ret = 1; goto cleanup; }
|
if (!currentFile) { ret = 1; goto cleanup; }
|
||||||
NSString *fullOutputPath = [extractionPath stringByAppendingPathComponent:currentFile];
|
NSString *fullOutputPath = [resolvedPath stringByAppendingPathComponent:currentFile];
|
||||||
archive_entry_set_pathname(entry, fullOutputPath.fileSystemRepresentation);
|
archive_entry_set_pathname(entry, fullOutputPath.fileSystemRepresentation);
|
||||||
|
|
||||||
r = archive_write_header(ext, entry);
|
r = archive_write_header(ext, entry);
|
||||||
if (r < ARCHIVE_OK)
|
if (r < ARCHIVE_OK)
|
||||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
NSLog(@"vphoned: archive_write_header(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||||
else if (archive_entry_size(entry) > 0) {
|
if (r < ARCHIVE_WARN) {
|
||||||
|
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_write_header failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||||
|
ret = 1; goto cleanup;
|
||||||
|
}
|
||||||
|
if (archive_entry_size(entry) > 0) {
|
||||||
r = copy_data(a, ext);
|
r = copy_data(a, ext);
|
||||||
if (r < ARCHIVE_OK)
|
if (r < ARCHIVE_OK)
|
||||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
NSLog(@"vphoned: copy_data(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
if (r < ARCHIVE_WARN) {
|
||||||
|
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"copy_data failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||||
|
ret = 1; goto cleanup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r = archive_write_finish_entry(ext);
|
r = archive_write_finish_entry(ext);
|
||||||
if (r < ARCHIVE_OK)
|
if (r < ARCHIVE_OK)
|
||||||
fprintf(stderr, "%s\n", archive_error_string(ext));
|
NSLog(@"vphoned: archive_write_finish_entry(%@): %s (r=%d)", currentFile, archive_error_string(ext), r);
|
||||||
if (r < ARCHIVE_WARN) { ret = 1; goto cleanup; }
|
if (r < ARCHIVE_WARN) {
|
||||||
|
if (errorOutput) *errorOutput = [NSString stringWithFormat:@"archive_write_finish_entry failed for %@: %s", currentFile, archive_error_string(ext)];
|
||||||
|
ret = 1; goto cleanup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
|||||||
@@ -745,9 +745,10 @@ static int vp_extract_package_to_directory(
|
|||||||
NSString *extractionPath,
|
NSString *extractionPath,
|
||||||
NSString **detailOutput
|
NSString **detailOutput
|
||||||
) {
|
) {
|
||||||
int ret = vp_extract_archive(fileToExtract, extractionPath);
|
NSString *archiveError = nil;
|
||||||
|
int ret = vp_extract_archive(fileToExtract, extractionPath, &archiveError);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (detailOutput) *detailOutput = @"libarchive extraction failed";
|
if (detailOutput) *detailOutput = archiveError ?: @"libarchive extraction failed";
|
||||||
return 168;
|
return 168;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -796,7 +797,7 @@ NSDictionary *vp_handle_custom_install(NSDictionary *msg) {
|
|||||||
certPath = nil;
|
certPath = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *tmpPackagePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
NSString *tmpPackagePath = [[NSTemporaryDirectory() stringByResolvingSymlinksInPath] stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||||
if (![[NSFileManager defaultManager] createDirectoryAtPath:tmpPackagePath withIntermediateDirectories:NO attributes:nil error:nil]) {
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:tmpPackagePath withIntermediateDirectories:NO attributes:nil error:nil]) {
|
||||||
NSMutableDictionary *response = vp_make_response(@"err", reqId);
|
NSMutableDictionary *response = vp_make_response(@"err", reqId);
|
||||||
response[@"msg"] = @"failed to create temporary extraction directory";
|
response[@"msg"] = @"failed to create temporary extraction directory";
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ class VPhoneControl {
|
|||||||
if let reqId, let pending = removePending(id: reqId) {
|
if let reqId, let pending = removePending(id: reqId) {
|
||||||
if type == "err" {
|
if type == "err" {
|
||||||
let detail = msg["msg"] as? String ?? "unknown error"
|
let detail = msg["msg"] as? String ?? "unknown error"
|
||||||
pending.handler(.failure(ControlError.guestError(detail)))
|
DispatchQueue.main.async { pending.handler(.failure(ControlError.guestError(detail))) }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,19 +543,19 @@ class VPhoneControl {
|
|||||||
if Self.readFully(fd: fd, buf: buf, count: size) {
|
if Self.readFully(fd: fd, buf: buf, count: size) {
|
||||||
let data = Data(bytes: buf, count: size)
|
let data = Data(bytes: buf, count: size)
|
||||||
buf.deallocate()
|
buf.deallocate()
|
||||||
pending.handler(.success((msg, data)))
|
DispatchQueue.main.async { pending.handler(.success((msg, data))) }
|
||||||
} else {
|
} else {
|
||||||
buf.deallocate()
|
buf.deallocate()
|
||||||
pending.handler(.failure(ControlError.protocolError("failed to read file data")))
|
DispatchQueue.main.async { pending.handler(.failure(ControlError.protocolError("failed to read file data"))) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pending.handler(.success((msg, Data())))
|
DispatchQueue.main.async { pending.handler(.success((msg, Data()))) }
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal response (ok, pong, etc.)
|
// Normal response (ok, pong, etc.)
|
||||||
pending.handler(.success((msg, nil)))
|
DispatchQueue.main.async { pending.handler(.success((msg, nil))) }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +602,7 @@ class VPhoneControl {
|
|||||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeout) { [weak self] in
|
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeout) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
guard let pending = removePending(id: id) else { return }
|
guard let pending = removePending(id: id) else { return }
|
||||||
pending.handler(.failure(ControlError.requestTimedOut(type: type, seconds: timeoutSeconds)))
|
DispatchQueue.main.async { pending.handler(.failure(ControlError.requestTimedOut(type: type, seconds: timeoutSeconds))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@preconcurrency import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user