Extend GoodBuddy Agent packaging, installation, SSH attachment, Runtime lifecycle, and Desktop package management to macOS arm64. Linux x64, Linux arm64, and Darwin arm64 share the existing signed catalog. Preserve Ask read-only behavior and Execute account permissions, and stop detached tool descendants during Mac cancellation. Upgrade Desktop before publishing the first mixed-platform catalog because older Linux-only readers reject Darwin entries. Intel Macs are not supported; no launchd service or administrator access is required. This commit does not publish a release. Validation: 3520 tests passed, 57 skipped; typecheck, lint, and development build passed. Real Mac installation, Ask/Execute, cancellation and reconnect passed; final Linux installation, Ask and reconnect passed. Nine real model requests were recorded. Release note: 支持将 Apple Silicon Mac 用作远程项目 Host,可安装 macOS arm64 Agent 并使用 Ask、Execute 和断线重连;与 Linux 共用一个包目录,发布混合目录前需升级 Desktop。
624 lines
25 KiB
YAML
624 lines
25 KiB
YAML
name: GoodBuddy Agent Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'agent-v*'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: agent-release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
authorize:
|
|
name: Authorize Agent release
|
|
if: >-
|
|
github.ref_type == 'tag' &&
|
|
startsWith(github.ref_name, 'agent-v')
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
outputs:
|
|
authorized: ${{ steps.policy.outputs.authorized }}
|
|
version: ${{ steps.policy.outputs.version }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Verify immutable Agent release tag
|
|
id: policy
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
version="$(node -p "require('./agent-runtime-lock.json').agentVersion")"
|
|
expected="agent-v$version"
|
|
test "$GITHUB_REF_NAME" = "$expected"
|
|
test "$(git cat-file -t "refs/tags/$GITHUB_REF_NAME")" = "tag"
|
|
test "$(git rev-parse "refs/tags/$GITHUB_REF_NAME^{commit}")" = "$GITHUB_SHA"
|
|
git fetch --no-tags origin main
|
|
git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main
|
|
echo "authorized=true" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
build:
|
|
name: Build ${{ matrix.platform }} ${{ matrix.arch }} compound package
|
|
needs: authorize
|
|
if: needs.authorize.outputs.authorized == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: linux
|
|
arch: x64
|
|
runner: ubuntu-24.04
|
|
- platform: linux
|
|
arch: arm64
|
|
runner: ubuntu-24.04-arm
|
|
- platform: darwin
|
|
arch: arm64
|
|
runner: macos-15
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 25
|
|
environment:
|
|
name: agent-signing
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: 24.19.0
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Verify native build target
|
|
run: node -e "if(process.platform !== '${{ matrix.platform }}' || process.arch !== '${{ matrix.arch }}') throw new Error('Native Agent target mismatch')"
|
|
|
|
- name: Resolve locked native inputs
|
|
id: inputs
|
|
shell: bash
|
|
env:
|
|
AGENT_ARCH: ${{ matrix.arch }}
|
|
AGENT_PLATFORM: ${{ matrix.platform }}
|
|
run: |
|
|
set -euo pipefail
|
|
node - <<'NODE'
|
|
const { appendFileSync } = require('node:fs')
|
|
const { createHash } = require('node:crypto')
|
|
const agent = require('./agent-runtime-lock.json')
|
|
const remote = require('./remote-runtime-lock.json')
|
|
const node = agent.node?.targets?.[`${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`]
|
|
const runtime = remote.runtimes?.opencode
|
|
const runtimeKey = process.env.AGENT_PLATFORM === 'linux'
|
|
? process.env.AGENT_ARCH : `${process.env.AGENT_PLATFORM}-${process.env.AGENT_ARCH}`
|
|
const opencode = runtime?.targets?.[runtimeKey]
|
|
if (
|
|
!node ||
|
|
!opencode ||
|
|
!/^[A-Za-z0-9._-]+\.tar\.gz$/u.test(node.archive) ||
|
|
!/^[a-f0-9]{64}$/u.test(node.sha256) ||
|
|
!/^[a-z0-9][a-z0-9._-]*$/u.test(opencode.package) ||
|
|
!/^sha512-[A-Za-z0-9+/]+={0,2}$/u.test(opencode.integrity)
|
|
) {
|
|
throw new Error('Locked native Agent package inputs are invalid')
|
|
}
|
|
const nodeUrl = new URL(node.archive, agent.node.source)
|
|
if (
|
|
nodeUrl.protocol !== 'https:' ||
|
|
nodeUrl.hostname !== 'nodejs.org' ||
|
|
nodeUrl.username ||
|
|
nodeUrl.password ||
|
|
nodeUrl.search ||
|
|
nodeUrl.hash
|
|
) {
|
|
throw new Error('Agent Node runtime must use the official HTTPS origin')
|
|
}
|
|
const opencodeArchive =
|
|
`${opencode.package}-${runtime.version}.tgz`
|
|
appendFileSync(
|
|
process.env.GITHUB_OUTPUT,
|
|
[
|
|
`node-archive=${node.archive}`,
|
|
`node-sha256=${node.sha256}`,
|
|
`node-url=${nodeUrl.href}`,
|
|
`opencode-package=${opencode.package}`,
|
|
`opencode-version=${runtime.version}`,
|
|
`opencode-integrity=${opencode.integrity}`,
|
|
`opencode-archive=${opencodeArchive}`,
|
|
`cache-key=${createHash('sha256')
|
|
.update(`${node.sha256}:${opencode.integrity}`)
|
|
.digest('hex')}`,
|
|
''
|
|
].join('\n')
|
|
)
|
|
NODE
|
|
|
|
- name: Cache verified native inputs
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: ${{ runner.temp }}/goodbuddy-agent-release-inputs
|
|
key: goodbuddy-agent-release-${{ matrix.platform }}-${{ matrix.arch }}-${{ steps.inputs.outputs.cache-key }}
|
|
|
|
- name: Acquire and verify native inputs
|
|
shell: bash
|
|
env:
|
|
NODE_ARCHIVE: ${{ steps.inputs.outputs.node-archive }}
|
|
NODE_SHA256: ${{ steps.inputs.outputs.node-sha256 }}
|
|
NODE_URL: ${{ steps.inputs.outputs.node-url }}
|
|
OPENCODE_ARCHIVE: ${{ steps.inputs.outputs.opencode-archive }}
|
|
OPENCODE_INTEGRITY: ${{ steps.inputs.outputs.opencode-integrity }}
|
|
OPENCODE_PACKAGE: ${{ steps.inputs.outputs.opencode-package }}
|
|
OPENCODE_VERSION: ${{ steps.inputs.outputs.opencode-version }}
|
|
run: |
|
|
set -euo pipefail
|
|
directory="$RUNNER_TEMP/goodbuddy-agent-release-inputs"
|
|
mkdir -p "$directory"
|
|
node_archive="$directory/$NODE_ARCHIVE"
|
|
if [[ ! -f "$node_archive" ]]; then
|
|
curl --proto '=https' --tlsv1.2 \
|
|
--fail --silent --show-error --location \
|
|
--retry 3 --retry-all-errors \
|
|
--connect-timeout 15 --max-time 300 \
|
|
--max-filesize 104857600 \
|
|
"$NODE_URL" --output "$node_archive.partial"
|
|
mv "$node_archive.partial" "$node_archive"
|
|
fi
|
|
node - "$node_archive" "$NODE_SHA256" <<'NODE'
|
|
const { createHash } = require('node:crypto')
|
|
const { readFileSync } = require('node:fs')
|
|
const [archive, expected] = process.argv.slice(2)
|
|
if (createHash('sha256').update(readFileSync(archive)).digest('hex') !== expected) {
|
|
throw new Error('Node runtime archive integrity mismatch')
|
|
}
|
|
NODE
|
|
|
|
opencode_archive="$directory/$OPENCODE_ARCHIVE"
|
|
if [[ ! -f "$opencode_archive" ]]; then
|
|
npm pack \
|
|
--ignore-scripts \
|
|
--registry https://registry.npmjs.org/ \
|
|
--pack-destination "$directory" \
|
|
"$OPENCODE_PACKAGE@$OPENCODE_VERSION"
|
|
fi
|
|
node - "$opencode_archive" "$OPENCODE_INTEGRITY" <<'NODE'
|
|
const { createHash } = require('node:crypto')
|
|
const { readFileSync, statSync } = require('node:fs')
|
|
const [archive, expected] = process.argv.slice(2)
|
|
const size = statSync(archive).size
|
|
if (size <= 0 || size > 536870912) {
|
|
throw new Error('OpenCode Runtime archive exceeds its safety limit')
|
|
}
|
|
const actual = `sha512-${createHash('sha512')
|
|
.update(readFileSync(archive))
|
|
.digest('base64')}`
|
|
if (actual !== expected) {
|
|
throw new Error('OpenCode Runtime archive integrity mismatch')
|
|
}
|
|
NODE
|
|
|
|
- name: Preflight production GoodBuddy signing identity
|
|
shell: bash
|
|
env:
|
|
GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }}
|
|
run: |
|
|
set -euo pipefail
|
|
node build/agent-bundle.cjs preflight
|
|
|
|
- name: Build production compound Agent package
|
|
shell: bash
|
|
env:
|
|
AGENT_ARCH: ${{ matrix.arch }}
|
|
AGENT_PLATFORM: ${{ matrix.platform }}
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }}
|
|
GOODBUDDY_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_SIGNING_PRIVATE_KEY }}
|
|
NODE_ARCHIVE: ${{ steps.inputs.outputs.node-archive }}
|
|
OPENCODE_ARCHIVE: ${{ steps.inputs.outputs.opencode-archive }}
|
|
run: |
|
|
set -euo pipefail
|
|
output="$RUNNER_TEMP/goodbuddy-agent-output"
|
|
mkdir -p "$output"
|
|
node build/agent-package.cjs build \
|
|
--platform "$AGENT_PLATFORM" \
|
|
--arch "$AGENT_ARCH" \
|
|
--minimum-desktop-version "$(node -p "require('./package.json').version")" \
|
|
--node-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$NODE_ARCHIVE" \
|
|
--runtime-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$OPENCODE_ARCHIVE" \
|
|
--output "$output/goodbuddy-agent-$AGENT_VERSION-$AGENT_PLATFORM-$AGENT_ARCH.gbagent" \
|
|
> "$output/result.json"
|
|
node - "$output/result.json" "$AGENT_ARCH" "$AGENT_VERSION" <<'NODE'
|
|
const { readFileSync } = require('node:fs')
|
|
const [resultPath, architecture, version] = process.argv.slice(2)
|
|
const result = JSON.parse(readFileSync(resultPath, 'utf8'))
|
|
if (
|
|
result.descriptor?.architecture !== architecture ||
|
|
result.descriptor?.platform !== process.env.AGENT_PLATFORM ||
|
|
result.descriptor?.version !== version ||
|
|
result.archive !==
|
|
`goodbuddy-agent-${version}-${process.env.AGENT_PLATFORM}-${architecture}.gbagent` ||
|
|
!/^[a-f0-9]{64}$/u.test(result.sha256)
|
|
) {
|
|
throw new Error('Production Agent package result is invalid')
|
|
}
|
|
NODE
|
|
|
|
- name: Upload verified compound package
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: goodbuddy-agent-package-${{ matrix.platform }}-${{ matrix.arch }}
|
|
path: ${{ runner.temp }}/goodbuddy-agent-output/goodbuddy-agent-*.gbagent
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 7
|
|
|
|
catalog:
|
|
name: Sign cumulative Agent catalog
|
|
needs:
|
|
- authorize
|
|
- build
|
|
if: needs.authorize.outputs.authorized == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
environment:
|
|
name: agent-signing
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: 24.19.0
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Download compound packages
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: goodbuddy-agent-package-*
|
|
path: dist/agent-release
|
|
merge-multiple: true
|
|
|
|
- name: Fetch previous signed catalog when available
|
|
id: previous
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
root="https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases"
|
|
current_catalog_status="$(
|
|
curl --silent --show-error \
|
|
--output dist/agent-release/previous.json \
|
|
--write-out '%{http_code}' \
|
|
"$root/v$AGENT_VERSION/agent-catalog.json"
|
|
)"
|
|
current_signature_status="$(
|
|
curl --silent --show-error \
|
|
--output dist/agent-release/previous.sig \
|
|
--write-out '%{http_code}' \
|
|
"$root/v$AGENT_VERSION/agent-catalog.sig"
|
|
)"
|
|
if [[ "$current_catalog_status" == 200 && "$current_signature_status" == 200 ]]; then
|
|
echo "arguments=--previous-catalog dist/agent-release/previous.json --previous-signature dist/agent-release/previous.sig" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
if [[ "$current_catalog_status" != 404 || "$current_signature_status" != 404 ]]; then
|
|
echo "Current Agent catalog objects are inconsistent: $current_catalog_status / $current_signature_status" >&2
|
|
exit 1
|
|
fi
|
|
rm -f dist/agent-release/previous.json dist/agent-release/previous.sig
|
|
pointer_status="$(
|
|
curl --silent --show-error \
|
|
--output dist/agent-release/previous-pointer.json \
|
|
--write-out '%{http_code}' \
|
|
"$root/latest.json"
|
|
)"
|
|
if [[ "$pointer_status" == 200 ]]; then
|
|
readarray -t paths < <(
|
|
node - dist/agent-release/previous-pointer.json <<'NODE'
|
|
const { readFileSync } = require('node:fs')
|
|
const pointer = JSON.parse(readFileSync(process.argv[2], 'utf8'))
|
|
if (
|
|
pointer?.formatVersion !== 1 ||
|
|
!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(pointer.version) ||
|
|
pointer.catalog !== `v${pointer.version}/agent-catalog.json` ||
|
|
pointer.signature !== `v${pointer.version}/agent-catalog.sig`
|
|
) {
|
|
throw new Error('Previous Agent catalog pointer is invalid')
|
|
}
|
|
process.stdout.write(`${pointer.catalog}\n${pointer.signature}\n`)
|
|
NODE
|
|
)
|
|
curl --fail --silent --show-error \
|
|
"$root/${paths[0]}" \
|
|
--output dist/agent-release/previous.json
|
|
curl --fail --silent --show-error \
|
|
"$root/${paths[1]}" \
|
|
--output dist/agent-release/previous.sig
|
|
echo "arguments=--previous-catalog dist/agent-release/previous.json --previous-signature dist/agent-release/previous.sig" >> "$GITHUB_OUTPUT"
|
|
elif [[ "$pointer_status" == 404 ]]; then
|
|
rm -f dist/agent-release/previous-pointer.json
|
|
echo "arguments=" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "Agent catalog pointer request failed: $pointer_status" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Sign cumulative Agent catalog
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
PREVIOUS_ARGUMENTS: ${{ steps.previous.outputs.arguments }}
|
|
GOODBUDDY_SIGNING_KEY_ID: ${{ vars.GOODBUDDY_SIGNING_KEY_ID }}
|
|
GOODBUDDY_SIGNING_PRIVATE_KEY: ${{ secrets.GOODBUDDY_SIGNING_PRIVATE_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
generated_at="$(git show -s --format=%cI "$GITHUB_SHA")"
|
|
read -r -a previous <<< "$PREVIOUS_ARGUMENTS"
|
|
node build/agent-catalog.cjs create \
|
|
--x64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-linux-x64.gbagent" \
|
|
--arm64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-linux-arm64.gbagent" \
|
|
--darwin-arm64-package "dist/agent-release/goodbuddy-agent-$AGENT_VERSION-darwin-arm64.gbagent" \
|
|
"${previous[@]}" \
|
|
--catalog dist/agent-release/agent-catalog.json \
|
|
--signature dist/agent-release/agent-catalog.sig \
|
|
--generated-at "$generated_at"
|
|
node build/agent-catalog.cjs verify \
|
|
--catalog dist/agent-release/agent-catalog.json \
|
|
--signature dist/agent-release/agent-catalog.sig
|
|
|
|
- name: Upload signed release set
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: goodbuddy-agent-release-${{ needs.authorize.outputs.version }}
|
|
path: |
|
|
dist/agent-release/goodbuddy-agent-*.gbagent
|
|
dist/agent-release/agent-catalog.json
|
|
dist/agent-release/agent-catalog.sig
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 7
|
|
|
|
publish:
|
|
name: Publish Agent to GitHub and OSS
|
|
needs:
|
|
- authorize
|
|
- catalog
|
|
if: needs.authorize.outputs.authorized == 'true'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 25
|
|
environment:
|
|
name: aliyun-oss-release
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
id-token: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: 24.19.0
|
|
|
|
- name: Install verification dependencies
|
|
run: npm ci
|
|
|
|
- name: Download signed release set
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: goodbuddy-agent-release-${{ needs.authorize.outputs.version }}
|
|
path: dist/agent-release
|
|
|
|
- name: Verify signed release set
|
|
run: |
|
|
node build/agent-catalog.cjs verify \
|
|
--catalog dist/agent-release/agent-catalog.json \
|
|
--signature dist/agent-release/agent-catalog.sig
|
|
|
|
- name: Verify OSS release configuration
|
|
shell: bash
|
|
env:
|
|
OSS_BUCKET: ${{ vars.ALIYUN_OSS_BUCKET }}
|
|
OSS_ENDPOINT: ${{ vars.ALIYUN_OSS_ENDPOINT }}
|
|
OIDC_PROVIDER_ARN: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
|
|
ROLE_ARN: ${{ vars.ALIYUN_ROLE_ARN }}
|
|
run: |
|
|
set -euo pipefail
|
|
test "$OSS_BUCKET" = "goodbuddy"
|
|
test "$OSS_ENDPOINT" = "https://oss-cn-beijing.aliyuncs.com"
|
|
case "$OIDC_PROVIDER_ARN" in
|
|
acs:ram::*:oidc-provider/*) ;;
|
|
*) echo "OIDC Provider ARN 无效" >&2; exit 1 ;;
|
|
esac
|
|
case "$ROLE_ARN" in
|
|
acs:ram::*:role/*) ;;
|
|
*) echo "RAM Role ARN 无效" >&2; exit 1 ;;
|
|
esac
|
|
|
|
- name: Authenticate to Alibaba Cloud
|
|
uses: aliyun/configure-aliyun-credentials-action@v1
|
|
with:
|
|
role-to-assume: ${{ vars.ALIYUN_ROLE_ARN }}
|
|
oidc-provider-arn: ${{ vars.ALIYUN_OIDC_PROVIDER_ARN }}
|
|
role-session-name: goodbuddy-agent-${{ github.run_id }}
|
|
role-session-expiration: 3600
|
|
audience: sts.aliyuncs.com
|
|
|
|
- name: Install ossutil
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
version="2.3.0"
|
|
archive="$RUNNER_TEMP/ossutil.zip"
|
|
directory="$RUNNER_TEMP/ossutil"
|
|
curl --fail --silent --show-error --location \
|
|
"https://gosspublic.alicdn.com/ossutil/v2/$version/ossutil-$version-linux-amd64.zip" \
|
|
--output "$archive"
|
|
mkdir "$directory"
|
|
unzip -q "$archive" -d "$directory"
|
|
binary="$(find "$directory" -type f -name ossutil -print -quit)"
|
|
test -n "$binary"
|
|
chmod +x "$binary"
|
|
echo "$(dirname "$binary")" >> "$GITHUB_PATH"
|
|
|
|
- name: Upload immutable Agent release to OSS
|
|
id: oss
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
OSS_BUCKET: ${{ vars.ALIYUN_OSS_BUCKET }}
|
|
OSS_ENDPOINT: ${{ vars.ALIYUN_OSS_ENDPOINT }}
|
|
run: |
|
|
set -euo pipefail
|
|
export OSS_ACCESS_KEY_ID="$ALIBABA_CLOUD_ACCESS_KEY_ID"
|
|
export OSS_ACCESS_KEY_SECRET="$ALIBABA_CLOUD_ACCESS_KEY_SECRET"
|
|
export OSS_SESSION_TOKEN="$ALIBABA_CLOUD_SECURITY_TOKEN"
|
|
endpoint_host="${OSS_ENDPOINT#https://}"
|
|
region="${endpoint_host#oss-}"
|
|
region="${region%.aliyuncs.com}"
|
|
test "$region" = "cn-beijing"
|
|
for file in dist/agent-release/*; do
|
|
name="$(basename "$file")"
|
|
ossutil cp "$file" \
|
|
"oss://${OSS_BUCKET}/agent-releases/v${AGENT_VERSION}/${name}" \
|
|
--endpoint "$OSS_ENDPOINT" \
|
|
--region "$region" \
|
|
--update
|
|
done
|
|
echo "region=$region" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Verify public immutable OSS packages
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
root="https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases/v$AGENT_VERSION"
|
|
for target in linux-x64 linux-arm64 darwin-arm64; do
|
|
name="goodbuddy-agent-$AGENT_VERSION-$target.gbagent"
|
|
curl --fail --silent --show-error --location \
|
|
"$root/$name" --output "$RUNNER_TEMP/$name"
|
|
cmp "dist/agent-release/$name" "$RUNNER_TEMP/$name"
|
|
done
|
|
for name in agent-catalog.json agent-catalog.sig; do
|
|
curl --fail --silent --show-error --location \
|
|
"$root/$name" --output "$RUNNER_TEMP/immutable-$name"
|
|
cmp "dist/agent-release/$name" \
|
|
"$RUNNER_TEMP/immutable-$name"
|
|
done
|
|
|
|
- name: Publish non-Latest GitHub Agent release
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag="agent-v$AGENT_VERSION"
|
|
title="GoodBuddy Agent $AGENT_VERSION"
|
|
notes="$RUNNER_TEMP/agent-release-notes.md"
|
|
cat > "$notes" <<EOF
|
|
GoodBuddy Agent $AGENT_VERSION
|
|
|
|
此版本供 GoodBuddy 的“设置 > 平台功能 > 远程项目”手动下载。软件包包含 Agent、固定 Node 和 GoodBuddy 适配的远端 OpenCode Runtime。
|
|
|
|
This release is downloaded manually from GoodBuddy Settings > Platform Features > Remote Projects. Each package contains the Agent, pinned Node, and the GoodBuddy-adapted remote OpenCode Runtime.
|
|
EOF
|
|
if gh release view "$tag" >/dev/null 2>&1; then
|
|
is_draft="$(gh release view "$tag" --json isDraft --jq .isDraft)"
|
|
if [[ "$is_draft" == true ]]; then
|
|
gh release edit "$tag" \
|
|
--title "$title" \
|
|
--notes-file "$notes" \
|
|
--latest=false
|
|
gh release upload "$tag" dist/agent-release/* --clobber
|
|
gh release edit "$tag" --draft=false --latest=false
|
|
else
|
|
verify="$RUNNER_TEMP/github-agent-release"
|
|
mkdir "$verify"
|
|
gh release download "$tag" --dir "$verify"
|
|
for file in dist/agent-release/*; do
|
|
cmp "$file" "$verify/$(basename "$file")"
|
|
done
|
|
fi
|
|
else
|
|
gh release create "$tag" \
|
|
--draft \
|
|
--verify-tag \
|
|
--title "$title" \
|
|
--notes-file "$notes" \
|
|
--latest=false
|
|
gh release upload "$tag" dist/agent-release/* --clobber
|
|
gh release edit "$tag" --draft=false --latest=false
|
|
fi
|
|
test "$(gh api repos/mesalogo/goodbuddy/releases/latest --jq .tag_name)" != "$tag"
|
|
|
|
- name: Point OSS Agent catalog to published release
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
OSS_BUCKET: ${{ vars.ALIYUN_OSS_BUCKET }}
|
|
OSS_ENDPOINT: ${{ vars.ALIYUN_OSS_ENDPOINT }}
|
|
OSS_REGION: ${{ steps.oss.outputs.region }}
|
|
run: |
|
|
set -euo pipefail
|
|
export OSS_ACCESS_KEY_ID="$ALIBABA_CLOUD_ACCESS_KEY_ID"
|
|
export OSS_ACCESS_KEY_SECRET="$ALIBABA_CLOUD_ACCESS_KEY_SECRET"
|
|
export OSS_SESSION_TOKEN="$ALIBABA_CLOUD_SECURITY_TOKEN"
|
|
node - "$AGENT_VERSION" > dist/agent-release/latest.json <<'NODE'
|
|
const version = process.argv[2]
|
|
process.stdout.write(`${JSON.stringify({
|
|
formatVersion: 1,
|
|
version,
|
|
catalog: `v${version}/agent-catalog.json`,
|
|
signature: `v${version}/agent-catalog.sig`
|
|
}, null, 2)}\n`)
|
|
NODE
|
|
ossutil cp dist/agent-release/latest.json \
|
|
"oss://${OSS_BUCKET}/agent-releases/latest.json" \
|
|
--endpoint "$OSS_ENDPOINT" \
|
|
--region "$OSS_REGION" \
|
|
--force
|
|
|
|
- name: Verify public Agent catalogs
|
|
shell: bash
|
|
env:
|
|
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
mirror="https://goodbuddy.oss-cn-beijing.aliyuncs.com/agent-releases"
|
|
github="https://github.com/mesalogo/goodbuddy/releases/download/agent-v$AGENT_VERSION"
|
|
curl --fail --silent --show-error --location \
|
|
"$mirror/latest.json" --output "$RUNNER_TEMP/mirror-pointer.json"
|
|
curl --fail --silent --show-error --location \
|
|
"$mirror/v$AGENT_VERSION/agent-catalog.json" \
|
|
--output "$RUNNER_TEMP/mirror.json"
|
|
curl --fail --silent --show-error --location \
|
|
"$mirror/v$AGENT_VERSION/agent-catalog.sig" \
|
|
--output "$RUNNER_TEMP/mirror.sig"
|
|
curl --fail --silent --show-error --location \
|
|
"$github/agent-catalog.json" --output "$RUNNER_TEMP/github.json"
|
|
curl --fail --silent --show-error --location \
|
|
"$github/agent-catalog.sig" --output "$RUNNER_TEMP/github.sig"
|
|
cmp dist/agent-release/latest.json "$RUNNER_TEMP/mirror-pointer.json"
|
|
cmp dist/agent-release/agent-catalog.json "$RUNNER_TEMP/mirror.json"
|
|
cmp dist/agent-release/agent-catalog.sig "$RUNNER_TEMP/mirror.sig"
|
|
cmp dist/agent-release/agent-catalog.json "$RUNNER_TEMP/github.json"
|
|
cmp dist/agent-release/agent-catalog.sig "$RUNNER_TEMP/github.sig"
|