Skip to content

v0.0.173

v0.0.173 #205

Workflow file for this run

name: Build & Release
run-name: ${{ github.ref_name }}
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: build-${{ github.ref_name }}
cancel-in-progress: true
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
IS_BETA: ${{ contains(github.ref_name, 'beta') }}
jobs:
# ── Releases are created first to avoid race condition ──
create-release:
runs-on: ubuntu-latest
name: Create Release
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
TITLE="You Claw v${{ steps.version.outputs.VERSION }}"
if [ "${IS_BETA}" = "true" ]; then
gh release create "$TAG" \
--title "$TITLE" \
--notes "" \
--draft \
--prerelease \
|| echo "Release already exists"
else
gh release create "$TAG" \
--title "$TITLE" \
--notes "" \
--latest \
|| echo "Release already exists"
fi
# ── macOS builds ──
build-mac:
needs: create-release
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64
runner: macos-latest
bun_target: bun-darwin-arm64
sidecar_suffix: aarch64-apple-darwin
tauri_args: "--target aarch64-apple-darwin"
platform_label: "macOS Apple Silicon"
- arch: x86_64
runner: macos-latest
bun_target: bun-darwin-x64
sidecar_suffix: x86_64-apple-darwin
rust_targets: x86_64-apple-darwin
tauri_args: "--target x86_64-apple-darwin"
platform_label: "macOS Intel"
runs-on: ${{ matrix.runner }}
name: Build ${{ matrix.platform_label }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup build environment
id: setup
uses: ./.github/actions/setup-build-env
with:
sidecar-suffix: ${{ matrix.sidecar_suffix }}
rust-targets: ${{ matrix.rust_targets || '' }}
oss-bucket: ${{ secrets.OSS_BUCKET }}
oss-endpoint: ${{ secrets.OSS_ENDPOINT }}
cdn-base-url: ${{ secrets.CDN_BASE_URL }}
- name: Build sidecar
env:
YOUCLAW_WEBSITE_URL: ${{ secrets.YOUCLAW_WEBSITE_URL }}
YOUCLAW_API_URL: ${{ secrets.YOUCLAW_API_URL }}
YOUCLAW_BUILTIN_API_URL: ${{ secrets.YOUCLAW_BUILTIN_API_URL }}
YOUCLAW_BUILTIN_AUTH_TOKEN: ${{ secrets.YOUCLAW_BUILTIN_AUTH_TOKEN }}
run: |
mkdir -p src-tauri/bin
node -e "
const e = process.env;
const c = {};
for (const k of ['YOUCLAW_WEBSITE_URL','YOUCLAW_API_URL','YOUCLAW_BUILTIN_API_URL','YOUCLAW_BUILTIN_AUTH_TOKEN']) {
if (e[k]) c[k] = e[k];
}
require('fs').writeFileSync('src/config/build-constants.ts',
'export const BUILD_CONSTANTS: Record<string, string> = ' + JSON.stringify(c, null, 2) + '\n');
console.log('build-constants.ts:', Object.keys(c));
"
bun build --compile --target=${{ matrix.bun_target }} src/index.ts --outfile src-tauri/bin/youclaw-server-${{ matrix.sidecar_suffix }}
- name: Import macOS certificates
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.MAC_CERTS }}
p12-password: ${{ secrets.MAC_CERTS_PASSWORD }}
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.MAC_CERTS }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
with:
tagName: ${{ github.ref_name }}
releaseName: "You Claw v${{ steps.setup.outputs.version }}"
releaseBody: ""
releaseDraft: ${{ env.IS_BETA == 'true' }}
prerelease: ${{ env.IS_BETA == 'true' }}
tauriScript: bun tauri
args: ${{ matrix.tauri_args }}
includeUpdaterJson: ${{ env.IS_BETA != 'true' }}
- name: Notify Feishu
if: always()
uses: ./.github/actions/notify-feishu
with:
webhook-url: ${{ secrets.FEISHU_WEBHOOK }}
title: "You Claw v${{ steps.setup.outputs.version }} · ${{ matrix.platform_label }}"
run-number: ${{ github.run_number }}
run-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
status: ${{ job.status }}
# ── Windows build ──
build-windows:
needs: create-release
runs-on: windows-latest
name: Build Windows x64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup build environment
id: setup
uses: ./.github/actions/setup-build-env
with:
sidecar-suffix: x86_64-pc-windows-msvc
oss-bucket: ${{ secrets.OSS_BUCKET }}
oss-endpoint: ${{ secrets.OSS_ENDPOINT }}
cdn-base-url: ${{ secrets.CDN_BASE_URL }}
- name: Build sidecar
shell: bash
env:
YOUCLAW_WEBSITE_URL: ${{ secrets.YOUCLAW_WEBSITE_URL }}
YOUCLAW_API_URL: ${{ secrets.YOUCLAW_API_URL }}
YOUCLAW_BUILTIN_API_URL: ${{ secrets.YOUCLAW_BUILTIN_API_URL }}
YOUCLAW_BUILTIN_AUTH_TOKEN: ${{ secrets.YOUCLAW_BUILTIN_AUTH_TOKEN }}
run: |
mkdir -p src-tauri/bin
node -e "
const e = process.env;
const c = {};
for (const k of ['YOUCLAW_WEBSITE_URL','YOUCLAW_API_URL','YOUCLAW_BUILTIN_API_URL','YOUCLAW_BUILTIN_AUTH_TOKEN']) {
if (e[k]) c[k] = e[k];
}
require('fs').writeFileSync('src/config/build-constants.ts',
'export const BUILD_CONSTANTS: Record<string, string> = ' + JSON.stringify(c, null, 2) + '\n');
console.log('build-constants.ts:', Object.keys(c));
"
bun build --compile --target=bun-windows-x64 src/index.ts --outfile src-tauri/bin/youclaw-server-x86_64-pc-windows-msvc.exe
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
with:
tagName: ${{ github.ref_name }}
releaseName: "You Claw v${{ steps.setup.outputs.version }}"
releaseBody: ""
releaseDraft: ${{ env.IS_BETA == 'true' }}
prerelease: ${{ env.IS_BETA == 'true' }}
tauriScript: bun tauri
includeUpdaterJson: ${{ env.IS_BETA != 'true' }}
- name: Notify Feishu
if: always()
uses: ./.github/actions/notify-feishu
with:
webhook-url: ${{ secrets.FEISHU_WEBHOOK }}
title: "You Claw v${{ steps.setup.outputs.version }} · Windows x64"
run-number: ${{ github.run_number }}
run-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
status: ${{ job.status }}
# ── Windows code signing (self-hosted runner with USB token) ──
sign-windows:
if: ${{ !contains(github.ref_name, 'beta') }}
needs: build-windows
runs-on: self-hosted
name: Sign Windows exe
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract version
id: version
shell: bash
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Download Windows exe from release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
REPO="${{ github.repository }}"
mkdir -p unsigned
# Wait for release to be created
for i in $(seq 1 30); do
if gh release view "$TAG" -R "$REPO" > /dev/null 2>&1; then
echo "Release found"
break
fi
echo "Waiting for release... ($i/30)"
sleep 10
done
gh release download "$TAG" -R "$REPO" --dir unsigned --pattern "*.exe" --pattern "*.exe.sig" --pattern "*.nsis.zip" --pattern "*.nsis.zip.sig"
- name: Sign exe with USB token
shell: bash
run: |
EXE_PATH=$(ls unsigned/*.exe | head -1)
echo "Signing: $EXE_PATH"
sign.sh "$EXE_PATH"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Tauri CLI for re-signing
shell: bash
run: npm install -g @tauri-apps/cli@latest
- name: Regenerate Tauri updater signatures for signed exe
shell: bash
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ""
run: |
EXE_PATH=$(ls unsigned/*.exe | head -1)
# The exe was modified by code signing, so regenerate its Tauri updater signature
echo "Regenerating .exe.sig for code-signed exe..."
npx tauri signer sign "$EXE_PATH"
- name: Upload signed artifacts to release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
REPO="${{ github.repository }}"
for f in unsigned/*.exe unsigned/*.exe.sig unsigned/*.nsis.zip unsigned/*.nsis.zip.sig; do
[ ! -f "$f" ] && continue
echo "Uploading: $(basename "$f")"
gh release upload "$TAG" "$f" -R "$REPO" --clobber
done
- name: Notify Feishu
if: always()
uses: ./.github/actions/notify-feishu
with:
webhook-url: ${{ secrets.FEISHU_WEBHOOK }}
title: "You Claw v${{ steps.version.outputs.VERSION }} · Windows Signing"
run-number: ${{ github.run_number }}
run-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
status: ${{ job.status }}
- name: Cleanup
if: always()
shell: bash
run: rm -rf unsigned
# ── Linux build ── [temporarily disabled]
# To enable: 1) set if to true 2) add build-linux to updater job's needs
# 3) add build-linux.result == 'success' to updater job's if condition
build-linux:
needs: create-release
if: ${{ false }}
runs-on: ubuntu-22.04
name: Build Linux x64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup build environment
id: setup
uses: ./.github/actions/setup-build-env
with:
sidecar-suffix: x86_64-unknown-linux-gnu
oss-bucket: ${{ secrets.OSS_BUCKET }}
oss-endpoint: ${{ secrets.OSS_ENDPOINT }}
cdn-base-url: ${{ secrets.CDN_BASE_URL }}
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Build sidecar
env:
YOUCLAW_WEBSITE_URL: ${{ secrets.YOUCLAW_WEBSITE_URL }}
YOUCLAW_API_URL: ${{ secrets.YOUCLAW_API_URL }}
YOUCLAW_BUILTIN_API_URL: ${{ secrets.YOUCLAW_BUILTIN_API_URL }}
YOUCLAW_BUILTIN_AUTH_TOKEN: ${{ secrets.YOUCLAW_BUILTIN_AUTH_TOKEN }}
run: |
mkdir -p src-tauri/bin
node -e "
const e = process.env;
const c = {};
for (const k of ['YOUCLAW_WEBSITE_URL','YOUCLAW_API_URL','YOUCLAW_BUILTIN_API_URL','YOUCLAW_BUILTIN_AUTH_TOKEN']) {
if (e[k]) c[k] = e[k];
}
require('fs').writeFileSync('src/config/build-constants.ts',
'export const BUILD_CONSTANTS: Record<string, string> = ' + JSON.stringify(c, null, 2) + '\n');
console.log('build-constants.ts:', Object.keys(c));
"
bun build --compile --target=bun-linux-x64 src/index.ts --outfile src-tauri/bin/youclaw-server-x86_64-unknown-linux-gnu
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
with:
tagName: ${{ github.ref_name }}
releaseName: "You Claw v${{ steps.setup.outputs.version }}"
releaseBody: ""
releaseDraft: false
prerelease: false
tauriScript: bun tauri
includeUpdaterJson: true
- name: Notify Feishu
if: always()
uses: ./.github/actions/notify-feishu
with:
webhook-url: ${{ secrets.FEISHU_WEBHOOK }}
title: "You Claw v${{ steps.setup.outputs.version }} · Linux x64"
run-number: ${{ github.run_number }}
run-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
status: ${{ job.status }}
# ── Upload to OSS & generate updater manifest ──
updater:
if: ${{ !contains(github.ref_name, 'beta') && always() && (needs.build-mac.result == 'success' && needs.sign-windows.result == 'success') }}
needs: [build-mac, sign-windows]
runs-on: ubuntu-latest
name: Upload to OSS & generate latest.json
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Install ossutil
run: |
curl -fsSL https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash
- name: Configure ossutil
env:
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
run: |
ossutil config \
-e "$OSS_ENDPOINT" \
-i "$OSS_ACCESS_KEY_ID" \
-k "$OSS_ACCESS_KEY_SECRET"
- name: Download release assets & upload to OSS
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
OSS_BUCKET: ${{ secrets.OSS_BUCKET }}
CDN_BASE_URL: ${{ secrets.CDN_BASE_URL }}
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
TAG="${{ github.ref_name }}"
REPO="${{ github.repository }}"
OSS_BASE="oss://${OSS_BUCKET}/youclaw/releases/${TAG}"
CDN_BASE="${CDN_BASE_URL}/releases/${TAG}"
mkdir -p assets
gh release download "$TAG" -R "$REPO" --dir assets
echo "=== Downloaded assets ==="
ls -la assets/
# Detect beta release
IS_BETA=false
[[ "$TAG" == *beta* ]] && IS_BETA=true
echo "IS_BETA=${IS_BETA}"
# Upload assets to OSS (versioned dir; latest dir only for stable releases)
for f in assets/*; do
[ ! -f "$f" ] && continue
NAME=$(basename "$f")
# Skip tauri-action's latest.json, we generate our own
[ "$NAME" = "latest.json" ] && continue
echo "Uploading: $NAME"
ossutil cp "$f" "${OSS_BASE}/${NAME}" -f
# Strip version from filename for stable download link
if [ "$IS_BETA" = "false" ]; then
LATEST_NAME=$(echo "$NAME" | sed "s/_${VERSION}//")
ossutil cp "$f" "oss://${OSS_BUCKET}/youclaw/releases/latest/${LATEST_NAME}" -f
fi
done
echo "=== All .sig files ==="
ls -la assets/*.sig 2>/dev/null || echo "No .sig files found"
SIG_COUNT=$(ls assets/*.sig 2>/dev/null | wc -l)
echo "Found ${SIG_COUNT} signature files"
if [ "$SIG_COUNT" -eq 0 ]; then
echo "::error::No signature files found — updater manifest would be empty"
exit 1
fi
# Build latest.json (version without v prefix, as Tauri updater expects)
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
PLATFORMS='{}'
for f in assets/*.sig; do
[ ! -f "$f" ] && continue
SIG=$(cat "$f")
NAME=$(basename "$f")
# Derive the actual asset filename by stripping .sig suffix
ASSET_NAME="${NAME%.sig}"
echo "Processing sig: $NAME -> asset: $ASSET_NAME"
case "$NAME" in
*_aarch64.app.tar.gz.sig)
URL="${CDN_BASE}/${ASSET_NAME}"
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg sig "$SIG" --arg url "$URL" \
'. + {"darwin-aarch64": {"signature": $sig, "url": $url}, "darwin-aarch64-app": {"signature": $sig, "url": $url}}')
;;
*_x64.app.tar.gz.sig)
URL="${CDN_BASE}/${ASSET_NAME}"
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg sig "$SIG" --arg url "$URL" \
'. + {"darwin-x86_64": {"signature": $sig, "url": $url}, "darwin-x86_64-app": {"signature": $sig, "url": $url}}')
;;
*_x64-setup.exe.sig)
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg sig "$SIG" \
--arg url "${CDN_BASE}/${ASSET_NAME}" \
'. + {"windows-x86_64": {"signature": $sig, "url": $url}, "windows-x86_64-nsis": {"signature": $sig, "url": $url}}')
;;
*_x64_en-US.msi.sig)
PLATFORMS=$(echo "$PLATFORMS" | jq \
--arg sig "$SIG" \
--arg url "${CDN_BASE}/${ASSET_NAME}" \
'. + {"windows-x86_64-msi": {"signature": $sig, "url": $url}}')
;;
esac
done
PLATFORM_COUNT=$(echo "$PLATFORMS" | jq 'keys | length')
echo "Platforms in manifest: ${PLATFORM_COUNT}"
if [ "$PLATFORM_COUNT" -eq 0 ]; then
echo "::error::No platforms matched in latest.json — check sig file naming"
exit 1
fi
jq -n \
--arg version "$VERSION" \
--arg date "$DATE" \
--argjson platforms "$PLATFORMS" \
'{version: $version, notes: "", pub_date: $date, platforms: $platforms}' > latest.json
echo "=== latest.json ==="
cat latest.json
# Upload latest.json: beta only to versioned OSS path, stable to fixed path + GitHub Release
ossutil cp latest.json "${OSS_BASE}/latest.json" -f
if [ "$IS_BETA" = "false" ]; then
# Stable: overwrite the fixed updater endpoint and GitHub Release fallback
ossutil cp latest.json "oss://${OSS_BUCKET}/youclaw/releases/latest.json" -f
gh release upload "$TAG" latest.json -R "$REPO" --clobber
else
echo "Beta release — skipping latest.json promotion to stable endpoint"
fi
- name: Notify Feishu
if: always()
uses: ./.github/actions/notify-feishu
with:
webhook-url: ${{ secrets.FEISHU_WEBHOOK }}
title: "You Claw ${{ github.ref_name }} · OSS Upload"
run-number: ${{ github.run_number }}
run-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
status: ${{ job.status }}