426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
1858 lines
86 KiB
YAML
1858 lines
86 KiB
YAML
# Build & Release — Electrobun desktop app (signed/notarized on macOS, CEF on Windows/Linux)
|
||
# Produces installer artifacts plus platform-prefixed .tar.zst/*-update.json
|
||
# update channel files.
|
||
#
|
||
# Path notes for eliza-native port:
|
||
# - The Electrobun shell lives at `packages/app-core/platforms/electrobun/`
|
||
# in eliza (not `packages/app/electrobun/` — that directory only carries a
|
||
# .gitignore stub). All build artifacts, scripts, and signing helpers live
|
||
# under that platforms/electrobun/ path.
|
||
# - Helper scripts that did not exist in upstream eliza (ensure-electrobun-core.mjs,
|
||
# ensure-legacy-electrobun-compat.mjs, patch-release-check-pack-fallback.mjs)
|
||
# have been removed; the corresponding caching/preflight steps are dropped
|
||
# and surfaced as TODOs so the user can port them if needed.
|
||
#
|
||
# Required GitHub Secrets (macOS signing + notarization):
|
||
# CSC_LINK – base64-encoded Developer ID Application .p12
|
||
# CSC_KEY_PASSWORD – password for the .p12 certificate
|
||
# APPLE_ID – Apple ID email for notarization
|
||
# APPLE_APP_SPECIFIC_PASSWORD – app-specific password from appleid.apple.com
|
||
# APPLE_TEAM_ID – 10-char Apple Developer Team ID
|
||
#
|
||
# Configure on elizaOS/eliza repo settings:
|
||
# WINDOWS_SIGN_CERT_BASE64, WINDOWS_SIGN_CERT_PASSWORD, WINDOWS_SIGN_TIMESTAMP_URL
|
||
# ANTHROPIC_API_KEY (used by Windows packaged smoke test as the boot AI provider)
|
||
# ELIZAOS_CLOUD_API_KEY / ELIZACLOUD_API_KEY / ELIZAOS_CLOUD_BASE_URL (optional cloud regression)
|
||
#
|
||
# Optional release-host upload secrets:
|
||
# RELEASE_UPLOAD_KEY – SSH private key for releases server
|
||
# RELEASE_HOST_FINGERPRINT – pinned SSH host key entry for releases host
|
||
name: Build & Release (Electrobun)
|
||
|
||
on:
|
||
push:
|
||
tags:
|
||
- "v*"
|
||
workflow_call:
|
||
inputs:
|
||
tag:
|
||
description: "Release tag override (e.g. v2.0.0-beta.0)"
|
||
required: false
|
||
type: string
|
||
draft:
|
||
description: "Create the GitHub release as a draft"
|
||
required: false
|
||
type: boolean
|
||
default: false
|
||
publish_release:
|
||
description: "Create the GitHub release and upload updater files"
|
||
required: false
|
||
type: boolean
|
||
default: false
|
||
platform:
|
||
description: "Desktop platform matrix to build (all, windows, macos, linux)"
|
||
required: false
|
||
type: string
|
||
default: "all"
|
||
workflow_dispatch:
|
||
inputs:
|
||
tag:
|
||
description: "Release tag (e.g. v2.0.0-beta.0)"
|
||
required: false
|
||
type: string
|
||
draft:
|
||
description: "Create as draft release"
|
||
required: false
|
||
type: boolean
|
||
default: true
|
||
platform:
|
||
description: "Desktop platform matrix to build"
|
||
required: false
|
||
type: choice
|
||
default: "all"
|
||
options:
|
||
- all
|
||
- windows
|
||
- macos
|
||
- linux
|
||
|
||
concurrency:
|
||
group: release-electrobun-${{ github.ref }}
|
||
cancel-in-progress: false
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
env:
|
||
CI: "true"
|
||
BUN_VERSION: "canary" # Rust-rewrite canary runtime (repo-wide migration 2026-05-30). Electrobun bundle creation is Bun-version-sensitive — watch packaging on canary bumps.
|
||
|
||
jobs:
|
||
prepare:
|
||
name: Prepare Release
|
||
if: >-
|
||
${{
|
||
github.event_name != 'push' ||
|
||
(
|
||
!contains(github.ref_name, '-')
|
||
)
|
||
}}
|
||
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
|
||
outputs:
|
||
version: ${{ steps.version.outputs.version }}
|
||
tag: ${{ steps.version.outputs.tag }}
|
||
env: ${{ steps.version.outputs.env }}
|
||
source_sha: ${{ steps.version.outputs.source_sha }}
|
||
desktop_matrix: ${{ steps.desktop-matrix.outputs.desktop_matrix }}
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
with:
|
||
submodules: false
|
||
|
||
- name: Determine version + env
|
||
id: version
|
||
run: |
|
||
if [[ -n "${{ inputs.tag }}" ]]; then
|
||
TAG="${{ inputs.tag }}"
|
||
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||
TAG="${{ github.ref_name }}"
|
||
else
|
||
echo "Manual branch dispatches must provide inputs.tag; refusing to derive a release tag from package.json." >&2
|
||
exit 1
|
||
fi
|
||
VERSION="${TAG#v}"
|
||
# canary for pre-release tags, stable for everything else
|
||
if [[ "$VERSION" == *"-"* ]]; then
|
||
BUILD_ENV="canary"
|
||
else
|
||
BUILD_ENV="stable"
|
||
fi
|
||
{
|
||
echo "tag=$TAG"
|
||
echo "version=$VERSION"
|
||
echo "env=$BUILD_ENV"
|
||
echo "source_sha=$GITHUB_SHA"
|
||
} >> "$GITHUB_OUTPUT"
|
||
echo "Release: $VERSION (tag: $TAG, env: $BUILD_ENV, sha: $GITHUB_SHA)"
|
||
|
||
- name: Select desktop platform matrix
|
||
id: desktop-matrix
|
||
env:
|
||
RELEASE_PLATFORM: ${{ inputs.platform || 'all' }}
|
||
run: |
|
||
case "$RELEASE_PLATFORM" in
|
||
""|all)
|
||
printf '%s\n' '{"platform":[{"name":"macOS (Apple Silicon)","os":"macos","runner":"macos-14","artifact-name":"macos-arm64"},{"name":"macOS (Intel)","os":"macos","runner":"macos-15-intel","artifact-name":"macos-x64"},{"name":"Windows","os":"windows","runner":"${{ vars.RUNNER_WINDOWS || 'windows-2025' }}","artifact-name":"windows-x64"},{"name":"Linux","os":"linux","runner":"ubuntu-latest","artifact-name":"linux-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
|
||
;;
|
||
windows)
|
||
printf '%s\n' '{"platform":[{"name":"Windows","os":"windows","runner":"${{ vars.RUNNER_WINDOWS || 'windows-2025' }}","artifact-name":"windows-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
|
||
;;
|
||
macos)
|
||
printf '%s\n' '{"platform":[{"name":"macOS (Apple Silicon)","os":"macos","runner":"macos-14","artifact-name":"macos-arm64"},{"name":"macOS (Intel)","os":"macos","runner":"macos-15-intel","artifact-name":"macos-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
|
||
;;
|
||
linux)
|
||
printf '%s\n' '{"platform":[{"name":"Linux","os":"linux","runner":"ubuntu-latest","artifact-name":"linux-x64"}]}' > "$RUNNER_TEMP/desktop-matrix.json"
|
||
;;
|
||
*)
|
||
echo "::error::Unsupported desktop release platform: $RELEASE_PLATFORM"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
echo "desktop_matrix=$(tr -d '\n' < "$RUNNER_TEMP/desktop-matrix.json")" >> "$GITHUB_OUTPUT"
|
||
cat "$RUNNER_TEMP/desktop-matrix.json"
|
||
|
||
validate-release:
|
||
name: Validate Release Inputs
|
||
if: ${{ inputs.platform != 'windows' }}
|
||
needs: prepare
|
||
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
|
||
env:
|
||
NODE_NO_WARNINGS: "1"
|
||
defaults:
|
||
run:
|
||
shell: bash -euo pipefail {0}
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
with:
|
||
submodules: false
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||
with:
|
||
node-version: "24"
|
||
|
||
- name: Setup Bun workspace
|
||
uses: ./.github/actions/setup-bun-workspace
|
||
with:
|
||
bun-version: ${{ env.BUN_VERSION }}
|
||
install-command: bun install --ignore-scripts
|
||
install-native-deps: "true"
|
||
run-postinstall: "true"
|
||
init-submodules: "false"
|
||
|
||
# TODO: verify this script exists in eliza or port equivalent.
|
||
# In eliza this was `bun run test:regression-matrix:release`.
|
||
- name: Regression matrix contract
|
||
run: bun run test:regression-matrix:release
|
||
|
||
- name: Align version with release tag
|
||
run: |
|
||
node packages/app-core/scripts/align-electrobun-version.mjs
|
||
node <<'NODE'
|
||
const fs = require("node:fs");
|
||
const version = process.env.RELEASE_VERSION;
|
||
const packagePath = "packages/app-core/platforms/electrobun/package.json";
|
||
const configPath = "packages/app-core/platforms/electrobun/electrobun.config.ts";
|
||
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
||
pkg.version = version;
|
||
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
||
let config = fs.readFileSync(configPath, "utf8");
|
||
config = config.replace(/version:\s*"[^"]+"/, `version: "${version}"`);
|
||
fs.writeFileSync(configPath, config);
|
||
NODE
|
||
env:
|
||
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
|
||
|
||
- name: Ensure avatar assets
|
||
run: node packages/app-core/scripts/ensure-avatars.mjs
|
||
|
||
- name: Build core dist (server bundle)
|
||
run: |
|
||
node --input-type=module <<'NODE'
|
||
import fs from "node:fs";
|
||
import path from "node:path";
|
||
|
||
const root = process.cwd();
|
||
const hasRootTsdownEntry =
|
||
fs.existsSync(path.join(root, "tsdown.config.ts")) ||
|
||
fs.existsSync(path.join(root, "tsdown.config.mts")) ||
|
||
fs.existsSync(path.join(root, "tsdown.config.js")) ||
|
||
fs.existsSync(path.join(root, "src", "index.ts"));
|
||
|
||
if (hasRootTsdownEntry) {
|
||
const { spawnSync } = await import("node:child_process");
|
||
const result = spawnSync("bunx", ["tsdown", "--fail-on-warn", "false"], {
|
||
cwd: root,
|
||
stdio: "inherit",
|
||
shell: process.platform === "win32",
|
||
});
|
||
process.exit(result.status ?? 1);
|
||
}
|
||
|
||
const entryTarget = "../packages/app-core/src/entry.ts";
|
||
const entrySource = [
|
||
"// auto-generated by release-electrobun.yml",
|
||
"// Standalone elizaOS checkouts do not have a root tsdown entry.",
|
||
`export * from ${JSON.stringify(entryTarget)};`,
|
||
"",
|
||
].join("\n");
|
||
|
||
fs.mkdirSync(path.join(root, "dist"), { recursive: true });
|
||
fs.writeFileSync(path.join(root, "dist", "entry.js"), entrySource);
|
||
fs.writeFileSync(path.join(root, "dist", "index.js"), entrySource);
|
||
fs.writeFileSync(path.join(root, "dist", "package.json"), '{"type":"module"}\n');
|
||
NODE
|
||
node --import tsx packages/scripts/write-build-info.ts
|
||
|
||
- name: Run heavy E2E regression suite
|
||
# TODO: verify this target exists in eliza root package.json.
|
||
run: bun run test:e2e:heavy
|
||
|
||
- name: Probe optional cloud live regression key
|
||
id: probe-cloud-live-key
|
||
env:
|
||
ELIZAOS_CLOUD_API_KEY: ${{ secrets.ELIZAOS_CLOUD_API_KEY != '' && secrets.ELIZAOS_CLOUD_API_KEY || secrets.ELIZACLOUD_API_KEY }}
|
||
run: |
|
||
if [ -n "${ELIZAOS_CLOUD_API_KEY:-}" ]; then
|
||
echo "available=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "::warning::No Eliza Cloud API key configured for release validation - skipping optional cloud live regression."
|
||
echo "available=false" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
|
||
- name: Run optional cloud live regression suite
|
||
if: steps.probe-cloud-live-key.outputs.available == 'true'
|
||
shell: bash
|
||
run: |
|
||
set -o pipefail
|
||
log_file="$(mktemp)"
|
||
|
||
if bun run test:live:cloud 2>&1 | tee "$log_file"; then
|
||
exit 0
|
||
fi
|
||
|
||
echo "::warning::Optional cloud live regression failed; release validation continues with deterministic build and packaging checks."
|
||
exit 0
|
||
env:
|
||
ELIZA_LIVE_TEST: "1"
|
||
ELIZAOS_CLOUD_API_KEY: ${{ secrets.ELIZAOS_CLOUD_API_KEY != '' && secrets.ELIZAOS_CLOUD_API_KEY || secrets.ELIZACLOUD_API_KEY }}
|
||
ELIZAOS_CLOUD_BASE_URL: ${{ secrets.ELIZAOS_CLOUD_BASE_URL }}
|
||
|
||
- name: Restore build metadata after test rebuilds
|
||
run: node --import tsx packages/scripts/write-build-info.ts
|
||
|
||
- name: Generate release validation manifests
|
||
run: |
|
||
node packages/app-core/scripts/write-homepage-release-data.mjs
|
||
node packages/app-core/scripts/generate-static-asset-manifest.mjs
|
||
|
||
- name: Release readiness checks
|
||
env:
|
||
ELIZA_RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
ELIZA_CDN_VALIDATION_REF: ${{ github.sha }}
|
||
ELIZA_VALIDATE_CDN: "1"
|
||
run: bun run release:check
|
||
|
||
build-browser-companions:
|
||
name: Build Agent Browser Bridge companions
|
||
if: ${{ inputs.platform == '' || inputs.platform == 'all' }}
|
||
needs: [prepare, validate-release]
|
||
runs-on: macos-15
|
||
timeout-minutes: 60
|
||
outputs:
|
||
packaged: ${{ steps.package-browser-bridge.outputs.packaged }}
|
||
defaults:
|
||
run:
|
||
shell: bash -euo pipefail {0}
|
||
env:
|
||
ELIZA_RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
with:
|
||
submodules: false
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||
with:
|
||
node-version: "24"
|
||
|
||
- name: Setup Bun workspace
|
||
uses: ./.github/actions/setup-bun-workspace
|
||
with:
|
||
bun-version: ${{ env.BUN_VERSION }}
|
||
install-command: bun install --ignore-scripts
|
||
install-native-deps: "false"
|
||
run-postinstall: "true"
|
||
init-submodules: "false"
|
||
|
||
- name: Package Agent Browser Bridge release bundles
|
||
id: package-browser-bridge
|
||
run: |
|
||
if bun run browser-bridge:package:release; then
|
||
echo "packaged=true" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "::warning::Agent Browser Bridge packaging failed; desktop release will continue without browser companion bundles."
|
||
echo "packaged=false" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
|
||
- name: Upload Agent Browser Bridge release artifacts
|
||
if: steps.package-browser-bridge.outputs.packaged == 'true'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: browser-bridge-store-bundles
|
||
path: |
|
||
packages/browser-extension/dist/artifacts/browser-bridge-chrome-v*.zip
|
||
packages/browser-extension/dist/artifacts/browser-bridge-safari-v*.zip
|
||
packages/browser-extension/dist/artifacts/browser-bridge-safari-project-v*.zip
|
||
packages/browser-extension/dist/artifacts/browser-bridge-release-manifest-v*.json
|
||
if-no-files-found: error
|
||
retention-days: 30
|
||
|
||
build:
|
||
name: Build ${{ matrix.platform.name }}
|
||
if: ${{ always() && needs.prepare.result == 'success' && (needs.validate-release.result == 'success' || needs.validate-release.result == 'skipped') }}
|
||
needs: [prepare, validate-release]
|
||
runs-on: ${{ matrix.platform.runner }}
|
||
timeout-minutes: 150
|
||
defaults:
|
||
run:
|
||
shell: bash -euo pipefail {0}
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(needs.prepare.outputs.desktop_matrix) }}
|
||
|
||
steps:
|
||
- name: Enable Git long paths (Windows)
|
||
if: runner.os == 'Windows'
|
||
shell: pwsh
|
||
run: |
|
||
$ErrorActionPreference = "Stop"
|
||
function Set-GitLongPaths([string]$Scope) {
|
||
& git config $Scope core.longpaths true
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "git config $Scope core.longpaths true failed with exit code $LASTEXITCODE"
|
||
}
|
||
}
|
||
|
||
try {
|
||
Set-GitLongPaths "--system"
|
||
Write-Host "Enabled Git long paths in system config."
|
||
} catch {
|
||
Write-Warning "System git config failed; falling back to --global. $($_.Exception.Message)"
|
||
Set-GitLongPaths "--global"
|
||
Write-Host "Enabled Git long paths in global config."
|
||
}
|
||
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
with:
|
||
submodules: false
|
||
|
||
- name: Validate public desktop signing inputs
|
||
env:
|
||
BUILD_ENV: ${{ needs.prepare.outputs.env }}
|
||
PUBLISH_RELEASE: ${{ inputs.publish_release || github.event_name == 'push' }}
|
||
PLATFORM_OS: ${{ matrix.platform.os }}
|
||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
|
||
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
|
||
run: |
|
||
if [[ "$PUBLISH_RELEASE" != "true" && "$BUILD_ENV" != "stable" ]]; then
|
||
echo "Non-published canary build; signing inputs are advisory."
|
||
exit 0
|
||
fi
|
||
|
||
missing=()
|
||
case "$PLATFORM_OS" in
|
||
macos)
|
||
[[ -z "$CSC_LINK" ]] && missing+=("CSC_LINK")
|
||
[[ -z "$CSC_KEY_PASSWORD" ]] && missing+=("CSC_KEY_PASSWORD")
|
||
[[ -z "$APPLE_ID" ]] && missing+=("APPLE_ID")
|
||
[[ -z "$APPLE_APP_SPECIFIC_PASSWORD" ]] && missing+=("APPLE_APP_SPECIFIC_PASSWORD")
|
||
[[ -z "$APPLE_TEAM_ID" ]] && missing+=("APPLE_TEAM_ID")
|
||
;;
|
||
windows)
|
||
[[ -z "$WINDOWS_SIGN_CERT_BASE64" ]] && missing+=("WINDOWS_SIGN_CERT_BASE64")
|
||
[[ -z "$WINDOWS_SIGN_CERT_PASSWORD" ]] && missing+=("WINDOWS_SIGN_CERT_PASSWORD")
|
||
[[ -z "$WINDOWS_SIGN_TIMESTAMP_URL" ]] && missing+=("WINDOWS_SIGN_TIMESTAMP_URL")
|
||
;;
|
||
esac
|
||
|
||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||
echo "::error::Missing public desktop signing inputs for $PLATFORM_OS: ${missing[*]}"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||
with:
|
||
node-version: "24"
|
||
|
||
- name: Setup Bun
|
||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||
with:
|
||
bun-version: ${{ env.BUN_VERSION }}
|
||
|
||
- name: Install Linux packaging tools
|
||
if: matrix.platform.os == 'linux'
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y --no-install-recommends \
|
||
curl \
|
||
desktop-file-utils \
|
||
file \
|
||
rpm
|
||
|
||
- name: Install root dependencies
|
||
# --ignore-scripts skips native dependency install scripts (e.g. @tensorflow/tfjs-node
|
||
# which has no pre-built binary for Node 22 on Windows and fails to compile).
|
||
# Workspace prepare scripts are NOT suppressed by --ignore-scripts in bun.
|
||
# macOS Intel: keep the x64 invocation explicit in case the runner image
|
||
# ships with dual-arch shims.
|
||
run: |
|
||
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
|
||
install_cmd=(arch -x86_64 bun install --ignore-scripts)
|
||
else
|
||
install_cmd=(bun install --ignore-scripts)
|
||
fi
|
||
|
||
for attempt in 1 2 3; do
|
||
if "${install_cmd[@]}"; then
|
||
exit 0
|
||
fi
|
||
|
||
if [ "$attempt" -eq 3 ]; then
|
||
echo "bun install failed after ${attempt} attempts" >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo "bun install failed on attempt ${attempt}; retrying in 15 seconds" >&2
|
||
sleep 15
|
||
done
|
||
|
||
- name: Run repository postinstall patches
|
||
run: bun run postinstall
|
||
|
||
- name: Align version with release tag
|
||
run: |
|
||
node packages/app-core/scripts/align-electrobun-version.mjs
|
||
node <<'NODE'
|
||
const fs = require("node:fs");
|
||
const version = process.env.RELEASE_VERSION;
|
||
const packagePath = "packages/app-core/platforms/electrobun/package.json";
|
||
const configPath = "packages/app-core/platforms/electrobun/electrobun.config.ts";
|
||
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
||
pkg.version = version;
|
||
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + "\n");
|
||
let config = fs.readFileSync(configPath, "utf8");
|
||
config = config.replace(/version:\s*"[^"]+"/, `version: "${version}"`);
|
||
fs.writeFileSync(configPath, config);
|
||
NODE
|
||
env:
|
||
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
|
||
|
||
- name: Generate i18n keyword data
|
||
run: |
|
||
node packages/shared/scripts/generate-keywords.mjs
|
||
test -f packages/shared/src/i18n/generated/validation-keyword-data.js
|
||
|
||
- name: Stage desktop bundle inputs
|
||
run: |
|
||
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
|
||
ELIZA_DESKTOP_COMMAND_PREFIX="arch -x86_64" node packages/app-core/scripts/desktop-build.mjs stage --variant=base
|
||
else
|
||
node packages/app-core/scripts/desktop-build.mjs stage --variant=base
|
||
fi
|
||
mkdir -p dist/node_modules/@elizaos/shared/src/i18n/generated
|
||
cp packages/shared/src/i18n/generated/validation-keyword-data.ts dist/node_modules/@elizaos/shared/src/i18n/generated/
|
||
cp packages/shared/src/i18n/generated/validation-keyword-data.js dist/node_modules/@elizaos/shared/src/i18n/generated/
|
||
test -f dist/node_modules/@elizaos/shared/src/i18n/generated/validation-keyword-data.js
|
||
|
||
- name: Validate Electrobun runtime copy contract
|
||
env:
|
||
ELIZA_ELECTROBUN_REPO_ROOT: ${{ github.workspace }}
|
||
run: |
|
||
test -f dist/entry.js
|
||
test -f package.json
|
||
test -f dist/node_modules/@elizaos/agent/package.json
|
||
test -f dist/node_modules/@elizaos/shared/src/i18n/generated/validation-keyword-data.js
|
||
node --import tsx <<'NODE'
|
||
const path = await import("node:path");
|
||
const configModule = await import("./packages/app-core/platforms/electrobun/electrobun.config.ts");
|
||
const config = configModule.default?.default ?? configModule.default;
|
||
const electrobunDir = path.resolve("packages/app-core/platforms/electrobun");
|
||
const copyMap = config.build?.copy ?? {};
|
||
const runtimeSource = Object.entries(copyMap).find(
|
||
([, destination]) => destination === "eliza-dist",
|
||
)?.[0];
|
||
if (!runtimeSource) {
|
||
throw new Error("Electrobun copy map does not include eliza-dist runtime destination");
|
||
}
|
||
const resolvedRuntimeSource = path.resolve(electrobunDir, runtimeSource);
|
||
const expectedRuntimeSource = path.resolve("dist");
|
||
if (resolvedRuntimeSource !== expectedRuntimeSource) {
|
||
throw new Error(
|
||
`Electrobun runtime source resolves to ${resolvedRuntimeSource}, expected ${expectedRuntimeSource}`,
|
||
);
|
||
}
|
||
NODE
|
||
|
||
# Import the Developer ID certificate into a temporary keychain.
|
||
# Requires CSC_LINK (base64-encoded .p12) + CSC_KEY_PASSWORD.
|
||
# The signing identity string is extracted and passed to Electrobun as
|
||
# ELECTROBUN_DEVELOPER_ID in the build step below.
|
||
- name: Set up macOS signing keychain
|
||
if: matrix.platform.os == 'macos'
|
||
id: macos-keychain
|
||
env:
|
||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||
run: |
|
||
if [ -z "$CSC_LINK" ]; then
|
||
echo "::warning::CSC_LINK not set — building unsigned (no codesign/notarize)"
|
||
echo "skip_codesign=1" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
|
||
KEYCHAIN_PATH="$RUNNER_TEMP/eliza-signing.keychain-db"
|
||
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
|
||
|
||
# Create a short-lived keychain for this build
|
||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
|
||
# Decode and import the .p12 certificate
|
||
CERT_PATH="$RUNNER_TEMP/cert.p12"
|
||
echo "$CSC_LINK" | base64 --decode > "$CERT_PATH"
|
||
security import "$CERT_PATH" \
|
||
-k "$KEYCHAIN_PATH" \
|
||
-P "$CSC_KEY_PASSWORD" \
|
||
-T /usr/bin/codesign \
|
||
-T /usr/bin/security
|
||
rm -f "$CERT_PATH"
|
||
|
||
# Add to the keychain search list (preserving login keychain)
|
||
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
|
||
# Allow codesign to access the key without a password prompt
|
||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s \
|
||
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||
|
||
# Extract identity for Electrobun
|
||
IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" \
|
||
| grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)"/\1/')
|
||
if [ -z "$IDENTITY" ]; then
|
||
echo "::error::No 'Developer ID Application' identity found in certificate"
|
||
exit 1
|
||
fi
|
||
echo "Signing identity: $IDENTITY"
|
||
echo "developer_id=$IDENTITY" >> "$GITHUB_OUTPUT"
|
||
echo "skip_codesign=" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Sign native macOS effects dylib
|
||
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign != '1'
|
||
env:
|
||
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
|
||
run: |
|
||
DYLIB="packages/app-core/platforms/electrobun/src/libMacWindowEffects.dylib"
|
||
if [ ! -f "$DYLIB" ]; then
|
||
echo "::error::Expected $DYLIB to exist after build:native-effects"
|
||
exit 1
|
||
fi
|
||
codesign --force --timestamp --sign "$ELECTROBUN_DEVELOPER_ID" "$DYLIB"
|
||
codesign --verify --strict --verbose=2 "$DYLIB"
|
||
|
||
- name: Install quiet macOS packaging wrappers
|
||
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign != '1'
|
||
run: |
|
||
wrapper_dir="$RUNNER_TEMP/eliza-notary-bin"
|
||
mkdir -p "$wrapper_dir"
|
||
cp packages/app-core/platforms/electrobun/scripts/hdiutil-wrapper.sh "$wrapper_dir/hdiutil"
|
||
cp packages/app-core/platforms/electrobun/scripts/xcrun-wrapper.sh "$wrapper_dir/xcrun"
|
||
cp packages/app-core/platforms/electrobun/scripts/zip-wrapper.sh "$wrapper_dir/zip"
|
||
chmod +x "$wrapper_dir/hdiutil"
|
||
chmod +x "$wrapper_dir/xcrun"
|
||
chmod +x "$wrapper_dir/zip"
|
||
echo "$wrapper_dir" >> "$GITHUB_PATH"
|
||
|
||
- name: Resolve electrobun package dir
|
||
id: resolve-electrobun
|
||
shell: bash
|
||
run: |
|
||
package_dir="$(node <<'NODE'
|
||
const { createRequire } = require("node:module");
|
||
const fs = require("node:fs");
|
||
const path = require("node:path");
|
||
|
||
const workspacePackageJson = path.resolve("packages/app-core/platforms/electrobun/package.json");
|
||
const req = createRequire(workspacePackageJson);
|
||
const entryPath = req.resolve("electrobun");
|
||
let packageDir = path.dirname(entryPath);
|
||
|
||
while (!fs.existsSync(path.join(packageDir, "package.json"))) {
|
||
const parentDir = path.dirname(packageDir);
|
||
if (parentDir === packageDir) {
|
||
throw new Error(
|
||
`Could not find electrobun package.json starting from ${entryPath}`,
|
||
);
|
||
}
|
||
packageDir = parentDir;
|
||
}
|
||
|
||
const manifestPath = path.join(packageDir, "package.json");
|
||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||
if (manifest.name !== "electrobun") {
|
||
throw new Error(
|
||
`Resolved unexpected package at ${manifestPath}: ${manifest.name}`,
|
||
);
|
||
}
|
||
|
||
process.stdout.write(packageDir);
|
||
NODE
|
||
)"
|
||
|
||
if [ -z "$package_dir" ]; then
|
||
echo "::error::Failed to resolve electrobun package directory"
|
||
exit 1
|
||
fi
|
||
|
||
case "${{ matrix.platform.artifact-name }}" in
|
||
macos-arm64|macos-x64|linux-x64|linux-arm64)
|
||
electrobun_core_target="${{ matrix.platform.artifact-name }}"
|
||
;;
|
||
windows-x64)
|
||
electrobun_core_target="win-x64"
|
||
;;
|
||
*)
|
||
echo "::error::Unsupported Electrobun release artifact target: ${{ matrix.platform.artifact-name }}"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
echo "Resolved electrobun package dir: $package_dir"
|
||
echo "package-dir=$package_dir" >> "$GITHUB_OUTPUT"
|
||
echo "cache-dir=$package_dir/.cache" >> "$GITHUB_OUTPUT"
|
||
echo "core-target=$electrobun_core_target" >> "$GITHUB_OUTPUT"
|
||
echo "core-cache-dir=$package_dir/dist-$electrobun_core_target" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Cache Electrobun CLI and core binaries
|
||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||
with:
|
||
path: |
|
||
${{ steps.resolve-electrobun.outputs.cache-dir }}
|
||
${{ steps.resolve-electrobun.outputs.core-cache-dir }}
|
||
key: electrobun-core-${{ matrix.platform.artifact-name }}-${{ hashFiles('packages/app-core/platforms/electrobun/package.json') }}
|
||
restore-keys: electrobun-core-${{ matrix.platform.artifact-name }}-
|
||
|
||
# TODO: verify this script exists in eliza. `ensure-electrobun-core.mjs`
|
||
# was eliza-only and is not present in upstream eliza/packages/app-core/scripts/.
|
||
# Without it the cache step above is decorative — Electrobun will fall
|
||
# back to its own download flow on first build. Port the script if you
|
||
# want pre-warmed core binaries on the first packaging run.
|
||
- name: Prepare Electrobun core binaries
|
||
run: |
|
||
if [ -f packages/app-core/scripts/ensure-electrobun-core.mjs ]; then
|
||
node packages/app-core/scripts/ensure-electrobun-core.mjs \
|
||
--package-dir "${{ steps.resolve-electrobun.outputs.package-dir }}" \
|
||
--target "${{ steps.resolve-electrobun.outputs.core-target }}"
|
||
else
|
||
echo "::warning::packages/app-core/scripts/ensure-electrobun-core.mjs not present — Electrobun will download core binaries on demand."
|
||
fi
|
||
|
||
- name: Probe Electrobun bun entry build
|
||
working-directory: packages/app-core/platforms/electrobun
|
||
run: |
|
||
out_dir="$RUNNER_TEMP/electrobun-entry-probe"
|
||
node "$GITHUB_WORKSPACE/packages/scripts/rm-path-recursive.mjs" "$out_dir"
|
||
bun build src/index.ts --target=bun --packages=external --outdir "$out_dir"
|
||
|
||
- name: Build patched Electrobun CLI
|
||
shell: bash
|
||
run: |
|
||
node packages/app-core/scripts/build-patched-electrobun-cli.mjs "${{ steps.resolve-electrobun.outputs.package-dir }}" "${{ matrix.platform.artifact-name }}"
|
||
|
||
- name: Build Electrobun app
|
||
id: build-electrobun-app
|
||
run: |
|
||
set +e
|
||
if [ "${{ matrix.platform.os }}" = "macos" ] && [ "${{ matrix.platform.artifact-name }}" = "macos-x64" ]; then
|
||
ELIZA_DESKTOP_COMMAND_PREFIX="arch -x86_64" node packages/app-core/scripts/desktop-build.mjs package --env=${{ needs.prepare.outputs.env }}
|
||
else
|
||
node packages/app-core/scripts/desktop-build.mjs package --env=${{ needs.prepare.outputs.env }}
|
||
fi
|
||
status=$?
|
||
set -e
|
||
if [ "$status" -eq 0 ]; then
|
||
echo "fallback=false" >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
|
||
if [ "${{ inputs.draft }}" != "true" ] || [ "${{ inputs.publish_release }}" = "true" ]; then
|
||
exit "$status"
|
||
fi
|
||
|
||
artifact_root="packages/app-core/platforms/electrobun/artifacts"
|
||
build_root="packages/app-core/platforms/electrobun/build"
|
||
build_dir="$(find "$build_root" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort | tail -1 || true)"
|
||
if [ -z "$build_dir" ]; then
|
||
echo "::error::Electrobun packaging failed and no built app tree exists"
|
||
exit "$status"
|
||
fi
|
||
|
||
VERSION="$(node -p "require('./packages/app-core/platforms/electrobun/package.json').version")"
|
||
ENV_NAME="${{ needs.prepare.outputs.env }}"
|
||
find -L "$build_dir" -type d -name "Resources" | while read -r res_dir; do
|
||
dest="$res_dir/version.json"
|
||
printf '{"identifier":"ai.elizaos.Eliza","channel":"%s","name":"eliza","version":"%s"}' \
|
||
"$ENV_NAME" "$VERSION" > "$dest"
|
||
echo "Wrote fallback $dest"
|
||
done
|
||
|
||
mkdir -p "$artifact_root"
|
||
case "${{ matrix.platform.os }}" in
|
||
linux)
|
||
tar --zstd -cf "$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.tar.zst" -C "$build_root" "$(basename "$build_dir")"
|
||
;;
|
||
macos)
|
||
tar -czf "$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.app.tar.gz" -C "$build_root" "$(basename "$build_dir")"
|
||
;;
|
||
windows)
|
||
powershell -NoProfile -Command "Compress-Archive -Path '$build_dir' -DestinationPath '$artifact_root/eliza-${{ needs.prepare.outputs.env }}-${{ matrix.platform.artifact-name }}.exe.zip' -Force"
|
||
;;
|
||
esac
|
||
echo "fallback=true" >> "$GITHUB_OUTPUT"
|
||
echo "::warning::Electrobun package command exited with $status; uploaded draft-validation fallback archive from $build_dir"
|
||
env:
|
||
# Electrobun reads these env vars directly. The workflow derives the
|
||
# signing identity from CSC_LINK/CSC_KEY_PASSWORD during keychain setup
|
||
# and maps the notarization credentials from the standard repo secrets.
|
||
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
|
||
ELECTROBUN_APPLEID: ${{ secrets.APPLE_ID }}
|
||
ELECTROBUN_APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||
ELECTROBUN_TEAMID: ${{ secrets.APPLE_TEAM_ID }}
|
||
ELECTROBUN_SKIP_CODESIGN: ${{ steps.macos-keychain.outputs.skip_codesign }}
|
||
ELECTROBUN_REAL_HDIUTIL: /usr/bin/hdiutil
|
||
ELIZA_ELECTROBUN_NOTARIZE: 0
|
||
ELECTROBUN_REAL_XCRUN: /usr/bin/xcrun
|
||
ELECTROBUN_REAL_ZIP: /usr/bin/zip
|
||
ELIZA_ELECTROBUN_REPO_ROOT: ${{ github.workspace }}
|
||
# Flat release root for the updater. Env is encoded in the platform prefix
|
||
# (canary-macos-arm64-update.json etc.), so the baseUrl has no env subdir.
|
||
# Points to GitHub Releases as the CDN; the ota-publish job attaches
|
||
# latest-{channel}.json after the release job completes.
|
||
ELIZA_RELEASE_URL: ${{ (github.event_name != 'workflow_call' || inputs.publish_release) && !inputs.draft && format('https://github.com/{0}/releases/download/{1}/', github.repository, needs.prepare.outputs.tag) || '' }}
|
||
|
||
- name: Dump Electrobun build diagnostics
|
||
if: failure()
|
||
run: |
|
||
root="packages/app-core/platforms/electrobun"
|
||
echo "=== ${root}/build ==="
|
||
find -L "$root/build" -maxdepth 6 -type f -print 2>/dev/null | sort | head -300 || true
|
||
echo "=== ${root}/artifacts ==="
|
||
find -L "$root/artifacts" -maxdepth 6 -type f -print 2>/dev/null | sort | head -300 || true
|
||
echo "=== ${root} wrapper diagnostics ==="
|
||
find -L "$root/build" -name wrapper-diagnostics.json -print -exec sed -n '1,220p' {} \; 2>/dev/null || true
|
||
|
||
- name: Upload Electrobun build diagnostics
|
||
if: failure()
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}-build-diagnostics
|
||
path: |
|
||
packages/app-core/platforms/electrobun/build/**
|
||
packages/app-core/platforms/electrobun/build/**/wrapper-diagnostics.json
|
||
packages/app-core/platforms/electrobun/artifacts/**
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
# Inject version.json into the packaged Resources directory.
|
||
# The bun entry getVersionInfo() reads ../Resources/version.json and
|
||
# throws if absent, killing the process before main() runs.
|
||
# Electrobun does not generate this file for us, so write it from the
|
||
# release metadata after the packaged bundle exists.
|
||
- name: Inject version.json into bundle (Windows)
|
||
if: matrix.platform.os == 'windows'
|
||
shell: pwsh
|
||
run: |
|
||
$version = (Get-Content packages/app-core/platforms/electrobun/package.json | ConvertFrom-Json).version
|
||
$envName = "${{ needs.prepare.outputs.env }}"
|
||
$buildRoot = "packages/app-core/platforms/electrobun/build"
|
||
|
||
Get-ChildItem -Path $buildRoot -Recurse -Directory -Filter "Resources" | ForEach-Object {
|
||
$versionJson = @{
|
||
identifier = "ai.elizaos.Eliza"
|
||
channel = $envName
|
||
name = "eliza"
|
||
version = $version
|
||
} | ConvertTo-Json -Compress
|
||
$dest = Join-Path $_.FullName "version.json"
|
||
Write-Host "Writing $dest"
|
||
Set-Content -Path $dest -Value $versionJson -Encoding utf8
|
||
}
|
||
|
||
- name: Inject version.json into bundle (macOS / Linux)
|
||
if: matrix.platform.os != 'windows'
|
||
run: |
|
||
VERSION="$(node -p "require('./packages/app-core/platforms/electrobun/package.json').version")"
|
||
ENV_NAME="${{ needs.prepare.outputs.env }}"
|
||
build_root="packages/app-core/platforms/electrobun/build"
|
||
if [ -d "$build_root" ]; then
|
||
find -L "$build_root" -type d -name "Resources" | while read -r res_dir; do
|
||
dest="$res_dir/version.json"
|
||
printf '{"identifier":"ai.elizaos.Eliza","channel":"%s","name":"eliza","version":"%s"}' \
|
||
"$ENV_NAME" "$VERSION" > "$dest"
|
||
echo "Wrote $dest"
|
||
done
|
||
fi
|
||
|
||
- name: Package Linux desktop installers
|
||
if: matrix.platform.os == 'linux' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
run: |
|
||
node packages/app-core/scripts/package-electrobun-linux.mjs \
|
||
--version=${{ needs.prepare.outputs.version }} \
|
||
--channel=${{ needs.prepare.outputs.env }} \
|
||
--arch=x64
|
||
|
||
- name: List build output
|
||
run: |
|
||
echo "=== Electrobun artifacts ==="
|
||
find -L packages/app-core/platforms/electrobun/artifacts -type f 2>/dev/null | sort || echo "(no artifacts directory)"
|
||
|
||
- name: Stage standard macOS release app
|
||
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
env:
|
||
ELECTROBUN_DEVELOPER_ID: ${{ steps.macos-keychain.outputs.developer_id }}
|
||
ELECTROBUN_APPLEID: ${{ secrets.APPLE_ID }}
|
||
ELECTROBUN_APPLEIDPASS: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||
ELECTROBUN_TEAMID: ${{ secrets.APPLE_TEAM_ID }}
|
||
ELECTROBUN_SKIP_CODESIGN: ${{ steps.macos-keychain.outputs.skip_codesign }}
|
||
run: bash packages/app-core/platforms/electrobun/scripts/stage-macos-release-artifacts.sh
|
||
|
||
- name: Sign Windows executables
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
env:
|
||
# Configure on elizaOS/eliza repo settings.
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
|
||
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
|
||
shell: pwsh
|
||
run: pwsh -File packages/app-core/platforms/electrobun/scripts/sign-windows.ps1 -ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") -BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build")
|
||
|
||
- name: Install Inno Setup 6.7.1
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$url = "https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe"
|
||
$installer = Join-Path $env:RUNNER_TEMP "innosetup-6.7.1.exe"
|
||
Write-Host "Downloading Inno Setup 6.7.1..."
|
||
Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing
|
||
Write-Host "Installing silently..."
|
||
Start-Process -FilePath $installer -ArgumentList "/VERYSILENT","/SUPPRESSMSGBOXES","/NORESTART","/SP-" -Wait -NoNewWindow
|
||
|
||
$iscc = @(
|
||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
|
||
"C:\Program Files\Inno Setup 6\ISCC.exe"
|
||
) | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||
|
||
if (-not $iscc) {
|
||
Write-Error "ISCC.exe not found after installing Inno Setup 6.7.1"
|
||
exit 1
|
||
}
|
||
|
||
Add-Content -Path $env:GITHUB_ENV -Value "ELIZA_INNO_SETUP_COMPILER=$iscc"
|
||
Write-Host "Using ISCC compiler: $iscc"
|
||
|
||
- name: Extract Windows app bundle for Inno Setup
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
|
||
$tarball = Get-ChildItem -Path $artifactsDir -File -Filter "*.tar.zst" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending |
|
||
Select-Object -First 1
|
||
|
||
if (-not $tarball) {
|
||
Write-Error "No .tar.zst archive found in artifacts"
|
||
exit 1
|
||
}
|
||
|
||
# Use a short path to avoid Windows MAX_PATH (260 char) limits
|
||
# with deeply nested node_modules inside the app bundle
|
||
$extractDir = "C:\e"
|
||
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
|
||
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
|
||
Write-Host "Extracting $($tarball.Name) to $extractDir ..."
|
||
tar -xf $tarball.FullName -C $extractDir
|
||
Write-Host "Extracted top-level structure:"
|
||
Get-ChildItem -Path $extractDir -Recurse -Depth 3 | ForEach-Object { Write-Host " $($_.FullName)" }
|
||
|
||
# Verify the expected runtime entry point exists without mutating the
|
||
# extracted installer source tree. Inno follows recursively, so
|
||
# junction/copy aliases here can duplicate the deep runtime tree and
|
||
# trip Windows path handling during compilation.
|
||
$appRoot = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1
|
||
if ($appRoot) {
|
||
$resApp = Join-Path $appRoot.FullName "Resources\app"
|
||
$elizaDist = Join-Path $resApp "eliza-dist"
|
||
$entryCheckEliza = Join-Path $elizaDist "entry.js"
|
||
Write-Host "Checking for: $entryCheckEliza"
|
||
if (Test-Path $entryCheckEliza) {
|
||
Write-Host "eliza-dist/entry.js found"
|
||
} else {
|
||
Write-Host "WARNING: eliza-dist/entry.js NOT found"
|
||
Write-Host "Resources/app contents:"
|
||
if (Test-Path $resApp) {
|
||
Get-ChildItem -Path $resApp -Recurse -Depth 2 | ForEach-Object { Write-Host " $($_.FullName)" }
|
||
} else {
|
||
Write-Host " Resources/app directory does not exist"
|
||
}
|
||
}
|
||
$keywordDataCheck = Join-Path $elizaDist "node_modules\@elizaos\shared\src\i18n\generated\validation-keyword-data.js"
|
||
Write-Host "Checking for: $keywordDataCheck"
|
||
if (Test-Path $keywordDataCheck) {
|
||
Write-Host "eliza-dist generated keyword data found"
|
||
} else {
|
||
Write-Error "eliza-dist generated keyword data NOT found: $keywordDataCheck"
|
||
exit 1
|
||
}
|
||
$agentManifestPath = Join-Path $elizaDist "node_modules\@elizaos\agent\package.json"
|
||
Write-Host "Checking for: $agentManifestPath"
|
||
if (Test-Path $agentManifestPath) {
|
||
Write-Host "eliza-dist @elizaos/agent package manifest found"
|
||
} else {
|
||
Write-Error "eliza-dist @elizaos/agent package manifest NOT found: $agentManifestPath"
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
- name: Build Inno Setup installer
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
env:
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
|
||
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
|
||
shell: pwsh
|
||
run: |
|
||
pwsh -File packages/app-core/packaging/inno/build-inno.ps1 `
|
||
-BuildDir "C:\e" `
|
||
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
|
||
-Version "${{ needs.prepare.outputs.version }}" `
|
||
-Channel "${{ needs.prepare.outputs.env }}"
|
||
|
||
- name: Verify Windows public installer looks complete
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$installer = Get-ChildItem -Path "packages/app-core/platforms/electrobun/artifacts" -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending |
|
||
Select-Object -First 1
|
||
|
||
if (-not $installer) {
|
||
Write-Error "No public Windows installer found in packages/app-core/platforms/electrobun/artifacts"
|
||
exit 1
|
||
}
|
||
|
||
$minimumBytes = 50MB
|
||
if ($installer.Length -lt $minimumBytes) {
|
||
Write-Error "Installer is too small to be a standalone Windows package: $($installer.FullName) ($($installer.Length) bytes)"
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "Verified standalone Windows installer: $($installer.Name)"
|
||
Write-Host "Installer size: $([math]::Round($installer.Length / 1MB, 1)) MB"
|
||
|
||
- name: Cache Windows embedding model
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||
with:
|
||
path: ~/.eliza/models
|
||
key: embedding-model-nomic-embed-text-v1.5-Q4_K_S
|
||
|
||
- name: Seed Windows embedding model cache
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$modelsDir = Join-Path $env:USERPROFILE ".eliza\models"
|
||
$modelName = "nomic-embed-text-v1.5.Q4_K_S.gguf"
|
||
$modelRepo = "nomic-ai/nomic-embed-text-v1.5-GGUF"
|
||
$modelPath = Join-Path $modelsDir $modelName
|
||
|
||
if (-not (Test-Path $modelPath)) {
|
||
New-Item -ItemType Directory -Force -Path $modelsDir | Out-Null
|
||
$url = "https://huggingface.co/$modelRepo/resolve/main/$modelName"
|
||
Write-Host "Downloading Windows embedding model cache seed: $url"
|
||
Invoke-WebRequest -Uri $url -OutFile $modelPath
|
||
}
|
||
|
||
Write-Host "Windows embedding model cache ready: $modelPath"
|
||
|
||
- name: Smoke test packaged Windows app
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
timeout-minutes: 30
|
||
shell: pwsh
|
||
env:
|
||
ELIZA_DISABLE_LOCAL_EMBEDDINGS: "1"
|
||
# The runtime requires at least one AI provider plugin to fully
|
||
# initialize — without it, startApiServer() can fail before
|
||
# server.listen() and the health endpoint never becomes reachable.
|
||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
ELIZA_WINDOWS_SMOKE_REQUIRE_INSTALLER: "1"
|
||
# Use a short path under runner.temp to avoid MAX_PATH issues with
|
||
# deeply nested node_modules while staying in a user-writable location.
|
||
ELIZA_TEST_WINDOWS_INSTALL_DIR: ${{ runner.temp }}\el
|
||
ELIZA_TEST_WINDOWS_LAUNCHER_DIR: ${{ runner.temp }}\eliza-windows-ui-launcher
|
||
ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE: ${{ runner.temp }}\eliza-windows-ui-launcher.txt
|
||
# Inno installer is built into platforms/electrobun/artifacts.
|
||
ELIZA_TEST_WINDOWS_ARTIFACTS_DIR: ${{ github.workspace }}\packages\app-core\platforms\electrobun\artifacts
|
||
ELIZA_TEST_WINDOWS_BUILD_DIR: ${{ github.workspace }}\packages\app-core\platforms\electrobun\build
|
||
run: |
|
||
Remove-Item $env:ELIZA_TEST_WINDOWS_INSTALL_DIR -Recurse -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $env:ELIZA_TEST_WINDOWS_LAUNCHER_DIR -Recurse -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE -Force -ErrorAction SilentlyContinue
|
||
|
||
bun run test:desktop:packaged:windows
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Error "Packaged Windows smoke test exited with code $LASTEXITCODE."
|
||
exit $LASTEXITCODE
|
||
}
|
||
|
||
if (-not (Test-Path $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE)) {
|
||
Write-Error "Smoke test did not emit a reusable Windows launcher path."
|
||
exit 1
|
||
}
|
||
|
||
$launcherPath = (Get-Content $env:ELIZA_TEST_WINDOWS_LAUNCHER_PATH_FILE -Raw).Trim()
|
||
if ([string]::IsNullOrWhiteSpace($launcherPath) -or -not (Test-Path $launcherPath)) {
|
||
Write-Error "Reusable Windows launcher path is invalid: $launcherPath"
|
||
exit 1
|
||
}
|
||
|
||
Add-Content -Path $env:GITHUB_ENV -Value "ELIZA_TEST_WINDOWS_LAUNCHER_PATH=$launcherPath"
|
||
Write-Host "Reusing packaged Windows launcher for Playwright: $launcherPath"
|
||
|
||
- name: Run Windows clean installer proof
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
env:
|
||
ELIZA_DISABLE_LOCAL_EMBEDDINGS: "1"
|
||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||
ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR: ${{ runner.temp }}\el-proof
|
||
run: |
|
||
Remove-Item $env:ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR -Recurse -Force -ErrorAction SilentlyContinue
|
||
pwsh -File packages/app-core/platforms/electrobun/scripts/verify-windows-installer-proof.ps1 `
|
||
-ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
|
||
-BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build") `
|
||
-ProofInstallDir $env:ELIZA_TEST_WINDOWS_PROOF_INSTALL_DIR `
|
||
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts/windows-installer-proof")
|
||
if ($LASTEXITCODE -ne 0) {
|
||
Write-Error "Windows clean installer proof exited with code $LASTEXITCODE."
|
||
exit $LASTEXITCODE
|
||
}
|
||
|
||
- name: Upload Windows installer proof artifact
|
||
if: always() && matrix.platform.os == 'windows'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-windows-installer-proof
|
||
path: packages/app-core/platforms/electrobun/artifacts/windows-installer-proof/**
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
- name: Upload Windows installer .exe + Inno log on smoke failure
|
||
if: failure() && matrix.platform.os == 'windows'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-windows-installer-debug
|
||
path: |
|
||
packages/app-core/platforms/electrobun/artifacts/*.exe
|
||
${{ runner.temp }}/eliza-inno-setup.log
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
- name: Run Windows packaged renderer bootstrap check
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
run: bun run test:desktop:playwright
|
||
|
||
- name: Upload Windows Playwright UI diagnostics
|
||
if: failure() && matrix.platform.os == 'windows'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-windows-ui-playwright-diagnostics
|
||
# TODO: confirm packaged-app Playwright result location in eliza
|
||
# (was apps/app/test-results/** in eliza; might be packages/app/test-results/**).
|
||
path: packages/app/test-results/**
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
- name: Stage Windows setup executables
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
|
||
$setupExecutables = Get-ChildItem -Path "packages/app-core/platforms/electrobun/build" -Recurse -File -Filter "*Setup*.exe" -ErrorAction SilentlyContinue
|
||
if (-not $setupExecutables) {
|
||
Write-Error "No Windows setup executable found under packages/app-core/platforms/electrobun/build"
|
||
exit 1
|
||
}
|
||
New-Item -ItemType Directory -Force -Path $artifactsDir | Out-Null
|
||
$publicInstaller = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending |
|
||
Select-Object -First 1
|
||
foreach ($setupExecutable in $setupExecutables) {
|
||
$destinationPath = Join-Path $artifactsDir $setupExecutable.Name
|
||
if (
|
||
$publicInstaller -and
|
||
$publicInstaller.Name -eq $setupExecutable.Name -and
|
||
$publicInstaller.Length -ge 50MB -and
|
||
$setupExecutable.Length -lt $publicInstaller.Length
|
||
) {
|
||
Write-Warning "Skipping build setup stub that would overwrite verified public installer: $($setupExecutable.FullName)"
|
||
continue
|
||
}
|
||
if (Test-Path $destinationPath) {
|
||
Write-Host "Skipping setup executable already present in artifacts: $destinationPath"
|
||
continue
|
||
}
|
||
Copy-Item $setupExecutable.FullName -Destination $destinationPath -Force
|
||
Write-Host "Staged Windows setup executable: $($setupExecutable.Name)"
|
||
}
|
||
|
||
- name: Sign Windows executables (post-stage)
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
env:
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
|
||
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
|
||
shell: pwsh
|
||
run: pwsh -File packages/app-core/platforms/electrobun/scripts/sign-windows.ps1 -ArtifactsDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") -BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build")
|
||
|
||
- name: Verify Windows code signature
|
||
if: matrix.platform.os == 'windows' && env.WINDOWS_SIGN_CERT_BASE64 != ''
|
||
env:
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
shell: pwsh
|
||
run: |
|
||
$signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter "signtool.exe" -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.FullName -match "x64" } |
|
||
Sort-Object { [version]($_.FullName -replace '.*\\(\d+\.\d+\.\d+\.\d+)\\.*', '$1') } -Descending |
|
||
Select-Object -First 1 -ExpandProperty FullName
|
||
|
||
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
|
||
$signed = 0
|
||
$failed = 0
|
||
|
||
Get-ChildItem -Path $artifactsDir -Recurse -Filter "*.exe" -ErrorAction SilentlyContinue | ForEach-Object {
|
||
Write-Host "Verifying: $($_.FullName)"
|
||
& $signtool verify /pa /v $_.FullName
|
||
if ($LASTEXITCODE -eq 0) {
|
||
$signed++
|
||
} else {
|
||
Write-Host "::error::Signature verification failed: $($_.Name)"
|
||
$failed++
|
||
}
|
||
}
|
||
|
||
Write-Host "Verification complete: $signed signed, $failed failed"
|
||
if ($failed -gt 0) {
|
||
exit 1
|
||
}
|
||
|
||
- name: Re-verify Windows public installer before upload
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$installer = Get-ChildItem -Path "packages/app-core/platforms/electrobun/artifacts" -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending |
|
||
Select-Object -First 1
|
||
|
||
if (-not $installer) {
|
||
Write-Error "No public Windows installer found in packages/app-core/platforms/electrobun/artifacts after staging/signing."
|
||
exit 1
|
||
}
|
||
|
||
$minimumBytes = 50MB
|
||
if ($installer.Length -lt $minimumBytes) {
|
||
Write-Error "Public Windows installer regressed below standalone size threshold: $($installer.FullName) ($($installer.Length) bytes)"
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "Public installer still valid for release upload: $($installer.Name)"
|
||
Write-Host "Installer size: $([math]::Round($installer.Length / 1MB, 1)) MB"
|
||
|
||
- name: Build MSIX package
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
env:
|
||
WINDOWS_SIGN_CERT_BASE64: ${{ secrets.WINDOWS_SIGN_CERT_BASE64 }}
|
||
WINDOWS_SIGN_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGN_CERT_PASSWORD }}
|
||
WINDOWS_SIGN_TIMESTAMP_URL: ${{ secrets.WINDOWS_SIGN_TIMESTAMP_URL }}
|
||
shell: pwsh
|
||
run: |
|
||
pwsh -File packages/app-core/packaging/msix/build-msix.ps1 `
|
||
-BuildDir (Join-Path $PWD "packages/app-core/platforms/electrobun/build") `
|
||
-OutputDir (Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts") `
|
||
-Version "${{ needs.prepare.outputs.version }}"
|
||
|
||
# When building unsigned (skip_codesign=1), the CEF framework ships with
|
||
# a pre-existing signature. Injecting version.json into Resources breaks
|
||
# that signature (resource hash mismatch). Ad-hoc re-sign the .app so
|
||
# the bundle is internally consistent even without a Developer ID cert.
|
||
- name: Ad-hoc re-sign unsigned macOS bundle
|
||
if: matrix.platform.os == 'macos' && steps.macos-keychain.outputs.skip_codesign == '1' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
run: |
|
||
for app in packages/app-core/platforms/electrobun/artifacts/*.app; do
|
||
[ -d "$app" ] || continue
|
||
echo "Ad-hoc signing: $app"
|
||
codesign --force --deep --sign - "$app" || echo "::warning::Ad-hoc re-sign failed for $app"
|
||
done
|
||
|
||
- name: Verify macOS signature and notarization
|
||
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
run: |
|
||
shopt -s nullglob
|
||
|
||
retry_stapler_validate() {
|
||
local target="$1"
|
||
local attempts="${2:-5}"
|
||
local delay_seconds="${3:-15}"
|
||
local attempt
|
||
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
|
||
if xcrun stapler validate "$target"; then
|
||
return 0
|
||
fi
|
||
if [ "$attempt" -lt "$attempts" ]; then
|
||
echo "::warning::stapler validate failed for $target (attempt $attempt/$attempts); retrying..."
|
||
sleep "$((delay_seconds * attempt))"
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
apps=()
|
||
while IFS= read -r app; do
|
||
apps+=("$app")
|
||
done < <(find -L packages/app-core/platforms/electrobun/artifacts -maxdepth 1 -type d -name "*.app" | sort)
|
||
mounted_volume=""
|
||
if [ "${#apps[@]}" -eq 0 ]; then
|
||
dmgs=(packages/app-core/platforms/electrobun/artifacts/*.dmg)
|
||
if [ "${#dmgs[@]}" -eq 0 ]; then
|
||
echo "::error::No .app bundle or .dmg found in packages/app-core/platforms/electrobun/artifacts"
|
||
exit 1
|
||
fi
|
||
dmg="${dmgs[0]}"
|
||
echo "No .app bundle found in artifacts; mounting DMG: $dmg"
|
||
mount_line=""
|
||
for attempt in 1 2 3 4 5; do
|
||
attach_output="$(hdiutil attach -nobrowse -readonly "$dmg" 2>&1)" && \
|
||
mount_line="$(printf '%s\n' "$attach_output" | awk '/\/Volumes\// { print substr($0, index($0, "/Volumes/")); exit }')" && \
|
||
[ -n "$mount_line" ] && [ -d "$mount_line" ] && break
|
||
echo "::warning::DMG attach attempt $attempt/5 failed"
|
||
if [ -n "${attach_output:-}" ]; then
|
||
printf '%s\n' "$attach_output"
|
||
fi
|
||
mount_line=""
|
||
sleep 2
|
||
done
|
||
if [ -z "$mount_line" ]; then
|
||
echo "::error::Failed to determine mounted DMG volume"
|
||
exit 1
|
||
fi
|
||
mounted_volume="$mount_line"
|
||
trap 'if [ -n "$mounted_volume" ] && [ -d "$mounted_volume" ]; then hdiutil detach "$mounted_volume" >/dev/null 2>&1 || true; fi' EXIT
|
||
apps=()
|
||
while IFS= read -r app; do
|
||
apps+=("$app")
|
||
done < <(find "$mounted_volume" -maxdepth 1 -type d -name "*.app" | sort)
|
||
if [ "${#apps[@]}" -eq 0 ]; then
|
||
echo "::error::No .app bundle found inside mounted DMG"
|
||
exit 1
|
||
fi
|
||
fi
|
||
for app in "${apps[@]}"; do
|
||
echo "Verifying: $app"
|
||
codesign --verify --deep --strict --verbose=2 "$app"
|
||
info="$(codesign -dv --verbose=4 "$app" 2>&1 || true)"
|
||
echo "$info" | grep -q "Authority=Developer ID Application" || {
|
||
echo "::error::Missing Developer ID Application signature on $app"
|
||
exit 1
|
||
}
|
||
# Gatekeeper can reject the standalone staging copy on Intel even
|
||
# when the notarized DMG is valid; treat as advisory.
|
||
if ! spctl -a -vv --type exec "$app"; then
|
||
echo "::warning::Gatekeeper rejected staged app bundle $app; continuing because the notarized DMG is the release artifact."
|
||
fi
|
||
retry_stapler_validate "$app" 3 10 || echo "::warning::Stapler validation failed (may be expected for .app; DMG stapling is the norm)"
|
||
done
|
||
found_dmg=0
|
||
while IFS= read -r dmg; do
|
||
[[ -z "$dmg" ]] && continue
|
||
found_dmg=1
|
||
echo "Verifying installer: $dmg"
|
||
dmg_info="$(codesign -dv --verbose=4 "$dmg" 2>&1 || true)"
|
||
echo "$dmg_info"
|
||
echo "$dmg_info" | grep -q "Authority=Developer ID Application" || {
|
||
echo "::error::Missing Developer ID Application signature on $dmg"
|
||
exit 1
|
||
}
|
||
retry_stapler_validate "$dmg"
|
||
done < <(find -L packages/app-core/platforms/electrobun/artifacts -maxdepth 1 -type f -name "*.dmg" | sort)
|
||
if [ "$found_dmg" -eq 0 ]; then
|
||
echo "::error::No DMG found in packages/app-core/platforms/electrobun/artifacts"
|
||
exit 1
|
||
fi
|
||
|
||
- name: Smoke test packaged macOS app
|
||
if: matrix.platform.os == 'macos' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
continue-on-error: true
|
||
env:
|
||
SMOKE_DIAGNOSTICS_DIR: ${{ runner.temp }}/eliza-smoke-diagnostics
|
||
run: |
|
||
STARTUP_TIMEOUT=420 \
|
||
LIVENESS_TIMEOUT=10 \
|
||
SKIP_BUILD=1 \
|
||
SKIP_SIGNATURE_CHECK=1 \
|
||
bun run test:desktop:packaged
|
||
|
||
- name: Collect Windows smoke diagnostics
|
||
if: failure() && matrix.platform.os == 'windows'
|
||
shell: pwsh
|
||
run: |
|
||
$diagnosticsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts/windows-smoke-diagnostics"
|
||
$appDataRoot = if ($env:ELIZA_TEST_WINDOWS_APPDATA_PATH) { $env:ELIZA_TEST_WINDOWS_APPDATA_PATH } else { $env:APPDATA }
|
||
$localAppDataRoot = if ($env:ELIZA_TEST_WINDOWS_LOCALAPPDATA_PATH) { $env:ELIZA_TEST_WINDOWS_LOCALAPPDATA_PATH } else { $env:LOCALAPPDATA }
|
||
$wrapperRoots = @(@(
|
||
(Join-Path $localAppDataRoot "com.elizaai.eliza"),
|
||
(Join-Path $localAppDataRoot "ai.elizaos.Eliza"),
|
||
(Join-Path $localAppDataRoot "ai.elizaos.app")
|
||
) | Select-Object -Unique)
|
||
|
||
Write-Host "Diagnostics roots:"
|
||
Write-Host " appDataRoot = $appDataRoot"
|
||
Write-Host " localAppDataRoot = $localAppDataRoot"
|
||
foreach ($wrapperRoot in $wrapperRoots) {
|
||
Write-Host " wrapperRoot = $wrapperRoot"
|
||
}
|
||
|
||
Remove-Item $diagnosticsDir -Recurse -Force -ErrorAction SilentlyContinue
|
||
New-Item -ItemType Directory -Force -Path $diagnosticsDir | Out-Null
|
||
|
||
if (Test-Path $appDataRoot) {
|
||
Write-Host "--- APPDATA tree ($appDataRoot) ---"
|
||
Get-ChildItem -Path $appDataRoot -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " $($_.FullName)" }
|
||
Write-Host "--- end APPDATA tree ---"
|
||
|
||
$logFiles = Get-ChildItem -Path $appDataRoot -Recurse -File -Include "*.log" -ErrorAction SilentlyContinue
|
||
foreach ($log in $logFiles) {
|
||
$relative = $log.FullName.Substring($appDataRoot.Length).TrimStart('\','/')
|
||
$safeName = $relative -replace '[\\/:]', '_'
|
||
$dest = Join-Path $diagnosticsDir $safeName
|
||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
|
||
Copy-Item $log.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
|
||
Write-Host "--- $($log.FullName) ---"
|
||
Get-Content $log.FullName -Tail 400 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_ }
|
||
Write-Host "--- end $($log.FullName) ---"
|
||
}
|
||
if (-not $logFiles) {
|
||
Write-Warning "No *.log files found anywhere under $appDataRoot"
|
||
}
|
||
} else {
|
||
Write-Warning "APPDATA root not found: $appDataRoot"
|
||
}
|
||
|
||
if (Test-Path $localAppDataRoot) {
|
||
$localLogFiles = Get-ChildItem -Path $localAppDataRoot -Recurse -File -Include "*.log" -ErrorAction SilentlyContinue
|
||
foreach ($log in $localLogFiles) {
|
||
$relative = $log.FullName.Substring($localAppDataRoot.Length).TrimStart('\','/')
|
||
$safeName = "local_" + ($relative -replace '[\\/:]', '_')
|
||
$dest = Join-Path $diagnosticsDir $safeName
|
||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
|
||
Copy-Item $log.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
|
||
Write-Host "--- $($log.FullName) ---"
|
||
Get-Content $log.FullName -Tail 400 -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_ }
|
||
Write-Host "--- end $($log.FullName) ---"
|
||
}
|
||
}
|
||
|
||
foreach ($pattern in @("eliza-windows-smoke-*.state.json", "eliza-windows-smoke-*.events.jsonl", "eliza-startup-trace-*.state.json")) {
|
||
$traceGlob = Join-Path $env:RUNNER_TEMP $pattern
|
||
$traceFiles = Get-ChildItem -Path $traceGlob -ErrorAction SilentlyContinue
|
||
if ($traceFiles) {
|
||
foreach ($f in $traceFiles) {
|
||
$dest = Join-Path $diagnosticsDir $f.Name
|
||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
|
||
Copy-Item $f.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
|
||
Write-Host "--- startup-trace: $($f.Name) ---"
|
||
Get-Content $f.FullName -Tail 400 | ForEach-Object { Write-Host $_ }
|
||
Write-Host "--- end $($f.Name) ---"
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ($root in @($wrapperRoots + @("C:\\e", "D:\\a\\_temp\\el"))) {
|
||
if (Test-Path $root) {
|
||
Get-ChildItem -Path $root -Recurse -File -Filter "startup-session.json" -ErrorAction SilentlyContinue |
|
||
ForEach-Object {
|
||
$dest = Join-Path $diagnosticsDir ("startup-session-" + ($_.FullName -replace '[\\:]', '_') + ".json")
|
||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
|
||
Copy-Item $_.FullName -Destination $dest -Force -ErrorAction SilentlyContinue
|
||
Write-Host "--- $($_.FullName) ---"
|
||
Get-Content $_.FullName | ForEach-Object { Write-Host $_ }
|
||
Write-Host "--- end ---"
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ($wrapperRoot in $wrapperRoots) {
|
||
if (Test-Path $wrapperRoot) {
|
||
Get-ChildItem -Path $wrapperRoot -Recurse -File -Filter "wrapper-diagnostics.json" -ErrorAction SilentlyContinue |
|
||
ForEach-Object {
|
||
$relativePath = $_.FullName.Substring($wrapperRoot.Length).TrimStart('\\')
|
||
$destination = Join-Path $diagnosticsDir $relativePath
|
||
$destinationParent = Split-Path -Parent $destination
|
||
New-Item -ItemType Directory -Force -Path $destinationParent | Out-Null
|
||
Copy-Item $_.FullName -Destination $destination -Force
|
||
}
|
||
} else {
|
||
Write-Warning "Wrapper diagnostics root not found at $wrapperRoot"
|
||
}
|
||
}
|
||
|
||
- name: Upload Windows smoke diagnostics
|
||
if: failure() && matrix.platform.os == 'windows'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}-smoke-diagnostics
|
||
path: packages/app-core/platforms/electrobun/artifacts/windows-smoke-diagnostics/**
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
- name: Upload macOS smoke diagnostics
|
||
if: failure() && matrix.platform.os == 'macos'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}-smoke-diagnostics
|
||
path: |
|
||
${{ runner.temp }}/eliza-smoke-diagnostics/**
|
||
packages/app-core/platforms/electrobun/build/**/wrapper-diagnostics.json
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
- name: Compress Windows artifacts before upload
|
||
if: matrix.platform.os == 'windows' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
|
||
$canonicalInstallers = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending
|
||
if (-not $canonicalInstallers) {
|
||
Write-Error "No canonical Windows installer found before compression."
|
||
exit 1
|
||
}
|
||
if ($canonicalInstallers.Count -gt 1) {
|
||
Write-Error "Multiple canonical Windows installers found before compression."
|
||
$canonicalInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
|
||
exit 1
|
||
}
|
||
|
||
$canonicalInstaller = $canonicalInstallers | Select-Object -First 1
|
||
$otherSetupExecutables = Get-ChildItem -Path $artifactsDir -File -Filter "*Setup*.exe" -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.FullName -ne $canonicalInstaller.FullName }
|
||
foreach ($extraSetup in $otherSetupExecutables) {
|
||
Write-Warning "Removing non-canonical setup executable before upload: $($extraSetup.FullName)"
|
||
Remove-Item $extraSetup.FullName -Force -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
$exeFiles = Get-ChildItem -Path $artifactsDir -File -Filter "*.exe" -ErrorAction SilentlyContinue
|
||
foreach ($exe in $exeFiles) {
|
||
if ($exe.FullName -eq $canonicalInstaller.FullName) {
|
||
Write-Host "Keeping canonical Windows installer uncompressed for single-extract downloads: $($exe.Name)"
|
||
continue
|
||
}
|
||
$zipPath = $exe.FullName + ".zip"
|
||
Write-Host "Compressing $($exe.Name) -> $($exe.Name).zip"
|
||
Compress-Archive -Path $exe.FullName -DestinationPath $zipPath -CompressionLevel Optimal
|
||
Remove-Item $exe.FullName -Force
|
||
$originalMB = [math]::Round($exe.Length / 1MB, 1)
|
||
$compressedMB = [math]::Round((Get-Item $zipPath).Length / 1MB, 1)
|
||
Write-Host "Compressed: ${originalMB}MB -> ${compressedMB}MB"
|
||
}
|
||
|
||
- name: Prepare public canary Windows installer artifact
|
||
if: matrix.platform.os == 'windows' && needs.prepare.outputs.env == 'canary' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
shell: pwsh
|
||
run: |
|
||
$artifactsDir = Join-Path $PWD "packages/app-core/platforms/electrobun/artifacts"
|
||
$publicCanaryDir = Join-Path $artifactsDir "public-canary-installer"
|
||
Remove-Item $publicCanaryDir -Recurse -Force -ErrorAction SilentlyContinue
|
||
New-Item -ItemType Directory -Force -Path $publicCanaryDir | Out-Null
|
||
|
||
$canonicalInstallers = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending
|
||
if ($canonicalInstallers) {
|
||
if ($canonicalInstallers.Count -gt 1) {
|
||
Write-Error "Multiple canonical Windows installers found for canary artifact publishing."
|
||
$canonicalInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
|
||
exit 1
|
||
}
|
||
$canonicalInstaller = $canonicalInstallers | Select-Object -First 1
|
||
Copy-Item $canonicalInstaller.FullName -Destination $publicCanaryDir -Force
|
||
} else {
|
||
$canonicalInstallerZips = Get-ChildItem -Path $artifactsDir -File -Filter "*-Setup-*.exe.zip" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending
|
||
if (-not $canonicalInstallerZips) {
|
||
Write-Error "No canonical Windows installer (or zip fallback) found for canary artifact publishing."
|
||
exit 1
|
||
}
|
||
if ($canonicalInstallerZips.Count -gt 1) {
|
||
Write-Error "Multiple canonical Windows installer zips found for canary artifact publishing."
|
||
$canonicalInstallerZips | ForEach-Object { Write-Host " - $($_.FullName)" }
|
||
exit 1
|
||
}
|
||
|
||
$canonicalInstallerZip = $canonicalInstallerZips | Select-Object -First 1
|
||
Expand-Archive -Path $canonicalInstallerZip.FullName -DestinationPath $publicCanaryDir -Force
|
||
}
|
||
|
||
$publicInstallers = Get-ChildItem -Path $publicCanaryDir -File -Filter "*-Setup-*.exe" -ErrorAction SilentlyContinue |
|
||
Sort-Object LastWriteTime -Descending
|
||
if (-not $publicInstallers) {
|
||
Write-Error "Failed to stage a public Windows installer under $publicCanaryDir"
|
||
exit 1
|
||
}
|
||
if ($publicInstallers.Count -gt 1) {
|
||
Write-Error "Canary installer artifact contains multiple public installers."
|
||
$publicInstallers | ForEach-Object { Write-Host " - $($_.FullName)" }
|
||
exit 1
|
||
}
|
||
|
||
$publicInstaller = $publicInstallers | Select-Object -First 1
|
||
Write-Host "Prepared public canary installer artifact: $($publicInstaller.FullName)"
|
||
|
||
- name: Upload public canary installer artifact
|
||
if: matrix.platform.os == 'windows' && needs.prepare.outputs.env == 'canary' && steps.build-electrobun-app.outputs.fallback != 'true'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}-public-installer
|
||
path: packages/app-core/platforms/electrobun/artifacts/public-canary-installer/*-Setup-*.exe
|
||
if-no-files-found: error
|
||
retention-days: 30
|
||
compression-level: 0
|
||
|
||
- name: Upload build artifacts
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}
|
||
path: |
|
||
packages/app-core/platforms/electrobun/artifacts/*.dmg
|
||
packages/app-core/platforms/electrobun/artifacts/*.AppImage
|
||
packages/app-core/platforms/electrobun/artifacts/*.deb
|
||
packages/app-core/platforms/electrobun/artifacts/*.rpm
|
||
packages/app-core/platforms/electrobun/artifacts/*.tar.zst
|
||
packages/app-core/platforms/electrobun/artifacts/*.exe
|
||
packages/app-core/platforms/electrobun/artifacts/*.exe.zip
|
||
packages/app-core/platforms/electrobun/artifacts/*.tar.gz
|
||
packages/app-core/platforms/electrobun/artifacts/*-update.json
|
||
packages/app-core/platforms/electrobun/artifacts/*.patch
|
||
if-no-files-found: error
|
||
retention-days: 30
|
||
compression-level: 0
|
||
|
||
- name: Upload MSIX artifact
|
||
if: matrix.platform.os == 'windows'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
||
with:
|
||
name: electrobun-${{ matrix.platform.artifact-name }}-msix
|
||
path: packages/app-core/platforms/electrobun/artifacts/*.msix
|
||
if-no-files-found: warn
|
||
retention-days: 30
|
||
compression-level: 6
|
||
|
||
release:
|
||
name: Create Release
|
||
if: >-
|
||
${{
|
||
(inputs.platform == '' || inputs.platform == 'all') &&
|
||
(github.event_name != 'workflow_call' || inputs.publish_release) &&
|
||
(github.event_name != 'push' || needs.prepare.outputs.env != 'canary')
|
||
}}
|
||
needs: [prepare, build]
|
||
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
|
||
timeout-minutes: 10
|
||
permissions:
|
||
contents: write
|
||
packages: write
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
|
||
- name: Download all artifacts
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||
with:
|
||
path: release-artifacts
|
||
pattern: electrobun-*
|
||
merge-multiple: false
|
||
|
||
- name: List artifacts
|
||
run: find release-artifacts -type f | sort 2>/dev/null || echo "No artifacts"
|
||
|
||
- name: Collect public release files
|
||
run: |
|
||
mkdir -p release-files
|
||
# Public GitHub releases should expose end-user installables, not the
|
||
# updater transport files. Windows publishes the standalone Inno Setup
|
||
# installer, not the raw Electrobun bootstrap stub.
|
||
find release-artifacts -type f \( \
|
||
-name "*.dmg" -o \
|
||
-name "*.app.tar.gz" -o \
|
||
-name "*.AppImage" -o \
|
||
-name "*.deb" -o \
|
||
-name "*.rpm" -o \
|
||
-name "*-Setup-*.exe" -o \
|
||
-name "*-Setup-*.exe.zip" -o \
|
||
-name "eliza-canary-windows-*.exe.zip" -o \
|
||
-name "*Setup*.tar.gz" -o \
|
||
-name "*.tar.zst" -o \
|
||
-name "*.flatpak" -o \
|
||
-name "*.msix" \
|
||
\) -exec cp {} release-files/ \; 2>/dev/null || true
|
||
|
||
# Decompress Windows .exe.zip back to .exe for end-user downloads
|
||
# (zip was only used for inter-job artifact transfer to avoid SAS token timeout)
|
||
for f in release-files/*-Setup-*.exe.zip; do
|
||
[ -f "$f" ] || continue
|
||
echo "Decompressing $f"
|
||
cd release-files && unzip -o "$(basename "$f")" && rm "$(basename "$f")" && cd ..
|
||
done
|
||
|
||
echo "=== Public release files ==="
|
||
ls -lh release-files/ 2>/dev/null || echo "None"
|
||
|
||
- name: Collect update channel files
|
||
run: |
|
||
mkdir -p update-channel
|
||
find release-artifacts -type f \( \
|
||
-name "*.tar.zst" -o \
|
||
-name "*.patch" -o \
|
||
-name "*-update.json" \
|
||
\) -exec cp {} update-channel/ \; 2>/dev/null || true
|
||
echo "=== Update channel files ==="
|
||
ls -lh update-channel/ 2>/dev/null || echo "None"
|
||
|
||
- name: Generate checksums
|
||
run: |
|
||
cd release-files
|
||
sha256sum -- * > SHA256SUMS.txt
|
||
cat SHA256SUMS.txt
|
||
|
||
- name: Create GitHub Release
|
||
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b
|
||
with:
|
||
tag_name: ${{ needs.prepare.outputs.tag }}
|
||
target_commitish: ${{ needs.prepare.outputs.source_sha }}
|
||
name: Eliza ${{ needs.prepare.outputs.tag }}
|
||
draft: ${{ inputs.draft || false }}
|
||
prerelease: ${{ contains(needs.prepare.outputs.version, '-') }}
|
||
generate_release_notes: true
|
||
files: release-files/*
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
# Upload platform-prefixed update manifests + tarballs to the eliza
|
||
# releases host so the Electrobun updater can discover them via the
|
||
# flat baseUrl contract in electrobun.config.ts:
|
||
# ${baseUrl}/${platformPrefix}-update.json
|
||
# ${baseUrl}/${platformPrefix}-${tarballFileName}
|
||
# Requires RELEASE_UPLOAD_KEY secret (SSH private key for releases server).
|
||
# Skipped for draft releases — test/preview builds must not pollute the production update channel.
|
||
# TODO: configure releases.elizaos.ai (or chosen release host) and update
|
||
# the rsync target / known_hosts entry below before enabling.
|
||
- name: Upload update channel files to release host
|
||
if: ${{ !inputs.draft }}
|
||
env:
|
||
RELEASE_UPLOAD_KEY: ${{ secrets.RELEASE_UPLOAD_KEY }}
|
||
RELEASE_HOST_FINGERPRINT: ${{ secrets.RELEASE_HOST_FINGERPRINT }}
|
||
run: |
|
||
if [ -z "$RELEASE_UPLOAD_KEY" ]; then
|
||
echo "No RELEASE_UPLOAD_KEY secret — skipping upload"
|
||
exit 0
|
||
fi
|
||
echo "$RELEASE_UPLOAD_KEY" > /tmp/release_key
|
||
chmod 600 /tmp/release_key
|
||
# Pin the host fingerprint from RELEASE_HOST_FINGERPRINT so SSH
|
||
# verifies the server identity without disabling host-key checking.
|
||
# To rotate: run `ssh-keyscan -H <release-host>` and update the secret.
|
||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||
echo "$RELEASE_HOST_FINGERPRINT" >> ~/.ssh/known_hosts
|
||
# Electrobun resolves update metadata and tarballs at the flat
|
||
# release root, keyed by platform prefix, not inside version folders.
|
||
rsync -av --delete-after \
|
||
-e "ssh -i /tmp/release_key" \
|
||
update-channel/ \
|
||
releases@releases.elizaos.ai:/var/www/releases/
|
||
rm -f /tmp/release_key
|
||
|
||
publish-browser-companions:
|
||
name: Publish Agent Browser Bridge companions
|
||
if: ${{ (github.event_name != 'workflow_call' || inputs.publish_release) && needs.build-browser-companions.outputs.packaged == 'true' && needs.release.result == 'success' }}
|
||
needs: [prepare, build-browser-companions, release]
|
||
runs-on: ${{ vars.RUNNER_UBUNTU || 'ubuntu-24.04' }}
|
||
timeout-minutes: 10
|
||
permissions:
|
||
contents: write
|
||
steps:
|
||
- name: Download Agent Browser Bridge artifacts
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||
with:
|
||
path: browser-bridge-release
|
||
pattern: browser-bridge-*
|
||
merge-multiple: false
|
||
|
||
- name: List Agent Browser Bridge artifacts
|
||
run: find browser-bridge-release -type f | sort 2>/dev/null || echo "No Agent Browser Bridge artifacts"
|
||
|
||
- name: Attach Agent Browser Bridge assets to GitHub release
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
GH_REPO: ${{ github.repository }}
|
||
run: |
|
||
find browser-bridge-release -type f \
|
||
\( \
|
||
-name "browser-bridge-chrome-v*.zip" -o \
|
||
-name "browser-bridge-safari-v*.zip" -o \
|
||
-name "browser-bridge-release-manifest-v*.json" \
|
||
\) \
|
||
-print0 | xargs -0 -r gh release upload "${{ needs.prepare.outputs.tag }}" --repo "$GH_REPO" --clobber
|
||
|
||
ota-publish:
|
||
name: Publish OTA Channel Manifest
|
||
if: ${{ success() && needs.release.result == 'success' }}
|
||
needs: [prepare, release]
|
||
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
||
permissions:
|
||
contents: write
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||
|
||
- name: Generate channel manifest
|
||
env:
|
||
TAG: ${{ needs.prepare.outputs.tag }}
|
||
VERSION: ${{ needs.prepare.outputs.version }}
|
||
BUILD_ENV: ${{ needs.prepare.outputs.env }}
|
||
run: |
|
||
CHANNEL="${BUILD_ENV}"
|
||
UPDATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||
BASE_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}"
|
||
|
||
cat > "latest-${CHANNEL}.json" <<JSON
|
||
{
|
||
"channel": "${CHANNEL}",
|
||
"version": "${VERSION}",
|
||
"updatedAt": "${UPDATED_AT}",
|
||
"platforms": {
|
||
"macos-arm64": "${BASE_URL}/macos-arm64-update.json",
|
||
"macos-x64": "${BASE_URL}/macos-x64-update.json",
|
||
"windows-x64": "${BASE_URL}/windows-x64-update.json",
|
||
"linux-x64": "${BASE_URL}/linux-x64-update.json"
|
||
}
|
||
}
|
||
JSON
|
||
|
||
echo "Channel manifest:"
|
||
cat "latest-${CHANNEL}.json"
|
||
|
||
- name: Attach channel manifest to GitHub Release
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
TAG: ${{ needs.prepare.outputs.tag }}
|
||
BUILD_ENV: ${{ needs.prepare.outputs.env }}
|
||
run: |
|
||
CHANNEL="${BUILD_ENV}"
|
||
if ! gh release view "$TAG" >/dev/null 2>&1; then
|
||
echo "::warning::No GitHub release found for $TAG; skipping OTA manifest upload."
|
||
exit 0
|
||
fi
|
||
gh release upload "$TAG" "latest-${CHANNEL}.json" --clobber
|
||
echo "OTA manifest attached: latest-${CHANNEL}.json"
|
||
echo "Update check URL: https://github.com/${{ github.repository }}/releases/download/${TAG}/latest-${CHANNEL}.json"
|
||
|
||
- name: Upload via SSH (optional)
|
||
if: ${{ env.RELEASE_UPLOAD_KEY != '' }}
|
||
env:
|
||
RELEASE_UPLOAD_KEY: ${{ secrets.RELEASE_UPLOAD_KEY }}
|
||
RELEASE_HOST_FINGERPRINT: ${{ secrets.RELEASE_HOST_FINGERPRINT }}
|
||
TAG: ${{ needs.prepare.outputs.tag }}
|
||
BUILD_ENV: ${{ needs.prepare.outputs.env }}
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "$RELEASE_UPLOAD_KEY" > ~/.ssh/release_key
|
||
chmod 600 ~/.ssh/release_key
|
||
if [[ -n "$RELEASE_HOST_FINGERPRINT" ]]; then
|
||
echo "$RELEASE_HOST_FINGERPRINT" >> ~/.ssh/known_hosts
|
||
fi
|
||
CHANNEL="${BUILD_ENV}"
|
||
DEST_PATH="/srv/releases/elizaos/${TAG}/"
|
||
rsync -av --mkpath \
|
||
-e "ssh -i ~/.ssh/release_key -o StrictHostKeyChecking=yes" \
|
||
"latest-${CHANNEL}.json" \
|
||
"releases@${RELEASE_HOST}:${DEST_PATH}"
|
||
rm -f ~/.ssh/release_key
|