apps/desktop/package.json, so v0.1.0 builds version 0.1.0.
How it runs
Pushing a tag that starts withv starts .github/workflows/release.yml. It has two jobs.
- package runs on Ubuntu, macOS and Windows. Each run checks the tag against the version, installs, runs the typecheck and the unit tests (
npm test), builds, and then packages withnpm run dist. The privacy suite and the end-to-end script do not run here, so check that CI is green on the commit before you tag it. The installers, their.blockmapfiles and thelatest*.ymlupdate files are uploaded as workflow artifacts. - draft-release runs on Ubuntu after all three package jobs succeed. It checks that
latest-mac.yml,latest.ymlandlatest-linux.ymlare all present, then creates a draft GitHub Release for the tag with every file and generated release notes.
companyOS-Twin-<version>-<arch>.<ext>, with no spaces. apps/desktop/electron-builder.yml sets this on purpose. See Update feed for why.
Before the first release
No tag has been pushed yet, so none of this has been run end to end. Go through the list in order.-
Add the repository secrets. In the repository on GitHub, open Settings, then Secrets and variables, then Actions, then New repository secret. Add each name below exactly as written. This needs the Apple Developer account for the macOS ones, so it is a job for the repository owner.
The Apple and Windows certificate steps themselves have not been checked here. The workflow only reads these names. No secret is stored in the repository.
-
Set the version. Change
versioninapps/desktop/package.json, commit it, and get it onto the branch you will tag. Every package job compares the tag with that file first, and fails on a mismatch with “The tag v0.2.0 does not match apps/desktop/package.json version 0.1.0”. The rootpackage.jsonversion is not checked. -
Check CI. The
ciworkflow should be green on that commit. -
Push the tag. From a clean checkout of that commit:
-
Watch the run on the Actions tab. The installers are large. A Linux
.debbuilt on a test machine was about 626 MB. -
Review the draft. Open Releases on GitHub and find the draft. Check that it lists the macOS
.dmgand.zip, the Windows.exe, the Linux.deband.rpm, the.blockmapfiles that go with them, and the threelatest*.ymlfiles. Read the generated notes, then publish it by hand. Publishing is also what makes it the “latest” release for the update feed.
Without the secrets
- macOS. The release build stops before it builds. The workflow sets
COMPANYOS_RELEASE=1for the macOS job, and in that modescripts/package.mjsneeds a certificate (CSC_LINKorCSC_NAME) and all three ofAPPLE_API_KEY,APPLE_API_KEY_IDandAPPLE_API_ISSUER. It names what is missing and exits. This is by design: there is no unsigned macOS release. - No draft either. The draft job waits for all three package jobs. If the macOS job fails, no draft is created, even though the Windows and Linux installers were built. They remain as artifacts of the workflow run. Until the secrets exist, a tag cannot produce a release.
- Windows. Without
WIN_CSC_LINKthe installer is built unsigned, and SmartScreen warns about it. - Linux. The
.deband.rpmare always unsigned.
macOS
The macOS build is for Apple silicon (arm64). In release mode, Electron Builder signs the app with the hardened runtime and notarises it.scripts/package.mjs then checks the signature, the hardened runtime, the notarisation ticket and the Gatekeeper assessment, and it notarises and staples the disk image. A failed check fails the build.
To build a signed release on your own Mac, set CSC_NAME to the name of the Developer ID Application identity in your keychain, and set APPLE_API_KEY (the path of the .p8 file), APPLE_API_KEY_ID and APPLE_API_ISSUER. Then run COMPANYOS_RELEASE=1 npm run dist.
Windows
The installer is an NSIS.exe for 64-bit Windows. It is unsigned until WIN_CSC_LINK and WIN_CSC_KEY_PASSWORD are set. Until then SmartScreen warns about it.
Linux
The build produces a.deb and an .rpm. They are unsigned. The rpm tool is installed on the runner for the .rpm.
Update feed
The app checks for updates only when it is a packaged build andCOMPANYOS_UPDATE_URL is set to an HTTPS address. With the variable unset, or set to anything that is not a plain HTTPS address, it checks nothing and makes no request. The address must not carry a user name, a password or a query string. This is the default, and nothing on this page changes it.
When the address is set, the app reads one channel file from it 30 seconds after start and then every six hours: latest.yml on Windows, latest-mac.yml on macOS, and latest-linux.yml on Linux (latest-linux-arm64.yml on arm64). A newer version downloads in the background and installs when the app quits. The app never restarts itself. Each check is written to the network ledger.
Use the GitHub Release as the feed
Set the variable to the release download path:latest/download/<file> with a redirect to that file in the latest release. The release workflow already uploads the latest*.yml files and the installers that they name, so the same release serves as the feed. To try it, start the packaged app with the variable in its environment, for example from a terminal. There is no setting for it in the app yet.
What was checked:
- The redirect works for an anonymous request. On a public repository,
latest/download/checksums.txtwas followed bycurl. It redirected to the tagged release URL, redirected again to GitHub’s file host, and returned the file. A?noCache=query, which electron-updater adds to the channel file request, made no difference. ARangerequest returned206, so partial downloads work. - The updater follows redirects. In
electron-updater6.8.10 the channel file and the downloads both follow up to 10 redirects. This route uses two. The app does not limit which hosts the updater may reach. Only the model download has a host list. - The app accepts the address.
resolveUpdateFeedreturns an enabled feed for it, and a plainhttpaddress, or one with a query string, is refused. - The names match. electron-builder writes the file name into
latest*.ymlexactly as it built it. GitHub only keeps letters, digits, dots, underscores and dashes in an asset name, and electron-builder’s own GitHub publisher renames files with spaces for that reason. The default names here start with “companyOS Twin”, so they would not match what GitHub stores.artifactNameinelectron-builder.ymlremoves the space, and a unit test keeps it that way.
- No release exists yet.
latest/download/latest.ymlon this repository returns 404 today. The whole loop, a packaged app finding a newer release, downloading it and installing it, has not been run. Test it with two real releases, for example0.1.0and0.1.1, before you tell anyone that updates work. - Drafts and pre-releases are not served. GitHub’s “latest” is the newest release that is published and not marked as a pre-release. It is ordered by when the release was created, not by version number. This is GitHub’s documented behaviour and was not tested here.
- The repository must be public. An anonymous request to a private repository gets a 404. The repository page is public today.
- The
.rpmname. The Linux.debwas built and named as expected. The.rpmwas not built here, becauserpmbuildis not installed on the test machine. It uses the same pattern.
Privacy
The ledger records the first request only: the hostgithub.com and the path of the channel file. GitHub then redirects to its file host (release-assets.githubusercontent.com when this was checked), and that request is not listed. The installer download is not listed either, as the privacy page says. GitHub sees the connection, including the IP address. The app sends no activity data.
Another host
Any HTTPS folder works the same way. Upload thelatest*.yml files and the installers that they name to the folder, and set COMPANYOS_UPDATE_URL to the folder’s address. Keep the names as built.