mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
* feat(updater): add hourly dev channel and build switching Adds an hourly macOS build channel plus a dev-only surface for switching update channels and jumping to any published build, including older ones. Hourly builds publish to a separate stablyai/orca-hourly repo. The routine update path resolves tags from the main repo's releases atom feed, which exposes only its 10 newest entries — 24 hourly tags a day would evict every stable/RC entry there and leave real users with nothing to update to. Hourly artifacts carry the release bundle id and Developer ID signature so Squirrel.Mac can swap them in place; only notarization is skipped, which in-place updates never check. Version tails are stripped to the base (1.4.160-hourly.<stamp>, not 1.4.160-rc.3-hourly.<stamp>) so hourlies sort below both rc.N and stable and are reachable only by an explicit pinned jump, never by an ordinary check. The picker is revealed by Option-clicking the Updates header, matching the Help menu's existing hidden admin affordance. Pinned jumps set allowDowngrade and release the feed on every settle path so a jump can never leave background checks permanently deferred. * chore(hourly): create orca-hourly and add token provisioning script Adds setup-hourly-release-token.sh, which provisions HOURLY_RELEASE_TOKEN without the value ever reaching stdout, argv, or shell history: it is read with `read -rs`, passed to gh through GH_TOKEN in the environment rather than as an argument (argv is world-readable via ps), piped into `gh secret set` on stdin, and scrubbed by an EXIT trap. Verification creates and deletes a draft release in orca-hourly to prove Contents:write for real rather than trusting the permission checkbox. Drafts are absent from the releases atom feed, so the probe cannot disturb users. Refuses to run without a controlling terminal instead of falling through having set nothing, and refuses to run under xtrace, which would echo the token on every expansion. * fix(updater): address review feedback on the hourly channel Renderer: - Guard listBuilds against out-of-order responses. activeChannel flips once getVersion resolves, and rapid channel clicks stack requests, so a slower earlier load could land last and fill the list with builds from a channel the picker was no longer showing. - Selecting the running build's own channel now clears the override instead of pinning it. There was previously no way back to "follow this build's channel", so merely opening the panel left background checks pinned. - Validate releaseChannelOverride on hydration, matching every other enum-like field in that function. Main: - Exclude pinned jumps from recordCompletedUpdateCheck() in update-available. A dev browsing the picker was persisting lastUpdateCheckAt and suppressing the next real background check for a full day. - parseHourlyVersionStamp now anchors on the whole version and round-trips the parsed fields. It accepted garbage prefixes, and Date.UTC rolled impossible dates forward, so ...hourly.202602300000 rendered as March 2. Workflow: - Publish into a draft and flip it live only after the manifest check. The window between creating the release and verifying its assets previously exposed a tag the picker would offer and the download would 404 on; a draft is invisible to listReleaseBuilds, so a job that dies in that window — including a hard kill by the job timeout, which runs no cleanup step — leaves nothing user-visible behind. - Add a failure handler that discards the draft, gated on the publish step not having succeeded so a later prune failure cannot delete a live release. - Align retry budgets with the job timeout (was 60min against a worst case of ~185min, so a mid-retry kill skipped the cleanup that step exists for). - Exclude drafts from the freshness and retention queries. - persist-credentials: false; the job only reads this repo and never pushes. * refactor(hourly): authenticate with a GitHub App instead of a PAT A fine-grained PAT expires, and the hourly build would then fail silently on a schedule nobody watches. A GitHub App's private key has no expiry, so this is set up once. It is also owned by the org rather than by the person who created it, so the credential survives that person leaving. The workflow mints a short-lived installation token via actions/create-github-app-token and passes it as GH_TOKEN. Installation tokens live one hour, which is ample: this job runs no tests, no notarization, and no Windows signing, so it is pack + upload. The retry budgets and job timeout are re-sized to that reality rather than copied from the release pipeline, whose 3x45 publish budget exists for notarization and SignPath. setup-hourly-release-token.sh now provisions HOURLY_RELEASE_APP_ID and HOURLY_RELEASE_APP_PRIVATE_KEY. The key is redirected from a file straight into `gh secret set` on stdin, so its contents never enter a shell variable, argv, or the terminal. * fix(hourly): make the xtrace guard fire and cover cancelled runs The xtrace guard disabled tracing before testing for it, so `[[ -o xtrace ]]` read the state the previous line had just cleared and never fired. `bash -x` ran straight through, tracing exactly the key handling the guard exists to prevent. Test first, then disable. The draft cleanup only ran on failure(), but a run stopped from the Actions UI is cancelled(), not failed — a manual cancel mid-publish stranded the draft. Cover both.
96 lines
3.8 KiB
Bash
Executable File
96 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Provisions the credentials hourly-mac-build.yml uses to publish into
|
|
# stablyai/orca-hourly. GITHUB_TOKEN cannot be used: it is scoped to the repo
|
|
# running the workflow, and hourly artifacts are published to a different one.
|
|
#
|
|
# A GitHub App is used rather than a PAT because its private key does not expire
|
|
# — no yearly rotation — and it belongs to the org rather than to the person who
|
|
# created it, so it survives that person leaving.
|
|
#
|
|
# The key is read from a file and piped straight into `gh secret set`. It is never
|
|
# echoed, never passed as a command-line argument (argv is world-readable via
|
|
# `ps`), and never copied anywhere on disk.
|
|
#
|
|
# Usage: bash config/scripts/setup-hourly-release-token.sh [path/to/key.pem]
|
|
#
|
|
set -euo pipefail
|
|
|
|
# Guard: xtrace would echo the key to stderr on every expansion. Test before
|
|
# disabling, or the check reads the state this line just cleared and never fires.
|
|
if [[ -o xtrace ]]; then
|
|
echo "Refusing to run with xtrace enabled; it would echo the private key." >&2
|
|
exit 1
|
|
fi
|
|
set +x
|
|
|
|
MAIN_REPO="stablyai/orca"
|
|
HOURLY_REPO="stablyai/orca-hourly"
|
|
APP_ID_SECRET="HOURLY_RELEASE_APP_ID"
|
|
APP_KEY_SECRET="HOURLY_RELEASE_APP_PRIVATE_KEY"
|
|
|
|
fail() {
|
|
echo "error: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
command -v gh >/dev/null 2>&1 || fail "gh CLI not found. See https://cli.github.com"
|
|
gh auth status >/dev/null 2>&1 || fail "Not logged in. Run: gh auth login"
|
|
|
|
# Setting repo secrets requires admin; check before asking for anything.
|
|
if [[ "$(gh api "repos/$MAIN_REPO" --jq '.permissions.admin' 2>/dev/null)" != "true" ]]; then
|
|
fail "You need admin on $MAIN_REPO to set repository secrets."
|
|
fi
|
|
gh api "repos/$HOURLY_REPO" --jq '.full_name' >/dev/null 2>&1 ||
|
|
fail "$HOURLY_REPO does not exist or you cannot see it."
|
|
|
|
cat <<EOF
|
|
|
|
Create a GitHub App (one time — the key never expires)
|
|
──────────────────────────────────────────────────────
|
|
1. Open: https://github.com/organizations/stablyai/settings/apps/new
|
|
2. Name .................. orca-hourly-release
|
|
Homepage URL .......... https://github.com/$HOURLY_REPO
|
|
Webhook ............... UNCHECK "Active"
|
|
3. Repository permissions -> Contents: Read and write
|
|
(leave everything else alone)
|
|
4. "Where can this app be installed?" -> Only on this account
|
|
5. Create, then note the App ID shown at the top of the page.
|
|
6. Generate a private key (bottom of the page) — a .pem downloads.
|
|
7. Install App -> Only select repositories -> $HOURLY_REPO
|
|
|
|
EOF
|
|
|
|
read -rp "App ID (numeric): " APP_ID
|
|
[[ "$APP_ID" =~ ^[0-9]+$ ]] || fail "App ID must be numeric, got: ${APP_ID:-<empty>}"
|
|
|
|
KEY_PATH="${1:-}"
|
|
if [[ -z "$KEY_PATH" ]]; then
|
|
read -rp "Path to the downloaded .pem: " KEY_PATH
|
|
fi
|
|
# Expand a leading ~ so a pasted path works without quoting rules.
|
|
KEY_PATH="${KEY_PATH/#\~/$HOME}"
|
|
[[ -r "$KEY_PATH" ]] || fail "Cannot read key file: $KEY_PATH"
|
|
grep -q "BEGIN.*PRIVATE KEY" "$KEY_PATH" ||
|
|
fail "$KEY_PATH does not look like a PEM private key."
|
|
|
|
echo "Storing $APP_ID_SECRET in $MAIN_REPO..."
|
|
printf '%s' "$APP_ID" | gh secret set "$APP_ID_SECRET" --repo "$MAIN_REPO" ||
|
|
fail "Could not set $APP_ID_SECRET."
|
|
|
|
# Piped on stdin so the key never appears in argv or in shell history.
|
|
echo "Storing $APP_KEY_SECRET in $MAIN_REPO..."
|
|
gh secret set "$APP_KEY_SECRET" --repo "$MAIN_REPO" <"$KEY_PATH" ||
|
|
fail "Could not set $APP_KEY_SECRET."
|
|
|
|
echo
|
|
echo "Done. Both secrets are set on $MAIN_REPO."
|
|
echo
|
|
echo "Delete your local copy of the key — the workflow reads it from the secret,"
|
|
echo "and a .pem sitting in ~/Downloads is a standing credential:"
|
|
echo " rm '$KEY_PATH'"
|
|
echo
|
|
echo "Smoke-test the pipeline without waiting for the hour (after this merges):"
|
|
echo " gh workflow run hourly-mac-build.yml --repo $MAIN_REPO -f force=true"
|
|
echo " gh run watch --repo $MAIN_REPO"
|