Initial release
This commit is contained in:
Executable
+298
@@ -0,0 +1,298 @@
|
||||
#!/bin/bash
|
||||
# HOT-Step CPP — macOS Release Packager
|
||||
#
|
||||
# Builds a portable .tar.gz release that requires ZERO prerequisites.
|
||||
# End users just extract and run ./launch.sh — no Xcode, no brew, no npm.
|
||||
#
|
||||
# What gets bundled:
|
||||
# - Pre-built C++ engine binaries (Metal GPU)
|
||||
# - Bundled Node.js 22 runtime (~40 MB)
|
||||
# - Pre-installed server dependencies (production only)
|
||||
# - Pre-built React UI
|
||||
# - Launch scripts
|
||||
#
|
||||
# Usage:
|
||||
# ./package-release.sh # Build release package
|
||||
# ./package-release.sh --skip-build # Package only (engine already built)
|
||||
#
|
||||
# Output:
|
||||
# HOT-Step-CPP-v{VERSION}-macOS-arm64.tar.gz
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
ROOT_DIR="$(pwd)"
|
||||
|
||||
# ── Config ───────────────────────────────────────────────────────────
|
||||
VERSION="${VERSION:-1.0.0}"
|
||||
ARCH="arm64"
|
||||
NODE_VERSION="22.22.2"
|
||||
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-${ARCH}.tar.xz"
|
||||
RELEASE_NAME="HOT-Step-CPP-v${VERSION}-macOS-${ARCH}"
|
||||
SKIP_BUILD=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--version=*) VERSION="${arg#*=}"; RELEASE_NAME="HOT-Step-CPP-v${VERSION}-macOS-${ARCH}" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
STAGING_DIR="${ROOT_DIR}/release-staging/${RELEASE_NAME}"
|
||||
OUTPUT_FILE="${ROOT_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# ── Colors ───────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
step() { echo -e "\n${CYAN}${BOLD}[$1/$TOTAL_STEPS]${NC} ${BOLD}$2${NC}"; }
|
||||
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
||||
fail() { echo -e " ${RED}✗ $1${NC}"; exit 1; }
|
||||
|
||||
TOTAL_STEPS=7
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}╔══════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BOLD}║ HOT-Step CPP — Release Packager ║${NC}"
|
||||
echo -e "${BOLD}║ macOS ${ARCH} Portable Build ║${NC}"
|
||||
echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo " Version: ${VERSION}"
|
||||
echo " Output: ${RELEASE_NAME}.tar.gz"
|
||||
echo ""
|
||||
|
||||
# ── Find compatible Node for the build process ──────────────────────
|
||||
find_node() {
|
||||
for bp in "/opt/homebrew/opt/node@22/bin" "/usr/local/opt/node@22/bin"; do
|
||||
if [ -x "${bp}/node" ]; then echo "$bp"; return 0; fi
|
||||
done
|
||||
if command -v node &>/dev/null; then
|
||||
local ver; ver="$(node --version 2>/dev/null | tr -d 'v')"
|
||||
local major="${ver%%.*}"
|
||||
if [ "$major" -ge 18 ] && [ "$major" -lt 24 ] 2>/dev/null; then
|
||||
echo "$(dirname "$(command -v node)")"; return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
NODE_DIR=""
|
||||
if NODE_DIR=$(find_node); then
|
||||
export PATH="${NODE_DIR}:${PATH}"
|
||||
else
|
||||
fail "Node.js 18-22 required for building. Run: brew install node@22"
|
||||
fi
|
||||
|
||||
# ── Step 1: Build engine ─────────────────────────────────────────────
|
||||
step 1 "Building C++ engine..."
|
||||
|
||||
if [ "$SKIP_BUILD" -eq 1 ] && [ -f "engine/build/ace-server" ]; then
|
||||
ok "Skipping build (--skip-build, binaries exist)"
|
||||
else
|
||||
cd engine
|
||||
rm -rf build && mkdir build && cd build
|
||||
cmake .. \
|
||||
-DGGML_METAL=ON \
|
||||
-DGGML_METAL_EMBED_LIBRARY=ON \
|
||||
-DGGML_BACKEND_DL=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
2>&1 | tail -3
|
||||
NCPU=$(sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
||||
cmake --build . --config Release -j "${NCPU}" 2>&1 | grep -E '^\[100|Built target|error:'
|
||||
cd "${ROOT_DIR}"
|
||||
fi
|
||||
|
||||
# Verify binaries
|
||||
for bin in ace-server mastering mp3-codec vst-host; do
|
||||
[ -f "engine/build/${bin}" ] || fail "engine/build/${bin} not found"
|
||||
done
|
||||
ok "All engine binaries built"
|
||||
|
||||
# Fix rpaths: replace any absolute build-dir rpaths with @executable_path
|
||||
# so dylibs are found relative to the binary (portable release requirement).
|
||||
echo " Fixing dylib rpaths for portability..."
|
||||
for bin in engine/build/ace-server engine/build/ace-lm engine/build/ace-synth \
|
||||
engine/build/ace-understand engine/build/neural-codec engine/build/quantize; do
|
||||
if [ -f "$bin" ]; then
|
||||
# Remove all existing rpaths (may be absolute build paths)
|
||||
for old_rpath in $(otool -l "$bin" 2>/dev/null | grep -A1 LC_RPATH | grep "path " | awk '{print $2}'); do
|
||||
install_name_tool -delete_rpath "$old_rpath" "$bin" 2>/dev/null || true
|
||||
done
|
||||
# Add the portable @executable_path rpath
|
||||
install_name_tool -add_rpath "@executable_path" "$bin" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
ok "Rpaths fixed (@executable_path)"
|
||||
|
||||
# ── Step 2: Install server deps (production) ─────────────────────────
|
||||
step 2 "Installing server dependencies (production)..."
|
||||
|
||||
cd server
|
||||
rm -rf node_modules
|
||||
npm install --omit=dev --loglevel=warn 2>&1 | tail -3
|
||||
# Rebuild native modules
|
||||
npm rebuild better-sqlite3 2>&1 | tail -1
|
||||
# tsx is in devDependencies but needed at runtime to run the TypeScript server
|
||||
npm install tsx --save-optional --loglevel=warn 2>&1 | tail -1
|
||||
ok "Server dependencies installed (production + tsx)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
# ── Step 3: Build UI ─────────────────────────────────────────────────
|
||||
step 3 "Building production UI..."
|
||||
|
||||
cd ui
|
||||
[ -d "node_modules" ] || npm install --loglevel=warn
|
||||
npx vite build 2>&1 | tail -3
|
||||
ok "UI built"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
# ── Step 4: Download Node.js runtime ─────────────────────────────────
|
||||
step 4 "Downloading Node.js ${NODE_VERSION} runtime..."
|
||||
|
||||
NODE_CACHE="${ROOT_DIR}/release-staging/node-cache"
|
||||
NODE_ARCHIVE="${NODE_CACHE}/node-v${NODE_VERSION}-darwin-${ARCH}.tar.xz"
|
||||
NODE_EXTRACTED="${NODE_CACHE}/node-v${NODE_VERSION}-darwin-${ARCH}"
|
||||
|
||||
mkdir -p "${NODE_CACHE}"
|
||||
|
||||
if [ -f "${NODE_EXTRACTED}/bin/node" ]; then
|
||||
ok "Using cached Node.js download"
|
||||
else
|
||||
echo " Downloading from ${NODE_URL}..."
|
||||
curl -L --progress-bar -o "${NODE_ARCHIVE}" "${NODE_URL}"
|
||||
cd "${NODE_CACHE}"
|
||||
tar xf "$(basename "${NODE_ARCHIVE}")"
|
||||
cd "${ROOT_DIR}"
|
||||
ok "Node.js ${NODE_VERSION} downloaded"
|
||||
fi
|
||||
|
||||
# ── Step 5: Assemble release ─────────────────────────────────────────
|
||||
step 5 "Assembling release package..."
|
||||
|
||||
rm -rf "${STAGING_DIR}"
|
||||
mkdir -p "${STAGING_DIR}"
|
||||
|
||||
# Engine binaries + dylibs
|
||||
mkdir -p "${STAGING_DIR}/engine/build"
|
||||
for bin in ace-server ace-lm ace-synth ace-understand mastering mp3-codec neural-codec quantize vst-host; do
|
||||
[ -f "engine/build/${bin}" ] && cp "engine/build/${bin}" "${STAGING_DIR}/engine/build/"
|
||||
done
|
||||
# Copy ggml dylibs
|
||||
cp engine/build/libggml*.dylib "${STAGING_DIR}/engine/build/" 2>/dev/null || true
|
||||
|
||||
# Lua plugins — dual directory system:
|
||||
# engine/plugins/ = native/built-in plugins (solvers, schedulers, guidance)
|
||||
# plugins/ = community/user plugins (same structure, overrides native)
|
||||
# ace-server scans both via engine_dir and project_dir resolution from binary path
|
||||
if [ -d "engine/plugins" ]; then
|
||||
mkdir -p "${STAGING_DIR}/engine/plugins"
|
||||
cp -r engine/plugins/* "${STAGING_DIR}/engine/plugins/"
|
||||
PLUGIN_COUNT=$(find "${STAGING_DIR}/engine/plugins" -name "*.lua" | wc -l | tr -d ' ')
|
||||
ok "${PLUGIN_COUNT} native plugins copied (engine/plugins/)"
|
||||
fi
|
||||
if [ -d "plugins" ]; then
|
||||
cp -r plugins "${STAGING_DIR}/"
|
||||
ok "Community plugins dir copied (plugins/)"
|
||||
fi
|
||||
|
||||
# Server (source + node_modules, no devDependencies)
|
||||
mkdir -p "${STAGING_DIR}/server"
|
||||
cp server/package.json server/package-lock.json "${STAGING_DIR}/server/"
|
||||
cp server/tsconfig.json "${STAGING_DIR}/server/"
|
||||
cp -r server/node_modules "${STAGING_DIR}/server/"
|
||||
cp -r server/src "${STAGING_DIR}/server/"
|
||||
[ -f server/migrate_lireek.cjs ] && cp server/migrate_lireek.cjs "${STAGING_DIR}/server/"
|
||||
ok "Server copied"
|
||||
|
||||
# Pre-built UI
|
||||
mkdir -p "${STAGING_DIR}/ui"
|
||||
cp -r ui/dist "${STAGING_DIR}/ui/"
|
||||
ok "UI copied"
|
||||
|
||||
# Node.js runtime (just the binary + npm essentials)
|
||||
mkdir -p "${STAGING_DIR}/runtime"
|
||||
cp "${NODE_EXTRACTED}/bin/node" "${STAGING_DIR}/runtime/node"
|
||||
# Copy npm/npx for server to use tsx
|
||||
mkdir -p "${STAGING_DIR}/runtime/lib"
|
||||
cp -r "${NODE_EXTRACTED}/lib/node_modules" "${STAGING_DIR}/runtime/lib/"
|
||||
# Create npx/npm symlinks
|
||||
ln -sf "../lib/node_modules/npm/bin/npm-cli.js" "${STAGING_DIR}/runtime/npm"
|
||||
ln -sf "../lib/node_modules/npm/bin/npx-cli.js" "${STAGING_DIR}/runtime/npx"
|
||||
ok "Node.js runtime bundled"
|
||||
|
||||
# Empty directories for user content
|
||||
mkdir -p "${STAGING_DIR}/models"
|
||||
mkdir -p "${STAGING_DIR}/adapters"
|
||||
|
||||
# Scripts
|
||||
cp launch.sh "${STAGING_DIR}/"
|
||||
cp .env.example "${STAGING_DIR}/"
|
||||
chmod +x "${STAGING_DIR}/launch.sh"
|
||||
|
||||
# README snippet for release
|
||||
cat > "${STAGING_DIR}/README.txt" << 'HEREDOC'
|
||||
HOT-Step CPP — macOS (Apple Silicon)
|
||||
|
||||
Quick Start:
|
||||
1. Extract this archive
|
||||
2. Run: ./launch.sh
|
||||
3. Open http://localhost:3001 in your browser
|
||||
4. Go to Models → Get More Models to download AI models (~7 GB)
|
||||
|
||||
If macOS blocks the app (unsigned binary):
|
||||
xattr -cr /path/to/HOT-Step-CPP-macOS-arm64/
|
||||
|
||||
Requirements:
|
||||
- macOS 13+ (Apple Silicon)
|
||||
- ~10 GB free disk space
|
||||
- No other software needed — Node.js is bundled
|
||||
|
||||
For the full README, source code, and updates:
|
||||
https://github.com/scragnog/HOT-Step-CPP
|
||||
HEREDOC
|
||||
ok "Release assembled"
|
||||
|
||||
# ── Step 6: Set permissions ──────────────────────────────────────────
|
||||
step 6 "Setting permissions..."
|
||||
|
||||
find "${STAGING_DIR}/engine/build" -type f -perm +0111 -exec chmod +x {} \; 2>/dev/null || true
|
||||
chmod +x "${STAGING_DIR}/engine/build/"* 2>/dev/null || true
|
||||
chmod +x "${STAGING_DIR}/runtime/node"
|
||||
# npm/npx are symlinks to .js files — make targets executable
|
||||
chmod +x "${STAGING_DIR}/runtime/lib/node_modules/npm/bin/npm-cli.js" 2>/dev/null || true
|
||||
chmod +x "${STAGING_DIR}/runtime/lib/node_modules/npm/bin/npx-cli.js" 2>/dev/null || true
|
||||
chmod +x "${STAGING_DIR}/launch.sh"
|
||||
ok "Permissions set"
|
||||
|
||||
# ── Step 7: Create archive ──────────────────────────────────────────
|
||||
step 7 "Creating archive..."
|
||||
|
||||
cd release-staging
|
||||
tar czf "${OUTPUT_FILE}" "${RELEASE_NAME}"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
# Size info
|
||||
SIZE_MB=$(du -sm "${OUTPUT_FILE}" | cut -f1)
|
||||
ok "${RELEASE_NAME}.tar.gz (${SIZE_MB} MB)"
|
||||
|
||||
# ── Done ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}${BOLD}║ Release package ready! 🎉 ║${NC}"
|
||||
echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo " File: ${OUTPUT_FILE}"
|
||||
echo " Size: ${SIZE_MB} MB"
|
||||
echo ""
|
||||
echo " Test locally:"
|
||||
echo " cd release-staging/${RELEASE_NAME}"
|
||||
echo " ./launch.sh"
|
||||
echo ""
|
||||
|
||||
# Clean up staging (keep the archive)
|
||||
# rm -rf release-staging
|
||||
Reference in New Issue
Block a user