mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
feat: add macOS ARM64 build workflow and script
- Introduced a new GitHub Actions workflow for building Tauri applications on macOS Apple Silicon (aarch64). - Created a script to facilitate local execution of the macOS ARM64 build process, including handling secrets and environment variables. - Updated existing workflows to replace the APPLE_APP_SPECIFIC_PASSWORD with a more generic APPLE_PASSWORD for consistency. - Modified example secrets file to reflect the new password structure.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# Standalone macOS Apple Silicon (aarch64) Tauri build — no prepare-release / tagging.
|
||||
#
|
||||
# GitHub: workflow_dispatch checks out the ref. Local act: ACT=true skips checkout (use -b bind + run
|
||||
# ./scripts/run-macos-arm64-build.sh so submodules are initialized before act copies the tree).
|
||||
name: macOS ARM64 build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-macos-arm64:
|
||||
name: Build macOS aarch64 (signed)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.XGH_TOKEN || github.token }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
if: ${{ env.ACT != 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set Xcode version
|
||||
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 stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin
|
||||
|
||||
- name: Cargo.lock fingerprint (deps only)
|
||||
id: cargo-lock-fingerprint
|
||||
shell: bash
|
||||
run: |
|
||||
echo "hash=$(tail -n +8 src-tauri/Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Cargo registry and git sources
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Install skills dependencies
|
||||
run: cd skills && yarn install --frozen-lockfile
|
||||
|
||||
- name: Build skills
|
||||
run: cd skills && yarn build
|
||||
|
||||
- name: Define Tauri configuration overrides
|
||||
id: config-overrides
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
BASE_URL: ${{ vars.BASE_URL }}
|
||||
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }}
|
||||
WITH_UPDATER: 'true'
|
||||
with:
|
||||
github-token: ${{ secrets.XGH_TOKEN || github.token }}
|
||||
script: |
|
||||
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
|
||||
const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///';
|
||||
const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`;
|
||||
const { default: prepareTauriConfig } = await import(moduleUrl);
|
||||
const config = prepareTauriConfig();
|
||||
core.setOutput('json', JSON.stringify(config));
|
||||
|
||||
- name: Build frontend
|
||||
run: yarn build
|
||||
env:
|
||||
NODE_ENV: production
|
||||
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
|
||||
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
|
||||
|
||||
- name: Build, package (no GitHub release upload)
|
||||
uses: tauri-apps/tauri-action@v0.6.2
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ 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 }}
|
||||
BASE_URL: ${{ vars.BASE_URL }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
WITH_UPDATER: 'true'
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.15'
|
||||
with:
|
||||
args: -c ${{ steps.config-overrides.outputs.json }} --target aarch64-apple-darwin
|
||||
includeDebug: false
|
||||
includeRelease: true
|
||||
includeUpdaterJson: false
|
||||
@@ -305,7 +305,7 @@ jobs:
|
||||
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_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
BASE_URL: ${{ vars.BASE_URL }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
{
|
||||
"secrets": {
|
||||
"APPLE_APP_SPECIFIC_PASSWORD": "",
|
||||
"APPLE_CERTIFICATE_BASE64": "",
|
||||
"APPLE_CERTIFICATE_PASSWORD": "",
|
||||
"APPLE_ID": "",
|
||||
"APPLE_PASSWORD": "",
|
||||
"APPLE_SIGNING_IDENTITY": "",
|
||||
"APPLE_TEAM_ID": "",
|
||||
"GITHUB_TOKEN": "",
|
||||
"UPDATER_GIST_ID": "",
|
||||
"UPDATER_GIST_URL": "",
|
||||
"UPDATER_PRIVATE_KEY_PASSWORD": "",
|
||||
"UPDATER_PRIVATE_KEY": "",
|
||||
"UPDATER_PUBLIC_KEY": "",
|
||||
"TAURI_SIGNING_PRIVATE_KEY_PASSWORD": "",
|
||||
"TAURI_SIGNING_PRIVATE_KEY": "",
|
||||
"XGH_TOKEN": "",
|
||||
"XGITHUB_APP_ID": "",
|
||||
"XGITHUB_APP_PRIVATE_KEY": ""
|
||||
|
||||
Executable
+143
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run the standalone macOS ARM64 Tauri build via nektos/act (self-hosted = your Mac).
|
||||
# No prepare-release, no tagging, no GitHub release upload (includeUpdaterJson: false).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/run-macos-arm64-build.sh # dry-run
|
||||
# ./scripts/run-macos-arm64-build.sh --run # full signed build on this machine
|
||||
#
|
||||
# Requires: act, jq, scripts/ci-secrets.json (copy from ci-secrets.example.json)
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
WORKFLOW=".github/workflows/macos-arm64-build.yml"
|
||||
SECRETS_JSON="scripts/ci-secrets.json"
|
||||
RUN_MODE="dryrun"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--run)
|
||||
RUN_MODE="run"
|
||||
shift
|
||||
;;
|
||||
--dryrun|-n)
|
||||
RUN_MODE="dryrun"
|
||||
shift
|
||||
;;
|
||||
--secrets-json)
|
||||
SECRETS_JSON="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -f "$SECRETS_JSON" ]]; then
|
||||
echo "Secrets JSON not found: $SECRETS_JSON" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v act >/dev/null 2>&1; then
|
||||
echo "act is required. Install with: brew install act" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "jq is required. Install with: brew install jq" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SECRETS_FILE="$(mktemp)"
|
||||
VARS_FILE="$(mktemp)"
|
||||
EVENT_JSON="$(mktemp)"
|
||||
MERGED_SECRETS="$(mktemp)"
|
||||
trap 'rm -f "$SECRETS_FILE" "$VARS_FILE" "$EVENT_JSON" "$MERGED_SECRETS"' EXIT
|
||||
|
||||
jq '
|
||||
.secrets |= (
|
||||
. + {
|
||||
APPLE_APP_SPECIFIC_PASSWORD: (
|
||||
if (.APPLE_APP_SPECIFIC_PASSWORD // "") | length > 0 then .APPLE_APP_SPECIFIC_PASSWORD
|
||||
else (.APPLE_PASSWORD // "") end
|
||||
)
|
||||
}
|
||||
)
|
||||
' "$SECRETS_JSON" > "$MERGED_SECRETS"
|
||||
|
||||
jq -r '
|
||||
def dotenv_escape:
|
||||
gsub("\""; "\\\"") | gsub("\r"; "\\r") | gsub("\n"; "\\n");
|
||||
(.secrets // {}) | to_entries[] | select(.key != "GITHUB_TOKEN") | "\(.key)=\"\(.value | dotenv_escape)\""
|
||||
' "$MERGED_SECRETS" > "$SECRETS_FILE"
|
||||
jq -r '
|
||||
def dotenv_escape:
|
||||
gsub("\""; "\\\"") | gsub("\r"; "\\r") | gsub("\n"; "\\n");
|
||||
(.vars // {}) | to_entries[] | "\(.key)=\"\(.value | dotenv_escape)\""
|
||||
' "$SECRETS_JSON" > "$VARS_FILE"
|
||||
|
||||
REPO_FULL="${GITHUB_REPOSITORY:-}"
|
||||
if [[ -z "$REPO_FULL" ]]; then
|
||||
REPO_FULL="$(git remote get-url origin 2>/dev/null | sed -E 's#^git@github\.com:([^/]+)/([^/.]+)(\.git)?$#\1/\2#; s#^https://github\.com/([^/]+)/([^/.]+)(\.git)?$#\1/\2#')"
|
||||
fi
|
||||
if [[ -z "$REPO_FULL" || "$REPO_FULL" != */* ]]; then
|
||||
echo "Could not resolve GitHub owner/repo (set GITHUB_REPOSITORY or fix git remote origin)" >&2
|
||||
exit 1
|
||||
fi
|
||||
OWNER="${REPO_FULL%%/*}"
|
||||
REPO_NAME="${REPO_FULL##*/}"
|
||||
|
||||
REF="$(git symbolic-ref -q HEAD || true)"
|
||||
if [[ -z "$REF" ]]; then
|
||||
REF="refs/heads/main"
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg ref "$REF" \
|
||||
--arg full "$REPO_FULL" \
|
||||
--arg owner "$OWNER" \
|
||||
--arg name "$REPO_NAME" \
|
||||
'{
|
||||
ref: $ref,
|
||||
inputs: {},
|
||||
repository: {
|
||||
full_name: $full,
|
||||
default_branch: "main",
|
||||
name: $name,
|
||||
owner: { login: $owner }
|
||||
},
|
||||
sender: { login: "local-dev" }
|
||||
}' > "$EVENT_JSON"
|
||||
|
||||
echo "Workflow: $WORKFLOW"
|
||||
echo "Secrets: $SECRETS_JSON"
|
||||
echo "Ref: $REF"
|
||||
echo "Mode: $RUN_MODE"
|
||||
echo
|
||||
|
||||
# act -b copies the tree without .git — submodules must be materialized here first.
|
||||
if [[ -d .git ]]; then
|
||||
echo "Syncing git submodules (required for skills/, etc.)..."
|
||||
git submodule update --init --recursive
|
||||
fi
|
||||
echo
|
||||
|
||||
ACT_ARGS=(
|
||||
workflow_dispatch
|
||||
-W "$WORKFLOW"
|
||||
--eventpath "$EVENT_JSON"
|
||||
--secret-file "$SECRETS_FILE"
|
||||
--var-file "$VARS_FILE"
|
||||
-b
|
||||
-P macos-latest=-self-hosted
|
||||
)
|
||||
|
||||
if [[ "$RUN_MODE" == "dryrun" ]]; then
|
||||
echo "Dry-run only. Use --run for the full macOS ARM64 build."
|
||||
act "${ACT_ARGS[@]}" -n
|
||||
else
|
||||
act "${ACT_ARGS[@]}"
|
||||
fi
|
||||
+66
-21
@@ -5,9 +5,10 @@
|
||||
# - Uses scripts/ci-secrets.example.json for secrets/vars.
|
||||
# - Runs in dry-run mode unless --run is passed.
|
||||
#
|
||||
# For --run: set XGH_TOKEN in scripts/ci-secrets.json (PAT with repo scope). nektos/act sets
|
||||
# ACT=true and uses a fake repository name, so the GitHub App token step is skipped and the
|
||||
# workflow uses XGH_TOKEN instead (see "Resolve release git token" in release.yml).
|
||||
# For --run: set XGH_TOKEN in scripts/ci-secrets.json (PAT with repo scope). prepare-release uses
|
||||
# XGH_TOKEN for checkout/push. Do not put a bad GITHUB_TOKEN in ci-secrets.json — act uses it to
|
||||
# clone action repos and an invalid PAT breaks even public clones. github-script steps use
|
||||
# secrets.XGH_TOKEN (see release.yml).
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/test-release-act.sh
|
||||
@@ -16,6 +17,9 @@
|
||||
# ./scripts/test-release-act.sh --job prepare-release
|
||||
# ./scripts/test-release-act.sh --release-type minor
|
||||
# ./scripts/test-release-act.sh --secrets-json scripts/ci-secrets.json --run
|
||||
# # Single macOS (Apple Silicon) build for signing — pass through to act --matrix:
|
||||
# ./scripts/test-release-act.sh --run --job build-artifacts \
|
||||
# --matrix 'settings.platform:macos-latest' --matrix 'settings.args:--target aarch64-apple-darwin'
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
@@ -25,6 +29,7 @@ SECRETS_JSON="scripts/ci-secrets.json"
|
||||
RELEASE_TYPE="patch"
|
||||
RUN_MODE="dryrun"
|
||||
JOB_NAME=""
|
||||
MATRIX_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -52,6 +57,10 @@ while [[ $# -gt 0 ]]; do
|
||||
SECRETS_JSON="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--matrix)
|
||||
MATRIX_ARGS+=(--matrix "${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
@@ -90,36 +99,64 @@ fi
|
||||
SECRETS_FILE="$(mktemp)"
|
||||
VARS_FILE="$(mktemp)"
|
||||
EVENT_JSON="$(mktemp)"
|
||||
trap 'rm -f "$SECRETS_FILE" "$VARS_FILE" "$EVENT_JSON"' EXIT
|
||||
MERGED_SECRETS="$(mktemp)"
|
||||
trap 'rm -f "$SECRETS_FILE" "$VARS_FILE" "$EVENT_JSON" "$MERGED_SECRETS"' EXIT
|
||||
|
||||
# Merge defaults: APPLE_APP_SPECIFIC_PASSWORD (APPLE_PASSWORD is a common alias).
|
||||
# Do not put GITHUB_TOKEN in the act secret file — an invalid PAT breaks act's clone of public actions.
|
||||
jq '
|
||||
.secrets |= (
|
||||
. + {
|
||||
APPLE_APP_SPECIFIC_PASSWORD: (
|
||||
if (.APPLE_APP_SPECIFIC_PASSWORD // "") | length > 0 then .APPLE_APP_SPECIFIC_PASSWORD
|
||||
else (.APPLE_PASSWORD // "") end
|
||||
)
|
||||
}
|
||||
)
|
||||
' "$SECRETS_JSON" > "$MERGED_SECRETS"
|
||||
|
||||
# act --secret-file/--var-file expect dotenv format. Unquoted multiline values break the
|
||||
# parser (PEM/private keys look like extra KEY= lines and trigger errors on '/' etc.).
|
||||
jq -r '
|
||||
def dotenv_escape:
|
||||
gsub("\""; "\\\"") | gsub("\r"; "\\r") | gsub("\n"; "\\n");
|
||||
(.secrets // {}) | to_entries[] | "\(.key)=\"\(.value | dotenv_escape)\""
|
||||
' "$SECRETS_JSON" > "$SECRETS_FILE"
|
||||
(.secrets // {}) | to_entries[] | select(.key != "GITHUB_TOKEN") | "\(.key)=\"\(.value | dotenv_escape)\""
|
||||
' "$MERGED_SECRETS" > "$SECRETS_FILE"
|
||||
jq -r '
|
||||
def dotenv_escape:
|
||||
gsub("\""; "\\\"") | gsub("\r"; "\\r") | gsub("\n"; "\\n");
|
||||
(.vars // {}) | to_entries[] | "\(.key)=\"\(.value | dotenv_escape)\""
|
||||
' "$SECRETS_JSON" > "$VARS_FILE"
|
||||
|
||||
cat > "$EVENT_JSON" <<EOF
|
||||
{
|
||||
"ref": "refs/heads/main",
|
||||
"inputs": {
|
||||
"release_type": "$RELEASE_TYPE"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "local/openhuman",
|
||||
"default_branch": "main",
|
||||
"name": "openhuman",
|
||||
"owner": { "login": "local" }
|
||||
},
|
||||
"sender": { "login": "local-dev" }
|
||||
}
|
||||
EOF
|
||||
# Use real owner/repo from git so context.repo and tauri-action match your fork (not local/openhuman).
|
||||
REPO_FULL="${GITHUB_REPOSITORY:-}"
|
||||
if [[ -z "$REPO_FULL" ]]; then
|
||||
REPO_FULL="$(git remote get-url origin 2>/dev/null | sed -E 's#^git@github\.com:([^/]+)/([^/.]+)(\.git)?$#\1/\2#; s#^https://github\.com/([^/]+)/([^/.]+)(\.git)?$#\1/\2#')"
|
||||
fi
|
||||
if [[ -z "$REPO_FULL" || "$REPO_FULL" != */* ]]; then
|
||||
echo "Could not resolve GitHub owner/repo (set GITHUB_REPOSITORY or fix git remote origin)" >&2
|
||||
exit 1
|
||||
fi
|
||||
OWNER="${REPO_FULL%%/*}"
|
||||
REPO_NAME="${REPO_FULL##*/}"
|
||||
|
||||
jq -n \
|
||||
--arg ref "refs/heads/main" \
|
||||
--arg rt "$RELEASE_TYPE" \
|
||||
--arg full "$REPO_FULL" \
|
||||
--arg owner "$OWNER" \
|
||||
--arg name "$REPO_NAME" \
|
||||
'{
|
||||
ref: $ref,
|
||||
inputs: { release_type: $rt },
|
||||
repository: {
|
||||
full_name: $full,
|
||||
default_branch: "main",
|
||||
name: $name,
|
||||
owner: { login: $owner }
|
||||
},
|
||||
sender: { login: "local-dev" }
|
||||
}' > "$EVENT_JSON"
|
||||
|
||||
echo "Workflow: $WORKFLOW"
|
||||
echo "Secrets: $SECRETS_JSON"
|
||||
@@ -128,6 +165,9 @@ echo "Mode: $RUN_MODE"
|
||||
if [[ -n "$JOB_NAME" ]]; then
|
||||
echo "Job: $JOB_NAME"
|
||||
fi
|
||||
if [[ ${#MATRIX_ARGS[@]} -gt 0 ]]; then
|
||||
echo "Matrix: ${MATRIX_ARGS[*]}"
|
||||
fi
|
||||
echo
|
||||
|
||||
ACT_ARGS=(
|
||||
@@ -136,6 +176,7 @@ ACT_ARGS=(
|
||||
--eventpath "$EVENT_JSON"
|
||||
--secret-file "$SECRETS_FILE"
|
||||
--var-file "$VARS_FILE"
|
||||
--container-architecture linux/amd64
|
||||
-P ubuntu-latest=catthehacker/ubuntu:act-latest
|
||||
-P ubuntu-22.04=catthehacker/ubuntu:act-22.04
|
||||
-P macos-latest=-self-hosted
|
||||
@@ -145,6 +186,10 @@ if [[ -n "$JOB_NAME" ]]; then
|
||||
ACT_ARGS+=(-j "$JOB_NAME")
|
||||
fi
|
||||
|
||||
if [[ ${#MATRIX_ARGS[@]} -gt 0 ]]; then
|
||||
ACT_ARGS+=("${MATRIX_ARGS[@]}")
|
||||
fi
|
||||
|
||||
if [[ "$RUN_MODE" == "dryrun" ]]; then
|
||||
echo "Dry-run only. Use --run to execute."
|
||||
act "${ACT_ARGS[@]}" -n
|
||||
|
||||
Reference in New Issue
Block a user