fix(windows): ARM64 installs — nsis7z-safe payloads, per-arch manifests, single draft - #1208
fix(windows): ARM64 installs — nsis7z-safe payloads, per-arch manifests, single draft#1208JamieRuderman wants to merge 34 commits into
Conversation
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
…ase checks keep working
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9acb4ed68d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| const distDir = path.resolve(process.argv[2] || 'dist') | ||
| const installers = fs.readdirSync(distDir).filter(f => /^Remote\.It-Installer-(ia32|x64|arm64)\.exe$/.test(f)) |
There was a problem hiding this comment.
Derive installer names from the selected brand
When the workflow runs with brand=cachengo, brand-electron.ts changes productName to Telepath, so the configured artifact pattern produces Telepath-Installer-<arch>.exe. This hard-coded Remote.It filter finds no installers and exits with status 1, making every Cachengo Windows release fail verification while leaving its actual assets untouched by the similarly hard-coded cleanup. Derive the expected name from the branded package configuration or match the architecture suffix independently of the product name.
Useful? React with 👍 / 👎.
| if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" --json isDraft >/dev/null 2>&1; then | ||
| echo "release $VERSION already exists" | ||
| else | ||
| gh release create "$VERSION" --repo "$GITHUB_REPOSITORY" --draft --title "${VERSION#v}" --notes "" --target "$GITHUB_SHA" |
There was a problem hiding this comment.
Create the draft in the branded release repository
When brand=cachengo is dispatched from this repository, branding changes the package repository—and therefore electron-builder's publish destination—to cachengo/desktop, but this creates the coordinating draft in $GITHUB_REPOSITORY (remoteit/desktop). The platform publishers therefore do not share the prepared draft, and the later manifest step also operates on the wrong release. Resolve the owner/repository from the selected brand and use it consistently for all gh operations.
Useful? React with 👍 / 👎.
…e Windows release steps
… the branded productName
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 280f393da0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - name: Resolve the release repository for the brand | ||
| id: repo | ||
| run: echo "RELEASE_REPO=$(node electron/scripts/release-repo.js)" >> $GITHUB_OUTPUT |
There was a problem hiding this comment.
Authenticate writes to the branded release repository
When brand=cachengo is dispatched from remoteit/desktop, this resolves RELEASE_REPO to cachengo/desktop, but GH_TOKEN is still ${{ secrets.github_token }} from the workflow repository. GitHub's automatic token is scoped to the repository containing the workflow, so the new draft-creation step receives a permission error before the build matrix starts, and the later asset operations have the same problem. Supply a token or GitHub App credential with write access to the selected repository.
Useful? React with 👍 / 👎.
| if gh release view "$VERSION" --repo "$RELEASE_REPO" --json isDraft >/dev/null 2>&1; then | ||
| echo "release $VERSION already exists in $RELEASE_REPO" | ||
| else | ||
| gh release create "$VERSION" --repo "$RELEASE_REPO" --draft --title "${VERSION#v}" --notes "" --target "$GITHUB_SHA" |
There was a problem hiding this comment.
Create cross-repository tags from a destination commit
On the first cachengo release for a version, the tag normally does not exist in cachengo/desktop, so gh release create must create it there; however, $GITHUB_SHA identifies the commit checked out from the source repository and is not guaranteed to exist in the destination. As gh release create --help defines --target as a target branch or full commit SHA for automatic tag creation, the API rejects this whenever the repositories do not share that exact object. Use a branch or commit that exists in RELEASE_REPO, or pre-create/push the destination tag.
Useful? React with 👍 / 👎.
| const newest = data.find(item => !item.draft && (autoUpdater.allowPrerelease || !item.prerelease)) | ||
| return newest?.assets?.some(a => a.name === asset) ? newest.tag_name : undefined |
There was a problem hiding this comment.
Preserve the current prerelease channel while steering
For an emulated Windows client already on a beta build with prerelease updates enabled, this treats every non-draft prerelease as eligible and pins whichever release the API returns first. If a newer alpha release exists, the beta client is therefore steered to its latest-<arch>.yml, bypassing electron-updater's normal rule that prevents beta users from moving onto alpha. Filter candidates using the current version's prerelease channel semantics before selecting the pinned release.
Useful? React with 👍 / 👎.
…rget the commit only in-repo
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 19262a3cfe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| GH_TOKEN: ${{ secrets.github_token }} | ||
| # A brand's release publishes to the brand's own repository, which the automatic token | ||
| # cannot reach; RELEASE_TOKEN (optional) supplies one that can. | ||
| GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.github_token }} |
There was a problem hiding this comment.
Select the release token only for cross-repository builds
When RELEASE_TOKEN is a least-privilege token granting access only to cachengo/desktop, it still replaces github_token for the default remoteit build. Since the permission check is skipped when RELEASE_REPO == GITHUB_REPOSITORY, the subsequent draft creation fails because that token cannot write to remoteit/desktop, blocking every normal release while the Cachengo credential is configured. Choose the token after resolving the destination repository, or fall back to github_token for same-repository builds.
Useful? React with 👍 / 👎.
| const channel = releaseChannel(newest.tag_name) || 'latest' | ||
| return newest.assets?.some(a => a.name === `${channel}-${nativeArch}.yml`) |
There was a problem hiding this comment.
Use the update-manifest channel emitted by the build
For a semantic prerelease such as v3.49.0-beta.2, this derives beta-arm64.yml, but the current GitHub electron-builder configuration has no explicit publish channel and emits latest.yml; the finalizer consequently creates latest-arm64.yml, not beta-arm64.yml. The asset check therefore rejects every such release and emulated clients remain on their old architecture, despite the new beta/alpha eligibility logic. Align the generated per-architecture manifest names with the tag-derived channel or have steering request the channel actually published.
Useful? React with 👍 / 👎.
… instead of a pinned release
…grade keeps its own folder
…ublish checks on branch builds
…e unused channel loop
…he uninstaller mirror
… ia32 installer compiles
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 419e5db94b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "::error::$drafts draft releases are tagged $VERSION. Keep one, delete the rest, and re-run - see RELEASE.md." | ||
| exit 1 | ||
| fi | ||
| gh release upload "$VERSION" $(cat manifests.txt) --repo "$RELEASE_REPO" --clobber ;; |
There was a problem hiding this comment.
Gate draft manifest uploads on tag builds
When a branch smoke build runs while a draft for the current package version exists, this draft arm executes even though prepare intentionally skips publishing for branches, and replaces that pending release's manifests with files generated by the branch build. This can make the draft point at different or unsigned installers; gh release upload --help confirms that --clobber deletes existing assets before uploading replacements. Require GITHUB_REF_TYPE=tag before this upload (and before the analogous installer cleanup) so branch builds cannot mutate a release draft.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a4ea2700de
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ; uninstaller was skipped. Copy only its entry: see RELEASE.md, "Cross-architecture upgrades". | ||
| SetRegView 64 | ||
| ReadRegStr $0 HKLM "${UNINSTALL_REGISTRY_KEY}" UninstallString | ||
| ${if} $0 == "" |
There was a problem hiding this comment.
Preserve cleanup state when a prior mirror already exists
If a native installer is canceled or aborts after preInit writes the 64-bit value but before customInstall, that registry change persists. On retry, this guard sees the mirrored UninstallString and never sets Mirrored32; after uninstalling the ia32 app, customInstall therefore skips deleting its 32-bit keys, leaving a ghost Programs and Features entry alongside the native installation. Detect and mark an existing mirror, or defer/roll back the mirror, so retried upgrades still clean the original registry view.
Useful? React with 👍 / 👎.
| await autoUpdater.checkForUpdatesAndNotify() | ||
| this.emitStatus() |
There was a problem hiding this comment.
Fall through when a fallback release offers no update
When the latest selected release lacks latest-<arch>.yml but an older release still contains it, this generic check succeeds even if that manifest's version is already at or below the installed version. Returning true then prevents the outer recovery path from calling checkWithoutSteering, so the client never considers the newer release's usable latest.yml and remains stuck. Inspect the check result and continue with the unsteered feed when the pinned manifest offers no update.
Useful? React with 👍 / 👎.
…rough when the fallback release is not newer
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Why
3.47.1 failed to install on every Windows ARM64 machine with "The Remote.It agent service could not be installed." Two independent defects, found in sequence:
latest.ymlfiles, written in build-completion order. In 3.47.1 that entry became the 301 MB multi-arch installer. Hot-fixed by hand on v3.47.1 (ia32 first).nsis7z.dll(built 2019) can't decode it and silently skips those entries. The installer removes the old install, then landsapp.asar, every.pak, and the x86elevate.exe— but noRemote.It.exe, no DLLs, noresources\*.exe. This is the 3.47.1 universal failure, the 3.47.1 standalone arm64 installer on remote.it/download, and the 3.48.1 pre-release on an ARM64 Surface. 3.46.1 (26.8.1) used BCJ2 throughout and works. Defender ruled out on the machine.What
ELECTRON_BUILDER_7Z_FILTER=BCJ2in thebuild-electronscript; reproduces the 26.8.1 method string exactly.electron/scripts/verify-win-installers.jscarves each installer's payload by its 7z signature, lists its coders, and fails the build — pulling the installers from the draft — on any coder nsis7z lacks or any missing executable. Validated locally: 3.48.1 arm64 fails on exactly the files that went missing; 3.48.1 ia32 and 3.46.1 arm64 pass.nsis.buildUniversalInstaller: false; names unchanged.finalize-win-update-manifests.jsordersfilesia32 → x64 → arm64, verifies sha512/size on disk, writeslatest-{ia32,x64,arm64}.yml, re-uploads with--clobber. Uploads only into a draft; a published version fails the job; several drafts sharing the tag fail the job.preparejob; runners racing on "release doesn't exist" produced two drafts with the installers split between them.AutoUpdatersets the GitHub feed'schanneloption tolatest-<arch>. That option only renames the manifest GitHubProvider fetches from the release it picks by its normal rules, so no API call, no pinning, and no hand-rolled channel logic;autoUpdater.channelis never set, because that one also changes which tags are considered and breaks every pre-release user's checks. If the picked release predates the per-arch files the app falls back tolatest.yml(current arch). Tags with a semver pre-release id (-beta.1) update on the current arch only, since GitHubProvider asks forbeta.ymland falls back tolatest.ymlitself; the native hop waits for the next plain-version tag.remoteit.exelocked, extraction couldn't replace it ("Remote.It cannot be closed… Retry"), and the result was an arm64 app with an ia32 agent plus two Programs-and-Features entries.preInitnow copies a 32-bit-onlyUninstallStringinto the 64-bit view so the normal upgrade path runs the old uninstaller (electron-builder derives the old folder from the uninstaller path);customInstallremoves the stale 32-bit keys only after that copy. Only the uninstaller entry is copied — a mirroredInstallLocationis adopted as$INSTDIR, which is how the 3.48.3 → 3.48.4 pre-release hop put the arm64 app underProgram Files (x86)(found by the self-review below); a public ia32 → native upgrade lands inProgram Files. Every currently-ia32 machine takes this hop when steering moves it native.resources\remoteit.exeexists beforeagent install(this is what turned "agent service could not be installed" into "remoteit.exe is missing" and made defect 2 diagnosable); distinguishes "could not start" from a non-zero exit.ui.tsemits{ ...preferences, language }on startup; before the backend's preferences reach the renderer that spread is{version:'', cliVersion:''}, andpreferences.setreplaced the file with it — clearingautoUpdate(no more update checks) andallowPrerelease.Preferences.setnow merges, andui.tssends only{ language }so the un-hydrated snapshot never reaches the backend at all (the merge alone still persistedversion: ''/cliVersion: ''). Seen on the Surface at first start of 3.48.2.electron/scripts/release-repo.jsresolves the publish repository frombrands/<brand>/config.ts(cachengo→cachengo/desktop) beforenpm install;prepare, the guard's asset cleanup and the manifest upload all target it.verify-win-installers.jsderives installer and app names from the brandedproductName(Telepath-Installer-*.exe,Telepath.exe) instead of hard-codingRemote.It./code-review—RELEASE_TOKENis used only when the brand publishes to another repository (a token scoped tocachengo/desktopno longer breaksremoteitreleases); the write-access check and the draft run on tag builds only, so branch builds of a brand keep working as a smoke test; a transientgh release viewfailure no longer creates a second draft; a branch build never touches a draft (no manifest upload, no installer cleanup) and no longer fails when its version is already published;--target $GITHUB_SHAonly for same-repository builds. Steering no longer derives<channel>-<arch>.ymlfrom the tag — electron-builder's GitHub publisher only ever writeslatest.yml, so that lookup could never succeed on a pre-release tag.js-yamlis declared (it was only reachable through hoisting) and the dead7zip-binlookup is gone. Round 4: a retried upgrade after an aborted run recognises the already-mirrored uninstaller entry and still cleans the 32-bit keys; a steered fallback that finds only an older release with the per-arch manifest falls through to the unsteered feed instead of reporting no update.npm versionon the branch (pre-release test builds). Tags are annotated and point at their bump commits — re-point at the shipped commit at release time; merge with a merge commit or rebase.Verified
prepare: one draft, 25 assets, no universal installer, hashes match, guard passes (BCJ2 LZMA LZMA2), livev3.47.1untouched.process.archmatch (cleanCustomRemoveFiles 3.47.1); the preferences race fired at 3.48.3's first start and the merge fix absorbed it (settings intact) → steered to 3.48.4 arm64: log showsMirrored 32-bit registration to the 64-bit view→CustomRemoveFiles 3.48.3→CustomInstall 3.48.4, no lock dialog,Remote.It.exeand everyresources\*.exearm64, agent running, a single Programs-and-Features entry in the 64-bit view, preferences intact, and the new process reportsnativeArch: null.process.arch, clean install, not steered.updateChannelunit tests and the full electron suite (49) pass;tscclean.Exposure
Remote.It-Installer-arm64.exeasset was replaced with the ia32 build,latest.ymlcut to ia32+x64, the release notes annotated, and the S3 mirror re-run; the public link now serves a working 32-bit build (verified byte-for-byte). Native arm64 for the public still needs a signed 3.48.x promoted to Latest.latest-arm64.ymlremoved so no pre-release user can be steered into it; 3.48.2 supersedes both.