diff --git a/scripts/build.sh b/scripts/build.sh index 8f86e62..810255b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -95,7 +95,11 @@ done # requirements.txt lets the app provision its own ~/.vphone/venv on first run # (see VPhoneResources.pythonExecutable) — the app carries no venv itself. cp -f requirements.txt "${RES}/requirements.txt" -echo " bundled: scripts/ (patchers+resources), tools/, .tools/bin/{trustcache,insert_dylib}, vphoned.signed, requirements.txt" +# debs.list = extra-deb manifest (fetch_debs.sh reads $base/debs.list); README.md +# = the Tested-Environments table fw_prepare.sh reads to label Supported firmwares. +cp -f debs.list "${RES}/debs.list" +cp -f README.md "${RES}/README.md" +echo " bundled: scripts/ (patchers+resources), tools/, .tools/bin/{trustcache,insert_dylib}, vphoned.signed, requirements.txt, debs.list, README.md" # Re-sign: codesign seals Contents/Resources at sign time, so the earlier # bundle-step signature (made before these assets existed) is now stale — diff --git a/scripts/cfw_install_exp.sh b/scripts/cfw_install_exp.sh index b31f030..8ec1822 100755 --- a/scripts/cfw_install_exp.sh +++ b/scripts/cfw_install_exp.sh @@ -539,7 +539,7 @@ fi # ── Extra debs: download from manifest, then stage the whole cache ────── echo " Fetching extra debs..." zsh "$SCRIPT_DIR/fetch_debs.sh" || true -DEBS_CACHE="${SCRIPT_DIR:h}/debs" +DEBS_CACHE="${VPHONE_DEBS_DIR:-${SCRIPT_DIR:h}/debs}" DEBS_DEST="$MNT5/$BOOT_HASH/debs" /bin/rm -rf "$DEBS_DEST" deb_count=0 diff --git a/scripts/cfw_install_jb.sh b/scripts/cfw_install_jb.sh index 5520a47..730fa53 100755 --- a/scripts/cfw_install_jb.sh +++ b/scripts/cfw_install_jb.sh @@ -336,7 +336,7 @@ fi # ── Extra debs: download from manifest, then stage the whole cache ────── echo " Fetching extra debs..." zsh "$SCRIPT_DIR/fetch_debs.sh" || true -DEBS_CACHE="${SCRIPT_DIR:h}/debs" +DEBS_CACHE="${VPHONE_DEBS_DIR:-${SCRIPT_DIR:h}/debs}" DEBS_DEST="$MNT5/$BOOT_HASH/debs" /bin/rm -rf "$DEBS_DEST" deb_count=0 diff --git a/scripts/fetch_debs.sh b/scripts/fetch_debs.sh index 8dc78c3..9117cb6 100755 --- a/scripts/fetch_debs.sh +++ b/scripts/fetch_debs.sh @@ -13,7 +13,10 @@ set -uo pipefail SCRIPT_DIR="${0:a:h}" REPO_ROOT="${SCRIPT_DIR:h}" -CACHE_DIR="${1:-$REPO_ROOT/debs}" +# VPHONE_DEBS_DIR (set by the app to a writable ~/.vphone/debs) keeps the cache +# out of the read-only .app bundle; the manifest is bundled next to this script's +# repo/Resources root, so $REPO_ROOT/debs.list resolves in both dev and .app. +CACHE_DIR="${1:-${VPHONE_DEBS_DIR:-$REPO_ROOT/debs}}" MANIFEST="${2:-$REPO_ROOT/debs.list}" mkdir -p "$CACHE_DIR" diff --git a/sources/VPhoneCore/VPhoneResources.swift b/sources/VPhoneCore/VPhoneResources.swift index 0909fa6..d6d2a4e 100644 --- a/sources/VPhoneCore/VPhoneResources.swift +++ b/sources/VPhoneCore/VPhoneResources.swift @@ -16,8 +16,21 @@ public struct VPhoneResources: Sendable { // MARK: - Resolution - public static func resolve(executablePath: String = CommandLine.arguments[0]) -> VPhoneResources { - let exe = URL(fileURLWithPath: executablePath).resolvingSymlinksInPath() + /// The running executable, resolved reliably. `CommandLine.arguments[0]` is + /// NOT reliable — under a PATH/symlink launch (e.g. a Homebrew symlink) it's + /// a bare name that `URL(fileURLWithPath:)` resolves against the CWD, so the + /// binary/base end up under `$HOME`. `Bundle.main.executableURL` is the + /// kernel-provided executable path, correct regardless of how the process + /// was invoked; resolve symlinks so a brew symlink lands on the real binary + /// inside the .app. + public static func runningExecutable() -> URL { + if let exe = Bundle.main.executableURL { return exe.resolvingSymlinksInPath() } + return URL(fileURLWithPath: CommandLine.arguments[0]).resolvingSymlinksInPath() + } + + public static func resolve(executablePath: String? = nil) -> VPhoneResources { + let exe = executablePath.map { URL(fileURLWithPath: $0).resolvingSymlinksInPath() } + ?? runningExecutable() let macos = exe.deletingLastPathComponent() // …/Contents/MacOS if macos.lastPathComponent == "MacOS", macos.deletingLastPathComponent().lastPathComponent == "Contents" { @@ -62,6 +75,7 @@ public struct VPhoneResources: Sendable { } public var ipswCacheDir: URL { userCacheDir.appendingPathComponent("ipsws") } public var sealVolumeCacheDir: URL { userCacheDir.appendingPathComponent("tools") } + public var debsCacheDir: URL { userCacheDir.appendingPathComponent("debs") } public var toolsBinDir: URL { base.appendingPathComponent(".tools/bin") } // MARK: - Python diff --git a/sources/vphone-cli/VPhoneCreateOrchestrator.swift b/sources/vphone-cli/VPhoneCreateOrchestrator.swift index cfb0ef3..83fa289 100644 --- a/sources/vphone-cli/VPhoneCreateOrchestrator.swift +++ b/sources/vphone-cli/VPhoneCreateOrchestrator.swift @@ -448,9 +448,11 @@ public struct VPhoneCreateOrchestrator { if options.forceDSCMaxSlide { env["FORCE_DSC_MAXSLIDE"] = "1" } try FileManager.default.createDirectory(at: resources.ipswCacheDir, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: resources.sealVolumeCacheDir, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: resources.debsCacheDir, withIntermediateDirectories: true) env["VPHONE_PYTHON"] = try resources.pythonExecutable().path env["IPSW_DIR"] = resources.ipswCacheDir.path env["VPHONE_SEAL_DIR"] = resources.sealVolumeCacheDir.path + env["VPHONE_DEBS_DIR"] = resources.debsCacheDir.path for (key, value) in sudoEnvExtras { env[key] = value } let args = [resources.cfwInstallHostScript.path, "--variant", options.variant, bundleURL.path] diff --git a/sources/vphone-cli/VPhoneRestoreCLI.swift b/sources/vphone-cli/VPhoneRestoreCLI.swift index 2cd9f89..ef2efb2 100644 --- a/sources/vphone-cli/VPhoneRestoreCLI.swift +++ b/sources/vphone-cli/VPhoneRestoreCLI.swift @@ -105,9 +105,11 @@ struct VPhoneCFWInstallCommand: ParsableCommand { // `fw patch` path (CryptexFilesystemPatcher). try FileManager.default.createDirectory(at: resources.ipswCacheDir, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: resources.sealVolumeCacheDir, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: resources.debsCacheDir, withIntermediateDirectories: true) env["VPHONE_PYTHON"] = try resources.pythonExecutable().path env["IPSW_DIR"] = resources.ipswCacheDir.path env["VPHONE_SEAL_DIR"] = resources.sealVolumeCacheDir.path + env["VPHONE_DEBS_DIR"] = resources.debsCacheDir.path let args = [resources.cfwInstallHostScript.path, "--variant", variant, bundle.url.path] if v.tracesInternals { diff --git a/sources/vphone-cli/VPhoneVMCreateCLI.swift b/sources/vphone-cli/VPhoneVMCreateCLI.swift index bec390b..5500e85 100644 --- a/sources/vphone-cli/VPhoneVMCreateCLI.swift +++ b/sources/vphone-cli/VPhoneVMCreateCLI.swift @@ -28,7 +28,7 @@ struct VPhoneVMCreateCommand: ParsableCommand { func run() throws { let resources = projectRoot.map { VPhoneResources(base: URL(fileURLWithPath: $0)) } ?? .resolve() - let selfExe = URL(fileURLWithPath: CommandLine.arguments[0]).resolvingSymlinksInPath() + let selfExe = VPhoneResources.runningExecutable() let orchestrator = VPhoneCreateOrchestrator( library: lib.library, resources: resources, selfExecutable: selfExe) try orchestrator.run(.init( diff --git a/sources/vphone-cli/VPhoneVMLaunchCLI.swift b/sources/vphone-cli/VPhoneVMLaunchCLI.swift index 784b141..da6b7a8 100644 --- a/sources/vphone-cli/VPhoneVMLaunchCLI.swift +++ b/sources/vphone-cli/VPhoneVMLaunchCLI.swift @@ -27,7 +27,7 @@ struct VPhoneVMLaunchCommand: ParsableCommand { // The running executable is BOTH what we boot from and what preflight // should check — a bundled .app is its own boot binary. - let bootBinary = URL(fileURLWithPath: CommandLine.arguments[0]).resolvingSymlinksInPath() + let bootBinary = VPhoneResources.runningExecutable() guard FileManager.default.isExecutableFile(atPath: bootBinary.path) else { FileHandle.standardError.write(Data( "error: \(bootBinary.path) not found — build it first (make build/bundle).\n".utf8))