mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
fix: resolve all paths correctly for the bundled / brew-installed app
A whole-codebase audit surfaced path bugs that only bite the bundled .app — especially when brew puts vphone-cli on $PATH via a symlink, so the process launches by a bare name from an arbitrary CWD: - HIGH (crash): CommandLine.arguments[0] was used to locate the running binary and the resource base (VPhoneResources.resolve default, vm launch bootBinary, vm create selfExe). Under a bare-name PATH launch argv[0] is just "vphone-cli", which URL(fileURLWithPath:) resolves against the CWD (e.g. $HOME/vphone-cli) — so `vm launch` errored "not found" and `vm create` couldn't respawn to boot. Add VPhoneResources.runningExecutable() using Bundle.main.executableURL (the kernel-provided path, correct regardless of argv[0]/CWD/symlink) and route all three through it. One helper fixes every .resolve() consumer. - MEDIUM (silent): the extra-deb feature resolved its cache + manifest under the read-only bundle (Resources/debs, Resources/debs.list). Honor VPHONE_DEBS_DIR (a writable ~/.vphone/debs the app now sets, mirroring IPSW_DIR/VPHONE_SEAL_DIR) and bundle debs.list. - LOW (cosmetic): fw_prepare read ../README.md (absent in the bundle → firmwares labeled "Not Tested"). Bundle README.md. Verified: Bundle.main.executableURL yields the real binary under a bare-name symlink launch (argv[0] → $HOME); build clean; 79 VPhoneCore tests pass.
This commit is contained in:
+5
-1
@@ -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 —
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user