Skip to content

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)


  • 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. The kn86-deckline.com Downloads 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.


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.

Terminal window
# One-time, from a scratch dir
git clone https://github.com/jschairb/kn86-emulator.git
cd kn86-emulator
cat > 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 tagged
builds 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 verify
the download:

shasum -a 256 -c kn86emu-vX.Y.Z-.tar.gz.sha256

EOF
git add README.md
git commit -m "chore: seed binaries repo"
git push origin main

The PAT authenticates the workflow against the public repo. It is stored as a secret on the private monorepo (jschairb/kn86-deckline).

  1. Go to https://github.com/settings/personal-access-tokens/new (classic PATs work too, but fine-grained are preferred).
  2. 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
  3. Classic PAT alternative (if fine-grained isn’t workable):
    • Scope: public_repo (sufficient while the mirror is public). Use full repo scope only if the mirror is ever made private.
  4. 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”
Terminal window
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:

Terminal window
gh secret list --repo jschairb/kn86-deckline | grep KN86_PUBLIC_MIRROR_PAT

The workflow’s first mirror step (Verify mirror PAT is configured) fails fast with a pointer to this document if the secret is missing.

Terminal window
git tag v0.1.0-smoke
git push origin v0.1.0-smoke

Watch 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:

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:

Terminal window
# Delete local tag
git tag -d v0.1.0-smoke
# Delete remote tag on the private repo
git 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-tag
gh release delete v0.1.0-smoke --repo jschairb/kn86-emulator --yes --cleanup-tag

  • 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 configured step (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.

  1. Bump the version in kn86-emulator/CMakeLists.txt (or wherever KN86_VERSION lives) — the -DKN86_VERSION_OVERRIDE=… from the tag name must be able to match the stored version for the Verify embedded version step to pass.
  2. Commit and merge to main.
  3. Tag and push:
    Terminal window
    git tag v0.2.0
    git push origin v0.2.0
  4. 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.

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:

Terminal window
gh release delete <tag> --repo jschairb/kn86-emulator --yes --cleanup-tag

Then re-run the workflow.


  • Apple notarization — the stub is present in release.yml; uncomment and provision APPLE_* 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.