Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/cli-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,20 +270,26 @@ jobs:
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_DISPATCH_TOKEN }}
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
OFFICIAL_REPOSITORY: ${{ github.repository == 'GCWing/OpenBitFun' }}
TAP_REPOSITORY: ${{ vars.HOMEBREW_TAP_REPOSITORY }}
shell: bash
run: |
set -euo pipefail
if [[ -z "${GH_TOKEN}" ]]; then
if [[ "${OFFICIAL_REPOSITORY}" == "true" ]]; then
echo "::error::HOMEBREW_TAP_DISPATCH_TOKEN is required for the official post-publication notification."
if [[ -n "${TAP_REPOSITORY}" ]]; then
echo "::error::HOMEBREW_TAP_DISPATCH_TOKEN is required for the configured tap."
exit 1
fi
echo "HOMEBREW_TAP_DISPATCH_TOKEN not set on fork; skipping tap dispatch."
echo "Homebrew tap notification is not configured."
exit 0
fi

echo "Dispatching openbitfun-release-published for ${RELEASE_TAG} to GCWing/homebrew-tap"
TAP_REPOSITORY="${TAP_REPOSITORY:-GCWing/homebrew-tap}"
[[ "$TAP_REPOSITORY" =~ ^[a-zA-Z0-9-]+/[a-zA-Z0-9._-]+$ &&
"${TAP_REPOSITORY#*/}" != . && "${TAP_REPOSITORY#*/}" != .. ]] || {
echo "::error::HOMEBREW_TAP_REPOSITORY must be an owner/repository pair."
exit 1
}
echo "Dispatching openbitfun-release-published for ${RELEASE_TAG} to ${TAP_REPOSITORY}"
PAYLOAD="$(jq -cn --arg tag_name "${RELEASE_TAG}" '{
event_type: "openbitfun-release-published",
client_payload: {
Expand All @@ -295,5 +301,5 @@ jobs:
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/GCWing/homebrew-tap/dispatches \
"${GITHUB_API_URL}/repos/${TAP_REPOSITORY}/dispatches" \
-d "$PAYLOAD"
81 changes: 56 additions & 25 deletions .github/workflows/desktop-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ jobs:

git fetch origin main --tags --force
CHECKOUT_SHA="$(git rev-parse "${CHECKOUT_REF}^{commit}")"
if [[ "${GITHUB_REPOSITORY}" == "GCWing/OpenBitFun" ]] && \
# The official repository ID survives an owner or name change.
if [[ "${GITHUB_REPOSITORY_ID}" == "1147866277" ]] && \
! git merge-base --is-ancestor "${CHECKOUT_SHA}" origin/main; then
echo "Ref ${CHECKOUT_REF} (${CHECKOUT_SHA}) is not part of the protected main history." >&2
exit 1
Expand Down Expand Up @@ -202,8 +203,8 @@ jobs:
NODE_OPTIONS: --max-old-space-size=6144
OPENBITFUN_ENABLE_UPDATER_ARTIFACTS: ${{ needs.prepare.outputs.upload_to_release }}
OPENBITFUN_RELEASE_CHANNEL: ${{ needs.prepare.outputs.release_channel }}
TAURI_UPDATER_ENDPOINT: ${{ github.repository != 'GCWing/OpenBitFun' && (needs.prepare.outputs.release_channel == 'beta' && format('https://github.com/{0}/releases/download/channel-v1-beta/latest-v1.json', github.repository) || format('https://github.com/{0}/releases/latest/download/latest-v1.json', github.repository)) || '' }}
TAURI_UPDATER_FALLBACK_ENDPOINT: ${{ github.repository != 'GCWing/OpenBitFun' && (needs.prepare.outputs.release_channel == 'beta' && format('https://github.com/{0}/releases/download/channel-v1-beta/latest-v1.json', github.repository) || format('https://github.com/{0}/releases/latest/download/latest-v1.json', github.repository)) || '' }}
TAURI_UPDATER_ENDPOINT: ${{ github.repository_id != '1147866277' && (needs.prepare.outputs.release_channel == 'beta' && format('https://github.com/{0}/releases/download/channel-v1-beta/latest-v1.json', github.repository) || format('https://github.com/{0}/releases/latest/download/latest-v1.json', github.repository)) || '' }}
TAURI_UPDATER_FALLBACK_ENDPOINT: ${{ github.repository_id != '1147866277' && (needs.prepare.outputs.release_channel == 'beta' && format('https://github.com/{0}/releases/download/channel-v1-beta/latest-v1.json', github.repository) || format('https://github.com/{0}/releases/latest/download/latest-v1.json', github.repository)) || '' }}
TAURI_UPDATER_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }}
# Same trust root, compiled into the Desktop binary so one-click relay
# deploy can verify the signed checksum locally and hand the remote host
Expand Down Expand Up @@ -722,6 +723,10 @@ jobs:
push: true
provenance: false
sbom: false
# Override the labels even when rebuilding a tag from before the transfer.
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
build-args: |
VERSION=${{ needs.prepare.outputs.version }}
REVISION=${{ needs.prepare.outputs.release_tag }}
Expand Down Expand Up @@ -767,16 +772,6 @@ jobs:
bash scripts/sign-release-assets.sh relay-image.json
test -s relay-image.json.sig

