fix(packaging): stop macOS packages relocating themselves elsewhere - #46
Open
glennmichael123 wants to merge 1 commit into
Open
fix(packaging): stop macOS packages relocating themselves elsewhere#46glennmichael123 wants to merge 1 commit into
glennmichael123 wants to merge 1 commit into
Conversation
`createPKG` let `pkgbuild` infer its component, and `pkgbuild`'s default
for a bundle is `BundleIsRelocatable = true`. Every .pkg craft has ever
produced therefore carries:
<relocate><bundle id="dev.example.app"/></relocate>
which tells `installer` the payload path is a suggestion. It looks the
bundle identifier up on the target volume and, if the system has a copy
of that bundle registered anywhere, writes the payload over *that* copy
instead — and exits 0. The user is told "The install was successful" and
finds nothing at /Applications.
That is the native lifecycle workflow's macOS failure, which I had
called Intel-specific. It is not: darwin-arm64 failed identically in an
adjacent run on the same code and the same image. What separates a green
leg from a red one is how long the job had been running beforehand — the
three failing legs reached the lifecycle step ~22 minutes in, the eight
passing ones 20-124 seconds in — which is what a race against the
system's bundle indexer looks like. The script leaves two copies of the
fixture bundle in its work directory, and those are the relocation
target.
Fixed by writing the component plist instead of inferring it, with
BundleIsRelocatable off, which reduces the directive to a bare
`<relocate/>` — nothing for installer to redirect to. Verified on a real
fixture package, not just in the unit test.
`BundleIsVersionChecked` is off too: with it on, installing an older
version over a newer one is skipped, which silently turns the rollback
step — and a user's rollback — into a no-op.
Two things found alongside:
- The staging directory came from mkdtemp, mode 0700, and pkgbuild
records the --root directory's own mode as the mode of `.`. Under
`--install-location /`, `.` is the target volume's root, so the
package asked for `/` to be 0700. Now staged in a 0755 child.
- `installer` reporting success while writing elsewhere left no trace
beyond a bare ENOENT from the launch step. The receipt is recorded
after each install now, into report.json and the uploaded evidence:
`location:` is where the payload actually landed. The workflow also
uploads the whole evidence directory, since the compiled fixtures sat
outside the old path and made the artifact useless for diagnosing this.
This is a user-facing bug, not a CI one. Any app packaged by craft can
install over a stray copy of itself and report success.
🔴 Benchmark ResultsPerformance regression detected!
Benchmark Details
|
✅ Binary Size Report
Size limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every
.pkgcraft has ever produced can install itself somewhere other than where it says, and report success.createPKGletpkgbuildinfer its component.pkgbuild's default for a bundle isBundleIsRelocatable = true, so the package carries:That directive tells
installerthe payload path is only a suggestion. It looks the bundle identifier up on the target volume and, if the system has a copy of that bundle registered anywhere, writes the payload over that copy instead — and exits 0. The user gets "The install was successful" and finds nothing at/Applications.I had this wrong, twice
I told you this was Intel-specific. It isn't —
darwin-arm64failed with a byte-identicalENOENTin an adjacent run, on the same code and the same runner image.And the relocation directive alone isn't the cause either — it's present in packages from green runs too. It's the enabler; the trigger is a registered copy of the same bundle id existing when the install happens. That's why it's intermittent, and what actually separates green from red is how long the job had already been running:
Which is what a race against the system's bundle indexer looks like. The lifecycle script leaves two copies of the fixture bundle in its work directory — those are the relocation target.
The fix
Write the component plist instead of inferring it, with
BundleIsRelocatableoff. Verified on a real fixture package, not just in the unit test:Empty
<relocate/>— nothing forinstallerto redirect to — with the payload unchanged.Note the
pkg-infoattributerelocatable="false"appears either way and does not govern this; only the<relocate>element does. I built both variants side by side to confirm that, because that attribute is exactly the thing that makes the current package look fine on inspection.BundleIsVersionCheckedis off too: with it on, installing an older version over a newer one is skipped — silently turning the rollback step, and a user's rollback, into a no-op.Two things found alongside
/to be0700. The staging dir came frommkdtemp(mode 0700), andpkgbuildrecords the--rootdirectory's own mode as the mode of.— which under--install-location /is the target volume's root. Measured:drwx------ ".". Now staged in a0755child, measureddrwxr-xr-x.installerreporting success while writing elsewhere surfaced only as a bareENOENTfrom the launch step, which cannot distinguish "nothing installed" from "installed elsewhere". The receipt is now recorded after each install, intoreport.jsonand the uploaded evidence —location:is where the payload actually landed. The workflow uploads the whole evidence directory too; the compiled fixtures sat outside the old upload path, which is why this needed a workflow dispatch to diagnose rather than an artifact download.Verification
bun test— 471 pass, including 3 new ones; I confirmed they discriminate by flippingBundleIsRelocatableback totrueand watching 18 pass / 1 failtsc --noEmitclean,pickiercleanpkgutil --expand/lsbom/pkgutil --payload-filesThis is a user-facing bug, not a CI one. The lifecycle workflow is just the thing that noticed.