From 057d0f1888e18f38b25bb8fb1d6b6b81f0d851b7 Mon Sep 17 00:00:00 2001 From: Valerii Kovalskii Date: Mon, 20 Jul 2026 21:46:32 +0300 Subject: [PATCH] docs+chore: signed-release docs, Neuraldeep footer, manual npm publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: desktop app is now signed & notarized (arm64 + x64), opens with no Gatekeeper warning (was "Apple Silicon" only). - desktop/RELEASE.md: rewritten for the real signed+notarized flow — keychain notary profile, the VPN/proxy timestamp gotcha, `--arm64 --x64`, DMG-container staple + latest-mac.yml regen, quarantined-mount verification; corrected the stale "no tracked workflow" CI note. - Sidebar footer: "Made by Valerii Kovalskii" → "Made by Neuraldeep community" (same t.me/neuraldeep link). - publish.yml: switch npm publish from auto-on-release to manual workflow_dispatch — desktop DMG releases no longer trigger (and 403-fail) a CLI publish; publishing stays a deliberate, short-lived-token action. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/publish.yml | 16 +++- README.md | 2 +- desktop/RELEASE.md | 140 ++++++++++++++++++++++------------ src/frontend/index.html | 2 +- 4 files changed, 107 insertions(+), 53 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index edd07f0..2662fb7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,8 +1,12 @@ name: Publish to npm +# Manual only. Publishing is done deliberately with a short-lived npm token, so +# this does NOT auto-fire on every GitHub release (releases are also cut for the +# desktop DMG, which must not trigger a CLI publish). To publish: bump the +# version, then run this workflow from the Actions tab. Requires an npm +# automation token with publish access to `codbash-app` in the NPM_TOKEN secret. on: - release: - types: [published] + workflow_dispatch: jobs: publish: @@ -20,6 +24,14 @@ jobs: run: | node bin/cli.js version node -c bin/cli.js + - name: Fail early if NPM_TOKEN is missing + run: | + if [ -z "${NODE_AUTH_TOKEN}" ]; then + echo "::error::NPM_TOKEN secret is not set — add an npm automation token with publish access to codbash-app." + exit 1 + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish run: npm publish --provenance --access public env: diff --git a/README.md b/README.md index 030c230..9fd901f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ npm i -g codbash-app codbash run ``` -Or grab the **macOS desktop app** from [Releases](https://github.com/vakovalskii/codbash/releases/latest) (Apple Silicon). +Or grab the **macOS desktop app** from [Releases](https://github.com/vakovalskii/codbash/releases/latest) — signed & notarized, Apple Silicon (`-arm64.dmg`) and Intel (`.dmg`). It opens with no Gatekeeper warning. ## Supported Agents diff --git a/desktop/RELEASE.md b/desktop/RELEASE.md index b3748d9..4603283 100644 --- a/desktop/RELEASE.md +++ b/desktop/RELEASE.md @@ -1,10 +1,11 @@ # Releasing the codbash macOS app (sign · notarize · publish) -The build is **signed** by electron-builder, **notarized** by the `afterSign` -hook (`scripts/notarize.js`), and **published** to GitHub Releases so the -in-app auto-updater (`electron-updater`) can pick it up. Every step degrades -gracefully: with no credentials you still get a working (unsigned) DMG for -local testing. +The build is **signed** with a Developer ID Application cert, **notarized** by +the `afterSign` hook (`scripts/notarize.js`) + a follow-up pass for the DMG +containers, and **published** to GitHub Releases where the in-app notify-updater +picks it up. Releases since **v7.14.4** are signed + notarized (arm64 + x64) and +open with no Gatekeeper warning. With no credentials the build still succeeds and +produces an unsigned DMG for local smoke-testing (the notarize hook no-ops). ## 0. One-time setup @@ -21,83 +22,124 @@ local testing. - Go to https://appleid.apple.com → **Sign-In & Security → App-Specific Passwords**. - Generate one (e.g. label it `codbash-notarize`). Copy the `xxxx-xxxx-xxxx-xxxx` value. -## 1. Environment variables +## 1. Notarization credentials + +**Preferred — a keychain profile** (no secret in env/shell history/CI logs): ```bash -# Signing (only if the Developer ID cert is NOT already in your login keychain): -export CSC_LINK="/absolute/path/DeveloperIDApplication.p12" -export CSC_KEY_PASSWORD="" +xcrun notarytool store-credentials "codbash-notary" \ + --apple-id "you@example.com" \ + --team-id "A933C2TJXU" +# prompts (hidden) for the app-specific password; stored in the login keychain. +``` + +The build reads it via `APPLE_KEYCHAIN_PROFILE` (see `scripts/notarize.js`). -# Notarization: +**Alternative — env vars** (e.g. CI): + +```bash export APPLE_ID="you@example.com" export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx" -export APPLE_TEAM_ID="ABCDE12345" - -# Publishing to GitHub Releases (needs repo write): -export GH_TOKEN="" +export APPLE_TEAM_ID="A933C2TJXU" +# and, only if the Developer ID cert is NOT already in the login keychain: +export CSC_LINK="/absolute/path/DeveloperIDApplication.p12" +export CSC_KEY_PASSWORD="" ``` -## 2. Build +## 2. Build (signed + notarized, both arches) ```bash cd desktop npm install # once, or after dependency changes -# Local unsigned DMG (no credentials needed) — for smoke-testing the package: -npm run dist:mac # → dist/codbash-.dmg +# ⚠️ Build over a VPN with proxy env vars UNSET (see the timestamp note below). +env -u HTTPS_PROXY -u HTTP_PROXY -u ALL_PROXY -u https_proxy -u http_proxy -u all_proxy \ + APPLE_KEYCHAIN_PROFILE=codbash-notary CSC_IDENTITY_AUTO_DISCOVERY=true \ + ./node_modules/.bin/electron-builder --mac --arm64 --x64 +``` + +- Pass `--arm64 --x64` explicitly. Passing a target on the CLI (`… dmg`) + **overrides** the `arch` array in `package.json`, so `npm run dist:mac` + builds only the host arch. +- The signing identity is pinned in `package.json` → `build.mac.identity` as + `"Valeriy Kovalsky (A933C2TJXU)"` — **without** the `Developer ID Application:` + prefix (electron-builder rejects the prefix). +- The `afterSign` hook (`scripts/notarize.js`) notarizes and staples **each + `.app`**. The DMG *containers* are notarized + stapled separately (step 2b), + since electron-builder builds the DMG after the hook runs. -# Signed + notarized + published to GitHub Releases: -npm run release:mac # electron-builder --mac dmg --publish always +**2b. Notarize + staple the DMG containers, then regenerate the update feed** +(the staple mutates the DMG, so blockmaps + `latest-mac.yml` must be recomputed): + +```bash +for dmg in dist/codbash--arm64.dmg dist/codbash-.dmg; do + xcrun notarytool submit "$dmg" --keychain-profile codbash-notary --wait + xcrun stapler staple "$dmg" + ./node_modules/app-builder-bin/mac/app-builder_arm64 blockmap \ + --input "$dmg" --output "$dmg.blockmap" # prints the {size,sha512} for latest-mac.yml +done +# then rewrite dist/latest-mac.yml with the new size+sha512 for both DMGs. ``` -`release:mac` will: -1. compile the app and **sign** it with your Developer ID cert, -2. run the `afterSign` hook → **notarize** with `notarytool` (a few minutes), -3. **staple** the ticket to the app/DMG, -4. **upload** the DMG + `latest-mac.yml` to a GitHub Release for the current tag. +Publish: -> Keep `version` in `desktop/package.json` in step with the codbash release -> (and create a matching git tag, e.g. `v7.14.0`) so the release/auto-update -> line up. +```bash +gh release create v --title "codbash (macOS desktop)" --target main \ + dist/codbash--arm64.dmg dist/codbash--arm64.dmg.blockmap \ + dist/codbash-.dmg dist/codbash-.dmg.blockmap \ + dist/latest-mac.yml +``` + +> Keep `version` in both `package.json` and `desktop/package.json` in step, and +> tag `v` so the release lines up. ## 3. Verify ```bash -# Notarization ticket is stapled: +# App: notarized + stapled + Gatekeeper-accepted xcrun stapler validate "dist/mac-arm64/codbash.app" -# Gatekeeper accepts it: -spctl -a -vvv -t install "dist/mac-arm64/codbash.app" +spctl -a -vvv -t exec "dist/mac-arm64/codbash.app" # → accepted, source=Notarized Developer ID +# DMG: notarized ticket stapled (spctl on the raw DMG is a false negative — the +# container isn't code-signed; Gatekeeper checks the notarization ticket) +xcrun stapler validate "dist/codbash--arm64.dmg" +# Real-world check: mount a QUARANTINED copy and assess the app inside +cp dist/codbash--arm64.dmg /tmp/q.dmg && xattr -w com.apple.quarantine "0083;0;Safari;" /tmp/q.dmg +hdiutil attach /tmp/q.dmg -nobrowse; spctl -a -vvv -t exec "/Volumes/codbash /codbash.app" ``` ## 4. Updates -Two models, depending on whether the build is signed: - -- **Now (unsigned): notify-only.** On launch and every 6h the app queries the +- **Current model — notify-only.** On launch and every 6h the app queries the GitHub `releases/latest` API and, if a newer version exists, offers to open - the download page (`main.js` → `checkForUpdates`). This works without signing. - Just publish each release (`gh release create v dist/*.dmg …` or - `--publish always`) and users get notified. -- **Later (signed): silent auto-update.** Once a Developer ID cert is in place, - swap `checkForUpdates` for `electron-updater`: `npm i electron-updater`, call - `autoUpdater.checkForUpdatesAndNotify()`, and publish with `--publish always` - so `latest-mac.yml` + the signed DMG land in the Release. macOS silent - in-place update **requires** the signature, which is why it's gated on the cert. - -The first desktop release (v7.13.0, unsigned, arm64) is published at -`https://github.com/vakovalskii/codbash/releases/tag/v7.13.0`. + the download page (`main.js` → `checkForUpdates`). Robust and dependency-free; + just publish each release. +- **Silent auto-update (now possible — builds are signed).** Swap + `checkForUpdates` for `electron-updater`: `npm i electron-updater`, call + `autoUpdater.checkForUpdatesAndNotify()`, and ensure `latest-mac.yml` + + signed/stapled DMGs are in the Release (they are — steps 2b/publish above). + macOS silent in-place update requires the signature, which is now in place. ## CI note -`.github/` is git-ignored in this repo, so there is no tracked GitHub Actions -workflow. To automate, add a macOS runner job that sets the env vars above from -repository secrets and runs `npm --prefix desktop ci && npm --prefix desktop run release:mac` -on tag push. Notarization requires a macOS runner. +Tracked workflows live in `.github/workflows/`: `ci.yml` (tests on push/PR) and +`publish.yml` (npm publish — **manual** `workflow_dispatch`, not on release, so a +desktop DMG release never triggers a CLI publish). There is **no** CI job for the +DMG: notarization needs a macOS runner plus the signing cert + notary creds, and +the signed build must run over a VPN with the proxy unset (see step 2 / the +timestamp note), so the DMG is built and published locally with the commands above. ## Troubleshooting +- **`The timestamp service is not available`** (repeated `signing … retrying`, + build aborts) → `codesign --timestamp` hits `timestamp.apple.com` via the + system network stack and **ignores** the `HTTPS_PROXY` env var. If the network + needs a proxy for outbound HTTPS, every nested file fails. **Fix: turn on a VPN + and build with the proxy env vars unset** (`env -u HTTPS_PROXY …`, as in step 2). + Apple's secure timestamp is mandatory for notarization — you can't swap the TSA. + Pre-flight: sign a throwaway file 5× and expect 5/5 OK before the full build. - **"app is damaged / unidentified developer"** → not notarized or not stapled; - confirm the three `APPLE_*` vars were set during the build. + confirm the notary credentials (keychain profile or `APPLE_*` vars) were + available during the build, and that step 2b stapled the DMG. - **node-pty / terminal fails in the packaged app** → ensure `../node_modules/@lydell` is present at build time (it is copied via `extraResources`); run `npm install` at the repo root first. diff --git a/src/frontend/index.html b/src/frontend/index.html index 26bf664..2ea5287 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -228,7 +228,7 @@