ci(cnb): rewrite sync workflow with concurrency + scoped push + retry
Closes the persistent "fatal: could not read Username for 'https://cnb.cool'" failure that bit about half of recent sync-cnb runs. Root causes from the failure log of run 25705666413 (2026-05-12) and 25697452832 (2026-05-11): 1. The previous tencentcom/git-sync Docker action discovered every local ref via fetch-depth: 0 and tried to push them all — including dependabot/* branches that GitHub had locally. Those follow-on pushes ran without the configured credential helper in scope and failed with the basic-auth prompt error above. 2. No concurrency guard meant the back-to-back `main` push and `v*` tag push that auto-tag.yml fires within ~15s of each other raced. Two workflow runs would attempt to push to the same CNB remote simultaneously; one would lose. Rewrite: * Hand-rolled `git push` with the CNB token URL-encoded inline so the credential is always in scope. No Docker action dependency. * `concurrency: group: cnb-sync, cancel-in-progress: false` so queued runs serialize cleanly rather than racing or dropping. * Only push the ref that triggered the run — `main` on branch push (with --force-with-lease for safety), the specific tag on tag push. Feature branches and dependabot refs no longer mirror. * 3-attempt retry with linear backoff (5s, 10s) for transient failures (CNB rate-limit, DNS blips, etc.). * `workflow_dispatch: {}` trigger so the maintainer can re-run against a specific ref manually if the automated run fails. Adds docs/CNB_MIRROR.md with: the verification steps after a release, the manual fallback procedure (one-time `git remote add cnb`, then `git push cnb vX.Y.Z`), the token-rotation procedure, and a note on why binary release assets aren't on CNB today. Cross-links from docs/RELEASE_RUNBOOK.md so the verify-after-release step doesn't get forgotten.
This commit is contained in:
@@ -1,9 +1,37 @@
|
||||
name: Sync to CNB
|
||||
|
||||
# Mirror commits and release tags to cnb.cool/deepseek-tui.com/DeepSeek-TUI
|
||||
# so users behind GitHub-blocking networks can fetch the source and tagged
|
||||
# releases from the Tencent-hosted mirror.
|
||||
#
|
||||
# Triggers:
|
||||
# * push to main → mirrors that commit to CNB main
|
||||
# * tag matching v* → mirrors that tag to CNB
|
||||
# * workflow_dispatch → manual fallback if either of the above fails
|
||||
#
|
||||
# Why the rewrite (v0.8.31):
|
||||
# The previous implementation used the opaque tencentcom/git-sync Docker
|
||||
# action, which discovered every local ref via fetch-depth: 0 and tried
|
||||
# to push them all — including dependabot/* branches that GitHub had
|
||||
# locally. Those follow-on pushes ran without the configured credential
|
||||
# helper in scope and failed with
|
||||
# `fatal: could not read Username for 'https://cnb.cool'`
|
||||
# No concurrency block meant the main-push and tag-push workflow runs
|
||||
# that auto-tag.yml fires within seconds of each other raced. About
|
||||
# half of recent runs failed for those two reasons combined.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch: {}
|
||||
|
||||
# Serialize runs so the back-to-back main-push + tag-push from auto-tag.yml
|
||||
# don't race each other rebasing onto CNB. cancel-in-progress: false so
|
||||
# every commit actually arrives — we'd rather queue than drop.
|
||||
concurrency:
|
||||
group: cnb-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,12 +43,62 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync to CNB Repository
|
||||
uses: docker://tencentcom/git-sync
|
||||
|
||||
- name: Push triggering ref to CNB
|
||||
env:
|
||||
PLUGIN_TARGET_URL: "https://cnb.cool/deepseek-tui.com/DeepSeek-TUI"
|
||||
PLUGIN_AUTH_TYPE: "https"
|
||||
PLUGIN_USERNAME: "cnb"
|
||||
PLUGIN_PASSWORD: ${{ secrets.CNB_GIT_TOKEN }}
|
||||
PLUGIN_SYNC_MODE: "rebase"
|
||||
CNB_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${CNB_TOKEN:-}" ]; then
|
||||
echo "::error::CNB_GIT_TOKEN secret is not set; cannot push to CNB." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# URL-encode any '%' in the token so basic-auth doesn't break on
|
||||
# special characters. CNB tokens are typically alphanumeric so
|
||||
# this is belt-and-suspenders.
|
||||
ENCODED_TOKEN="$(printf '%s' "${CNB_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
|
||||
REMOTE_URL="https://cnb:${ENCODED_TOKEN}@cnb.cool/deepseek-tui.com/DeepSeek-TUI.git"
|
||||
# Use a masked alias so the token never appears in log lines.
|
||||
git remote add cnb "${REMOTE_URL}"
|
||||
|
||||
# Push with retry on transient failures (CNB rate-limits, DNS
|
||||
# blips, etc.). Args after `kind` are forwarded to `git push`
|
||||
# so callers can pass `--force-with-lease`, multiple refspecs,
|
||||
# etc. without quoting them into one string.
|
||||
push_with_retry() {
|
||||
local kind="$1"
|
||||
shift
|
||||
local attempt
|
||||
for attempt in 1 2 3; do
|
||||
echo "Attempt ${attempt}: pushing ${kind} to CNB"
|
||||
if git push cnb "$@" 2>&1; then
|
||||
echo "Successfully pushed ${kind} to CNB"
|
||||
return 0
|
||||
fi
|
||||
if [ "${attempt}" -lt 3 ]; then
|
||||
sleep $((attempt * 5))
|
||||
fi
|
||||
done
|
||||
echo "::error::Failed to push ${kind} to CNB after 3 attempts" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
push_with_retry "tag ${TAG}" "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||
elif [[ "${GITHUB_REF}" == refs/heads/main ]]; then
|
||||
# --force-with-lease so an unexpected diverged state on CNB
|
||||
# surfaces as a failure (rather than silently overwriting).
|
||||
# The mirror is one-way; if CNB diverges, that's a bug worth
|
||||
# investigating manually before pushing again.
|
||||
push_with_retry "main" HEAD:refs/heads/main --force-with-lease
|
||||
else
|
||||
# workflow_dispatch from a non-main branch — push that branch
|
||||
# too, but never force. Useful for testing the mirror against
|
||||
# a feature branch before merging.
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
push_with_retry "branch ${BRANCH}" "HEAD:refs/heads/${BRANCH}"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# CNB Cool mirror
|
||||
|
||||
`cnb.cool/deepseek-tui.com/DeepSeek-TUI` is a one-way mirror of this
|
||||
GitHub repository for users on networks where GitHub is slow or blocked
|
||||
(primarily mainland China). The mirror receives every push to `main` and
|
||||
every `v*` release tag.
|
||||
|
||||
## How it works
|
||||
|
||||
The mirror is maintained by the [`Sync to CNB`](../.github/workflows/sync-cnb.yml)
|
||||
GitHub Actions workflow:
|
||||
|
||||
- **Trigger:** `push` to `main`, `push` of any `v*` tag, or
|
||||
`workflow_dispatch` for manual recovery.
|
||||
- **Auth:** HTTPS basic auth as user `cnb` with the `CNB_GIT_TOKEN`
|
||||
repository secret as the password.
|
||||
- **Scope:** only the ref that triggered the run is pushed. Tag pushes
|
||||
push exactly that tag. Branch pushes push only `main`
|
||||
(`--force-with-lease`). Feature branches and dependabot refs are
|
||||
intentionally *not* mirrored.
|
||||
- **Concurrency:** runs are serialized via a `cnb-sync` concurrency
|
||||
group so the back-to-back `main` push and tag push from
|
||||
`auto-tag.yml` cannot race each other.
|
||||
- **Retry:** each push is retried up to three times with linear
|
||||
backoff (5s, 10s) before the workflow gives up.
|
||||
|
||||
## Verifying the mirror after a release
|
||||
|
||||
After `release.yml` completes for a `vX.Y.Z` tag, the CNB mirror
|
||||
should have both the new commit on `main` and the new tag:
|
||||
|
||||
```bash
|
||||
# Quick check: does the new tag exist on CNB?
|
||||
git ls-remote https://cnb.cool/deepseek-tui.com/DeepSeek-TUI.git \
|
||||
refs/tags/vX.Y.Z
|
||||
|
||||
# Quick check: is CNB's main at the same commit as origin/main?
|
||||
gh_main=$(git ls-remote https://github.com/Hmbown/DeepSeek-TUI.git refs/heads/main | awk '{print $1}')
|
||||
cnb_main=$(git ls-remote https://cnb.cool/deepseek-tui.com/DeepSeek-TUI.git refs/heads/main | awk '{print $1}')
|
||||
test "$gh_main" = "$cnb_main" && echo "in sync" || echo "DIVERGED: gh=$gh_main cnb=$cnb_main"
|
||||
```
|
||||
|
||||
Or check the workflow run directly:
|
||||
|
||||
```bash
|
||||
gh run list --workflow=sync-cnb.yml --repo Hmbown/DeepSeek-TUI --limit 5
|
||||
```
|
||||
|
||||
If the most recent run for the release tag is `success`, the mirror
|
||||
caught it. If it's `failure`, follow the manual fallback below.
|
||||
|
||||
## Manual fallback
|
||||
|
||||
If the workflow fails for any reason (CNB rate-limit, token expired,
|
||||
GitHub outage, etc.), the maintainer can push to CNB by hand from
|
||||
their local checkout. This works because the CNB token is a personal
|
||||
PAT — the same token used by the workflow lives in the maintainer's
|
||||
password manager.
|
||||
|
||||
### One-time setup
|
||||
|
||||
```bash
|
||||
# Add the CNB remote alongside origin.
|
||||
git remote add cnb https://cnb:${CNB_TOKEN}@cnb.cool/deepseek-tui.com/DeepSeek-TUI.git
|
||||
|
||||
# Or, if you don't want the token in your shell history:
|
||||
git remote add cnb https://cnb.cool/deepseek-tui.com/DeepSeek-TUI.git
|
||||
# (you'll be prompted for username `cnb` and password ${CNB_TOKEN}
|
||||
# on the first push; subsequent pushes use the credential helper.)
|
||||
```
|
||||
|
||||
### Sync a release manually
|
||||
|
||||
```bash
|
||||
# Make sure main is current.
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git reset --hard origin/main
|
||||
|
||||
# Push main first, then the tag. Order matters: CNB should see the
|
||||
# commit before the tag that points at it.
|
||||
git push cnb main --force-with-lease
|
||||
git push cnb vX.Y.Z
|
||||
```
|
||||
|
||||
### Re-trigger the workflow manually
|
||||
|
||||
If the workflow is healthy but happened to fail on the release run
|
||||
(e.g. a transient CNB outage that's since cleared), retrigger it
|
||||
without pushing anything:
|
||||
|
||||
```bash
|
||||
gh workflow run sync-cnb.yml --repo Hmbown/DeepSeek-TUI
|
||||
```
|
||||
|
||||
`workflow_dispatch` runs against the workflow's default branch
|
||||
(`main`), so this will sync the current `main` to CNB. To re-sync
|
||||
a specific tag, the manual `git push cnb` path above is the way.
|
||||
|
||||
## Rotating `CNB_GIT_TOKEN`
|
||||
|
||||
If the workflow starts failing with auth errors and the token has
|
||||
expired:
|
||||
|
||||
1. Log in to `cnb.cool` and generate a new personal access token
|
||||
with `repo` (push) scope.
|
||||
2. Update the `CNB_GIT_TOKEN` repository secret:
|
||||
```bash
|
||||
gh secret set CNB_GIT_TOKEN --repo Hmbown/DeepSeek-TUI
|
||||
```
|
||||
3. Re-trigger the workflow on a recent commit:
|
||||
```bash
|
||||
gh workflow run sync-cnb.yml --repo Hmbown/DeepSeek-TUI
|
||||
```
|
||||
4. Confirm the run succeeds via `gh run list --workflow=sync-cnb.yml`.
|
||||
|
||||
## Binary release assets
|
||||
|
||||
CNB is a code mirror only — it does not host binary release assets.
|
||||
Users behind GitHub-blocking networks who need the prebuilt binaries
|
||||
have two options:
|
||||
|
||||
- **`cargo install`** from the CNB mirror:
|
||||
```bash
|
||||
cargo install --git https://cnb.cool/deepseek-tui.com/DeepSeek-TUI --tag vX.Y.Z deepseek-tui-cli
|
||||
cargo install --git https://cnb.cool/deepseek-tui.com/DeepSeek-TUI --tag vX.Y.Z deepseek-tui
|
||||
```
|
||||
(Both binaries are required — the dispatcher and the TUI ship
|
||||
separately; see `AGENTS.md` for the two-binary install rationale.)
|
||||
|
||||
- **`DEEPSEEK_TUI_RELEASE_BASE_URL`** environment variable, if a
|
||||
third-party CDN mirror of the GitHub Release assets exists. The
|
||||
npm wrapper installer in `npm/deepseek-tui/scripts/install.js`
|
||||
reads this variable to redirect binary downloads. The directory
|
||||
pointed to must contain `deepseek-artifacts-sha256.txt` and the
|
||||
platform binaries; format matches a GitHub Release asset
|
||||
directory.
|
||||
|
||||
A first-party binary CDN mirror for CNB users is on the v0.8.32+
|
||||
roadmap; it is not part of v0.8.31.
|
||||
Reference in New Issue
Block a user