mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 14:02:19 +00:00
* Enhance release workflow with build target input and improved job structure - Added a new input parameter `build_target` to specify the environment (production or staging) for the release process. - Made `release_type` input optional with a default value of `patch`. - Refactored job names and dependencies to reflect the new build target logic, including conditional steps for production and staging environments. - Introduced a `resolve` step to determine build outputs based on the selected environment, enhancing the workflow's flexibility and clarity. - Updated the `create-release` job to depend on the new `prepare-build` job, ensuring proper execution flow based on the build target. * feat(composio): enhance Composio integration with toolkit management and testing - Added `KNOWN_COMPOSIO_TOOLKITS` constant to facilitate access to available toolkits. - Implemented unit tests for `useComposioIntegrations` to ensure correct behavior during toolkit and connection fetching, including error handling scenarios. - Updated `Skills` page to utilize the new `KNOWN_COMPOSIO_TOOLKITS` for improved toolkit display logic. - Refactored hooks to handle connection errors gracefully and maintain toolkit visibility. - Enhanced backend integration by updating Composio client configuration to streamline toolkit management. * refactor(dispatch): remove channel delivery instructions for Telegram - Deleted the `channel_delivery_instructions` function, which provided response guidelines for Telegram messages. This change simplifies the message processing logic in the `process_channel_message` function by eliminating unnecessary instructions, enhancing clarity and maintainability. * refactor(composio): simplify Composio client configuration and remove toggles - Updated the `build_composio_client` function to remove unnecessary configuration checks, as Composio is always enabled when the user is signed in. - Revised the `resolve_client` function to clarify error handling related to user authentication. - Streamlined the `IntegrationsConfig` structure by removing toggles for Composio and related backend settings, ensuring a consistent configuration approach across integrations. - Adjusted tests to reflect the removal of integration toggles and focus on core API key usage. * refactor(composio): remove composio disabled state and improve error handling - Eliminated the `disabled` state from the `useComposioIntegrations` hook, as Composio is always enabled when the user is authenticated. - Updated error handling to surface backend connection issues directly, replacing previous checks for a disabled state. - Revised tests to reflect the new error handling logic, ensuring clarity in toolkit fetch error reporting. * refactor(integrations): update authentication handling for client configuration - Revised the `build_client` function to prioritize app-session JWT for user authentication, enhancing clarity in the fallback mechanism to `config.api_key`. - Improved error messages in `resolve_client` and `build_client` to provide clearer guidance on authentication issues related to session tokens. - Streamlined comments and documentation to reflect the updated authentication flow, ensuring consistency across integration components. * refactor(composio): unwrap CLI envelope for API responses - Introduced a new `unwrapCliEnvelope` function to handle the response format from the Rust side, allowing for easier access to the flat shapes defined in `./types`. - Updated `listToolkits`, `listConnections`, `listTools`, `authorize`, `deleteConnection`, and `execute` functions to utilize the new unwrapping logic, improving response handling consistency across the Composio API. - Enhanced error handling by ensuring that responses without logs pass through unchanged, maintaining backward compatibility. * refactor(skills_agent): update agent description and enhance Composio tool integration - Revised the `when_to_use` description in `agent.toml` to clarify the role of the Skills Agent as a service integration specialist, emphasizing its capability to execute both Composio and QuickJS skill tools. - Expanded the `prompt.md` documentation to detail available tool surfaces and typical Composio flow, improving clarity on how to interact with external services. - Implemented category overrides for Composio tools in `tools.rs` to ensure they are recognized as part of the Skill category, allowing proper access through the skills sub-agent. - Added tests to verify that Composio tools are correctly filtered and accessible by the skills sub-agent, ensuring robust integration and functionality. * style: apply formatter output from pre-push checks * refactor(tests): update Gmail and Notion integration tests for composio - Refactored tests for the Skills page to integrate Gmail and Notion as composio tools, enhancing the testing framework. - Removed mock data and streamlined the test setup to reflect the current state of available skills. - Updated assertions to verify the rendering of connected and disconnected states for Gmail and Notion integrations, respectively. - Improved clarity and maintainability of test cases by consolidating mock implementations and removing redundant code. * refactor(skills): update toolkit categorization and enhance test assertions - Modified the Skills component to assign categories dynamically based on toolkit metadata, improving organization of displayed tools. - Updated test cases to reflect the new categorization, ensuring accurate rendering of tools under their respective categories instead of a generic 'Other' group. - Enhanced assertions in tests to verify the presence of specific tools and their categories, improving test coverage and reliability. * refactor(skills): streamline item creation in Skills component - Simplified the item creation logic in the Skills component by removing unnecessary line breaks, enhancing code readability without altering functionality. - This change contributes to cleaner code structure and maintainability in the Skills page. * refactor(tests): enhance Gmail and Notion integration tests for improved clarity - Updated the integration tests for Gmail and Notion on the Skills page to utilize the `within` function for more precise querying of elements within their respective sections. - Improved test assertions to ensure that the connected and disconnected states are accurately verified, enhancing the reliability of the tests. - Streamlined the test setup to better reflect the current structure of the Skills component, contributing to overall test maintainability. * feat(skills): enhance Composio integration error handling and logging - Added error handling for Composio integrations in the Skills component, displaying a user-friendly message when integration status is stale or an error occurs. - Implemented logging in development mode to provide insights into the state of Composio toolkits and connections, aiding in debugging. - Updated the item rendering logic to reflect the error state, ensuring users can retry fetching integrations when an error is detected. - Enhanced tests to verify the display of error messages and the functionality of the retry mechanism, improving overall test coverage and reliability.
862 lines
35 KiB
YAML
862 lines
35 KiB
YAML
name: Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
build_target:
|
|
description: Build target environment
|
|
required: true
|
|
type: choice
|
|
default: production
|
|
options:
|
|
- production
|
|
- staging
|
|
release_type:
|
|
description: Version increment type
|
|
required: false
|
|
default: patch
|
|
type: choice
|
|
options:
|
|
- patch
|
|
- minor
|
|
- major
|
|
|
|
permissions:
|
|
contents: write
|
|
packages: write
|
|
|
|
concurrency:
|
|
group: release-${{ github.event.inputs.build_target || 'production' }}-main
|
|
cancel-in-progress: false
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Job dependency graph
|
|
#
|
|
# prepare-build
|
|
# │
|
|
# ├─── create-release
|
|
# │ │
|
|
# │ ┌────┴────────────┐
|
|
# │ │ │
|
|
# │ build-desktop build-docker
|
|
# │ (matrix: macOS, (GHCR image)
|
|
# │ linux, windows)
|
|
# │ │ │
|
|
# │ └────┬────────────┘
|
|
# │ │
|
|
# │ publish-release
|
|
# │ │
|
|
# │ notify-discord
|
|
# │
|
|
# └─── cleanup-failed-release (on failure)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
jobs:
|
|
# =========================================================================
|
|
# Phase 1: Version bump, commit, tag
|
|
# =========================================================================
|
|
prepare-build:
|
|
name: Prepare build context
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
outputs:
|
|
version: ${{ steps.resolve.outputs.version }}
|
|
tag: ${{ steps.resolve.outputs.tag }}
|
|
sha: ${{ steps.resolve.outputs.sha }}
|
|
build_ref: ${{ steps.resolve.outputs.build_ref }}
|
|
release_enabled: ${{ steps.resolve.outputs.release_enabled }}
|
|
base_url: ${{ steps.resolve.outputs.base_url }}
|
|
steps:
|
|
- name: Enforce main branch
|
|
if: github.ref != 'refs/heads/main'
|
|
run: |
|
|
echo "This workflow can only run from main. Current ref: $GITHUB_REF"
|
|
exit 1
|
|
|
|
- name: Generate GitHub App token
|
|
if: inputs.build_target == 'production'
|
|
id: app-token
|
|
uses: tibdex/github-app-token@v1
|
|
with:
|
|
app_id: ${{ secrets.XGITHUB_APP_ID }}
|
|
private_key: ${{ secrets.XGITHUB_APP_PRIVATE_KEY }}
|
|
|
|
- name: Checkout main for production release
|
|
if: inputs.build_target == 'production'
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: main
|
|
fetch-depth: 0
|
|
token: ${{ steps.app-token.outputs.token }}
|
|
|
|
- name: Checkout main for staging build
|
|
if: inputs.build_target == 'staging'
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: main
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
|
|
- name: Configure Git
|
|
if: inputs.build_target == 'production'
|
|
env:
|
|
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git remote set-url origin https://${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
|
git fetch origin --tags
|
|
git checkout main
|
|
git pull origin main --ff-only
|
|
|
|
- name: Compute next version and sync release files
|
|
if: inputs.build_target == 'production'
|
|
id: bump
|
|
run: node scripts/release/bump-version.js "${{ inputs.release_type }}"
|
|
|
|
- name: Verify release version sync
|
|
if: inputs.build_target == 'production'
|
|
run: node scripts/release/verify-version-sync.js "${{ steps.bump.outputs.version }}"
|
|
|
|
- name: Ensure tag does not already exist
|
|
if: inputs.build_target == 'production'
|
|
env:
|
|
TAG: ${{ steps.bump.outputs.tag }}
|
|
run: |
|
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
echo "Tag already exists locally: $TAG"
|
|
exit 1
|
|
fi
|
|
|
|
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
|
|
echo "Tag already exists on origin: $TAG"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Commit, push and tag
|
|
if: inputs.build_target == 'production'
|
|
id: push
|
|
env:
|
|
VERSION: ${{ steps.bump.outputs.version }}
|
|
TAG: ${{ steps.bump.outputs.tag }}
|
|
run: |
|
|
git add app/package.json app/src-tauri/tauri.conf.json app/src-tauri/Cargo.toml Cargo.toml
|
|
git commit -m "chore(release): v${VERSION}"
|
|
git push origin main
|
|
|
|
git tag -a "$TAG" -m "Release $TAG"
|
|
git push origin "$TAG"
|
|
|
|
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Resolve build outputs
|
|
id: resolve
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ inputs.build_target }}" = "production" ]; then
|
|
VERSION="${{ steps.bump.outputs.version }}"
|
|
TAG="${{ steps.bump.outputs.tag }}"
|
|
SHA="${{ steps.push.outputs.sha }}"
|
|
BUILD_REF="$TAG"
|
|
RELEASE_ENABLED="true"
|
|
BASE_URL="https://api.tinyhumans.ai/"
|
|
else
|
|
VERSION="$(node -p "require('./app/package.json').version")"
|
|
TAG=""
|
|
SHA="$(git rev-parse HEAD)"
|
|
BUILD_REF="$SHA"
|
|
RELEASE_ENABLED="false"
|
|
BASE_URL="https://staging-api.tinyhumans.ai/"
|
|
fi
|
|
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
|
echo "build_ref=$BUILD_REF" >> "$GITHUB_OUTPUT"
|
|
echo "release_enabled=$RELEASE_ENABLED" >> "$GITHUB_OUTPUT"
|
|
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
|
|
|
|
# =========================================================================
|
|
# Phase 2: Create draft GitHub release
|
|
# =========================================================================
|
|
create-release:
|
|
name: Create GitHub release
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
if: needs.prepare-build.outputs.release_enabled == 'true'
|
|
needs: prepare-build
|
|
outputs:
|
|
release_id: ${{ steps.create.outputs.release_id }}
|
|
upload_url: ${{ steps.create.outputs.upload_url }}
|
|
steps:
|
|
- name: Create draft release with generated notes
|
|
id: create
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const tag = '${{ needs.prepare-build.outputs.tag }}';
|
|
const version = '${{ needs.prepare-build.outputs.version }}';
|
|
const target = '${{ needs.prepare-build.outputs.sha }}';
|
|
|
|
const { owner, repo } = context.repo;
|
|
|
|
try {
|
|
await github.rest.repos.getReleaseByTag({ owner, repo, tag });
|
|
core.setFailed(`Release already exists for ${tag}`);
|
|
return;
|
|
} catch (error) {
|
|
if (error.status !== 404) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const release = await github.rest.repos.createRelease({
|
|
owner,
|
|
repo,
|
|
tag_name: tag,
|
|
target_commitish: target,
|
|
name: `OpenHuman v${version}`,
|
|
draft: true,
|
|
prerelease: false,
|
|
generate_release_notes: true,
|
|
});
|
|
|
|
core.setOutput('release_id', String(release.data.id));
|
|
core.setOutput('upload_url', release.data.upload_url);
|
|
|
|
# =========================================================================
|
|
# Phase 3a: Build desktop artifacts (parallel matrix)
|
|
# =========================================================================
|
|
build-desktop:
|
|
name: "Desktop: ${{ matrix.settings.artifact_suffix }}"
|
|
needs: [prepare-build, create-release]
|
|
if: >-
|
|
always()
|
|
&& needs.prepare-build.result == 'success'
|
|
&& (needs.prepare-build.outputs.release_enabled != 'true'
|
|
|| needs.create-release.result == 'success')
|
|
runs-on: ${{ matrix.settings.platform }}
|
|
environment: Production
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
settings:
|
|
- platform: macos-latest
|
|
args: --target aarch64-apple-darwin
|
|
target: aarch64-apple-darwin
|
|
artifact_suffix: aarch64-apple-darwin
|
|
- platform: macos-latest
|
|
args: --target x86_64-apple-darwin
|
|
target: x86_64-apple-darwin
|
|
artifact_suffix: x86_64-apple-darwin
|
|
- platform: ubuntu-22.04
|
|
args: --target x86_64-unknown-linux-gnu
|
|
target: x86_64-unknown-linux-gnu
|
|
artifact_suffix: ubuntu
|
|
- platform: windows-latest
|
|
args: --target x86_64-pc-windows-msvc
|
|
target: x86_64-pc-windows-msvc
|
|
artifact_suffix: windows
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
steps:
|
|
- name: Checkout build ref
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare-build.outputs.build_ref }}
|
|
fetch-depth: 1
|
|
submodules: true
|
|
|
|
- name: Set Xcode version
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
uses: maxim-lobanov/setup-xcode@v1
|
|
with:
|
|
xcode-version: latest-stable
|
|
|
|
- name: Setup Node.js 24.x
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24.x
|
|
cache: yarn
|
|
|
|
- name: Install Rust (rust-toolchain.toml)
|
|
uses: dtolnay/rust-toolchain@1.93.0
|
|
with:
|
|
targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
|
|
|
- name: Install Tauri dependencies (ubuntu only)
|
|
if: matrix.settings.platform == 'ubuntu-22.04'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf cmake libasound2-dev libxdo-dev libxtst-dev libx11-dev libxi-dev libevdev-dev libssl-dev libclang-dev
|
|
|
|
# Skip first 7 lines of Cargo.lock (workspace package version bumps) so the key tracks dependency changes only
|
|
- name: Cargo.lock fingerprint (deps only)
|
|
id: cargo-lock-fingerprint
|
|
shell: bash
|
|
run: |
|
|
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache Cargo registry and git sources
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }}
|
|
restore-keys: |
|
|
${{ runner.os }}-cargo-registry-
|
|
|
|
- name: Install dependencies
|
|
run: yarn install --frozen-lockfile
|
|
|
|
- name: Validate signing prerequisites
|
|
shell: bash
|
|
env:
|
|
MATRIX_PLATFORM: ${{ matrix.settings.platform }}
|
|
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY || vars.UPDATER_PUBLIC_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }}
|
|
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
if [ -z "$UPDATER_PUBLIC_KEY" ]; then
|
|
echo "Missing UPDATER_PUBLIC_KEY (set as secret or repo/environment variable)."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
|
|
echo "Missing TAURI_SIGNING_PRIVATE_KEY (or fallback UPDATER_PRIVATE_KEY)."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$MATRIX_PLATFORM" = "macos-latest" ]; then
|
|
for var in APPLE_CERTIFICATE_BASE64 APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do
|
|
if [ -z "${!var}" ]; then
|
|
echo "Missing required macOS signing secret: $var"
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
- name: Define Tauri configuration overrides
|
|
id: config-overrides
|
|
uses: actions/github-script@v7
|
|
env:
|
|
BASE_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY || vars.UPDATER_PUBLIC_KEY }}
|
|
UPDATER_ENDPOINT: ${{ vars.UPDATER_ENDPOINT }}
|
|
UPDATER_REPO: tinyhumansai/openhuman
|
|
WITH_UPDATER: "true"
|
|
with:
|
|
script: |
|
|
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
|
|
const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///';
|
|
const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`;
|
|
const { default: prepareTauriConfig } = await import(moduleUrl);
|
|
const config = prepareTauriConfig();
|
|
core.setOutput('json', JSON.stringify(config));
|
|
|
|
- name: Build frontend
|
|
run: yarn build
|
|
env:
|
|
NODE_ENV: production
|
|
VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
|
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
|
# OAuth guardrails (#365): block openhuman://oauth/success on outdated desktop builds.
|
|
VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }}
|
|
VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }}
|
|
|
|
- name: Resolve core manifest and binary names
|
|
id: core-paths
|
|
shell: bash
|
|
run: |
|
|
if [ -f "openhuman_core/Cargo.toml" ]; then
|
|
CORE_DIR="openhuman_core"
|
|
elif [ -f "rust-core/Cargo.toml" ]; then
|
|
CORE_DIR="rust-core"
|
|
elif [ -f "Cargo.toml" ] && grep -q '^name = "openhuman"' Cargo.toml; then
|
|
CORE_DIR="."
|
|
else
|
|
echo "No core Cargo manifest found (expected root Cargo.toml with openhuman, openhuman_core/Cargo.toml, or rust-core/Cargo.toml)"
|
|
exit 1
|
|
fi
|
|
|
|
SIDE_CAR_BASE="$(node -e "const fs=require('fs');const c=JSON.parse(fs.readFileSync('app/src-tauri/tauri.conf.json','utf8'));const b=(c.bundle&&Array.isArray(c.bundle.externalBin)&&c.bundle.externalBin[0])||'binaries/openhuman-core';process.stdout.write(String(b).split('/').pop());")"
|
|
CORE_BIN_NAME="${SIDE_CAR_BASE}"
|
|
|
|
echo "core_dir=$CORE_DIR" >> "$GITHUB_OUTPUT"
|
|
echo "core_manifest=$CORE_DIR/Cargo.toml" >> "$GITHUB_OUTPUT"
|
|
# Cargo workspace: release artifacts are under repo root target/, not <member>/target/
|
|
echo "core_target_dir=target/$MATRIX_TARGET/release" >> "$GITHUB_OUTPUT"
|
|
echo "core_bin_name=$CORE_BIN_NAME" >> "$GITHUB_OUTPUT"
|
|
echo "sidecar_base=$SIDE_CAR_BASE" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
MATRIX_TARGET: ${{ matrix.settings.target }}
|
|
|
|
- name: Build sidecar core binary
|
|
shell: bash
|
|
run: |
|
|
cargo build \
|
|
--manifest-path "$CORE_MANIFEST" \
|
|
--release \
|
|
--target "$MATRIX_TARGET" \
|
|
--bin "$CORE_BIN_NAME"
|
|
env:
|
|
MATRIX_TARGET: ${{ matrix.settings.target }}
|
|
CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }}
|
|
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
|
|
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
|
|
|
|
- name: Stage sidecar for Tauri bundler
|
|
shell: bash
|
|
run: |
|
|
bash scripts/release/stage-sidecar.sh \
|
|
"${{ matrix.settings.target }}" \
|
|
"${{ steps.core-paths.outputs.core_target_dir }}" \
|
|
"${{ steps.core-paths.outputs.core_bin_name }}" \
|
|
"${{ steps.core-paths.outputs.sidecar_base }}"
|
|
|
|
- name: Resolve standalone core CLI artifact path
|
|
id: cli-paths
|
|
shell: bash
|
|
run: |
|
|
BASE_DIR="$CORE_TARGET_DIR"
|
|
EXE_SUFFIX=""
|
|
if [[ "$MATRIX_TARGET" == *"windows"* ]]; then
|
|
EXE_SUFFIX=".exe"
|
|
fi
|
|
echo "base_dir=$BASE_DIR" >> "$GITHUB_OUTPUT"
|
|
echo "cli_path=$BASE_DIR/${CORE_BIN_NAME}${EXE_SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
MATRIX_TARGET: ${{ matrix.settings.target }}
|
|
CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }}
|
|
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
|
|
|
|
- name: Build, package and upload to release
|
|
if: needs.prepare-build.outputs.release_enabled == 'true'
|
|
uses: tauri-apps/tauri-action@v0.6.2
|
|
id: tauri-build
|
|
env:
|
|
# NOTE: APPLE_CERTIFICATE, APPLE_SIGNING_IDENTITY, APPLE_ID, etc. are
|
|
# intentionally omitted so tauri-action builds WITHOUT signing.
|
|
# Signing + notarization are handled in a dedicated step afterwards
|
|
# where we sign everything with hardened runtime + entitlements
|
|
# (required by Apple notarization).
|
|
BASE_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }}
|
|
WITH_UPDATER: "true"
|
|
# Must match standalone "Build frontend" so tauri-action's beforeBuildCommand embeds the gate.
|
|
VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }}
|
|
VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }}
|
|
with:
|
|
projectPath: app
|
|
args: -c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }}
|
|
includeDebug: false
|
|
includeRelease: true
|
|
releaseId: ${{ needs.create-release.outputs.release_id }}
|
|
owner: tinyhumansai
|
|
repo: openhuman
|
|
|
|
- name: Build and package staging artifacts
|
|
if: needs.prepare-build.outputs.release_enabled != 'true'
|
|
shell: bash
|
|
env:
|
|
BASE_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }}
|
|
WITH_UPDATER: "true"
|
|
VITE_MINIMUM_SUPPORTED_APP_VERSION: ${{ vars.VITE_MINIMUM_SUPPORTED_APP_VERSION }}
|
|
VITE_LATEST_APP_DOWNLOAD_URL: ${{ vars.VITE_LATEST_APP_DOWNLOAD_URL }}
|
|
run: |
|
|
yarn --cwd app tauri build \
|
|
-c '${{ steps.config-overrides.outputs.json }}' \
|
|
${{ matrix.settings.args }}
|
|
|
|
- name: Locate macOS .app bundle
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
id: locate-app
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# tauri-action with projectPath: app may output to either location
|
|
# depending on workspace config — search both
|
|
APP_PATH=""
|
|
for candidate in \
|
|
"app/src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app" \
|
|
"target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app"; do
|
|
if [ -d "$candidate" ]; then
|
|
APP_PATH="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Fallback: search for it
|
|
if [ -z "$APP_PATH" ]; then
|
|
APP_PATH="$(find . -path '*/release/bundle/macos/OpenHuman.app' -type d 2>/dev/null | head -1)"
|
|
fi
|
|
|
|
if [ -z "$APP_PATH" ]; then
|
|
echo "ERROR: Could not find OpenHuman.app bundle anywhere"
|
|
find . -name 'OpenHuman.app' -type d 2>/dev/null || true
|
|
exit 1
|
|
fi
|
|
|
|
BUNDLE_DIR="$(dirname "$(dirname "$APP_PATH")")"
|
|
echo "app_path=$APP_PATH" >> "$GITHUB_OUTPUT"
|
|
echo "bundle_dir=$BUNDLE_DIR" >> "$GITHUB_OUTPUT"
|
|
echo "Found .app at: $APP_PATH"
|
|
echo "Bundle dir: $BUNDLE_DIR"
|
|
|
|
- name: Re-sign sidecar with hardened runtime and notarize
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
shell: bash
|
|
env:
|
|
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
bash scripts/release/sign-and-notarize-macos.sh \
|
|
"${{ steps.locate-app.outputs.app_path }}" \
|
|
"app/src-tauri/entitlements.sidecar.plist"
|
|
|
|
- name: Re-package DMG after notarization
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
shell: bash
|
|
env:
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
bash scripts/release/repackage-dmg.sh \
|
|
"${{ steps.locate-app.outputs.app_path }}" \
|
|
"${{ steps.locate-app.outputs.bundle_dir }}"
|
|
|
|
- name: Re-upload notarized macOS artifacts to release
|
|
if: matrix.settings.platform == 'macos-latest' && needs.prepare-build.outputs.release_enabled == 'true'
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
|
|
run: |
|
|
bash scripts/release/upload-macos-artifacts.sh \
|
|
"${{ steps.locate-app.outputs.app_path }}" \
|
|
"${{ steps.locate-app.outputs.bundle_dir }}" \
|
|
"${{ needs.prepare-build.outputs.version }}" \
|
|
"${{ matrix.settings.target }}"
|
|
|
|
- name: Verify macOS app bundle sidecar layout
|
|
if: matrix.settings.platform == 'macos-latest'
|
|
shell: bash
|
|
run: |
|
|
APP_PATH="${{ steps.locate-app.outputs.app_path }}"
|
|
echo "Inspecting bundle at: $APP_PATH"
|
|
ls -la "$APP_PATH/Contents/MacOS"
|
|
ls -la "$APP_PATH/Contents/Resources" | grep openhuman || true
|
|
|
|
# Sidecar may be in MacOS/ or Resources/ depending on Tauri version
|
|
FOUND=false
|
|
if ls "$APP_PATH/Contents/MacOS"/openhuman* 2>/dev/null | grep -qv OpenHuman; then
|
|
echo "Sidecar found in Contents/MacOS/"
|
|
FOUND=true
|
|
fi
|
|
if ls "$APP_PATH/Contents/Resources"/openhuman-* 2>/dev/null; then
|
|
echo "Sidecar found in Contents/Resources/"
|
|
FOUND=true
|
|
fi
|
|
if [ "$FOUND" = "false" ]; then
|
|
echo "WARNING: Sidecar binary not found in expected locations"
|
|
fi
|
|
|
|
# Verify notarization staple
|
|
echo "Checking staple..."
|
|
xcrun stapler validate "$APP_PATH" || echo "WARNING: Staple validation failed"
|
|
|
|
- name: Package CLI tarball and upload to release (unix)
|
|
if: matrix.settings.platform != 'windows-latest' && needs.prepare-build.outputs.release_enabled == 'true'
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# For macOS, prefer the notarized binary from inside the .app bundle
|
|
if [[ "${{ matrix.settings.platform }}" == "macos-latest" ]]; then
|
|
APP_PATH="${{ steps.locate-app.outputs.app_path }}"
|
|
BIN=$(find "$APP_PATH/Contents/MacOS" -maxdepth 1 -name "openhuman-core-*" \
|
|
! -name "*.sig" 2>/dev/null | head -1 || true)
|
|
[[ -z "$BIN" ]] && BIN=$(find "$APP_PATH/Contents/Resources" -maxdepth 1 \
|
|
-name "openhuman-core-*" ! -name "*.sig" 2>/dev/null | head -1 || true)
|
|
if [[ -z "$BIN" ]]; then
|
|
echo "[pkg] Falling back to target dir binary (no notarized sidecar found)"
|
|
BIN="${{ steps.cli-paths.outputs.cli_path }}"
|
|
fi
|
|
else
|
|
BIN="${{ steps.cli-paths.outputs.cli_path }}"
|
|
fi
|
|
|
|
bash scripts/release/package-cli-tarball.sh \
|
|
"$BIN" \
|
|
"${{ needs.prepare-build.outputs.version }}" \
|
|
"${{ matrix.settings.target }}"
|
|
|
|
- name: Package CLI zip and upload to release (windows)
|
|
if: matrix.settings.platform == 'windows-latest' && needs.prepare-build.outputs.release_enabled == 'true'
|
|
shell: pwsh
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
$Version = "${{ needs.prepare-build.outputs.version }}"
|
|
$Target = "${{ matrix.settings.target }}"
|
|
$BinPath = "${{ steps.cli-paths.outputs.cli_path }}"
|
|
$ZipName = "openhuman-core-${Version}-${Target}.zip"
|
|
|
|
$Work = New-Item -ItemType Directory -Path (Join-Path $env:TEMP "cli-pkg-$(Get-Random)")
|
|
Copy-Item $BinPath (Join-Path $Work.FullName "openhuman-core.exe")
|
|
Compress-Archive -Path (Join-Path $Work.FullName "openhuman-core.exe") -DestinationPath $ZipName
|
|
|
|
$Hash = (Get-FileHash -Path $ZipName -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
Set-Content -Path "${ZipName}.sha256" -Value $Hash -NoNewline
|
|
|
|
Write-Host "[package-cli] Created $ZipName (sha256: $Hash)"
|
|
|
|
gh release upload "v${Version}" $ZipName "${ZipName}.sha256" --repo tinyhumansai/openhuman --clobber
|
|
Write-Host "[package-cli] Uploaded $ZipName to v${Version}"
|
|
|
|
- name: Upload staging desktop bundles
|
|
if: needs.prepare-build.outputs.release_enabled != 'true'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: desktop-bundles-${{ matrix.settings.platform }}-${{ matrix.settings.artifact_suffix }}
|
|
path: |
|
|
app/src-tauri/target/${{ matrix.settings.target }}/release/bundle/**
|
|
target/${{ matrix.settings.target }}/release/bundle/**
|
|
|
|
- name: Upload standalone CLI artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: standalone-bins-${{ matrix.settings.platform }}-${{ matrix.settings.artifact_suffix }}
|
|
path: |
|
|
${{ steps.cli-paths.outputs.cli_path }}
|
|
|
|
# =========================================================================
|
|
# Phase 3b: Build & push Docker image (runs parallel with build-desktop)
|
|
# =========================================================================
|
|
build-docker:
|
|
name: "Docker: build and push"
|
|
needs: [prepare-build, create-release]
|
|
if: >-
|
|
always()
|
|
&& needs.prepare-build.result == 'success'
|
|
&& (needs.prepare-build.outputs.release_enabled != 'true'
|
|
|| needs.create-release.result == 'success')
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: tinyhumansai/openhuman-core
|
|
steps:
|
|
- name: Checkout build ref
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare-build.outputs.build_ref }}
|
|
fetch-depth: 1
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# TODO: Uncomment this when we have a staging tag
|
|
# - name: Build and push staging tag
|
|
# uses: docker/build-push-action@v6
|
|
# with:
|
|
# context: .
|
|
# file: Dockerfile
|
|
# push: true
|
|
# tags: |
|
|
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:staging-${{ needs.prepare-release.outputs.tag }}
|
|
# cache-from: type=gha
|
|
# cache-to: type=gha,mode=max
|
|
# labels: |
|
|
# org.opencontainers.image.title=openhuman-core
|
|
# org.opencontainers.image.description=OpenHuman core JSON-RPC server
|
|
# org.opencontainers.image.version=${{ needs.prepare-release.outputs.version }}
|
|
# org.opencontainers.image.source=https://github.com/tinyhumansai/openhuman
|
|
# org.opencontainers.image.revision=${{ needs.prepare-release.outputs.sha }}
|
|
|
|
# =========================================================================
|
|
# Phase 4: Publish the draft release (waits for ALL build phases)
|
|
# =========================================================================
|
|
publish-release:
|
|
name: Publish draft release
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
needs: [prepare-build, create-release, build-desktop, build-docker]
|
|
if: >-
|
|
needs.prepare-build.outputs.release_enabled == 'true'
|
|
&& needs.build-desktop.result == 'success'
|
|
&& needs.build-docker.result == 'success'
|
|
steps:
|
|
- name: Validate required installer assets exist
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const releaseId = Number('${{ needs.create-release.outputs.release_id }}');
|
|
const { owner, repo } = context.repo;
|
|
const { data: assets } = await github.rest.repos.listReleaseAssets({
|
|
owner,
|
|
repo,
|
|
release_id: releaseId,
|
|
per_page: 100,
|
|
});
|
|
|
|
const names = assets.map((a) => a.name);
|
|
const requiredPatterns = [
|
|
/OpenHuman_.*_aarch64\.dmg$/,
|
|
/OpenHuman_.*_x64\.dmg$/,
|
|
/OpenHuman_.*_amd64\.AppImage$/,
|
|
/OpenHuman_.*_amd64\.deb$/,
|
|
/(OpenHuman_.*_x64-setup\.exe$|OpenHuman_.*_x64.*\.msi$)/,
|
|
];
|
|
|
|
const missing = requiredPatterns.filter((pattern) => !names.some((name) => pattern.test(name)));
|
|
if (missing.length > 0) {
|
|
core.setFailed(`Missing required installer assets. Got: ${names.join(', ')}`);
|
|
return;
|
|
}
|
|
core.info('All required installer assets are present.');
|
|
|
|
# - name: Promote Docker image from staging to release tags
|
|
# env:
|
|
# REGISTRY: ghcr.io
|
|
# IMAGE_NAME: tinyhumansai/openhuman-core
|
|
# TAG: ${{ needs.prepare-release.outputs.tag }}
|
|
# run: |
|
|
# # Log in to GHCR
|
|
# echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
|
|
|
# STAGING="${REGISTRY}/${IMAGE_NAME}:staging-${TAG}"
|
|
# VERSIONED="${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
|
# LATEST="${REGISTRY}/${IMAGE_NAME}:latest"
|
|
|
|
# echo "Promoting ${STAGING} → ${VERSIONED}, ${LATEST}"
|
|
# docker buildx imagetools create --tag "${VERSIONED}" --tag "${LATEST}" "${STAGING}"
|
|
|
|
# - name: Append Docker pull instructions to release notes
|
|
# env:
|
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
# TAG: ${{ needs.prepare-release.outputs.tag }}
|
|
# IMAGE_NAME: tinyhumansai/openhuman-core
|
|
# run: |
|
|
# gh release view "${TAG}" --repo tinyhumansai/openhuman --json body -q .body > /tmp/body.md
|
|
# printf '\n---\n\n### Docker\n\n```\ndocker pull ghcr.io/%s:%s\ndocker run -p 7788:7788 --env-file .env ghcr.io/%s:%s\n```\n' \
|
|
# "${IMAGE_NAME}" "${TAG}" "${IMAGE_NAME}" "${TAG}" >> /tmp/body.md
|
|
# gh release edit "${TAG}" --repo tinyhumansai/openhuman --notes-file /tmp/body.md
|
|
|
|
- name: Publish release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const releaseId = Number('${{ needs.create-release.outputs.release_id }}');
|
|
await github.rest.repos.updateRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
release_id: releaseId,
|
|
draft: false,
|
|
});
|
|
core.info(`Published release ${releaseId}`);
|
|
|
|
# =========================================================================
|
|
# Cleanup: remove draft release + tag if ANY build phase failed
|
|
# =========================================================================
|
|
cleanup-failed-release:
|
|
name: Remove release and tag if build failed
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
needs: [prepare-build, create-release, build-desktop, build-docker]
|
|
if: >-
|
|
always()
|
|
&& needs.prepare-build.outputs.release_enabled == 'true'
|
|
&& needs.create-release.result == 'success'
|
|
&& (needs.build-desktop.result == 'failure' || needs.build-desktop.result == 'cancelled'
|
|
|| needs.build-docker.result == 'failure' || needs.build-docker.result == 'cancelled')
|
|
steps:
|
|
- name: Delete GitHub release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const releaseId = Number('${{ needs.create-release.outputs.release_id }}');
|
|
|
|
if (!Number.isFinite(releaseId) || releaseId <= 0) {
|
|
core.setFailed('Invalid or missing release_id; cannot delete release.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await github.rest.repos.deleteRelease({ owner, repo, release_id: releaseId });
|
|
core.info(`Deleted release ${releaseId}`);
|
|
} catch (e) {
|
|
core.warning(`deleteRelease failed: ${e.message}`);
|
|
}
|
|
|
|
- name: Delete remote tag
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const tag = '${{ needs.prepare-build.outputs.tag }}';
|
|
|
|
try {
|
|
await github.rest.git.deleteRef({ owner, repo, ref: `tags/${tag}` });
|
|
core.info(`Deleted remote tag ${tag}`);
|
|
} catch (e) {
|
|
if (e.status === 404) {
|
|
core.info(`Tag ${tag} already absent on remote`);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
- name: Delete staging Docker image
|
|
continue-on-error: true
|
|
env:
|
|
TAG: ${{ needs.prepare-build.outputs.tag }}
|
|
run: |
|
|
PACKAGE="openhuman-core"
|
|
STAGING_TAG="staging-${TAG}"
|
|
echo "Attempting to delete staging Docker tag: ${STAGING_TAG}"
|
|
# List package versions and find the staging tag
|
|
VERSION_ID="$(gh api \
|
|
-H "Accept: application/vnd.github+json" \
|
|
"/orgs/tinyhumansai/packages/container/${PACKAGE}/versions" \
|
|
--paginate --jq ".[] | select(.metadata.container.tags[] == \"${STAGING_TAG}\") | .id" 2>/dev/null | head -1)"
|
|
if [ -n "$VERSION_ID" ]; then
|
|
gh api -X DELETE "/orgs/tinyhumansai/packages/container/${PACKAGE}/versions/${VERSION_ID}" || true
|
|
echo "Deleted staging image version ${VERSION_ID}"
|
|
else
|
|
echo "Staging tag ${STAGING_TAG} not found or already deleted"
|
|
fi
|