- name: Upload signed image descriptor
uses: actions/upload-artifact@v6
with:
name: openbitfun-relay-image-${{ needs.prepare.outputs.release_tag }}
if-no-files-found: error
retention-days: 7
path: |
relay-image.json
relay-image.json.sig

# The package is private on first creation. This deliberately fails until
# its visibility is changed to public, preventing an apparently green
# release that anonymous customer servers cannot pull.
Expand All @@ -792,6 +787,16 @@ jobs:
DOCKER_CONFIG="$clean_config" docker buildx imagetools inspect \
"${IMAGE}@${IMAGE_DIGEST}" >/dev/null

- name: Upload signed image descriptor
uses: actions/upload-artifact@v6
with:
name: openbitfun-relay-image-${{ needs.prepare.outputs.release_tag }}
if-no-files-found: error
retention-days: 7
path: |
relay-image.json
relay-image.json.sig

- name: Attach descriptor to an existing release (image-only backfill)
if: needs.prepare.outputs.relay_image_only == 'true'
uses: softprops/action-gh-release@v3
Expand Down Expand Up @@ -890,13 +895,20 @@ jobs:
run: bash scripts/sign-release-assets.sh release-manual-assets/*.exe

- name: Collect updater assets
shell: bash
env:
RELEASE_SOURCE: ${{ needs.prepare.outputs.checkout_ref }}
run: |
node scripts/collect-tauri-updater-assets.mjs \
--assets-dir release-assets \
--version "${{ needs.prepare.outputs.version }}" \
--out-dir release-updater-assets \
--windows-installer-dir release-manual-assets \
--required-platforms "${REQUIRED_UPDATER_PLATFORMS}"
set -euo pipefail
# Releases predating the maintenance installer use the signed NSIS updater.
args=(--assets-dir release-assets --version "${{ needs.prepare.outputs.version }}"
--out-dir release-updater-assets --required-platforms "${REQUIRED_UPDATER_PLATFORMS}")
if git grep -q OPENBITFUN_MAINTENANCE_V1 "$RELEASE_SOURCE" -- OpenBitFun-Installer/src-tauri/src/installer/commands.rs; then
args+=(--windows-installer-dir release-manual-assets)
elif [[ $? != 1 ]]; then
exit 1
fi
node scripts/collect-tauri-updater-assets.mjs "${args[@]}"

- name: Generate Linux binaries manifest
run: |
Expand Down Expand Up @@ -961,6 +973,30 @@ jobs:
--required-platforms "${REQUIRED_UPDATER_PLATFORMS}" \
--required-manual-platforms "windows-x86_64,darwin-aarch64,darwin-x86_64"

- name: Verify staged Relay image descriptor
shell: bash
env:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
ARTIFACT_RUN_IDS: ${{ inputs.artifact_run_ids }}
run: |
set -euo pipefail
owner="$(printf '%s' "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
image="ghcr.io/${owner}/openbitfun-relay-server"
legacy_image=""
# Trusted recovery artifacts may have been signed before the repository transfer.
if [[ "$GITHUB_REPOSITORY_ID" == "1147866277" && -n "$ARTIFACT_RUN_IDS" ]]; then
legacy_image="ghcr.io/gcwing/openbitfun-relay-server"
fi
jq -e --arg tag "$RELEASE_TAG" --arg version "$RELEASE_VERSION" \
--arg image "$image" --arg legacy "$legacy_image" '
.schema_version == 1 and .tag == $tag and .version == $version and
(.image == $image or ($legacy != "" and .image == $legacy)) and
(.platforms | sort == ["linux/amd64", "linux/arm64"]) and
(.digest | test("^sha256:[0-9a-f]{64}$"))
' relay-image-assets/relay-image.json >/dev/null
test -s relay-image-assets/relay-image.json.sig

- name: Stage release assets
shell: bash
run: |
Expand Down Expand Up @@ -1101,11 +1137,6 @@ jobs:
curl -fsSL --retry 5 --retry-delay 3 \
"https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.release_tag }}/relay-image.json" \
-o relay-image.published.json
test "$(jq -r '.tag' relay-image.published.json)" = "${{ needs.prepare.outputs.release_tag }}"
test "$(jq -r '.version' relay-image.published.json)" = "${{ needs.prepare.outputs.version }}"
test "$(jq -r '.image' relay-image.published.json)" = "ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/openbitfun-relay-server"
jq -e '.platforms | sort == ["linux/amd64", "linux/arm64"]' relay-image.published.json >/dev/null
jq -e '.digest | test("^sha256:[0-9a-f]{64}$")' relay-image.published.json >/dev/null
curl -fsSL --retry 5 --retry-delay 3 \
"https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.release_tag }}/relay-image.json.sig" \
-o relay-image.published.json.sig
Expand Down Expand Up @@ -1205,7 +1236,7 @@ jobs:
# still the source of truth, so a failed or unconfigured ping never fails
# the release. Receiver: scripts/openbitfun-release-sync.sh.
- name: Request openbitfun mirror sync
if: github.repository == 'GCWing/OpenBitFun'
if: github.repository_id == '1147866277'
continue-on-error: true
env:
SYNC_WEBHOOK_URL: ${{ secrets.OPENBITFUN_SYNC_WEBHOOK_URL }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
OPENBITFUN_RELEASE_CHANNEL: nightly
# Nightly does not publish a Tauri latest-v1.json feed yet. Preserve its
# existing stable updater endpoints until that publishing path exists.
TAURI_UPDATER_ENDPOINT: https://github.com/GCWing/OpenBitFun/releases/latest/download/latest-v1.json
TAURI_UPDATER_ENDPOINT: https://github.com/openBitFun/OpenBitFun/releases/latest/download/latest-v1.json
TAURI_UPDATER_FALLBACK_ENDPOINT: https://openbitfun.com/release/latest-v1.json
# Nightly relay archives are signed too (linux-binaries.yml receives the
# key), so nightly Desktop needs the same trust root to verify them.
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:
upload_artifacts: true
# Fork validation is intentionally cache-read-only. Only the upstream
# repository may seed long-lived Nightly caches.
cache_write: ${{ github.repository_owner == 'GCWing' }}
cache_write: ${{ github.repository_id == '1147866277' }}

# ── Publish nightly pre-release ────────────────────────────────────
publish-nightly:
Expand Down Expand Up @@ -169,6 +169,9 @@ jobs:
push: true
provenance: false
sbom: false
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}
build-args: |
VERSION=${{ needs.check-changes.outputs.nightly_version }}
REVISION=${{ github.sha }}
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/publish-ready-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,14 @@ jobs:
test "${#packages[@]}" -eq 6
bash scripts/sign-release-assets.sh "${packages[@]}"
node scripts/write-minisign-public-key.mjs --out release-assets/minisign.pub
node scripts/collect-tauri-updater-assets.mjs --assets-dir release-assets \
--version "$RELEASE_VERSION" --out-dir release-updater-assets \
--windows-installer-dir release-manual-assets \
--required-platforms windows-x86_64,linux-x86_64,linux-aarch64,linux-x86_64-deb,linux-aarch64-deb,linux-x86_64-rpm,linux-aarch64-rpm
args=(--assets-dir release-assets --version "$RELEASE_VERSION" --out-dir release-updater-assets
--required-platforms windows-x86_64,linux-x86_64,linux-aarch64,linux-x86_64-deb,linux-aarch64-deb,linux-x86_64-rpm,linux-aarch64-rpm)
if git grep -q OPENBITFUN_MAINTENANCE_V1 "$RELEASE_TAG" -- OpenBitFun-Installer/src-tauri/src/installer/commands.rs; then
args+=(--windows-installer-dir release-manual-assets)
elif [[ $? != 1 ]]; then
exit 1
fi
node scripts/collect-tauri-updater-assets.mjs "${args[@]}"
node scripts/generate-linux-binaries-manifest.mjs --assets-dir linux-release-assets \
--version "$RELEASE_VERSION" --tag "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \
--out linux-release-assets/linux-binaries-v1.json
Expand Down
31 changes: 30 additions & 1 deletion docs/development/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ its tag automatically. Release creation and editing use GitHub CLI without
`--target`: sending `target_commitish` for workflow-changing commits can require
`workflows:write`, even for an existing tag. Asset upload precedes publication.

## Release infrastructure after the repository transfer

The official repository is `openBitFun/OpenBitFun` (GitHub repository ID
`1147866277`). Official Actions use the repository ID for release-only settings
such as the protected `main` history check and download mirror webhook. New
official Relay images go to `ghcr.io/openbitfun/openbitfun-relay-server`.

Set the organization package to public and give `openBitFun/OpenBitFun` write
access under the package's **Manage Actions access** settings. If a first push
creates the package as private, change its visibility and rerun the failed image
job. The image job checks anonymous access before the Desktop Release can
publish. Keep the public `gcwing/openbitfun-relay-server` package and its existing
digests available for older releases.

Deploy the updated mirror script from the official checkout before the first
release in the organization namespace. The mirror accepts signed descriptors
from either official namespace and copies their bytes and signatures unchanged.
Finalizing trusted Actions artifacts from before the transfer can also reuse
their original signed descriptor and the Windows updater format built by the
original source. The standalone Relay deploy script tries the
organization package first and can retry the same tag or digest in the old
namespace. For a default `latest` request it resolves the current GitHub release
tag before retrying the old namespace.

For the optional Homebrew notification from `CLI Package`, set
`HOMEBREW_TAP_REPOSITORY` to the tap's `owner/repository` and configure
`HOMEBREW_TAP_DISPATCH_TOKEN`. Existing token-only configurations still target
`GCWing/homebrew-tap`.

## Recovering one macOS package

For an existing immutable tag, dispatch `Desktop Package` from the current
Expand Down Expand Up @@ -191,7 +220,7 @@ The selected ref must resolve to a commit in the protected `main` history. The
workflow pins that SHA before dispatching platform jobs and rejects an existing
release tag if it points somewhere else. Configure the signing secrets and the
public beta approval policy so untrusted pull-request code cannot access them.
This protected-history requirement applies to the canonical `GCWing/OpenBitFun`
This protected-history requirement applies to the canonical `openBitFun/OpenBitFun`
repository; forks may run packaging from their own test branches. A fork beta
uses that fork's `channel-v1-beta` release as both updater origins, so it cannot
silently consume or mutate the canonical beta channel.
Expand Down
Loading
Loading