Contributing
Releases
For maintainers: how Startica is versioned, how a tag becomes a draft GitHub release with installers and download links for every OS, how the download page finds them, and how to set up code signing and notarization.
A release is a version tag on main. Pushing the tag starts a GitHub Actions workflow that builds the installers for macOS, Windows and Linux and uploads them to a draft GitHub release. You review the draft and publish it by hand; the installed app’s auto-update only sees published releases. This page covers versioning, cutting a release, what the workflow builds, the release notes and the download page, signing and notarization, local builds, and what isn’t done yet.
Current state
Warning
The pipeline is built, but nothing has been released through it yet. Read this before you cut the first release.
- Not signed yet. The Apple Developer ID and a Windows code-signing certificate haven’t been bought, so every build is unsigned. Unsigned builds work, but users see OS warnings, and macOS can’t install updates (see Unsigned builds).
- Auto-update can’t reach the releases yet. The app reads the releases of
chriscasper/starticawithout a token, so it only works once the repository is public. Until then every check fails quietly. - Windows and Linux builds haven’t been tried by hand. They build on GitHub’s runners, but nobody has installed and run them yet. See Test Windows and Linux builds.
- Users still build from source. Install from source stays the documented way in until signed releases are published.
Versioning
Startica follows semantic versioning. While the version is below 1.0, minor versions (0.x.0) may include breaking changes, and patch versions (0.1.x) are fixes only.
The app version is the version in apps/desktop/package.json. electron-builder uses it for the installer names and the app’s About information, the release workflow checks the tag against it, and auto-update compares it with the latest release. Keep the root package.json and apps/desktop-web/package.json on the same version. packages/core and packages/db aren’t published on their own and stay at 0.0.0.
A database migration is not a breaking change on its own, because migrations run automatically on startup. A change that drops or rewrites user data, or changes where data is stored, is.
Changelog
CHANGELOG.md at the repo root follows Keep a Changelog. Add your change under ## [Unreleased] in the same pull request, in one of these groups: Added, Changed, Deprecated, Removed, Fixed, Security. Write entries for users, not for reviewers: what changed for them, not which files moved.
Cutting a release
-
Make sure
mainis green in CI. -
Run the full checks locally, plus the desktop smoke test on a built app:
yarn typecheck && yarn lint && yarn test && yarn smoke:desktop yarn build:desktop yarn workspace @startica/desktop smoke --packaged -
Bump the version in
apps/desktop/package.json, the rootpackage.jsonandapps/desktop-web/package.json. -
In
CHANGELOG.md, rename## [Unreleased]to## [0.0.2] - YYYY-MM-DD, add a new empty## [Unreleased]above it, and update the comparison links at the bottom. -
Commit (
Release 0.0.2), then tag and push. The tag must bevplus the exact version inapps/desktop/package.json:git tag -a v0.0.2 -m "Startica 0.0.2" git push origin main v0.0.2 -
Watch Actions → Release. The three build jobs (macOS, Windows, Linux) take a while; each uploads its files to the same draft release named after the version. A last job, Release notes, then writes the draft’s notes (see Release notes).
-
Open the draft under Releases. Check that every file below is there and the notes read well, then click Publish release. Publishing also rebuilds mystartica.com, so the download page links to the new version once you approve the
productiondeployment.
If the tag and the version don’t match, every job stops at Check the tag matches the app version with “Tag v0.0.2 doesn’t match apps/desktop/package.json version 0.0.1”. Delete the tag (git push origin :refs/tags/v0.0.2 and git tag -d v0.0.2), fix the version, and tag again.
Warning
Publishing is what ships the update. Within six hours, every installed copy that checks automatically downloads it. Don’t publish a release you haven’t installed and opened at least on macOS.
Don’t delete or rename the latest*.yml files on a release: latest-mac.yml, latest.yml (Windows) and latest-linux.yml are what the app reads to find the update. Pre-releases and drafts are ignored by auto-update.
What the workflow builds
.github/workflows/release.yml runs one job per OS on GitHub’s hosted runners, with Node 24 and yarn install --immutable. Each job runs yarn workspace @startica/desktop build with its OS flag. electron-builder’s settings are the build field in apps/desktop/package.json.
| Runner | Files | Architectures |
|---|---|---|
macos-latest |
.dmg (to install) and .zip (what auto-update downloads), each with a .blockmap, plus latest-mac.yml |
Apple Silicon (arm64) and Intel (x64), as separate files |
windows-latest |
NSIS installer .exe with a .blockmap, plus latest.yml |
x64 |
ubuntu-latest |
.AppImage and .deb, plus latest-linux.yml |
x64 |
The Windows installer isn’t one-click: it asks where to install. On Linux the executable is startica and the .deb maintainer is Startica <hello@mystartica.com>.
The workflow has two triggers:
| Trigger | What happens |
|---|---|
Pushing a v* tag |
Builds, signs and notarizes when the secrets are set, and uploads everything to a draft release (--publish always, with the publish setting github, chriscasper/startica, releaseType: draft). The workflow’s own GITHUB_TOKEN (with contents: write) does the upload; you don’t add a token. |
| Run workflow by hand | Builds without publishing (--publish never) and keeps the installers as workflow artifacts named startica-macOS, startica-Windows and startica-Linux. For testing. |
Release notes
After the three build jobs, the Release notes job runs apps/desktop/scripts/release-notes.mjs and replaces the draft’s notes with:
- Download: a table with a link and size for each installer: macOS Apple Silicon and Intel
.dmg, the WindowsSetup .exe, the Linux.AppImageand.deb. The links use the real tag (/releases/download/v0.0.2/…), so they work once the draft is published (a draft’s own file links use a temporaryuntagged-…address). - Install notes for unsigned builds, for each system whose certificate secret (
CSC_LINK,WIN_CSC_LINK) isn’t set: how to get past Gatekeeper or SmartScreen. A hidden<!-- startica:unsigned=macos,windows -->line records the same for the download page. - Beta: while the version is 0.x, the release is titled “Startica 0.x.y beta” and the notes open with a beta note; the download page shows the same. It’s still a normal published release, not a GitHub pre-release, because auto-update and the download page skip pre-releases.
- What’s new: the version’s entry from
CHANGELOG.md. Without one, the job warns and the notes list only the downloads.
The job fails if any of the five installers is missing from the draft, which usually means a build job failed. Which files count as installers is in apps/desktop/scripts/installers.mjs; if you change an artifactName in apps/desktop/package.json, update its patterns too.
Edit the notes on the draft by hand if you like before publishing; rerunning the job overwrites them. To preview them locally against a draft:
node apps/desktop/scripts/release-notes.mjs v0.0.2
The download page
mystartica.com/download is built from the latest published release (drafts and pre-releases are skipped, as auto-update skips them). apps/web/scripts/release.mjs reads it from the GitHub API during yarn build and writes apps/web/content/download.md (git-ignored), which layouts/download.njk turns into one card per system. The page marks the card for the visitor’s system; on a Mac in Chrome or Edge, which report the chip, it puts the matching .dmg first. Safari doesn’t, so Apple Silicon stays first there.
- The
Sitesworkflow runs onrelease: publishedas well as pushes tomain, so publishing a release redeploys the site frommain. - In CI, a failed request to GitHub fails the site build, so a deploy never swaps real links for “no release yet”. Locally it only warns.
GH_TOKENorGITHUB_TOKENraises the API’s rate limit. - With nothing published, the page says the first release isn’t out yet and links to building from source.
Signing and notarization
Every secret is optional. Without them the workflow still builds, unsigned. With them, macOS builds are signed with your Developer ID and notarized by Apple, and Windows builds are signed.
| Secret | What it is |
|---|---|
CSC_LINK |
The macOS “Developer ID Application” certificate and its private key, as a .p12 file, base64-encoded |
CSC_KEY_PASSWORD |
The password you set when exporting the .p12 |
APPLE_API_KEY |
The contents of the App Store Connect API key file (AuthKey_XXXXXXXXXX.p8), for notarization |
APPLE_API_KEY_ID |
That key’s ID (10 characters) |
APPLE_API_ISSUER |
Your team’s Issuer ID (a UUID) |
WIN_CSC_LINK |
The Windows code-signing certificate as a .pfx file, base64-encoded |
WIN_CSC_KEY_PASSWORD |
The .pfx password |
When CSC_LINK is empty for a job, the workflow sets CSC_IDENTITY_AUTO_DISCOVERY=false, so electron-builder builds unsigned instead of looking for a certificate on the runner. When APPLE_API_KEY is set, the macOS job writes it to ~/private_keys/AuthKey.p8 and adds -c.mac.notarize=true; notarize is false in apps/desktop/package.json so local builds don’t try to notarize.
macOS signing certificate
You need a paid Apple Developer Program membership ($99 a year). Only the team’s Account Holder can create a Developer ID certificate.
-
On a Mac, open Xcode → Settings → Accounts, select the team, click Manage Certificates, then + → Developer ID Application. (Without Xcode: create a certificate signing request in Keychain Access → Certificate Assistant, then create the certificate at developer.apple.com/account/resources/certificates and double-click the download.)
-
Open Keychain Access → login → My Certificates. Find “Developer ID Application: your name (team ID)” and expand it to check the private key is under it.
-
Right-click the certificate (not the key) → Export, choose Personal Information Exchange (.p12), and set a strong password.
-
Base64-encode it and save both as repository secrets (Settings → Secrets and variables → Actions, or with the GitHub CLI):
base64 -i DeveloperID.p12 -o DeveloperID.p12.b64 gh secret set CSC_LINK < DeveloperID.p12.b64 gh secret set CSC_KEY_PASSWORD -
Delete the
.p12and.b64files, or keep them only in a password manager. The certificate can sign apps as you.
It must be a Developer ID Application certificate. “Apple Development”, “Apple Distribution” and “Mac App Distribution” certificates don’t work outside the Mac App Store.
macOS notarization
Notarization uploads the signed app to Apple, which scans it and issues a ticket that Gatekeeper checks. The workflow uses an App Store Connect API key, not your Apple ID password.
-
In App Store Connect, go to Users and Access → Integrations → App Store Connect API → Team Keys, and click +.
-
Name it (for example “Startica notarization”) and give it the Developer role.
-
Download the
.p8file. Apple only lets you download it once. -
Note the Key ID next to the key and the Issuer ID above the list.
-
Save the three secrets:
gh secret set APPLE_API_KEY < AuthKey_XXXXXXXXXX.p8 gh secret set APPLE_API_KEY_ID --body XXXXXXXXXX gh secret set APPLE_API_ISSUER --body 00000000-0000-0000-0000-000000000000
APPLE_API_KEY is the file’s contents (the -----BEGIN PRIVATE KEY----- block), not a path.
After the next tagged build, download the .dmg from the draft and check it on a Mac:
spctl -a -vvv -t install /Applications/Startica.app
xcrun stapler validate /Applications/Startica.app
The first should say accepted and source=Notarized Developer ID. If it says Unnotarized Developer ID, the build was signed but not notarized: look for the notarization step in the macOS job’s log.
Why the hardened runtime entitlements
Notarization requires the hardened runtime (hardenedRuntime: true). Electron needs three exceptions to it, in apps/desktop/resources/entitlements.mac.plist, used for the app and every helper:
| Entitlement | Why |
|---|---|
com.apple.security.cs.allow-jit |
V8, the JavaScript engine, compiles code at run time. |
com.apple.security.cs.allow-unsigned-executable-memory |
V8 also writes executable memory that isn’t signed. |
com.apple.security.cs.disable-library-validation |
The local server runs the same Electron binary in Node mode (ELECTRON_RUN_AS_NODE) and loads the app’s own modules, including native ones, that aren’t signed by Apple. |
Don’t add entitlements without a reason in the file’s comment. The app isn’t sandboxed (no com.apple.security.app-sandbox): it’s distributed outside the Mac App Store.
Windows signing
With WIN_CSC_LINK and WIN_CSC_KEY_PASSWORD set, the Windows job signs the installer and the app. The workflow passes them to electron-builder as CSC_LINK and CSC_KEY_PASSWORD.
-
Buy an OV (organization validation) or EV code-signing certificate from a certificate authority.
-
If the certificate comes as an exportable
.pfx, base64-encode it and save the secrets:base64 -i startica.pfx -o startica.pfx.b64 gh secret set WIN_CSC_LINK < startica.pfx.b64 gh secret set WIN_CSC_KEY_PASSWORD
Note
Most certificate authorities now issue code-signing keys only on a hardware token or in a cloud key store, so there’s no .pfx to export. Those need a different signing step in the workflow. The likely later option is Azure Trusted Signing, which electron-builder supports through win.azureSignOptions and which needs an Azure account and identity validation. Neither is set up yet; discuss before adding one.
A signed installer still starts with little SmartScreen reputation, so some users may see the warning for the first releases.
Unsigned builds
Without the secrets, the workflow builds the same files, unsigned. They install and run, but:
macOS
A downloaded app that isn’t signed and notarized is blocked by Gatekeeper. macOS says Apple couldn’t verify that Startica is free of malware (on older versions, that it’s from an “unidentified developer”). Users get past it in System Settings → Privacy & Security → Open Anyway. On Apple Silicon, macOS may instead say the app “is damaged and can’t be opened”; removing the download quarantine works around that:
xattr -dr com.apple.quarantine /Applications/Startica.app
Auto-update doesn’t work: macOS only installs an update signed by the same developer as the running app. The app finds and downloads the update, then fails to install it.
Windows
SmartScreen shows “Windows protected your PC”. Users click More info, then Run anyway. Auto-update works: electron-updater only checks the update’s signature when the installed app was signed.
Linux
Linux has no signing requirement. The AppImage runs after chmod +x; the .deb installs with sudo apt install ./startica_*.deb.
Local builds
The release workflow runs the same build script you can run yourself. Installers land in apps/desktop/dist/. Build each OS on that OS (see Cross-building).
yarn build:desktop --mac --publish never
yarn build:desktop is yarn workspace @startica/desktop build. The build script compiles the Next.js server, assembles it in apps/desktop/build/server, and passes every other argument to electron-builder (see Architecture).
| OS | Command | Output |
|---|---|---|
| macOS | yarn build:desktop --mac |
.dmg and .zip for arm64 and x64, latest-mac.yml, plus dist/mac*/Startica.app |
| Windows | yarn build:desktop --win |
NSIS Setup .exe, latest.yml, plus dist/win-unpacked/ |
| Linux | yarn build:desktop --linux |
.AppImage, .deb, latest-linux.yml, plus dist/linux-unpacked/ |
Notes:
-
Nothing is uploaded from your machine unless you pass
--publish alwayswith aGH_TOKEN. Pass--publish neverto be sure. -
Signing on your Mac. If your keychain has a Developer ID Application certificate, electron-builder finds it and signs with it. To build unsigned anyway, set
CSC_IDENTITY_AUTO_DISCOVERY=false. Notarization is off unless you pass-c.mac.notarize=truewithAPPLE_API_KEY(a path to the.p8),APPLE_API_KEY_IDandAPPLE_API_ISSUERset. -
Your build checks for updates too. Every packaged build contains
app-update.ymlpointing at this repository’s releases. Once releases are published, a copy you built yourself offers to update to a newer official release. Turn off Check for Updates Automatically in its tray menu if you don’t want that. -
--no-packagestops after assembling the server, for debugging the standalone server without electron-builder:yarn workspace @startica/desktop build --no-package
Cross-building
Build each OS’s installer on that OS. electron-builder can produce some targets from another platform (for example Windows installers on Linux with Wine), but Startica doesn’t support or test that: the bundled server’s node_modules are traced and copied on the build machine, so platform-specific packages would come from the wrong OS. DMGs can only be built on macOS. If you don’t have a Windows or Linux machine, use a manual workflow run.
Test Windows and Linux builds
A manual run builds all three OSes on GitHub’s runners without creating a release.
-
Go to Actions → Release → Run workflow, pick your branch (not a tag), and run it. Or:
gh workflow run release.yml --ref your-branch -
When it finishes, download the artifacts from the run’s page, or:
gh run download --name startica-Windows gh run download --name startica-Linux -
On a real or virtual machine, install it and check: the installer runs; the window opens; the welcome wizard saves an API key (Windows DPAPI, or the Linux keyring); the tray icon and its menu work; closing the window keeps it in the tray; Launch at Login starts it hidden after logging in again; Quit Startica stops it; uninstalling leaves the data folder.
Report what you find on the pull request or in GitHub Discussions.
Still to do
These need a decision or money first. Discuss before starting on any of them.
- Buy the Apple Developer ID ($99 a year) and set the five macOS secrets. Until then, macOS users see Gatekeeper warnings and can’t auto-update.
- Windows signing: an exportable code-signing certificate, or Azure Trusted Signing with a change to the workflow.
- Make the repository public, so auto-update can read the releases.
- Run the workflow by hand once and test the Windows and Linux installers.
- Point Get started at the download page once a release is published: the home page, Get started and the install guide still lead with building from source.