Make macOS pkg per-user only (~/Applications)
System installs land in /Applications where Velopack's UpdateMac may not be able to replace the .app during auto-update. Restricting the distribution domains to currentUserHome skips the Destination Select page and needs no admin rights; the postinstall script's sudo prefixes are dropped since it already runs as the installing user.
This commit is contained in:
@@ -381,6 +381,8 @@ def build_velopack(version: str) -> Path:
|
|||||||
setup = next(iter(sorted(releases_dir.glob(f"*{artifact_ext}"))), None)
|
setup = next(iter(sorted(releases_dir.glob(f"*{artifact_ext}"))), None)
|
||||||
if setup is None:
|
if setup is None:
|
||||||
raise RuntimeError(f"vpk produced no *{artifact_ext} in {releases_dir}")
|
raise RuntimeError(f"vpk produced no *{artifact_ext} in {releases_dir}")
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
force_macos_user_install(setup)
|
||||||
artifact = (
|
artifact = (
|
||||||
_REPO_ROOT
|
_REPO_ROOT
|
||||||
/ "build"
|
/ "build"
|
||||||
@@ -391,6 +393,46 @@ def build_velopack(version: str) -> Path:
|
|||||||
return artifact
|
return artifact
|
||||||
|
|
||||||
|
|
||||||
|
def force_macos_user_install(pkg: Path) -> None:
|
||||||
|
"""Restrict the Velopack-generated pkg to per-user installs (~/Applications).
|
||||||
|
|
||||||
|
Velopack hardcodes two install domains (currentUserHome + localSystem) in
|
||||||
|
the distribution XML. System installs land in /Applications, which the
|
||||||
|
user may not own — Velopack's UpdateMac then cannot replace the .app on
|
||||||
|
auto-update. With a single domain, macOS Installer skips the Destination
|
||||||
|
Select page and installs to ~/Applications without admin rights.
|
||||||
|
|
||||||
|
Also drops the `sudo -u "$USER"` prefix from Velopack's postinstall
|
||||||
|
script: under a per-user install the script already runs as the
|
||||||
|
installing user, and sudo would fail for lack of a tty.
|
||||||
|
"""
|
||||||
|
expanded = pkg.with_name(pkg.stem + "-expanded")
|
||||||
|
shutil.rmtree(expanded, ignore_errors=True)
|
||||||
|
# --expand-full also expands the component pkg, exposing its Scripts dir
|
||||||
|
subprocess.run(["pkgutil", "--expand-full", str(pkg), str(expanded)], check=True)
|
||||||
|
|
||||||
|
dist_xml = expanded / "Distribution"
|
||||||
|
xml = dist_xml.read_text()
|
||||||
|
new_xml, count = re.subn(
|
||||||
|
r"<domains [^>]*/>",
|
||||||
|
'<domains enable_anywhere="false" enable_currentUserHome="true" enable_localSystem="false" />',
|
||||||
|
xml,
|
||||||
|
)
|
||||||
|
if count != 1:
|
||||||
|
raise RuntimeError("Unexpected distribution.xml: <domains> not found")
|
||||||
|
dist_xml.write_text(new_xml)
|
||||||
|
|
||||||
|
postinstalls = list(expanded.glob("*.pkg/Scripts/postinstall"))
|
||||||
|
if len(postinstalls) != 1:
|
||||||
|
raise RuntimeError(f"Unexpected pkg layout: postinstalls={postinstalls}")
|
||||||
|
postinstall = postinstalls[0]
|
||||||
|
script = postinstall.read_text()
|
||||||
|
postinstall.write_text(script.replace('sudo -u "$USER" ', ""))
|
||||||
|
|
||||||
|
subprocess.run(["pkgutil", "--flatten", str(expanded), str(pkg)], check=True)
|
||||||
|
shutil.rmtree(expanded)
|
||||||
|
|
||||||
|
|
||||||
def read_version() -> str:
|
def read_version() -> str:
|
||||||
"""Read version via setuptools_scm (same logic as hatch-vcs)."""
|
"""Read version via setuptools_scm (same logic as hatch-vcs)."""
|
||||||
return setuptools_scm.get_version(root=str(_REPO_ROOT))
|
return setuptools_scm.get_version(root=str(_REPO_ROOT))
|
||||||
|
|||||||
Reference in New Issue
Block a user