mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 05:12:33 +00:00
- Updated the release workflow to include signing of macOS .app tarballs with the Tauri updater key, ensuring integrity for installed clients. - Modified the upload script to handle the signing process and added error handling for missing signing keys. - Removed Linux x86_64 asset handling from the updater manifest to streamline the release process. These changes improve the security and reliability of macOS artifact uploads in the release workflow.
1014 lines
42 KiB
YAML
1014 lines
42 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 }}
|
|
# 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
|
|
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 }}
|
|
|
|
- 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 }}
|
|
|
|
- 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 }}
|
|
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 }}
|
|
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
|