mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
refactor: extract release workflow inline bash into modular scripts (#175)
Split monolithic inline bash from release.yml and release-packages.yml into standalone scripts under scripts/release/ for easier debugging and manual execution. New scripts: - bump-version.js: version bumping across package.json/tauri/Cargo - stage-sidecar.sh: stage + verify sidecar binary for Tauri bundler - sign-and-notarize-macos.sh: macOS code signing and notarization - repackage-dmg.sh: re-create and notarize DMG post-signing - upload-macos-artifacts.sh: re-upload notarized artifacts to release - package-cli-tarball.sh: package CLI binary into release tarball - build-linux-arm64.sh: build Linux arm64 CLI tarball - update-homebrew.sh: render and commit Homebrew formula to tap - build-apt-packages.sh: build .deb packages and apt repository - publish-npm.sh: stamp version and publish npm package Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6f8b5d6c9f
commit
88a4647dae
@@ -60,33 +60,17 @@ jobs:
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
pkg-config libssl-dev build-essential
|
||||
|
||||
- name: Build CLI binary
|
||||
run: cargo build --release --bin openhuman-core
|
||||
env:
|
||||
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
|
||||
|
||||
- name: Package tarball and upload to release
|
||||
- name: Build CLI binary and package tarball
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENHUMAN_SENTRY_DSN: ${{ vars.OPENHUMAN_SENTRY_DSN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo build --release --bin openhuman-core
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
TARGET="aarch64-unknown-linux-gnu"
|
||||
TARBALL="openhuman-core-${VERSION}-${TARGET}.tar.gz"
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
cp target/release/openhuman-core "$WORK/"
|
||||
chmod +x "$WORK/openhuman-core"
|
||||
tar -czf "$TARBALL" -C "$WORK" openhuman-core
|
||||
openssl dgst -sha256 -r "$TARBALL" | awk '{print $1}' > "${TARBALL}.sha256"
|
||||
|
||||
gh release upload "${{ github.event.release.tag_name }}" \
|
||||
"$TARBALL" "${TARBALL}.sha256" \
|
||||
--repo tinyhumansai/openhuman --clobber
|
||||
|
||||
echo "[pkg] Uploaded $TARBALL"
|
||||
bash scripts/release/package-cli-tarball.sh \
|
||||
target/release/openhuman-core \
|
||||
"${VERSION#v}" \
|
||||
aarch64-unknown-linux-gnu
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# 2. Update Homebrew tap
|
||||
@@ -111,55 +95,14 @@ jobs:
|
||||
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
path: tap
|
||||
|
||||
- name: Download tarballs and compute checksums
|
||||
- name: Update Homebrew formula
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
VERSION="${TAG#v}"
|
||||
mkdir -p /tmp/tarballs
|
||||
|
||||
for row in \
|
||||
"aarch64-apple-darwin:SHA256_MACOS_ARM64" \
|
||||
"x86_64-apple-darwin:SHA256_MACOS_X64" \
|
||||
"x86_64-unknown-linux-gnu:SHA256_LINUX_X64" \
|
||||
"aarch64-unknown-linux-gnu:SHA256_LINUX_ARM64"
|
||||
do
|
||||
TARGET="${row%%:*}"
|
||||
VAR="${row##*:}"
|
||||
TARBALL="openhuman-core-${VERSION}-${TARGET}.tar.gz"
|
||||
echo "[homebrew] Downloading $TARBALL ..."
|
||||
gh release download "$TAG" \
|
||||
--pattern "$TARBALL" \
|
||||
--repo tinyhumansai/openhuman \
|
||||
--dir /tmp/tarballs
|
||||
SHA=$(openssl dgst -sha256 -r "/tmp/tarballs/$TARBALL" | awk '{print $1}')
|
||||
echo "${VAR}=${SHA}" >> "$GITHUB_ENV"
|
||||
echo "[homebrew] $TARGET → $SHA"
|
||||
done
|
||||
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Render and commit formula
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p tap/Formula
|
||||
|
||||
sed \
|
||||
-e "s/@VERSION@/${VERSION}/g" \
|
||||
-e "s/@SHA256_MACOS_ARM64@/${SHA256_MACOS_ARM64}/g" \
|
||||
-e "s/@SHA256_MACOS_X64@/${SHA256_MACOS_X64}/g" \
|
||||
-e "s/@SHA256_LINUX_X64@/${SHA256_LINUX_X64}/g" \
|
||||
-e "s/@SHA256_LINUX_ARM64@/${SHA256_LINUX_ARM64}/g" \
|
||||
src/packages/homebrew/openhuman.rb > tap/Formula/openhuman.rb
|
||||
|
||||
cd tap
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add Formula/openhuman.rb
|
||||
git diff --cached --quiet && echo "[homebrew] No changes to commit." && exit 0
|
||||
git commit -m "chore: update formula to v${VERSION}"
|
||||
git push
|
||||
bash src/scripts/release/update-homebrew.sh \
|
||||
"${{ github.event.release.tag_name }}" \
|
||||
src/packages/homebrew/openhuman.rb \
|
||||
tap
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# 3. Build Debian apt repository and deploy to GitHub Pages
|
||||
@@ -191,76 +134,21 @@ jobs:
|
||||
echo "$APT_SIGNING_KEY" | gpg --batch --import
|
||||
gpg --list-secret-keys
|
||||
|
||||
- name: Download Linux CLI tarballs from release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
VERSION="${TAG#v}"
|
||||
mkdir -p /tmp/tarballs
|
||||
|
||||
for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
||||
TARBALL="openhuman-core-${VERSION}-${target}.tar.gz"
|
||||
gh release download "$TAG" \
|
||||
--pattern "$TARBALL" \
|
||||
--repo tinyhumansai/openhuman \
|
||||
--dir /tmp/tarballs
|
||||
echo "[apt] Downloaded $TARBALL"
|
||||
done
|
||||
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Extract binaries and build .deb packages
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p /tmp/bins
|
||||
|
||||
tar -xzf "/tmp/tarballs/openhuman-core-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
|
||||
-C /tmp/bins
|
||||
mv /tmp/bins/openhuman-core /tmp/bins/openhuman-core-amd64
|
||||
|
||||
tar -xzf "/tmp/tarballs/openhuman-core-${VERSION}-aarch64-unknown-linux-gnu.tar.gz" \
|
||||
-C /tmp/bins
|
||||
mv /tmp/bins/openhuman-core /tmp/bins/openhuman-core-arm64
|
||||
|
||||
chmod +x /tmp/bins/openhuman-core-amd64 /tmp/bins/openhuman-core-arm64
|
||||
|
||||
bash packages/deb/build.sh /tmp/bins/openhuman-core-amd64 "${VERSION}" amd64
|
||||
bash packages/deb/build.sh /tmp/bins/openhuman-core-arm64 "${VERSION}" arm64
|
||||
|
||||
ls -lh openhuman_*.deb
|
||||
|
||||
- name: Build apt repository
|
||||
env:
|
||||
APT_SIGNING_KEY_ID: ${{ secrets.APT_SIGNING_KEY_ID }}
|
||||
run: |
|
||||
bash scripts/build-apt-repo.sh /tmp/apt-repo \
|
||||
"openhuman_${VERSION}_amd64.deb" \
|
||||
"openhuman_${VERSION}_arm64.deb"
|
||||
|
||||
- name: Checkout gh-pages branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: gh-pages
|
||||
path: gh-pages
|
||||
# Create the branch if it doesn't exist
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update apt/ directory on gh-pages
|
||||
- name: Build .deb packages, apt repo, and deploy to gh-pages
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APT_SIGNING_KEY_ID: ${{ secrets.APT_SIGNING_KEY_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p gh-pages/apt
|
||||
# Remove old apt content, replace with freshly built repo
|
||||
rm -rf gh-pages/apt/*
|
||||
cp -r /tmp/apt-repo/. gh-pages/apt/
|
||||
|
||||
cd gh-pages
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add apt/
|
||||
git diff --cached --quiet && echo "[apt] No changes." && exit 0
|
||||
git commit -m "chore(apt): publish v${VERSION}"
|
||||
git push origin gh-pages
|
||||
bash scripts/release/build-apt-packages.sh \
|
||||
"${{ github.event.release.tag_name }}" \
|
||||
--deploy-gh-pages gh-pages
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# 4. Publish npm package
|
||||
@@ -285,21 +173,7 @@ jobs:
|
||||
- name: Set version and publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
|
||||
cd packages/npm
|
||||
|
||||
# Stamp version without creating a git commit
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
|
||||
# SKIP_OPENHUMAN_BINARY_DOWNLOAD prevents postinstall from running
|
||||
# during publish (the binary doesn't exist yet on the publish runner)
|
||||
SKIP_OPENHUMAN_BINARY_DOWNLOAD=1 npm publish --access public
|
||||
|
||||
echo "[npm] Published openhuman@${VERSION}"
|
||||
run: bash scripts/release/publish-npm.sh "${{ github.event.release.tag_name }}"
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# 5. Smoke test: Homebrew
|
||||
|
||||
+23
-248
@@ -93,65 +93,7 @@ jobs:
|
||||
|
||||
- name: Compute next version and sync release files
|
||||
id: bump
|
||||
env:
|
||||
RELEASE_TYPE: ${{ inputs.release_type }}
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
|
||||
const releaseType = process.env.RELEASE_TYPE;
|
||||
const allowed = new Set(['patch', 'minor', 'major']);
|
||||
if (!allowed.has(releaseType)) {
|
||||
throw new Error(`Invalid release_type: ${releaseType}`);
|
||||
}
|
||||
|
||||
const packagePath = 'app/package.json';
|
||||
const tauriPath = 'app/src-tauri/tauri.conf.json';
|
||||
const cargoPath = 'app/src-tauri/Cargo.toml';
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
const match = String(pkg.version || '').match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`package.json version must be SemVer X.Y.Z, found: ${pkg.version}`);
|
||||
}
|
||||
|
||||
let major = Number(match[1]);
|
||||
let minor = Number(match[2]);
|
||||
let patch = Number(match[3]);
|
||||
|
||||
if (releaseType === 'major') {
|
||||
major += 1;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
} else if (releaseType === 'minor') {
|
||||
minor += 1;
|
||||
patch = 0;
|
||||
} else {
|
||||
patch += 1;
|
||||
}
|
||||
|
||||
const nextVersion = `${major}.${minor}.${patch}`;
|
||||
|
||||
pkg.version = nextVersion;
|
||||
fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
|
||||
const tauri = JSON.parse(fs.readFileSync(tauriPath, 'utf8'));
|
||||
tauri.version = nextVersion;
|
||||
fs.writeFileSync(tauriPath, `${JSON.stringify(tauri, null, 2)}\n`);
|
||||
|
||||
const cargo = fs.readFileSync(cargoPath, 'utf8');
|
||||
const updatedCargo = cargo.replace(
|
||||
/(\[package\][\s\S]*?^version\s*=\s*")([^"]+)(")/m,
|
||||
`$1${nextVersion}$3`,
|
||||
);
|
||||
if (updatedCargo === cargo) {
|
||||
throw new Error('Failed to update [package].version in app/src-tauri/Cargo.toml');
|
||||
}
|
||||
fs.writeFileSync(cargoPath, updatedCargo);
|
||||
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${nextVersion}\n`);
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=v${nextVersion}\n`);
|
||||
NODE
|
||||
run: node scripts/release/bump-version.js "${{ inputs.release_type }}"
|
||||
|
||||
- name: Ensure tag does not already exist
|
||||
env:
|
||||
@@ -412,44 +354,11 @@ jobs:
|
||||
- name: Stage sidecar for Tauri bundler
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p app/src-tauri/binaries
|
||||
EXE_SUFFIX=""
|
||||
if [[ "$MATRIX_TARGET" == *"windows"* ]]; then
|
||||
EXE_SUFFIX=".exe"
|
||||
fi
|
||||
SOURCE="$CORE_TARGET_DIR/${CORE_BIN_NAME}${EXE_SUFFIX}"
|
||||
DEST="app/src-tauri/binaries/${SIDECAR_BASE}-${MATRIX_TARGET}${EXE_SUFFIX}"
|
||||
cp "$SOURCE" "$DEST"
|
||||
if [[ "$MATRIX_TARGET" != *"windows"* ]]; then
|
||||
chmod +x "$DEST"
|
||||
fi
|
||||
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 }}
|
||||
SIDECAR_BASE: ${{ steps.core-paths.outputs.sidecar_base }}
|
||||
|
||||
- name: Verify staged sidecar for bundler (all platforms)
|
||||
shell: bash
|
||||
run: |
|
||||
EXE_SUFFIX=""
|
||||
if [[ "$MATRIX_TARGET" == *"windows"* ]]; then
|
||||
EXE_SUFFIX=".exe"
|
||||
fi
|
||||
SIDE_CAR_PATH="app/src-tauri/binaries/${SIDECAR_BASE}-${MATRIX_TARGET}${EXE_SUFFIX}"
|
||||
echo "Checking staged sidecar: $SIDE_CAR_PATH"
|
||||
if [ ! -f "$SIDE_CAR_PATH" ]; then
|
||||
echo "Missing staged sidecar binary: $SIDE_CAR_PATH"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$MATRIX_PLATFORM" != "windows-latest" ] && [ ! -x "$SIDE_CAR_PATH" ]; then
|
||||
echo "Staged sidecar is not executable: $SIDE_CAR_PATH"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
MATRIX_PLATFORM: ${{ matrix.settings.platform }}
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
SIDECAR_BASE: ${{ steps.core-paths.outputs.sidecar_base }}
|
||||
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
|
||||
@@ -536,84 +445,9 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APP_PATH="${{ steps.locate-app.outputs.app_path }}"
|
||||
ENTITLEMENTS="app/src-tauri/entitlements.sidecar.plist"
|
||||
|
||||
# Ensure signing identity is available — re-import cert into a keychain
|
||||
# in case tauri-action's keychain was cleaned up
|
||||
KEYCHAIN="resign-$$.keychain-db"
|
||||
KEYCHAIN_PW="$(openssl rand -base64 24)"
|
||||
CERT_FILE="$(mktemp /tmp/cert-XXXXXX.p12)"
|
||||
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERT_FILE"
|
||||
security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security import "$CERT_FILE" -k "$KEYCHAIN" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
|
||||
rm -f "$CERT_FILE"
|
||||
echo "Signing identity imported into $KEYCHAIN"
|
||||
|
||||
echo "=== Signing .app contents and bundle ==="
|
||||
|
||||
echo "Bundle contents:"
|
||||
ls -la "$APP_PATH/Contents/MacOS/"
|
||||
|
||||
MAIN_EXE="$(defaults read "$APP_PATH/Contents/Info.plist" CFBundleExecutable 2>/dev/null || echo "OpenHuman")"
|
||||
echo "Main executable (from plist): $MAIN_EXE"
|
||||
|
||||
# Sign all non-main binaries (sidecars) first
|
||||
for bin in "$APP_PATH/Contents/MacOS/"*; do
|
||||
[ -f "$bin" ] && [ -x "$bin" ] || continue
|
||||
BASENAME="$(basename "$bin")"
|
||||
[ "$BASENAME" = "$MAIN_EXE" ] && continue
|
||||
echo " Signing sidecar: $BASENAME"
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$bin"
|
||||
done
|
||||
|
||||
# Sign sidecars in Resources/ if any
|
||||
for bin in "$APP_PATH/Contents/Resources/"openhuman-core-*; do
|
||||
[ -f "$bin" ] || continue
|
||||
echo " Signing resource sidecar: $(basename "$bin")"
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$bin"
|
||||
done
|
||||
|
||||
# Sign the .app bundle (signs main exe + updates seal)
|
||||
echo " Signing .app bundle..."
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$APP_PATH"
|
||||
|
||||
echo "=== Verifying signatures ==="
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
|
||||
echo "=== Notarizing ==="
|
||||
NOTARIZE_ZIP="$(mktemp /tmp/OpenHuman-notarize-XXXXXX.zip)"
|
||||
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_ZIP"
|
||||
|
||||
xcrun notarytool submit "$NOTARIZE_ZIP" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
|
||||
rm -f "$NOTARIZE_ZIP"
|
||||
|
||||
echo "=== Stapling ==="
|
||||
xcrun stapler staple "$APP_PATH"
|
||||
|
||||
echo "=== Notarization complete ==="
|
||||
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'
|
||||
@@ -623,30 +457,9 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APP_PATH="${{ steps.locate-app.outputs.app_path }}"
|
||||
BUNDLE_DIR="${{ steps.locate-app.outputs.bundle_dir }}"
|
||||
|
||||
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
||||
if [ -z "$DMG_PATH" ]; then
|
||||
echo "No DMG found — skipping DMG re-package"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Re-creating DMG with notarized .app..."
|
||||
DMG_TEMP="$(mktemp /tmp/OpenHuman-XXXXXX.dmg)"
|
||||
hdiutil create -volname "OpenHuman" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_TEMP"
|
||||
mv "$DMG_TEMP" "$DMG_PATH"
|
||||
|
||||
echo "Notarizing DMG..."
|
||||
xcrun notarytool submit "$DMG_PATH" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
|
||||
xcrun stapler staple "$DMG_PATH"
|
||||
echo "DMG notarization complete: $DMG_PATH"
|
||||
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'
|
||||
@@ -655,33 +468,11 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_ID: ${{ needs.create-release.outputs.release_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUNDLE_DIR="${{ steps.locate-app.outputs.bundle_dir }}"
|
||||
APP_PATH="${{ steps.locate-app.outputs.app_path }}"
|
||||
VERSION="${{ needs.prepare-release.outputs.version }}"
|
||||
ARCH="${{ matrix.settings.target }}"
|
||||
|
||||
# Re-upload the DMG (overwrite the one tauri-action uploaded)
|
||||
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
||||
if [ -n "$DMG_PATH" ]; then
|
||||
DMG_NAME="$(basename "$DMG_PATH")"
|
||||
echo "Deleting old DMG asset from release..."
|
||||
ASSET_ID="$(gh api "repos/tinyhumansai/openhuman/releases/$RELEASE_ID/assets" \
|
||||
--jq ".[] | select(.name == \"$DMG_NAME\") | .id" 2>/dev/null || true)"
|
||||
if [ -n "$ASSET_ID" ]; then
|
||||
gh api -X DELETE "repos/tinyhumansai/openhuman/releases/assets/$ASSET_ID" || true
|
||||
fi
|
||||
echo "Uploading notarized DMG..."
|
||||
gh release upload "v${VERSION}" "$DMG_PATH" --repo tinyhumansai/openhuman --clobber
|
||||
fi
|
||||
|
||||
# Re-upload the .app as a zip if tauri-action uploaded one
|
||||
if [ -n "$APP_PATH" ]; then
|
||||
APP_ZIP="/tmp/OpenHuman_${VERSION}_${ARCH}.app.tar.gz"
|
||||
tar -czf "$APP_ZIP" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
|
||||
gh release upload "v${VERSION}" "$APP_ZIP" --repo tinyhumansai/openhuman --clobber
|
||||
rm -f "$APP_ZIP"
|
||||
fi
|
||||
bash scripts/release/upload-macos-artifacts.sh \
|
||||
"${{ steps.locate-app.outputs.app_path }}" \
|
||||
"${{ steps.locate-app.outputs.bundle_dir }}" \
|
||||
"${{ needs.prepare-release.outputs.version }}" \
|
||||
"${{ matrix.settings.target }}"
|
||||
|
||||
- name: Verify macOS app bundle sidecar layout
|
||||
if: matrix.settings.platform == 'macos-latest'
|
||||
@@ -715,18 +506,9 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ needs.prepare-release.outputs.version }}
|
||||
MATRIX_PLATFORM: ${{ matrix.settings.platform }}
|
||||
MATRIX_TARGET: ${{ matrix.settings.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TARBALL="openhuman-core-${VERSION}-${MATRIX_TARGET}.tar.gz"
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
if [[ "$MATRIX_PLATFORM" == "macos-latest" ]]; then
|
||||
# Prefer the notarized+signed binary extracted from inside the .app bundle
|
||||
# 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)
|
||||
@@ -740,17 +522,10 @@ jobs:
|
||||
BIN="${{ steps.cli-paths.outputs.cli_path }}"
|
||||
fi
|
||||
|
||||
cp "$BIN" "$WORK/openhuman-core"
|
||||
chmod +x "$WORK/openhuman-core"
|
||||
tar -czf "$TARBALL" -C "$WORK" openhuman-core
|
||||
|
||||
# openssl dgst works on both macOS and Linux runners
|
||||
openssl dgst -sha256 -r "$TARBALL" | awk '{print $1}' > "${TARBALL}.sha256"
|
||||
|
||||
gh release upload "v${VERSION}" "$TARBALL" "${TARBALL}.sha256" \
|
||||
--repo tinyhumansai/openhuman --clobber
|
||||
|
||||
echo "[pkg] Uploaded $TARBALL and ${TARBALL}.sha256"
|
||||
bash scripts/release/package-cli-tarball.sh \
|
||||
"$BIN" \
|
||||
"${{ needs.prepare-release.outputs.version }}" \
|
||||
"${{ matrix.settings.target }}"
|
||||
|
||||
- name: Upload standalone CLI artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download Linux CLI tarballs from a GitHub release, build .deb packages,
|
||||
# then build a signed apt repository and optionally deploy to gh-pages.
|
||||
#
|
||||
# Usage:
|
||||
# build-apt-packages.sh <tag> [--deploy-gh-pages <gh_pages_dir>]
|
||||
#
|
||||
# Required environment:
|
||||
# GITHUB_TOKEN — download release assets
|
||||
# APT_SIGNING_KEY_ID — GPG key ID for signing (must be imported)
|
||||
#
|
||||
# Optional environment:
|
||||
# UPLOAD_REPO — GitHub repo slug (default: tinyhumansai/openhuman)
|
||||
# DRY_RUN — set to "true" to skip git push
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${1:?Usage: build-apt-packages.sh <tag> [--deploy-gh-pages <gh_pages_dir>]}"
|
||||
shift
|
||||
VERSION="${TAG#v}"
|
||||
UPLOAD_REPO="${UPLOAD_REPO:-tinyhumansai/openhuman}"
|
||||
|
||||
DEPLOY_GH_PAGES=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--deploy-gh-pages) DEPLOY_GH_PAGES="${2:?--deploy-gh-pages requires a path}"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TMPDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
# ── Download tarballs ────────────────────────────────────────────────────────
|
||||
echo "[apt] Downloading Linux CLI tarballs for $TAG ..."
|
||||
mkdir -p "$TMPDIR/tarballs" "$TMPDIR/bins"
|
||||
|
||||
for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
||||
TARBALL="openhuman-core-${VERSION}-${target}.tar.gz"
|
||||
gh release download "$TAG" \
|
||||
--pattern "$TARBALL" \
|
||||
--repo "$UPLOAD_REPO" \
|
||||
--dir "$TMPDIR/tarballs"
|
||||
echo "[apt] Downloaded $TARBALL"
|
||||
done
|
||||
|
||||
# ── Extract binaries ─────────────────────────────────────────────────────────
|
||||
tar -xzf "$TMPDIR/tarballs/openhuman-core-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" \
|
||||
-C "$TMPDIR/bins"
|
||||
mv "$TMPDIR/bins/openhuman-core" "$TMPDIR/bins/openhuman-core-amd64"
|
||||
|
||||
tar -xzf "$TMPDIR/tarballs/openhuman-core-${VERSION}-aarch64-unknown-linux-gnu.tar.gz" \
|
||||
-C "$TMPDIR/bins"
|
||||
mv "$TMPDIR/bins/openhuman-core" "$TMPDIR/bins/openhuman-core-arm64"
|
||||
|
||||
chmod +x "$TMPDIR/bins/openhuman-core-amd64" "$TMPDIR/bins/openhuman-core-arm64"
|
||||
|
||||
# ── Build .deb packages ─────────────────────────────────────────────────────
|
||||
echo "[apt] Building .deb packages ..."
|
||||
bash "$REPO_ROOT/packages/deb/build.sh" "$TMPDIR/bins/openhuman-core-amd64" "${VERSION}" amd64
|
||||
bash "$REPO_ROOT/packages/deb/build.sh" "$TMPDIR/bins/openhuman-core-arm64" "${VERSION}" arm64
|
||||
|
||||
ls -lh openhuman_*.deb
|
||||
|
||||
# ── Build apt repository ────────────────────────────────────────────────────
|
||||
APT_REPO_DIR="$TMPDIR/apt-repo"
|
||||
echo "[apt] Building apt repository ..."
|
||||
bash "$REPO_ROOT/scripts/build-apt-repo.sh" "$APT_REPO_DIR" \
|
||||
"openhuman_${VERSION}_amd64.deb" \
|
||||
"openhuman_${VERSION}_arm64.deb"
|
||||
|
||||
# ── Deploy to gh-pages ───────────────────────────────────────────────────────
|
||||
if [[ -n "$DEPLOY_GH_PAGES" ]]; then
|
||||
echo "[apt] Deploying apt repo to gh-pages at $DEPLOY_GH_PAGES ..."
|
||||
mkdir -p "$DEPLOY_GH_PAGES/apt"
|
||||
rm -rf "$DEPLOY_GH_PAGES/apt/"*
|
||||
cp -r "$APT_REPO_DIR/." "$DEPLOY_GH_PAGES/apt/"
|
||||
|
||||
cd "$DEPLOY_GH_PAGES"
|
||||
git config user.name "${GIT_AUTHOR_NAME:-github-actions[bot]}"
|
||||
git config user.email "${GIT_AUTHOR_EMAIL:-github-actions[bot]@users.noreply.github.com}"
|
||||
git add apt/
|
||||
if git diff --cached --quiet; then
|
||||
echo "[apt] No changes."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore(apt): publish v${VERSION}"
|
||||
|
||||
if [[ "${DRY_RUN:-}" == "true" ]]; then
|
||||
echo "[apt] DRY_RUN: skipping push"
|
||||
else
|
||||
git push origin gh-pages
|
||||
echo "[apt] Pushed to gh-pages"
|
||||
fi
|
||||
fi
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Linux arm64 CLI tarball and optionally upload to a GitHub release.
|
||||
#
|
||||
# Usage:
|
||||
# build-linux-arm64.sh <tag>
|
||||
#
|
||||
# Environment:
|
||||
# GITHUB_TOKEN — upload to release when set
|
||||
# OPENHUMAN_SENTRY_DSN — optional Sentry DSN baked into the binary
|
||||
# UPLOAD_REPO — GitHub repo slug (default: tinyhumansai/openhuman)
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${1:?Usage: build-linux-arm64.sh <tag>}"
|
||||
VERSION="${TAG#v}"
|
||||
TARGET="aarch64-unknown-linux-gnu"
|
||||
UPLOAD_REPO="${UPLOAD_REPO:-tinyhumansai/openhuman}"
|
||||
|
||||
echo "[linux-arm64] Building openhuman-core for $TARGET ..."
|
||||
cargo build --release --bin openhuman-core
|
||||
|
||||
TARBALL="openhuman-core-${VERSION}-${TARGET}.tar.gz"
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
cp target/release/openhuman-core "$WORK/"
|
||||
chmod +x "$WORK/openhuman-core"
|
||||
tar -czf "$TARBALL" -C "$WORK" openhuman-core
|
||||
openssl dgst -sha256 -r "$TARBALL" | awk '{print $1}' > "${TARBALL}.sha256"
|
||||
|
||||
echo "[linux-arm64] Created $TARBALL (sha256: $(cat "${TARBALL}.sha256"))"
|
||||
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
gh release upload "$TAG" \
|
||||
"$TARBALL" "${TARBALL}.sha256" \
|
||||
--repo "$UPLOAD_REPO" --clobber
|
||||
echo "[linux-arm64] Uploaded $TARBALL"
|
||||
fi
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env node
|
||||
// Bump version in package.json, tauri.conf.json, and Cargo.toml.
|
||||
//
|
||||
// Usage:
|
||||
// node scripts/release/bump-version.js <patch|minor|major>
|
||||
//
|
||||
// Outputs (to stdout, one per line):
|
||||
// version=X.Y.Z
|
||||
// tag=vX.Y.Z
|
||||
//
|
||||
// When GITHUB_OUTPUT is set (CI), the same key=value pairs are appended there.
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const RELEASE_TYPE = process.argv[2] || process.env.RELEASE_TYPE;
|
||||
const allowed = new Set(['patch', 'minor', 'major']);
|
||||
if (!allowed.has(RELEASE_TYPE)) {
|
||||
console.error(`Usage: bump-version.js <patch|minor|major> (got: "${RELEASE_TYPE}")`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const packagePath = path.join(root, 'app/package.json');
|
||||
const tauriPath = path.join(root, 'app/src-tauri/tauri.conf.json');
|
||||
const cargoPath = path.join(root, 'app/src-tauri/Cargo.toml');
|
||||
|
||||
// ── Read current version ────────────────────────────────────────────────────
|
||||
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
const match = String(pkg.version || '').match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`package.json version must be SemVer X.Y.Z, found: ${pkg.version}`);
|
||||
}
|
||||
|
||||
let major = Number(match[1]);
|
||||
let minor = Number(match[2]);
|
||||
let patch = Number(match[3]);
|
||||
|
||||
// ── Bump ────────────────────────────────────────────────────────────────────
|
||||
if (RELEASE_TYPE === 'major') {
|
||||
major += 1; minor = 0; patch = 0;
|
||||
} else if (RELEASE_TYPE === 'minor') {
|
||||
minor += 1; patch = 0;
|
||||
} else {
|
||||
patch += 1;
|
||||
}
|
||||
const nextVersion = `${major}.${minor}.${patch}`;
|
||||
|
||||
// ── Write package.json ──────────────────────────────────────────────────────
|
||||
pkg.version = nextVersion;
|
||||
fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
|
||||
// ── Write tauri.conf.json ───────────────────────────────────────────────────
|
||||
const tauri = JSON.parse(fs.readFileSync(tauriPath, 'utf8'));
|
||||
tauri.version = nextVersion;
|
||||
fs.writeFileSync(tauriPath, `${JSON.stringify(tauri, null, 2)}\n`);
|
||||
|
||||
// ── Write Cargo.toml ────────────────────────────────────────────────────────
|
||||
const cargo = fs.readFileSync(cargoPath, 'utf8');
|
||||
const updatedCargo = cargo.replace(
|
||||
/(\[package\][\s\S]*?^version\s*=\s*")([^"]+)(")/m,
|
||||
`$1${nextVersion}$3`,
|
||||
);
|
||||
if (updatedCargo === cargo) {
|
||||
throw new Error('Failed to update [package].version in app/src-tauri/Cargo.toml');
|
||||
}
|
||||
fs.writeFileSync(cargoPath, updatedCargo);
|
||||
|
||||
// ── Output ──────────────────────────────────────────────────────────────────
|
||||
const lines = `version=${nextVersion}\ntag=v${nextVersion}\n`;
|
||||
process.stdout.write(lines);
|
||||
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, lines);
|
||||
}
|
||||
|
||||
console.error(`[bump-version] ${pkg.version} → ${nextVersion}`);
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Package the core CLI binary into a release tarball and optionally upload it.
|
||||
#
|
||||
# Usage:
|
||||
# package-cli-tarball.sh <binary_path> <version> <target>
|
||||
#
|
||||
# Environment:
|
||||
# GITHUB_TOKEN — if set, uploads tarball + sha256 to the GitHub release
|
||||
# UPLOAD_REPO — GitHub repo slug (default: tinyhumansai/openhuman)
|
||||
#
|
||||
# Example:
|
||||
# package-cli-tarball.sh target/release/openhuman-core 0.5.0 aarch64-apple-darwin
|
||||
set -euo pipefail
|
||||
|
||||
BIN_PATH="${1:?Usage: package-cli-tarball.sh <binary_path> <version> <target>}"
|
||||
VERSION="${2:?}"
|
||||
TARGET="${3:?}"
|
||||
UPLOAD_REPO="${UPLOAD_REPO:-tinyhumansai/openhuman}"
|
||||
|
||||
TARBALL="openhuman-core-${VERSION}-${TARGET}.tar.gz"
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
cp "$BIN_PATH" "$WORK/openhuman-core"
|
||||
chmod +x "$WORK/openhuman-core"
|
||||
tar -czf "$TARBALL" -C "$WORK" openhuman-core
|
||||
|
||||
# openssl dgst works on both macOS and Linux
|
||||
openssl dgst -sha256 -r "$TARBALL" | awk '{print $1}' > "${TARBALL}.sha256"
|
||||
|
||||
echo "[package-cli] Created $TARBALL (sha256: $(cat "${TARBALL}.sha256"))"
|
||||
|
||||
# ── Optional upload ──────────────────────────────────────────────────────────
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
gh release upload "v${VERSION}" \
|
||||
"$TARBALL" "${TARBALL}.sha256" \
|
||||
--repo "$UPLOAD_REPO" --clobber
|
||||
echo "[package-cli] Uploaded $TARBALL to v${VERSION}"
|
||||
fi
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Publish the openhuman npm package for a given version.
|
||||
#
|
||||
# Usage:
|
||||
# publish-npm.sh <tag>
|
||||
#
|
||||
# Required environment:
|
||||
# NODE_AUTH_TOKEN — npm automation token
|
||||
#
|
||||
# Optional environment:
|
||||
# DRY_RUN — set to "true" to run npm publish --dry-run
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${1:?Usage: publish-npm.sh <tag>}"
|
||||
VERSION="${TAG#v}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
cd "$REPO_ROOT/packages/npm"
|
||||
|
||||
# Stamp version without creating a git commit
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
|
||||
PUBLISH_ARGS=(--access public)
|
||||
if [[ "${DRY_RUN:-}" == "true" ]]; then
|
||||
PUBLISH_ARGS+=(--dry-run)
|
||||
fi
|
||||
|
||||
# SKIP_OPENHUMAN_BINARY_DOWNLOAD prevents postinstall from running
|
||||
# during publish (the binary doesn't exist yet on the publish runner)
|
||||
SKIP_OPENHUMAN_BINARY_DOWNLOAD=1 npm publish "${PUBLISH_ARGS[@]}"
|
||||
|
||||
echo "[npm] Published openhuman@${VERSION}"
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Re-create and notarize a DMG after the .app has been notarized.
|
||||
#
|
||||
# Usage:
|
||||
# repackage-dmg.sh <app_path> <bundle_dir>
|
||||
#
|
||||
# Required environment variables:
|
||||
# APPLE_ID
|
||||
# APPLE_PASSWORD (app-specific password)
|
||||
# APPLE_TEAM_ID
|
||||
set -euo pipefail
|
||||
|
||||
APP_PATH="${1:?Usage: repackage-dmg.sh <app_path> <bundle_dir>}"
|
||||
BUNDLE_DIR="${2:?}"
|
||||
|
||||
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
||||
if [ -z "$DMG_PATH" ]; then
|
||||
echo "[dmg] No DMG found — skipping DMG re-package"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[dmg] Re-creating DMG with notarized .app..."
|
||||
DMG_TEMP="$(mktemp /tmp/OpenHuman-XXXXXX.dmg)"
|
||||
hdiutil create -volname "OpenHuman" -srcfolder "$APP_PATH" -ov -format UDZO "$DMG_TEMP"
|
||||
mv "$DMG_TEMP" "$DMG_PATH"
|
||||
|
||||
echo "[dmg] Notarizing DMG..."
|
||||
xcrun notarytool submit "$DMG_PATH" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
|
||||
xcrun stapler staple "$DMG_PATH"
|
||||
echo "[dmg] DMG notarization complete: $DMG_PATH"
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
# Re-sign all binaries inside a macOS .app bundle with hardened runtime
|
||||
# and submit for Apple notarization.
|
||||
#
|
||||
# Usage:
|
||||
# sign-and-notarize-macos.sh <app_path> [entitlements_plist]
|
||||
#
|
||||
# Required environment variables:
|
||||
# APPLE_CERTIFICATE_BASE64
|
||||
# APPLE_CERTIFICATE_PASSWORD
|
||||
# APPLE_SIGNING_IDENTITY
|
||||
# APPLE_ID
|
||||
# APPLE_PASSWORD (app-specific password)
|
||||
# APPLE_TEAM_ID
|
||||
set -euo pipefail
|
||||
|
||||
APP_PATH="${1:?Usage: sign-and-notarize-macos.sh <app_path> [entitlements_plist]}"
|
||||
ENTITLEMENTS="${2:-app/src-tauri/entitlements.sidecar.plist}"
|
||||
|
||||
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 "[sign] ERROR: Missing required env var: $var"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Import signing certificate ───────────────────────────────────────────────
|
||||
KEYCHAIN="resign-$$.keychain-db"
|
||||
KEYCHAIN_PW="$(openssl rand -base64 24)"
|
||||
CERT_FILE="$(mktemp /tmp/cert-XXXXXX.p12)"
|
||||
|
||||
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERT_FILE"
|
||||
security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security import "$CERT_FILE" -k "$KEYCHAIN" \
|
||||
-P "$APPLE_CERTIFICATE_PASSWORD" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PW" "$KEYCHAIN"
|
||||
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
|
||||
rm -f "$CERT_FILE"
|
||||
echo "[sign] Signing identity imported into $KEYCHAIN"
|
||||
|
||||
# ── Sign .app contents ──────────────────────────────────────────────────────
|
||||
echo "[sign] Signing .app contents and bundle"
|
||||
echo "[sign] Bundle contents:"
|
||||
ls -la "$APP_PATH/Contents/MacOS/"
|
||||
|
||||
MAIN_EXE="$(defaults read "$APP_PATH/Contents/Info.plist" CFBundleExecutable 2>/dev/null || echo "OpenHuman")"
|
||||
echo "[sign] Main executable (from plist): $MAIN_EXE"
|
||||
|
||||
# Sign all non-main binaries (sidecars) in MacOS/
|
||||
for bin in "$APP_PATH/Contents/MacOS/"*; do
|
||||
[ -f "$bin" ] && [ -x "$bin" ] || continue
|
||||
BASENAME="$(basename "$bin")"
|
||||
[ "$BASENAME" = "$MAIN_EXE" ] && continue
|
||||
echo "[sign] Signing sidecar: $BASENAME"
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$bin"
|
||||
done
|
||||
|
||||
# Sign sidecars in Resources/ if any
|
||||
for bin in "$APP_PATH/Contents/Resources/"openhuman-core-*; do
|
||||
[ -f "$bin" ] || continue
|
||||
echo "[sign] Signing resource sidecar: $(basename "$bin")"
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$bin"
|
||||
done
|
||||
|
||||
# Sign the .app bundle itself
|
||||
echo "[sign] Signing .app bundle..."
|
||||
codesign --force --options runtime \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
--sign "$APPLE_SIGNING_IDENTITY" \
|
||||
--timestamp \
|
||||
"$APP_PATH"
|
||||
|
||||
# ── Verify ───────────────────────────────────────────────────────────────────
|
||||
echo "[sign] Verifying signatures"
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
|
||||
# ── Notarize ─────────────────────────────────────────────────────────────────
|
||||
echo "[sign] Notarizing..."
|
||||
NOTARIZE_ZIP="$(mktemp /tmp/OpenHuman-notarize-XXXXXX.zip)"
|
||||
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_ZIP"
|
||||
|
||||
xcrun notarytool submit "$NOTARIZE_ZIP" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
|
||||
rm -f "$NOTARIZE_ZIP"
|
||||
|
||||
# ── Staple ───────────────────────────────────────────────────────────────────
|
||||
echo "[sign] Stapling..."
|
||||
xcrun stapler staple "$APP_PATH"
|
||||
|
||||
echo "[sign] Notarization complete"
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage the core sidecar binary next to Tauri resources for bundling.
|
||||
#
|
||||
# Usage:
|
||||
# stage-sidecar.sh <target> <core_target_dir> <core_bin_name> <sidecar_base>
|
||||
#
|
||||
# Example:
|
||||
# stage-sidecar.sh aarch64-apple-darwin target/aarch64-apple-darwin/release openhuman-core openhuman-core
|
||||
set -euo pipefail
|
||||
|
||||
TARGET="${1:?Usage: stage-sidecar.sh <target> <core_target_dir> <core_bin_name> <sidecar_base>}"
|
||||
CORE_TARGET_DIR="${2:?}"
|
||||
CORE_BIN_NAME="${3:?}"
|
||||
SIDECAR_BASE="${4:?}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
EXE_SUFFIX=""
|
||||
if [[ "$TARGET" == *"windows"* ]]; then
|
||||
EXE_SUFFIX=".exe"
|
||||
fi
|
||||
|
||||
SOURCE="${REPO_ROOT}/${CORE_TARGET_DIR}/${CORE_BIN_NAME}${EXE_SUFFIX}"
|
||||
DEST_DIR="${REPO_ROOT}/app/src-tauri/binaries"
|
||||
DEST="${DEST_DIR}/${SIDECAR_BASE}-${TARGET}${EXE_SUFFIX}"
|
||||
|
||||
mkdir -p "$DEST_DIR"
|
||||
cp "$SOURCE" "$DEST"
|
||||
|
||||
if [[ "$TARGET" != *"windows"* ]]; then
|
||||
chmod +x "$DEST"
|
||||
fi
|
||||
|
||||
echo "[stage-sidecar] Staged: $DEST"
|
||||
|
||||
# ── Verify ───────────────────────────────────────────────────────────────────
|
||||
if [ ! -f "$DEST" ]; then
|
||||
echo "[stage-sidecar] ERROR: Missing staged sidecar binary: $DEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" != *"windows"* ]] && [ ! -x "$DEST" ]; then
|
||||
echo "[stage-sidecar] ERROR: Staged sidecar is not executable: $DEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[stage-sidecar] Verified OK"
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download release tarballs, compute SHA-256 checksums, render the Homebrew
|
||||
# formula from the template and commit it to the tap repository.
|
||||
#
|
||||
# Usage:
|
||||
# update-homebrew.sh <tag> <formula_template> <tap_dir>
|
||||
#
|
||||
# Example:
|
||||
# update-homebrew.sh v0.5.0 packages/homebrew/openhuman.rb /tmp/tap
|
||||
#
|
||||
# Required environment:
|
||||
# GITHUB_TOKEN — to download release assets
|
||||
#
|
||||
# The tap directory must be a git checkout of tinyhumansai/homebrew-openhuman.
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${1:?Usage: update-homebrew.sh <tag> <formula_template> <tap_dir>}"
|
||||
TEMPLATE="${2:?}"
|
||||
TAP_DIR="${3:?}"
|
||||
VERSION="${TAG#v}"
|
||||
UPLOAD_REPO="${UPLOAD_REPO:-tinyhumansai/openhuman}"
|
||||
|
||||
TMPDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
echo "[homebrew] Downloading release tarballs for $TAG ..."
|
||||
|
||||
SHA256_MACOS_ARM64=""
|
||||
SHA256_MACOS_X64=""
|
||||
SHA256_LINUX_X64=""
|
||||
SHA256_LINUX_ARM64=""
|
||||
|
||||
for row in \
|
||||
"aarch64-apple-darwin:SHA256_MACOS_ARM64" \
|
||||
"x86_64-apple-darwin:SHA256_MACOS_X64" \
|
||||
"x86_64-unknown-linux-gnu:SHA256_LINUX_X64" \
|
||||
"aarch64-unknown-linux-gnu:SHA256_LINUX_ARM64"
|
||||
do
|
||||
TARGET="${row%%:*}"
|
||||
VAR="${row##*:}"
|
||||
TARBALL="openhuman-core-${VERSION}-${TARGET}.tar.gz"
|
||||
echo "[homebrew] Downloading $TARBALL ..."
|
||||
gh release download "$TAG" \
|
||||
--pattern "$TARBALL" \
|
||||
--repo "$UPLOAD_REPO" \
|
||||
--dir "$TMPDIR"
|
||||
SHA=$(openssl dgst -sha256 -r "$TMPDIR/$TARBALL" | awk '{print $1}')
|
||||
eval "${VAR}=${SHA}"
|
||||
echo "[homebrew] $TARGET → $SHA"
|
||||
done
|
||||
|
||||
# ── Render formula ───────────────────────────────────────────────────────────
|
||||
mkdir -p "$TAP_DIR/Formula"
|
||||
|
||||
sed \
|
||||
-e "s/@VERSION@/${VERSION}/g" \
|
||||
-e "s/@SHA256_MACOS_ARM64@/${SHA256_MACOS_ARM64}/g" \
|
||||
-e "s/@SHA256_MACOS_X64@/${SHA256_MACOS_X64}/g" \
|
||||
-e "s/@SHA256_LINUX_X64@/${SHA256_LINUX_X64}/g" \
|
||||
-e "s/@SHA256_LINUX_ARM64@/${SHA256_LINUX_ARM64}/g" \
|
||||
"$TEMPLATE" > "$TAP_DIR/Formula/openhuman.rb"
|
||||
|
||||
echo "[homebrew] Rendered formula → $TAP_DIR/Formula/openhuman.rb"
|
||||
|
||||
# ── Commit and push ──────────────────────────────────────────────────────────
|
||||
cd "$TAP_DIR"
|
||||
git config user.name "${GIT_AUTHOR_NAME:-github-actions[bot]}"
|
||||
git config user.email "${GIT_AUTHOR_EMAIL:-github-actions[bot]@users.noreply.github.com}"
|
||||
git add Formula/openhuman.rb
|
||||
if git diff --cached --quiet; then
|
||||
echo "[homebrew] No changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore: update formula to v${VERSION}"
|
||||
|
||||
if [[ "${DRY_RUN:-}" == "true" ]]; then
|
||||
echo "[homebrew] DRY_RUN: skipping push"
|
||||
else
|
||||
git push
|
||||
echo "[homebrew] Pushed to tap"
|
||||
fi
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Re-upload notarized macOS artifacts (DMG + .app tarball) to GitHub release.
|
||||
#
|
||||
# Usage:
|
||||
# upload-macos-artifacts.sh <app_path> <bundle_dir> <version> <arch>
|
||||
#
|
||||
# Required environment:
|
||||
# GITHUB_TOKEN
|
||||
# RELEASE_ID
|
||||
set -euo pipefail
|
||||
|
||||
APP_PATH="${1:?Usage: upload-macos-artifacts.sh <app_path> <bundle_dir> <version> <arch>}"
|
||||
BUNDLE_DIR="${2:?}"
|
||||
VERSION="${3:?}"
|
||||
ARCH="${4:?}"
|
||||
UPLOAD_REPO="${UPLOAD_REPO:-tinyhumansai/openhuman}"
|
||||
|
||||
# ── Re-upload DMG ────────────────────────────────────────────────────────────
|
||||
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
||||
if [ -n "$DMG_PATH" ]; then
|
||||
DMG_NAME="$(basename "$DMG_PATH")"
|
||||
echo "[upload] Deleting old DMG asset from release..."
|
||||
ASSET_ID="$(gh api "repos/${UPLOAD_REPO}/releases/${RELEASE_ID}/assets" \
|
||||
--jq ".[] | select(.name == \"$DMG_NAME\") | .id" 2>/dev/null || true)"
|
||||
if [ -n "$ASSET_ID" ]; then
|
||||
gh api -X DELETE "repos/${UPLOAD_REPO}/releases/assets/$ASSET_ID" || true
|
||||
fi
|
||||
echo "[upload] Uploading notarized DMG..."
|
||||
gh release upload "v${VERSION}" "$DMG_PATH" --repo "$UPLOAD_REPO" --clobber
|
||||
fi
|
||||
|
||||
# ── Upload .app as tar.gz ────────────────────────────────────────────────────
|
||||
if [ -n "$APP_PATH" ] && [ -d "$APP_PATH" ]; then
|
||||
APP_ZIP="/tmp/OpenHuman_${VERSION}_${ARCH}.app.tar.gz"
|
||||
tar -czf "$APP_ZIP" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
|
||||
gh release upload "v${VERSION}" "$APP_ZIP" --repo "$UPLOAD_REPO" --clobber
|
||||
rm -f "$APP_ZIP"
|
||||
echo "[upload] Uploaded .app tarball"
|
||||
fi
|
||||
Reference in New Issue
Block a user