ci: main publishes, so a tag stops being required — and :latest stops shipping a dev client
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 17s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m36s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m24s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 17s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 6m36s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 9m24s
Step 3 of M314. Note 3127 §0's diagnostic is "is `main` publishing sufficient for a user to receive the build" — and here it was not. The desktop and Android lanes BUILT on main and published nothing: `Publish release` was gated on `refs/tags/v*`, the channel publishes on `refs/heads/dev`, the manifest job on dev-or-tag. So the stable channel moved only when somebody cut a tag, which made a `v*` tag load-bearing rather than the optional bookmark the model wants. Both channels are rolling fixed-tag releases now. `dev` from dev, `stable` from main, same machinery — `publish-release.sh` already took RELEASE_TAG, `write-manifest.sh` already pruned, and both already PATCHed a stale description on 409 (#2182). This is wiring, not new mechanism. ## The defect this carried `ci.yml`'s "Fetch the Android client to bake in" read `releases/download/dev` UNCONDITIONALLY, on every branch. Every image baked in the dev APK — `:latest` included — so a stable server served a dev-channel client to anyone who downloaded it from there. That has nothing to do with versioning; it is fixed here because this is the step that finally gives `stable` an APK to point at. It also means Android needs no channel machinery of its own. The APK is served FROM the image, so the channel is already a property of which image you run — note 3127 §7's "nothing to hand off" shape, arrived at here by accident. One branch-conditional line, not a second channel in `client_dist.py` as this milestone first assumed. ## The break this nearly shipped `install.sh --channel stable` read the version out of `stable/latest.json` and then fetched `releases/tags/v<version>` for the bundles — correct while stable was a manifest-only pointer, and broken the moment stable holds its own. Stable is the DEFAULT channel, so `curl … | sh` would have failed for everyone between this commit and the first merge to main. Both channels are one lookup now: fetch the fixed-tag release, install what is on it. A transitional fallback covers the window where `stable` still has no bundles, marked for deletion in step 7 — without it the default channel is broken for however long it takes to merge, and that window is gated on an operator request rather than on this lane. ## The two writers problem `stable`'s manifest was written by tag builds. It is written by main now, and the tag path stops writing it — two writers for one channel is a race with no winner worth having. A `v*` tag still writes its own versioned manifest; its build consequence goes entirely in step 7. Also corrected: `update.rs`'s header still described stable as following `v*` tags. Nothing in that file moved — it only ever read `<channel>/latest.json` — but the comment was a lie, and it is the file somebody reads to understand the feed. #3143 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -199,19 +199,29 @@ jobs:
|
||||
JSON
|
||||
cat dist/thoughtsync-android.json
|
||||
|
||||
# The rolling dev channel, same fixed-tag release the desktop bundles use.
|
||||
# CI artifacts are per-run and auth-gated, so they are no use as a fetch
|
||||
# target; a release asset has a permanent URL. Only ever a SIGNED build —
|
||||
# publishing an unsigned APK would offer people something they cannot
|
||||
# install over what they already have.
|
||||
- name: Publish to the dev channel
|
||||
if: github.ref == 'refs/heads/dev' && steps.build.outputs.keystore != ''
|
||||
# The rolling channel for this branch, the same fixed-tag releases the desktop
|
||||
# bundles use. CI artifacts are per-run and auth-gated, so they are no use as a
|
||||
# fetch target; a release asset has a permanent URL. Only ever a SIGNED build —
|
||||
# publishing an unsigned APK would offer people something they cannot install
|
||||
# over what they already have.
|
||||
#
|
||||
# `stable` from main is new in M314 step 3, and it is what lets the server image
|
||||
# bake in a client that matches its own channel: a :latest image fetches the APK
|
||||
# from `stable`, a :dev image from `dev`. Before this, main published no APK at
|
||||
# all and every image — stable included — baked in the dev one.
|
||||
- name: Publish to the channel for this branch
|
||||
if: (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && steps.build.outputs.keystore != ''
|
||||
working-directory: .
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: dev
|
||||
RELEASE_PRERELEASE: "true"
|
||||
run: bash desktop/packaging/publish-release.sh
|
||||
run: |
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing the APK to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
|
||||
- name: Upload the APK
|
||||
# Mirrored action, never actions/upload-artifact. @v4+ throws
|
||||
|
||||
@@ -330,7 +330,18 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir -p client
|
||||
base="${{ github.server_url }}/${{ github.repository }}/releases/download/dev"
|
||||
# THE CHANNEL IS A PROPERTY OF THE IMAGE. A :dev image serves the dev
|
||||
# client; :latest serves the stable one. This read `download/dev`
|
||||
# unconditionally until M314 step 3, on every branch — so every stable
|
||||
# server shipped a dev-channel APK to anyone who downloaded the client
|
||||
# from it. Not a versioning gap; a plain defect, fixed here because this
|
||||
# is the step that gave `stable` an APK to point at.
|
||||
case "${{ github.ref_name }}" in
|
||||
main|v*) channel=stable ;;
|
||||
*) channel=dev ;;
|
||||
esac
|
||||
echo "Baking in the $channel client."
|
||||
base="${{ github.server_url }}/${{ github.repository }}/releases/download/$channel"
|
||||
ok=1
|
||||
for f in thoughtsync.apk thoughtsync-android.json; do
|
||||
curl -fsSL -H "Authorization: token $GITHUB_TOKEN" -o "client/$f" "$base/$f" || ok=0
|
||||
|
||||
@@ -216,26 +216,40 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: bash desktop/packaging/publish-release.sh
|
||||
|
||||
# The rolling DEVELOPMENT channel (M10.9): a release whose tag never moves, so
|
||||
# the updater has a permanent URL to read — Forgejo has no
|
||||
# /releases/latest/download/<asset> route, so "newest" can't be named in a URL.
|
||||
# The rolling channel for this branch: `dev` from dev, `stable` from main. Both
|
||||
# are releases whose tag never moves, so the updater has a permanent URL to
|
||||
# read — Forgejo has no /releases/latest/download/<asset> route, so "newest"
|
||||
# cannot be named in a URL.
|
||||
#
|
||||
# MAIN PUBLISHING HERE is what makes a `v*` tag optional (note 3127 §0). Until
|
||||
# M314 step 3 this job built on main and published nothing, so the stable
|
||||
# channel moved only when somebody cut a tag — that section's diagnostic
|
||||
# failing outright: main publishing was not sufficient for a user to receive
|
||||
# the build.
|
||||
#
|
||||
# Gated on the signing key INSIDE the script rather than with an `if:`, because
|
||||
# the secrets context isn't reliably available to step conditions. Publishing
|
||||
# bundles the app would then refuse to verify is worse than publishing nothing:
|
||||
# it looks like a working feed.
|
||||
- name: Publish to the dev channel
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
- name: Publish to the channel for this branch
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
RELEASE_TAG: dev
|
||||
RELEASE_PRERELEASE: "true"
|
||||
run: |
|
||||
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the dev channel publish."
|
||||
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the channel publish."
|
||||
exit 0
|
||||
fi
|
||||
# POSIX `case`, not bash `[[ ]]` — these run under busybox sh (rule 81).
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
|
||||
# Windows installer, CROSS-COMPILED from Linux — there is no Windows build host.
|
||||
@@ -326,26 +340,40 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: bash desktop/packaging/publish-release.sh
|
||||
|
||||
# The rolling DEVELOPMENT channel (M10.9): a release whose tag never moves, so
|
||||
# the updater has a permanent URL to read — Forgejo has no
|
||||
# /releases/latest/download/<asset> route, so "newest" can't be named in a URL.
|
||||
# The rolling channel for this branch: `dev` from dev, `stable` from main. Both
|
||||
# are releases whose tag never moves, so the updater has a permanent URL to
|
||||
# read — Forgejo has no /releases/latest/download/<asset> route, so "newest"
|
||||
# cannot be named in a URL.
|
||||
#
|
||||
# MAIN PUBLISHING HERE is what makes a `v*` tag optional (note 3127 §0). Until
|
||||
# M314 step 3 this job built on main and published nothing, so the stable
|
||||
# channel moved only when somebody cut a tag — that section's diagnostic
|
||||
# failing outright: main publishing was not sufficient for a user to receive
|
||||
# the build.
|
||||
#
|
||||
# Gated on the signing key INSIDE the script rather than with an `if:`, because
|
||||
# the secrets context isn't reliably available to step conditions. Publishing
|
||||
# bundles the app would then refuse to verify is worse than publishing nothing:
|
||||
# it looks like a working feed.
|
||||
- name: Publish to the dev channel
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
- name: Publish to the channel for this branch
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
RELEASE_TAG: dev
|
||||
RELEASE_PRERELEASE: "true"
|
||||
run: |
|
||||
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
|
||||
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the dev channel publish."
|
||||
echo "No TAURI_SIGNING_PRIVATE_KEY — skipping the channel publish."
|
||||
exit 0
|
||||
fi
|
||||
# POSIX `case`, not bash `[[ ]]` — these run under busybox sh (rule 81).
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
|
||||
# The updater manifest, written AFTER both bundle jobs — they run in separate
|
||||
@@ -359,7 +387,7 @@ jobs:
|
||||
manifest:
|
||||
name: Update manifest
|
||||
needs: [build, windows]
|
||||
if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v')
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: python-ci
|
||||
container:
|
||||
image: git.fabledsword.com/bvandeusen/ci-tauri:1.97
|
||||
@@ -380,19 +408,31 @@ jobs:
|
||||
# could drift, and a manifest whose version doesn't match the binary it
|
||||
# points at is an updater that never settles.
|
||||
version="$(sh desktop/packaging/build-version.sh)"
|
||||
if [ "${GITHUB_REF_NAME}" = "dev" ]; then
|
||||
export RELEASE_TAG=dev
|
||||
export RELEASE_NOTES="Development build from ${GITHUB_SHA}"
|
||||
# Rolling channel: drop the previous build's bundles once the manifest
|
||||
# points at this one. Nothing can reach them, and they're ~100 MB a push.
|
||||
export PRUNE_OLD_ASSETS=true
|
||||
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh
|
||||
else
|
||||
export RELEASE_TAG="${GITHUB_REF_NAME}"
|
||||
export RELEASE_NOTES="ThoughtSync ${GITHUB_REF_NAME}"
|
||||
# Twice: once onto the versioned release itself, and once onto the
|
||||
# permanent `stable` pointer the app actually reads. Same manifest both
|
||||
# times — its URLs point at the versioned assets either way.
|
||||
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh
|
||||
APP_VERSION="$version" MANIFEST_TAG=stable bash desktop/packaging/write-manifest.sh
|
||||
fi
|
||||
# POSIX `case` (rule 81). For a tag, GITHUB_REF_NAME is the tag name, so
|
||||
# the default arm is the tag path.
|
||||
case "${GITHUB_REF_NAME}" in
|
||||
dev|main)
|
||||
# Both are rolling channels: the manifest lands on the same release
|
||||
# that holds the bundles, and the previous build's bundles are dropped
|
||||
# once it points at this one. Nothing can reach them, and they are
|
||||
# ~100 MB a push.
|
||||
case "${GITHUB_REF_NAME}" in
|
||||
main) export RELEASE_TAG=stable
|
||||
export RELEASE_NOTES="Stable build from ${GITHUB_SHA}" ;;
|
||||
*) export RELEASE_TAG=dev
|
||||
export RELEASE_NOTES="Development build from ${GITHUB_SHA}" ;;
|
||||
esac
|
||||
export PRUNE_OLD_ASSETS=true
|
||||
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh
|
||||
;;
|
||||
*)
|
||||
# A versioned release gets its own manifest and NOTHING ELSE. It used
|
||||
# to also write the `stable` pointer — that moved to main above, and
|
||||
# two writers for one channel is a race with no winner worth having.
|
||||
# The tag's build consequence goes entirely in M314 step 7; this only
|
||||
# stops it fighting over `stable`.
|
||||
export RELEASE_TAG="${GITHUB_REF_NAME}"
|
||||
export RELEASE_NOTES="ThoughtSync ${GITHUB_REF_NAME}"
|
||||
APP_VERSION="$version" bash desktop/packaging/write-manifest.sh
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user