Files
transcriber/scripts/setup.sh
T

677 lines
25 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Local AI Image Generator - Linux/macOS Setup Script
# Self-contained: no apt/yum/pacman, no global Node.js install.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
FRONTEND_DIR="$APP_DIR/frontend"
TOOLS_DIR="$APP_DIR/tools"
DIST_DIR="$APP_DIR/dist"
PLATFORM="$(uname -s)"
ARCH="$(uname -m)"
if [[ "$PLATFORM" == "Darwin" ]]; then
PLATFORM_LABEL="macOS"
NODE_DIR="$TOOLS_DIR/node-mac"
BACKEND_DIR="$APP_DIR/backend/mac"
else
PLATFORM_LABEL="Linux"
NODE_DIR="$TOOLS_DIR/node-linux"
BACKEND_DIR="$APP_DIR/backend/linux"
fi
NODE_BIN="$NODE_DIR/bin/node"
NPM_BIN="$NODE_DIR/bin/npm"
# Check if filesystem supports symlinks
USE_SYMLINKS=true
TEST_LINK="$ROOT_DIR/.test_symlink"
rm -f "$TEST_LINK"
if ln -s "test" "$TEST_LINK" 2>/dev/null; then
rm -f "$TEST_LINK"
else
USE_SYMLINKS=false
fi
# Release pins
SD_RELEASE="master-685-19bdfe2"
SD_SHORT_HASH="${SD_RELEASE##*-}"
SD_BASE_URL="https://github.com/leejet/stable-diffusion.cpp/releases/download/$SD_RELEASE"
NODE_VERSION="22.12.0"
if [[ "$PLATFORM" == "Darwin" ]]; then
if [[ "$ARCH" == "arm64" ]]; then
NODE_PLATFORM_ARCH="darwin-arm64"
else
NODE_PLATFORM_ARCH="darwin-x64"
fi
NODE_TARBALL="node-v${NODE_VERSION}-${NODE_PLATFORM_ARCH}.tar.gz"
else
NODE_PLATFORM_ARCH="linux-x64"
NODE_TARBALL="node-v${NODE_VERSION}-${NODE_PLATFORM_ARCH}.tar.xz"
fi
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/$NODE_TARBALL"
# Flags
MAX_PERF=0
if [[ "${1:-}" == "--max-perf" ]]; then
MAX_PERF=1
fi
# ── Helpers ─────────────────────────────────────────────────────────────────
print_header() {
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR - $PLATFORM_LABEL First-Time Setup"
echo " 100% Self-Contained | No System Install Required"
echo " ============================================================"
echo ""
}
print_step() {
local n="$1" total="$2" title="$3"
echo ""
echo " [$n/$total] $title"
echo " ------------------------------------------------------------"
}
print_ok() { echo " OK $1"; }
print_info() { echo " >> $1"; }
print_warn() { echo " !! $1"; }
print_fail() { echo " XX $1"; }
format_bytes() {
local b="${1:-0}"
if command -v bc >/dev/null 2>&1; then
if (( b > 1073741824 )); then printf "%.2f GB" "$(echo "scale=4; $b / 1073741824" | bc)"; return; fi
if (( b > 1048576 )); then printf "%.1f MB" "$(echo "scale=4; $b / 1048576" | bc)"; return; fi
if (( b > 1024 )); then printf "%.0f KB" "$(echo "scale=4; $b / 1024" | bc)"; return; fi
else
if (( b > 1073741824 )); then printf "%.2f GB" "$(awk "BEGIN {printf \"%.2f\", $b/1073741824}")"; return; fi
if (( b > 1048576 )); then printf "%.1f MB" "$(awk "BEGIN {printf \"%.1f\", $b/1048576}")"; return; fi
if (( b > 1024 )); then printf "%.0f KB" "$(awk "BEGIN {printf \"%.0f\", $b/1024}")"; return; fi
fi
printf "%s B" "$b"
}
# Rich download with progress bar
download_file() {
local url="$1" dest="$2" label="$3"
print_info "Downloading: $label"
echo ""
local tmp_dest="${dest}.part"
rm -f "$tmp_dest"
curl -fSL --progress-bar "$url" -o "$tmp_dest" || {
print_fail "Download failed: $url"
rm -f "$tmp_dest"
return 1
}
mv "$tmp_dest" "$dest"
echo ""
local fsize
fsize="$(stat -c%s "$dest" 2>/dev/null || stat -f%z "$dest" 2>/dev/null || echo 0)"
print_ok "Downloaded $(format_bytes "$fsize")"
}
# Extract ZIP using Python zipfile (preferred), unzip, or Node.js adm-zip
extract_zip() {
local zip_path="$1" dest="$2" label="$3"
print_info "Extracting: $label"
mkdir -p "$dest"
# Prefer Python zipfile (most reliable, no host deps beyond python3)
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import zipfile, sys, os
with zipfile.ZipFile(sys.argv[1], 'r') as z:
for member in z.namelist():
z.extract(member, sys.argv[2])
" "$zip_path" "$dest" && return 0
fi
if command -v unzip >/dev/null 2>&1; then
unzip -o -q "$zip_path" -d "$dest" && return 0
fi
if command -v python >/dev/null 2>&1; then
python -c "import zipfile, sys; zipfile.ZipFile(sys.argv[1], 'r').extractall(sys.argv[2])" "$zip_path" "$dest" && return 0
fi
print_fail "No ZIP extractor available (tried python3, unzip, python)."
return 1
}
# Extract tar.xz
extract_tarxz() {
local tar_path="$1" dest="$2" label="$3"
print_info "Extracting: $label"
mkdir -p "$dest"
if [ "$USE_SYMLINKS" = false ]; then
# On filesystems that do not support symlinks, tar will fail when attempting to create symlinks
# (e.g. for npm/npx/corepack in Node.js bin/). We allow it to continue but verify key files.
local tar_exit=0
tar -xf "$tar_path" -C "$dest" || tar_exit=$?
if [[ $tar_exit -ne 0 ]]; then
# Check if node binary was extracted successfully
local check_file
check_file="$(find "$dest" -type f -name "node" | head -n 1)"
if [[ -n "$check_file" ]]; then
print_warn "tar returned exit code $tar_exit due to symlink failures on this filesystem, but bin/node was successfully extracted."
else
print_fail "tar extraction failed with exit code $tar_exit (could not find bin/node)"
exit $tar_exit
fi
fi
else
tar -xf "$tar_path" -C "$dest"
fi
print_ok "Extracted $label"
}
detect_gpu_vendor() {
local vendor=""
if [[ -d /sys/bus/pci/devices ]]; then
for vfile in /sys/bus/pci/devices/*/vendor; do
if [[ -f "$vfile" ]]; then
local vid
vid="$(cat "$vfile" 2>/dev/null || true)"
case "$vid" in
"0x10de") vendor="nvidia" ;;
"0x1002") vendor="amd" ;;
"0x8086") [[ -z "$vendor" ]] && vendor="intel" ;;
esac
fi
done
fi
echo "$vendor"
}
# Official Linux binaries are built on Ubuntu 24.04 and link against glibc 2.38+.
# This routine prints a warning on older distributions so users know upfront.
check_glibc() {
local required="2.38"
local current=""
if command -v ldd >/dev/null 2>&1; then
current="$(ldd --version 2>/dev/null | head -n1 | grep -oP '[0-9]+\.[0-9]+' | head -n1 || true)"
fi
if [[ -z "$current" ]]; then
print_warn "Could not detect glibc version. Prebuilt Linux backends require glibc $required+ (Ubuntu 24.04)."
return
fi
if [[ "$(printf '%s\n' "$required" "$current" | sort -V | head -n1)" != "$required" ]]; then
print_warn "Detected glibc $current. Prebuilt Linux backends require glibc $required+ (Ubuntu 24.04)."
print_info "You can still use the app, but the downloaded backends will not start on this system."
print_info "To fix: upgrade to Ubuntu 24.04+ or build stable-diffusion.cpp from source (see README)."
fi
}
copy_binaries_from_extracted() {
local extracted_dir="$1" dest_dir="$2" main_name="$3" server_name="$4"
while IFS= read -r -d '' f; do
local base
base="$(basename "$f")"
case "$base" in
sd|sd-cli) cp "$f" "$dest_dir/$main_name" ;;
sd-server) cp "$f" "$dest_dir/$server_name" ;;
esac
done < <(find "$extracted_dir" -type f \( -name "sd" -o -name "sd-cli" -o -name "sd-server" \) -print0 2>/dev/null)
# Copy any .so files
find "$extracted_dir" -type f -name "*.so" -exec cp {} "$dest_dir/" \; 2>/dev/null || true
}
copy_macos_backend_from_extracted() {
local extracted_dir="$1" dest_dir="$2"
local target="$dest_dir/sd"
local server_bin=""
local cli_bin=""
server_bin="$(find "$extracted_dir" -type f -name "sd-server" | head -n 1)"
cli_bin="$(find "$extracted_dir" -type f \( -name "sd" -o -name "sd-cli" \) | head -n 1)"
if [[ -n "$server_bin" ]]; then
cp "$server_bin" "$target"
elif [[ -n "$cli_bin" ]]; then
cp "$cli_bin" "$target"
else
print_fail "No sd-server, sd, or sd-cli binary was found in the macOS backend archive."
return 1
fi
find "$extracted_dir" -type f \( -name "*.dylib" -o -name "*.metallib" \) -exec cp {} "$dest_dir/" \; 2>/dev/null || true
chmod +x "$target" 2>/dev/null || true
}
# ════════════════════════════════════════════════════════════════════════════
print_header
if [[ "$PLATFORM" == "Linux" ]]; then
check_glibc
fi
TOTAL_STEPS=4
# ── Step 1: Portable Node.js ────────────────────────────────────────────────
print_step 1 $TOTAL_STEPS "Setting up portable Node.js ($NODE_DIR/)"
if [[ -x "$NODE_BIN" && -x "$NPM_BIN" ]]; then
VERSION=$("$NODE_BIN" --version)
print_ok "Portable Node.js already ready: $VERSION"
else
mkdir -p "$TOOLS_DIR"
NODE_TAR_PATH="$TOOLS_DIR/$NODE_TARBALL"
download_file "$NODE_URL" "$NODE_TAR_PATH" "Node.js v${NODE_VERSION} LTS (Portable tarball)"
extract_tarxz "$NODE_TAR_PATH" "$TOOLS_DIR" "Node.js"
rm -f "$NODE_TAR_PATH"
EXTRACTED_DIR="$(find "$TOOLS_DIR" -maxdepth 1 -type d -name "node-v*-${NODE_PLATFORM_ARCH}" | head -n 1)"
if [[ -d "$EXTRACTED_DIR" ]]; then
rm -rf "$NODE_DIR"
mv "$EXTRACTED_DIR" "$NODE_DIR"
fi
if [ "$USE_SYMLINKS" = false ]; then
print_info "Filesystem does not support symlinks. Creating shell wrappers for npm, npx, and corepack..."
rm -f "$NODE_DIR/bin/npm" "$NODE_DIR/bin/npx" "$NODE_DIR/bin/corepack"
cat << 'EOF' > "$NODE_DIR/bin/npm"
#!/bin/sh
basedir=$(dirname "$0")
exec "$basedir/node" "$basedir/../lib/node_modules/npm/bin/npm-cli.js" "$@"
EOF
chmod +x "$NODE_DIR/bin/npm"
cat << 'EOF' > "$NODE_DIR/bin/npx"
#!/bin/sh
basedir=$(dirname "$0")
exec "$basedir/node" "$basedir/../lib/node_modules/npm/bin/npx-cli.js" "$@"
EOF
chmod +x "$NODE_DIR/bin/npx"
cat << 'EOF' > "$NODE_DIR/bin/corepack"
#!/bin/sh
basedir=$(dirname "$0")
exec "$basedir/node" "$basedir/../lib/node_modules/corepack/dist/corepack.js" "$@"
EOF
chmod +x "$NODE_DIR/bin/corepack"
print_ok "Created shell wrappers."
fi
if [[ ! -x "$NODE_BIN" || ! -x "$NPM_BIN" ]]; then
print_fail "Portable Node.js install is incomplete."
exit 1
fi
VERSION=$("$NODE_BIN" --version)
print_ok "Portable Node.js ready: $VERSION"
fi
# ── Step 2: stable-diffusion.cpp Backends ───────────────────────────────────
mkdir -p "$BACKEND_DIR"
if [[ "$PLATFORM" == "Darwin" ]]; then
print_step 2 $TOTAL_STEPS "Setting up stable-diffusion.cpp Metal backend (app/backend/mac/)"
if [[ "$ARCH" != "arm64" ]]; then
print_fail "The official macOS backend binary is Apple Silicon only (arm64)."
print_info "macOS Intel hardware is completely unsupported and has not been tested."
exit 1
fi
MAC_BACKEND="$BACKEND_DIR/sd"
if [[ -x "$MAC_BACKEND" ]]; then
print_ok "macOS Metal backend already ready."
else
MAC_ZIP="$TOOLS_DIR/sd-mac-metal.zip"
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Darwin-macOS-15.7.7-arm64.zip" "$MAC_ZIP" "stable-diffusion.cpp Metal Backend (macOS arm64)"
extract_zip "$MAC_ZIP" "$BACKEND_DIR/extracted" "macOS Metal Backend"
rm -f "$MAC_ZIP"
copy_macos_backend_from_extracted "$BACKEND_DIR/extracted" "$BACKEND_DIR"
rm -rf "$BACKEND_DIR/extracted"
print_ok "macOS Metal backend installed."
fi
# CoreML NPU Environment setup (macOS Apple Silicon only)
print_info "Setting up CoreML Python virtual environment for Apple Silicon ANE (NPU)..."
VENV_DIR="$BACKEND_DIR/coreml_venv"
PYTHON_BIN="$VENV_DIR/bin/python"
if [[ ! -x "$PYTHON_BIN" ]]; then
print_info "Creating Python virtual environment at $VENV_DIR..."
if ! python3 -m venv "$VENV_DIR"; then
print_warn "Could not create the virtual environment for CoreML. CoreML NPU mode will be unavailable."
fi
fi
if [[ -x "$PYTHON_BIN" ]]; then
print_info "Installing CoreML dependencies (this may take a couple of minutes)..."
if "$PYTHON_BIN" -m pip install --upgrade pip >/dev/null 2>&1 && \
"$PYTHON_BIN" -m pip install numpy coremltools diffusers transformers huggingface-hub pillow >/dev/null 2>&1 && \
"$PYTHON_BIN" -m pip install "git+https://github.com/apple/ml-stable-diffusion.git" >/dev/null 2>&1; then
print_ok "CoreML ANE (NPU) environment ready."
else
print_warn "CoreML dependencies installation failed. CoreML NPU mode will be unavailable."
fi
fi
else
VENDOR="$(detect_gpu_vendor)"
print_step 2 $TOTAL_STEPS "Detecting GPU vendor: ${VENDOR:-none}"
# CPU backend (always)
CPU_BACKEND_DIR="$BACKEND_DIR/cpu"
if [[ ! -f "$CPU_BACKEND_DIR/sd-cpu" || ! -f "$CPU_BACKEND_DIR/sd-server-cpu" ]]; then
mkdir -p "$CPU_BACKEND_DIR"
CPU_ZIP="$TOOLS_DIR/sd-cpu.zip"
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64.zip" "$CPU_ZIP" "stable-diffusion.cpp CPU Backend (Linux x86_64)"
extract_zip "$CPU_ZIP" "$CPU_BACKEND_DIR/extracted" "CPU Backend"
rm -f "$CPU_ZIP"
copy_binaries_from_extracted "$CPU_BACKEND_DIR/extracted" "$CPU_BACKEND_DIR" "sd-cpu" "sd-server-cpu"
rm -rf "$CPU_BACKEND_DIR/extracted"
print_ok "CPU backend installed."
else
print_ok "CPU backend already ready."
fi
chmod +x "$CPU_BACKEND_DIR/sd-cpu" "$CPU_BACKEND_DIR/sd-server-cpu" 2>/dev/null || true
# Vulkan backend (always - cross-vendor GPU fallback)
VULKAN_BACKEND_DIR="$BACKEND_DIR/vulkan"
if [[ ! -f "$VULKAN_BACKEND_DIR/sd-vulkan" || ! -f "$VULKAN_BACKEND_DIR/sd-server-vulkan" ]]; then
mkdir -p "$VULKAN_BACKEND_DIR"
VULKAN_ZIP="$TOOLS_DIR/sd-vulkan.zip"
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip" "$VULKAN_ZIP" "stable-diffusion.cpp Vulkan Backend (Linux x86_64)"
extract_zip "$VULKAN_ZIP" "$VULKAN_BACKEND_DIR/extracted" "Vulkan Backend"
rm -f "$VULKAN_ZIP"
copy_binaries_from_extracted "$VULKAN_BACKEND_DIR/extracted" "$VULKAN_BACKEND_DIR" "sd-vulkan" "sd-server-vulkan"
rm -rf "$VULKAN_BACKEND_DIR/extracted"
print_ok "Vulkan backend installed."
else
print_ok "Vulkan backend already ready."
fi
chmod +x "$VULKAN_BACKEND_DIR/sd-vulkan" "$VULKAN_BACKEND_DIR/sd-server-vulkan" 2>/dev/null || true
# ROCm backend (optional --max-perf, or auto-detected AMD)
ROCM_BACKEND_DIR="$BACKEND_DIR/rocm"
if [[ $MAX_PERF -eq 1 ]] && [[ "$VENDOR" == "amd" || "$VENDOR" == "" ]]; then
if [[ ! -f "$ROCM_BACKEND_DIR/sd-rocm" || ! -f "$ROCM_BACKEND_DIR/sd-server-rocm" ]]; then
mkdir -p "$ROCM_BACKEND_DIR"
ROCM_ZIP="$TOOLS_DIR/sd-rocm.zip"
print_warn "ROCm backend is ~1.2 GB. This may take a while..."
download_file "$SD_BASE_URL/sd-master-${SD_SHORT_HASH}-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.13.0.zip" "$ROCM_ZIP" "stable-diffusion.cpp ROCm Backend (Linux x86_64)"
extract_zip "$ROCM_ZIP" "$ROCM_BACKEND_DIR/extracted" "ROCm Backend"
rm -f "$ROCM_ZIP"
copy_binaries_from_extracted "$ROCM_BACKEND_DIR/extracted" "$ROCM_BACKEND_DIR" "sd-rocm" "sd-server-rocm"
rm -rf "$ROCM_BACKEND_DIR/extracted"
print_ok "ROCm backend installed."
else
print_ok "ROCm backend already ready."
fi
chmod +x "$ROCM_BACKEND_DIR/sd-rocm" "$ROCM_BACKEND_DIR/sd-server-rocm" 2>/dev/null || true
fi
# CUDA backend on Linux: ask user, download prebuilt first, and compile from source as a fallback.
CUDA_BACKEND_DIR="$BACKEND_DIR/cuda"
if [[ "$VENDOR" == "nvidia" ]]; then
if [[ ! -f "$CUDA_BACKEND_DIR/sd-cuda" || ! -f "$CUDA_BACKEND_DIR/sd-server-cuda" ]]; then
echo ""
echo " ============================================================"
echo " NVIDIA GPU Detected"
echo " ============================================================"
echo " To get the best performance, you can use the CUDA backend."
echo " Setting this up can download a prebuilt binary or compile from"
echo " source (which takes 10-15 minutes)."
echo ""
echo " Alternatively, you can use the Vulkan backend which is already"
echo " installed and runs immediately (recommended for GTX cards)."
echo " ============================================================"
echo ""
CHOOSE_CUDA="n"
if [[ -t 0 ]]; then
read -t 30 -rp " Do you want to proceed with CUDA setup? [y/N]: " CHOOSE_CUDA || CHOOSE_CUDA="n"
else
print_info "Non-interactive environment detected; defaulting to Vulkan."
fi
if [[ "$CHOOSE_CUDA" =~ ^[Yy]$ ]]; then
TRY_DOWNLOAD=1
PREBUILT_URL="https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-server-x86_64-unknown-linux-gnu-cuda"
PREBUILT_CLI_URL="https://github.com/leaxer-ai/leaxer-stable-diffusion/releases/download/v0.1.0/sd-x86_64-unknown-linux-gnu-cuda"
mkdir -p "$CUDA_BACKEND_DIR"
print_info "Attempting to download prebuilt CUDA binary..."
if download_file "$PREBUILT_URL" "$CUDA_BACKEND_DIR/sd-server-cuda" "Prebuilt Linux CUDA Server" && \
download_file "$PREBUILT_CLI_URL" "$CUDA_BACKEND_DIR/sd-cuda" "Prebuilt Linux CUDA CLI"; then
chmod +x "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda" 2>/dev/null || true
print_info "Testing downloaded prebuilt CUDA binary..."
if "$CUDA_BACKEND_DIR/sd-server-cuda" --help >/dev/null 2>&1; then
print_ok "Prebuilt CUDA binary verified and works! Skipping compilation."
TRY_DOWNLOAD=0
else
print_warn "Prebuilt CUDA binary failed verification test (missing libraries or library mismatch)."
rm -f "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda"
fi
else
print_warn "Failed to download prebuilt CUDA binary."
rm -f "$CUDA_BACKEND_DIR/sd-server-cuda" "$CUDA_BACKEND_DIR/sd-cuda"
fi
if [[ $TRY_DOWNLOAD -eq 1 ]]; then
if command -v nvcc >/dev/null 2>&1 && command -v cmake >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
print_info "NVIDIA GPU and CUDA compilation tools (nvcc, cmake, git) detected."
print_info "Building CUDA backend from source..."
BUILD_DIR="$TOOLS_DIR/build-sd"
JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
if [[ ! -d "$BUILD_DIR" ]]; then
print_info "Cloning stable-diffusion.cpp..."
git clone https://github.com/leejet/stable-diffusion.cpp.git "$BUILD_DIR"
fi
PUSHED_DIR="$(pwd)"
cd "$BUILD_DIR"
print_info "Checking out pinned tag $SD_RELEASE..."
git fetch origin
git checkout -f "$SD_RELEASE"
git submodule update --init --recursive
rm -rf build-cuda && mkdir build-cuda && cd build-cuda
print_info "Running cmake for CUDA backend..."
if cmake .. -DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA_FORCE_MMQ=ON && \
cmake --build . --config Release -j"$JOBS"; then
mkdir -p "$CUDA_BACKEND_DIR"
if [[ -f bin/sd-server ]]; then
cp bin/sd-server "$CUDA_BACKEND_DIR/sd-cuda"
cp bin/sd-server "$CUDA_BACKEND_DIR/sd-server-cuda"
else
print_fail "CUDA build succeeded but bin/sd-server was not found."
cd "$PUSHED_DIR"
exit 1
fi
if [[ -f bin/sd ]]; then
cp bin/sd "$CUDA_BACKEND_DIR/sd-cli-cuda"
elif [[ -f bin/sd-cli ]]; then
cp bin/sd-cli "$CUDA_BACKEND_DIR/sd-cli-cuda"
fi
SO_PATH_CUDA=$(find . -name "libstable-diffusion.so" | head -n 1)
if [[ -n "$SO_PATH_CUDA" ]]; then
cp "$SO_PATH_CUDA" "$CUDA_BACKEND_DIR/"
fi
chmod +x "$CUDA_BACKEND_DIR/sd-cuda" "$CUDA_BACKEND_DIR/sd-server-cuda" 2>/dev/null || true
if [[ -f "$CUDA_BACKEND_DIR/sd-cli-cuda" ]]; then
chmod +x "$CUDA_BACKEND_DIR/sd-cli-cuda" 2>/dev/null || true
fi
print_ok "CUDA backend compiled and installed successfully from source."
else
print_warn "CUDA backend build from source failed. Falling back to Vulkan."
fi
cd "$PUSHED_DIR"
else
print_warn "CUDA compilation tools (nvcc, cmake, and/or git) are missing."
print_info "To compile the CUDA backend, install the NVIDIA CUDA Toolkit, cmake, and git."
print_info "Falling back to Vulkan."
fi
fi
else
print_info "Declined CUDA setup. Using Vulkan GPU backend instead."
fi
else
print_ok "CUDA backend already ready."
fi
fi
fi
# ── Step 3: npm install ─────────────────────────────────────────────────────
print_step 3 $TOTAL_STEPS "Installing frontend dependencies (app/frontend/)"
if [[ ! -x "$NPM_BIN" ]]; then
print_fail "Portable npm was not found at $NPM_BIN"
exit 1
fi
# Ensure correct OS-specific node_modules folder is symlinked or swapped to avoid conflicts
FRONTEND_NODE_MODULES="$FRONTEND_DIR/node_modules"
ACTIVE_OS_FILE="$FRONTEND_DIR/.active_modules_os"
if [[ "$PLATFORM" == "Darwin" ]]; then
OS_NODE_MODULES="$FRONTEND_DIR/node_modules_mac"
OS_LABEL="node_modules_mac"
CURRENT_OS="mac"
else
OS_NODE_MODULES="$FRONTEND_DIR/node_modules_linux"
OS_LABEL="node_modules_linux"
CURRENT_OS="linux"
fi
# Attempt to create a test symlink to check if filesystem supports symlinks
USE_SYMLINKS=true
TEST_LINK="$FRONTEND_DIR/.test_symlink"
rm -f "$TEST_LINK"
if ln -s "$OS_LABEL" "$TEST_LINK" 2>/dev/null; then
rm -f "$TEST_LINK"
else
USE_SYMLINKS=false
fi
if [ "$USE_SYMLINKS" = true ]; then
if [[ -d "$FRONTEND_NODE_MODULES" && ! -L "$FRONTEND_NODE_MODULES" ]]; then
print_info "Migrating existing node_modules to $OS_LABEL..."
if [[ -d "$OS_NODE_MODULES" ]]; then
rm -rf "$FRONTEND_NODE_MODULES"
else
mv "$FRONTEND_NODE_MODULES" "$OS_NODE_MODULES"
fi
fi
rm -rf "$FRONTEND_NODE_MODULES"
mkdir -p "$OS_NODE_MODULES"
ln -sf "$OS_LABEL" "$FRONTEND_NODE_MODULES"
else
# Fallback: Filesystem does not support symlinks (e.g. FAT32/exFAT)
print_info "Filesystem does not support symlinks. Using directory swapping fallback..."
if [[ -L "$FRONTEND_NODE_MODULES" || -f "$FRONTEND_NODE_MODULES" ]]; then
rm -rf "$FRONTEND_NODE_MODULES"
fi
PREV_OS=""
if [[ -f "$ACTIVE_OS_FILE" ]]; then
PREV_OS=$(cat "$ACTIVE_OS_FILE")
fi
if [[ -d "$FRONTEND_NODE_MODULES" && "$PREV_OS" != "$CURRENT_OS" ]]; then
if [[ -n "$PREV_OS" ]]; then
print_info "Swapping out node_modules to node_modules_$PREV_OS..."
rm -rf "$FRONTEND_DIR/node_modules_$PREV_OS"
mv "$FRONTEND_NODE_MODULES" "$FRONTEND_DIR/node_modules_$PREV_OS"
else
print_info "Saving node_modules as node_modules_windows..."
rm -rf "$FRONTEND_DIR/node_modules_windows"
mv "$FRONTEND_NODE_MODULES" "$FRONTEND_DIR/node_modules_windows"
fi
fi
if [[ -d "$OS_NODE_MODULES" && ! -d "$FRONTEND_NODE_MODULES" ]]; then
print_info "Swapping in $OS_LABEL..."
mv "$OS_NODE_MODULES" "$FRONTEND_NODE_MODULES"
elif [[ ! -d "$FRONTEND_NODE_MODULES" ]]; then
mkdir -p "$FRONTEND_NODE_MODULES"
fi
echo "$CURRENT_OS" > "$ACTIVE_OS_FILE"
fi
cd "$FRONTEND_DIR"
export PATH="$NODE_DIR/bin:$PATH"
if [ "$USE_SYMLINKS" = false ]; then
if "$NPM_BIN" install --prefer-offline --no-bin-links; then
print_ok "Dependencies installed!"
else
print_fail "npm install failed."
exit 1
fi
else
if "$NPM_BIN" install --prefer-offline; then
print_ok "Dependencies installed!"
else
print_fail "npm install failed."
exit 1
fi
fi
# ── Step 4: Build frontend ──────────────────────────────────────────────────
print_step 4 $TOTAL_STEPS "Building frontend -> app/dist/"
if [ "$USE_SYMLINKS" = false ]; then
# If symlinks are disabled, run vite directly using the local node executable.
if "$NODE_BIN" node_modules/vite/bin/vite.js build; then
print_ok "Frontend built!"
else
print_fail "Frontend build failed."
exit 1
fi
else
if "$NPM_BIN" run build; then
print_ok "Frontend built!"
else
print_fail "Frontend build failed."
exit 1
fi
fi
# ── Done ────────────────────────────────────────────────────────────────────
echo ""
echo " ============================================================"
if [[ "$PLATFORM" == "Darwin" ]]; then
echo " Setup complete! Run ./mac.sh to launch."
else
echo " Setup complete! Run ./linux.sh to launch."
fi
echo " ============================================================"
echo ""
if [[ "$PLATFORM" == "Linux" && $MAX_PERF -eq 0 ]] && [[ "$VENDOR" == "nvidia" || "$VENDOR" == "amd" ]]; then
echo " Tip: For maximum GPU performance, re-run with:"
echo " ./scripts/setup.sh --max-perf"
echo ""
fi