mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
1042 lines
44 KiB
YAML
1042 lines
44 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 --bundles deb
|
|
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 }}
|
|
# Keep in sync with DEFAULT_TELEGRAM_BOT_USERNAME_* in channels/controllers/ops.rs
|
|
OPENHUMAN_TELEGRAM_BOT_USERNAME: ${{ inputs.build_target == 'staging' && 'alphahumantest_bot' || 'openhumanaibot' }}
|
|
VITE_TELEGRAM_BOT_USERNAME: ${{ inputs.build_target == 'staging' && 'alphahumantest_bot' || 'openhumanaibot' }}
|
|
steps:
|
|
- name: Checkout build ref
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare-build.outputs.build_ref }}
|
|
fetch-depth: 1
|
|
submodules: recursive
|
|
|
|
- name: Configure staging app environment
|
|
if: inputs.build_target == 'staging'
|
|
shell: bash
|
|
run: |
|
|
echo "OPENHUMAN_APP_ENV=staging" >> "$GITHUB_ENV"
|
|
echo "VITE_OPENHUMAN_APP_ENV=staging" >> "$GITHUB_ENV"
|
|
echo "OPENHUMAN_TELEGRAM_BOT_USERNAME=alphahumantest_bot" >> "$GITHUB_ENV"
|
|
echo "VITE_TELEGRAM_BOT_USERNAME=alphahumantest_bot" >> "$GITHUB_ENV"
|
|
|
|
- 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 libayatana-appindicator3-dev librsvg2-dev \
|
|
patchelf cmake libasound2-dev libxdo-dev libxtst-dev libx11-dev libxi-dev \
|
|
libevdev-dev libssl-dev libclang-dev \
|
|
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 \
|
|
libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
|
|
libgbm1 libpango-1.0-0 libcairo2 libatspi2.0-0 libxshmfence1 libu2f-udev
|
|
|
|
- name: Dump missing shared libs (debug)
|
|
if: matrix.settings.platform == 'ubuntu-22.04'
|
|
shell: bash
|
|
run: |
|
|
set +e
|
|
for BIN in \
|
|
app/src-tauri/target/${{ matrix.settings.target }}/release/OpenHuman \
|
|
target/${{ matrix.settings.target }}/release/OpenHuman; do
|
|
if [ -x "$BIN" ]; then
|
|
echo "ldd $BIN"
|
|
ldd "$BIN" | grep 'not found' || echo " all resolved"
|
|
fi
|
|
done
|
|
|
|
# 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-
|
|
|
|
# CEF is now the default runtime — cef-dll-sys + vendored tauri-cli
|
|
# auto-download the ~400MB Chromium distribution on first build. Cache
|
|
# it per-OS across runs. Default path is `dirs::cache_dir()/tauri-cef`
|
|
# (macOS: ~/Library/Caches; Linux: ~/.cache; Windows: %LOCALAPPDATA%).
|
|
- name: Cache CEF binary distribution (unix)
|
|
if: matrix.settings.platform != 'windows-latest'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/Library/Caches/tauri-cef
|
|
~/.cache/tauri-cef
|
|
key: cef-${{ matrix.settings.target }}-${{ hashFiles('app/src-tauri/Cargo.toml') }}
|
|
restore-keys: |
|
|
cef-${{ matrix.settings.target }}-
|
|
|
|
- name: Cache CEF binary distribution (windows)
|
|
if: matrix.settings.platform == 'windows-latest'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/AppData/Local/tauri-cef
|
|
key: cef-${{ matrix.settings.target }}-${{ hashFiles('app/src-tauri/Cargo.toml') }}
|
|
restore-keys: |
|
|
cef-${{ matrix.settings.target }}-
|
|
|
|
# Upstream @tauri-apps/cli (used by tauri-action) doesn't bundle CEF
|
|
# framework files into the produced installer. Only the fork at
|
|
# vendor/tauri-cef does. Build and install that CLI so `cargo tauri
|
|
# build` invokes the cef-aware bundler.
|
|
- name: Cache vendored tauri-cli binary (unix)
|
|
if: matrix.settings.platform != 'windows-latest'
|
|
id: tauri-cli-cache-unix
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.cargo/bin/cargo-tauri
|
|
key: vendored-tauri-cli-${{ runner.os }}-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }}
|
|
|
|
- name: Cache vendored tauri-cli binary (windows)
|
|
if: matrix.settings.platform == 'windows-latest'
|
|
id: tauri-cli-cache-windows
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.cargo/bin/cargo-tauri.exe
|
|
key: vendored-tauri-cli-${{ runner.os }}-${{ hashFiles('app/src-tauri/vendor/tauri-cef/crates/tauri-cli/Cargo.toml') }}
|
|
|
|
- name: Install vendored tauri-cli (cef-aware bundler)
|
|
if: (matrix.settings.platform == 'windows-latest' && steps.tauri-cli-cache-windows.outputs.cache-hit != 'true') || (matrix.settings.platform != 'windows-latest' && steps.tauri-cli-cache-unix.outputs.cache-hit != 'true')
|
|
shell: bash
|
|
run: cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli
|
|
|
|
- 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
|
|
NODE_OPTIONS: --max-old-space-size=4096
|
|
VITE_OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || '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 }}
|
|
# Sentry release tracking (#405): baked into the bundle so every event
|
|
# groups under the same release, and @sentry/vite-plugin uploads the
|
|
# matching source maps when SENTRY_AUTH_TOKEN is present.
|
|
VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }}
|
|
SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ needs.prepare-build.outputs.sha }}
|
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
|
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_FRONTEND }}
|
|
|
|
- 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_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }}
|
|
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
|
|
# Sentry release tracking (#405): `option_env!("OPENHUMAN_BUILD_SHA")`
|
|
# in src/main.rs bakes the short SHA into the release tag
|
|
# (`openhuman@<version>+<sha>`) so core events match the frontend.
|
|
OPENHUMAN_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }}
|
|
|
|
- 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 }}
|
|
|
|
# CEF is the default runtime — we build via the vendored tauri-cli so
|
|
# the bundler copies Chromium Embedded Framework files into the .app /
|
|
# .deb / .AppImage / .msi / NSIS setup. macOS signing is intentionally
|
|
# skipped here and handled by the dedicated re-sign + notarize step
|
|
# below (hardened runtime + entitlements are required for notarization).
|
|
- name: Build and package Tauri app (CEF, vendored CLI)
|
|
id: tauri-build
|
|
shell: bash
|
|
working-directory: app
|
|
env:
|
|
BASE_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }}
|
|
VITE_OPENHUMAN_APP_ENV: ${{ inputs.build_target == 'staging' && 'staging' || 'production' }}
|
|
VITE_BACKEND_URL: ${{ needs.prepare-build.outputs.base_url }}
|
|
# Re-declare Vite env so tauri.conf.json's `beforeBuildCommand`
|
|
# (`vite build`) bakes Sentry + debug flags into the final bundle.
|
|
# The earlier explicit `yarn build` step ran with these, but the
|
|
# rebuild triggered here would strip them and ship a DSN-less UI.
|
|
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
|
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
|
# Sentry release tracking (#405) — must match the first `yarn build`
|
|
# so source maps uploaded there still resolve against the final bundle
|
|
# produced by the tauri-driven rebuild.
|
|
VITE_BUILD_SHA: ${{ needs.prepare-build.outputs.sha }}
|
|
SENTRY_RELEASE: openhuman@${{ needs.prepare-build.outputs.version }}+${{ needs.prepare-build.outputs.sha }}
|
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
|
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT_FRONTEND }}
|
|
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 }}
|
|
TAURI_CONFIG_OVERRIDE: ${{ steps.config-overrides.outputs.json }}
|
|
MATRIX_ARGS: ${{ matrix.settings.args }}
|
|
NODE_OPTIONS: --max-old-space-size=4096
|
|
run: |
|
|
cargo tauri build -c "$TAURI_CONFIG_OVERRIDE" $MATRIX_ARGS
|
|
|
|
# tauri-action previously uploaded non-macOS installer assets directly
|
|
# to the release. Replicate that for Linux + Windows here (macOS goes
|
|
# through the re-sign + notarize path below and uploads separately).
|
|
- name: Upload non-macOS installers to release
|
|
if: needs.prepare-build.outputs.release_enabled == 'true' && matrix.settings.platform != 'macos-latest'
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ needs.prepare-build.outputs.tag }}
|
|
MATRIX_TARGET: ${{ matrix.settings.target }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
BUNDLE_ROOTS=(
|
|
"app/src-tauri/target/${MATRIX_TARGET}/release/bundle"
|
|
"target/${MATRIX_TARGET}/release/bundle"
|
|
)
|
|
UPLOAD=()
|
|
for root in "${BUNDLE_ROOTS[@]}"; do
|
|
[ -d "$root" ] || continue
|
|
for f in \
|
|
"$root"/deb/*.deb \
|
|
"$root"/appimage/*.AppImage \
|
|
"$root"/appimage/*.AppImage.sig \
|
|
"$root"/msi/*.msi \
|
|
"$root"/msi/*.msi.sig \
|
|
"$root"/nsis/*-setup.exe \
|
|
"$root"/nsis/*-setup.exe.sig; do
|
|
[ -e "$f" ] && UPLOAD+=("$f")
|
|
done
|
|
done
|
|
if [ ${#UPLOAD[@]} -eq 0 ]; then
|
|
echo "No installer artifacts found for ${MATRIX_TARGET}"
|
|
exit 1
|
|
fi
|
|
echo "Uploading:"
|
|
printf ' %s\n' "${UPLOAD[@]}"
|
|
gh release upload "$TAG" "${UPLOAD[@]}" --repo tinyhumansai/openhuman --clobber
|
|
|
|
- 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 }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY || secrets.UPDATER_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
|
|
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 3c: Generate and upload latest.json for the Tauri auto-updater.
|
|
# Runs after every platform has uploaded its updater artifact (.sig files
|
|
# from createUpdaterArtifacts) so the manifest can reference all four
|
|
# platform entries. Production-only — staging builds don't feed the public
|
|
# updater endpoint.
|
|
# =========================================================================
|
|
publish-updater-manifest:
|
|
name: Publish updater manifest (latest.json)
|
|
needs: [prepare-build, create-release, build-desktop]
|
|
if: >-
|
|
needs.prepare-build.outputs.release_enabled == 'true'
|
|
&& needs.build-desktop.result == 'success'
|
|
runs-on: ubuntu-latest
|
|
environment: Production
|
|
steps:
|
|
- name: Checkout build ref
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.prepare-build.outputs.build_ref }}
|
|
fetch-depth: 1
|
|
|
|
- name: Generate and upload latest.json
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ needs.prepare-build.outputs.tag }}
|
|
VERSION: ${{ needs.prepare-build.outputs.version }}
|
|
REPO: tinyhumansai/openhuman
|
|
run: bash scripts/release/publish-updater-manifest.sh
|
|
|
|
# =========================================================================
|
|
# 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,
|
|
publish-updater-manifest,
|
|
]
|
|
if: >-
|
|
needs.prepare-build.outputs.release_enabled == 'true'
|
|
&& needs.build-desktop.result == 'success'
|
|
&& needs.build-docker.result == 'success'
|
|
&& needs.publish-updater-manifest.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_.*_x64-setup\.exe$|OpenHuman_.*_x64.*\.msi$)/,
|
|
// Auto-updater manifest — without this, installed clients can't
|
|
// discover new releases via plugins.updater.endpoints.
|
|
/^latest\.json$/,
|
|
];
|
|
|
|
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,
|
|
publish-updater-manifest,
|
|
]
|
|
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'
|
|
|| needs.publish-updater-manifest.result == 'failure' || needs.publish-updater-manifest.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
|