Emulator Release Setup
One-time setup for the KN-86 emulator release pipeline that publishes tagged
binaries to both the private monorepo (jschairb/kn86-deckline) and the public
binaries repo (jschairb/kn86-emulator).
Workflow file: .github/workflows/release.yml
Tracked by: GWP-142
(child of GWP-130: kn86-deckline.com Downloads + release sync)
Why two repos?
Section titled “Why two repos?”- Private monorepo (
jschairb/kn86-deckline) — full project source; the release workflow runs here because this is where the emulator code lives. - Public binaries repo (
jschairb/kn86-emulator) — ships prebuilt archives and checksums only. No source. Thekn86-deckline.comDownloads page links to this repo’s Releases so users can grab binaries without source access.
The workflow’s mirror job pushes a Release (tag, title, notes, assets) to the
public repo without ever cloning or pushing source to it. Only .tar.gz and
.sha256 files are uploaded.
One-time setup
Section titled “One-time setup”1. Seed the public repo
Section titled “1. Seed the public repo”The mirror job creates releases on jschairb/kn86-emulator via
gh release create --target <default_branch>. An empty repo has no default
branch, so the first release would fail. Push a minimal commit (typically a
README explaining what the repo is for) to seed the default branch.
# One-time, from a scratch dirgit clone https://github.com/jschairb/kn86-emulator.gitcd kn86-emulatorcat > README.md <<'EOF'# KN-86 Emulator — Binary Releases
Prebuilt binaries for the Kinoshita KN-86 Deckline desktop emulator.Source lives in the private development repo; this repo ships taggedbuilds via the release automation in that repo.
Download the latest release from the **Releases** tab.
Each release archive contains:- `kn86emu` — the emulator executable (macOS universal2 or Linux x86_64)- `assets/` — test cartridges and fonts (when present)
Each release also publishes a `.sha256` file per archive so you can verifythe download:shasum -a 256 -c kn86emu-vX.Y.Z-
EOFgit add README.mdgit commit -m "chore: seed binaries repo"git push origin main2. Create the mirror PAT
Section titled “2. Create the mirror PAT”The PAT authenticates the workflow against the public repo. It is stored as
a secret on the private monorepo (jschairb/kn86-deckline).
- Go to https://github.com/settings/personal-access-tokens/new (classic PATs work too, but fine-grained are preferred).
- Fine-grained PAT (preferred):
- Token name:
kn86-emulator mirror (from kn86-deckline CI) - Expiration: 1 year (set a calendar reminder to rotate)
- Repository access: Only select repositories →
jschairb/kn86-emulator - Repository permissions:
- Contents: Read and write (required to create/update releases and tags)
- Metadata: Read-only (granted automatically)
- Everything else: no access
- Token name:
- Classic PAT alternative (if fine-grained isn’t workable):
- Scope:
public_repo(sufficient while the mirror is public). Use fullreposcope only if the mirror is ever made private.
- Scope:
- Copy the token once — you will not see it again.
3. Add the token as a repo secret on the PRIVATE repo
Section titled “3. Add the token as a repo secret on the PRIVATE repo”gh secret set KN86_PUBLIC_MIRROR_PAT \ --repo jschairb/kn86-deckline \ --body "<paste-token-here>"Or via UI: jschairb/kn86-deckline → Settings → Secrets and variables →
Actions → New repository secret → Name KN86_PUBLIC_MIRROR_PAT, value = token.
Verify it’s set:
gh secret list --repo jschairb/kn86-deckline | grep KN86_PUBLIC_MIRROR_PATThe workflow’s first mirror step (Verify mirror PAT is configured) fails
fast with a pointer to this document if the secret is missing.
4. Smoke test
Section titled “4. Smoke test”git tag v0.1.0-smokegit push origin v0.1.0-smokeWatch the run at https://github.com/jschairb/kn86-deckline/actions. Expect
three jobs: Build (macos-universal), Build (linux-x86_64), then
Publish Release (private) and Mirror to public repo in sequence.
When green, verify on both sides:
- Private: https://github.com/jschairb/kn86-deckline/releases/tag/v0.1.0-smoke
- Public: https://github.com/jschairb/kn86-emulator/releases/tag/v0.1.0-smoke
Both should show the same .tar.gz + .sha256 asset pairs. The public
release’s auto-generated source archives refer to the mirror repo’s own
contents (seed README), not private source.
Clean up the smoke test:
# Delete local taggit tag -d v0.1.0-smoke# Delete remote tag on the private repogit push --delete origin v0.1.0-smoke# Delete both releases (the tag deletion will trigger deletion of the# private release, but the mirror release lives on a separate tag in the# public repo and must be deleted explicitly):gh release delete v0.1.0-smoke --repo jschairb/kn86-deckline --yes --cleanup-taggh release delete v0.1.0-smoke --repo jschairb/kn86-emulator --yes --cleanup-tagRotation
Section titled “Rotation”- Fine-grained PAT: expires on the date you set. Watch the GitHub email reminder (sent ~7 days before expiry) and repeat steps 2–3 with a fresh token. Delete the old token from https://github.com/settings/personal-access-tokens.
- Rotation cadence: every 12 months minimum, or immediately after any suspected leak.
- If rotation is late: the first tagged release after expiry will fail at
the
Verify mirror PAT is configuredstep (the secret is still set but the underlying token is rejected by the API). The private release still succeeds, so production isn’t blocked — the public mirror just lags until the token is refreshed.
How to trigger a real release
Section titled “How to trigger a real release”- Bump the version in
kn86-emulator/CMakeLists.txt(or whereverKN86_VERSIONlives) — the-DKN86_VERSION_OVERRIDE=…from the tag name must be able to match the stored version for theVerify embedded versionstep to pass. - Commit and merge to
main. - Tag and push:
Terminal window git tag v0.2.0git push origin v0.2.0 - Stable releases use no hyphen in the tag (
v1.2.3). Pre-releases use a hyphen (v1.2.3-rc1,v0.1.0-beta) and are marked as GitHub “pre-release” on both repos automatically.
Troubleshooting
Section titled “Troubleshooting”Mirror job fails with ERROR: Mirror repo jschairb/kn86-emulator has no default branch.
→ The public repo is still empty. Run step 1 above.
Mirror job fails with ERROR: KN86_PUBLIC_MIRROR_PAT secret is not set.
→ Run step 3 above.
Mirror job fails with HTTP 403: Resource not accessible by personal access token
→ Fine-grained PAT is missing “Contents: Read and write” on the mirror repo.
Regenerate the PAT (step 2) with the correct permissions and update the
secret (step 3).
Private release succeeds but mirror release shows no assets.
→ Check the Collect release files step log on the mirror job — the
download-artifact output may be pointing at an unexpected layout.
Release is re-run on the same tag and fails with already exists.
→ The workflow’s mirror step attempts to delete any prior release for the tag
before re-creating it. If that still fails, delete manually:
gh release delete <tag> --repo jschairb/kn86-emulator --yes --cleanup-tagThen re-run the workflow.
What this workflow does NOT do
Section titled “What this workflow does NOT do”- Apple notarization — the stub is present in
release.yml; uncomment and provisionAPPLE_*secrets when ready to ship signed macOS binaries. - Windows builds — not in scope.
- Publish source to the public repo — never. Only binaries + checksums.
- Delete the private release if the mirror fails — the private release is the source of truth; mirror is best-effort. Rerun the workflow to retry the mirror without re-running the build.