From 84034531c40664291646d59fcabc998ccb7c5545 Mon Sep 17 00:00:00 2001 From: Tech Jarves Date: Thu, 11 Jun 2026 10:14:14 +0000 Subject: [PATCH] Refactor entry points: split launcher into linux.sh and mac.sh, rename start.bat to windows.bat, isolate OS node_modules --- .gitignore | 2 + README.md | 23 +++--- start.sh => linux.sh | 61 ++++++++-------- mac.sh | 149 +++++++++++++++++++++++++++++++++++++++ scripts/reset.ps1 | 13 ++++ scripts/reset.sh | 12 +++- scripts/setup.ps1 | 12 +++- scripts/setup.sh | 25 ++++++- start.bat => windows.bat | 0 9 files changed, 255 insertions(+), 42 deletions(-) rename start.sh => linux.sh (70%) create mode 100755 mac.sh rename start.bat => windows.bat (100%) diff --git a/.gitignore b/.gitignore index 35a19d6..541bb3b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ app/outputs/ # Ignore portable Node.js binaries and packages app/tools/ **/node_modules/ +**/node_modules_mac/ +**/node_modules_linux/ node_modules/ # Ignore backend folder completely (contains massive DLLs, EXEs, and ZIPs >1GB) diff --git a/README.md b/README.md index 7eecc48..4b69955 100644 --- a/README.md +++ b/README.md @@ -22,27 +22,27 @@ --- ## 📖 Overview -**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors/GGUF/CKPT) offline on Windows, Linux, and macOS. Double-clicking `start.bat` (Windows) or running `./start.sh` (Linux/macOS) automatically handles dependency setup, GPU backend matching, and launches a high-performance local web workspace. +**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors/GGUF/CKPT) offline on Windows, Linux, and macOS. Running `windows.bat` (Windows), `./linux.sh` (Linux), or `./mac.sh` (macOS) automatically handles dependency setup, GPU backend matching, and launches a high-performance local web workspace. --- ## ⚡ Quick Start ### Windows -1. **Launch:** Double-click **`start.bat`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run). +1. **Launch:** Double-click **`windows.bat`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run). 2. **Add Models:** Drop `.safetensors`, `.gguf`, or `.ckpt` weights into `app/models/` (or download them via the **Model Manager** tab in the UI). 3. **Generate:** Open `http://localhost:1420` in your browser, select your model, and write a prompt. ### Linux 1. **Check compatibility:** Prebuilt Linux backends are built on Ubuntu 24.04 and require **glibc 2.38+**. Run `ldd --version` to verify. -2. **Launch:** Open a terminal in the project folder and run **`./start.sh`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run). - - For maximum AMD GPU performance, use **`./start.sh --max-perf`** on first setup (adds the ROCm backend). +2. **Launch:** Open a terminal in the project folder and run **`./linux.sh`** (downloads portable Node.js and pre-compiled GPU backend binaries on first run). + - For maximum AMD GPU performance, use **`./linux.sh --max-perf`** on first setup (adds the ROCm backend). 3. **Add Models:** Drop `.safetensors`, `.gguf`, or `.ckpt` weights into `app/models/` (or download them via the **Model Manager** tab in the UI). 4. **Generate:** Open `http://localhost:1420` in your browser, select your model, and write a prompt. ### macOS 1. **Check compatibility:** The prebuilt macOS backend is for **Apple Silicon (M1 or newer)** and uses **Metal** GPU acceleration. -2. **Launch:** Open Terminal in the project folder and run **`./start.sh`** (downloads portable Node.js and the pre-compiled Metal backend on first run). +2. **Launch:** Open Terminal in the project folder and run **`./mac.sh`** (downloads portable Node.js and the pre-compiled Metal backend on first run). 3. **Add Models:** Drop `.safetensors`, `.gguf`, or `.ckpt` weights into `app/models/` (or download them via the **Model Manager** tab in the UI). 4. **Generate:** Open `http://localhost:1420` in your browser, select your model, and write a prompt. @@ -61,8 +61,9 @@ ## 📁 Repository Structure ``` local-ai-image-generator/ -├── start.bat # Main double-click entrypoint (Windows) -├── start.sh # Main terminal entrypoint (Linux/macOS) +├── windows.bat # Main double-click entrypoint (Windows) +├── linux.sh # Main terminal entrypoint (Linux) +├── mac.sh # Main terminal entrypoint (macOS) ├── PLAN.md # Linux port implementation plan ├── LICENSE # MIT Open Source license ├── .gitignore @@ -114,8 +115,8 @@ local-ai-image-generator/ - **Apple Silicon (M1 or newer)** is required for the prebuilt macOS Metal backend. **Linux setup modes:** -- **Default (`./start.sh`)**: Downloads CPU + Vulkan backends (~120–150 MB). -- **Maximum Performance (`./start.sh --max-perf`)**: Also downloads the ROCm backend. Total download ~1.3 GB. +- **Default (`./linux.sh`)**: Downloads CPU + Vulkan backends (~120–150 MB). +- **Maximum Performance (`./linux.sh --max-perf`)**: Also downloads the ROCm backend. Total download ~1.3 GB. --- @@ -137,7 +138,7 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution * **Linux backends fail to start with `GLIBC_2.38' not found`:** The prebuilt binaries require glibc 2.38+ (Ubuntu 24.04). Upgrade your distribution or build stable-diffusion.cpp from source (see below). * **Linux ROCm not loading:** Make sure your AMD GPU and kernel are compatible with ROCm 7.13. The app will automatically fall back to Vulkan if ROCm cannot initialize. * **Windows exits with code `3221225781`:** This is `0xC0000135`, which means Windows could not load a required backend DLL. For AMD/Intel Vulkan, update the GPU driver with Vulkan support, then rerun setup so `app/backend/win/vulkan/` is repaired. For NVIDIA CUDA, update the NVIDIA driver and rerun setup so CUDA runtime DLLs are restored. -* **Generation shows "server is not responding or crashed":** The backend process exited. Check the terminal where you ran `./start.sh` for the exact error (common causes are glibc mismatch, missing Vulkan drivers, or out-of-memory). +* **Generation shows \"server is not responding or crashed\":** The backend process exited. Check the terminal where you ran `./linux.sh` or `./mac.sh` for the exact error (common causes are glibc mismatch, missing Vulkan drivers, or out-of-memory). --- @@ -190,7 +191,7 @@ After copying, rename the server binary to match what `scripts/serve.cjs` expect - Vulkan: `sd` → `sd-vulkan` - ROCm: `sd` → `sd-rocm` -Then restart the app with `./start.sh`. +Then restart the app with `./linux.sh` (Linux) or `./mac.sh` (macOS). --- diff --git a/start.sh b/linux.sh similarity index 70% rename from start.sh rename to linux.sh index f6eeec8..5eccac2 100755 --- a/start.sh +++ b/linux.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Local AI Image Generator - Linux/macOS Launcher -# Double-click or run: ./start.sh +# Local AI Image Generator - Linux Launcher +# Double-click or run: ./linux.sh # Use --max-perf to enable ROCm backend downloads on Linux first setup. # @@ -10,17 +10,18 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_DIR="$SCRIPT_DIR/app" PLATFORM="$(uname -s)" -if [[ "$PLATFORM" == "Darwin" ]]; then - NODE_DIR="$APP_DIR/tools/node-mac" - BACKEND_PATH="$APP_DIR/backend/mac/sd" - PLATFORM_LABEL="macOS" -else - NODE_DIR="$APP_DIR/tools/node-linux" - BACKEND_PATH="$APP_DIR/backend/linux/vulkan/sd-vulkan" - CPU_BACKEND_PATH="$APP_DIR/backend/linux/cpu/sd-cpu" - PLATFORM_LABEL="Linux" + +if [[ "$PLATFORM" != "Linux" ]]; then + echo "[ERROR] This script is for Linux only. Please run ./mac.sh on macOS." >&2 + exit 1 fi + +NODE_DIR="$APP_DIR/tools/node-linux" NODE_BIN="$NODE_DIR/bin/node" +BACKEND_PATH="$APP_DIR/backend/linux/vulkan/sd-vulkan" +CPU_BACKEND_PATH="$APP_DIR/backend/linux/cpu/sd-cpu" +PLATFORM_LABEL="Linux" + DIST_INDEX="$APP_DIR/dist/index.html" SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh" SERVE_SCRIPT="$SCRIPT_DIR/scripts/serve.cjs" @@ -35,30 +36,37 @@ if [[ "${1:-}" == "--max-perf" ]]; then MAX_PERF_FLAG="--max-perf" fi +# ── Symlink node_modules to avoid OS conflicts ────────────────────────────── +FRONTEND_NODE_MODULES="$APP_DIR/frontend/node_modules" +LINUX_NODE_MODULES="$APP_DIR/frontend/node_modules_linux" + +if [[ -d "$FRONTEND_NODE_MODULES" && ! -L "$FRONTEND_NODE_MODULES" ]]; then + echo " >> Migrating existing node_modules to node_modules_linux..." + mv "$FRONTEND_NODE_MODULES" "$LINUX_NODE_MODULES" +fi + +rm -f "$FRONTEND_NODE_MODULES" +mkdir -p "$LINUX_NODE_MODULES" +ln -sf "node_modules_linux" "$FRONTEND_NODE_MODULES" + # ── First-time setup check ───────────────────────────────────────────────── if [[ ! -d "$NODE_DIR" ]]; then SETUP_MODE="First-Time Setup" fi if [[ ! -x "$NODE_BIN" ]]; then - SETUP_REASON="Portable Node.js is missing." + SETUP_REASON="Portable Node.js for Linux is missing." fi if [[ ! -f "$DIST_INDEX" ]]; then SETUP_REASON="Frontend build is missing." fi -if [[ "$PLATFORM" == "Darwin" ]]; then - if [[ ! -x "$BACKEND_PATH" ]]; then - SETUP_REASON="No macOS Metal backend binary is installed." - fi -else - # At minimum we need CPU or Vulkan backend on Linux, and both CLI and server binaries must be executable - CPU_SERVER_PATH="$APP_DIR/backend/linux/cpu/sd-server-cpu" - VULKAN_SERVER_PATH="$APP_DIR/backend/linux/vulkan/sd-server-vulkan" - if [[ ! -x "$CPU_BACKEND_PATH" || ! -x "$CPU_SERVER_PATH" ]] && [[ ! -x "$BACKEND_PATH" || ! -x "$VULKAN_SERVER_PATH" ]]; then - SETUP_REASON="Linux backend binaries are missing or not executable." - fi +# At minimum we need CPU or Vulkan backend on Linux, and both CLI and server binaries must be executable +CPU_SERVER_PATH="$APP_DIR/backend/linux/cpu/sd-server-cpu" +VULKAN_SERVER_PATH="$APP_DIR/backend/linux/vulkan/sd-server-vulkan" +if [[ ! -x "$CPU_BACKEND_PATH" || ! -x "$CPU_SERVER_PATH" ]] && [[ ! -x "$BACKEND_PATH" || ! -x "$VULKAN_SERVER_PATH" ]]; then + SETUP_REASON="Linux backend binaries are missing or not executable." fi if [[ -n "$SETUP_REASON" ]]; then @@ -68,7 +76,7 @@ if [[ -n "$SETUP_REASON" ]]; then echo " ============================================================" echo "" if [[ "$SETUP_MODE" == "First-Time Setup" ]]; then - echo " This looks like your first run. Setting up automatically..." + echo " This looks like your first run on Linux. Setting up automatically..." else echo " Local AI Image Generator needs a quick repair before launch." fi @@ -116,10 +124,7 @@ SERVER_PID=$! sleep 2 # Open browser -if [[ "$PLATFORM" == "Darwin" ]] && command -v open >/dev/null 2>&1; then - echo " Opening browser at http://localhost:${FRONTEND_PORT}" - open "http://localhost:${FRONTEND_PORT}" >/dev/null 2>&1 & -elif command -v xdg-open >/dev/null 2>&1; then +if command -v xdg-open >/dev/null 2>&1; then echo " Opening browser at http://localhost:${FRONTEND_PORT}" xdg-open "http://localhost:${FRONTEND_PORT}" >/dev/null 2>&1 & else diff --git a/mac.sh b/mac.sh new file mode 100755 index 0000000..594de33 --- /dev/null +++ b/mac.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# Local AI Image Generator - macOS Launcher +# Double-click or run: ./mac.sh +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APP_DIR="$SCRIPT_DIR/app" +PLATFORM="$(uname -s)" + +if [[ "$PLATFORM" != "Darwin" ]]; then + echo "[ERROR] This script is for macOS only. Please run ./linux.sh on Linux." >&2 + exit 1 +fi + +NODE_DIR="$APP_DIR/tools/node-mac" +NODE_BIN="$NODE_DIR/bin/node" +BACKEND_PATH="$APP_DIR/backend/mac/sd" +PLATFORM_LABEL="macOS" + +DIST_INDEX="$APP_DIR/dist/index.html" +SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh" +SERVE_SCRIPT="$SCRIPT_DIR/scripts/serve.cjs" + +FRONTEND_PORT="${FRONTEND_PORT:-1420}" +SETUP_REASON="" +SETUP_MODE="Repair" + +# ── Symlink node_modules to avoid OS conflicts ────────────────────────────── +FRONTEND_NODE_MODULES="$APP_DIR/frontend/node_modules" +MAC_NODE_MODULES="$APP_DIR/frontend/node_modules_mac" + +if [[ -d "$FRONTEND_NODE_MODULES" && ! -L "$FRONTEND_NODE_MODULES" ]]; then + echo " >> Migrating existing node_modules to node_modules_mac..." + mv "$FRONTEND_NODE_MODULES" "$MAC_NODE_MODULES" +fi + +rm -f "$FRONTEND_NODE_MODULES" +mkdir -p "$MAC_NODE_MODULES" +ln -sf "node_modules_mac" "$FRONTEND_NODE_MODULES" + +# ── First-time setup check ───────────────────────────────────────────────── +if [[ ! -d "$NODE_DIR" ]]; then + SETUP_MODE="First-Time Setup" +fi + +if [[ ! -x "$NODE_BIN" ]]; then + SETUP_REASON="Portable Node.js for macOS is missing." +fi + +if [[ ! -f "$DIST_INDEX" ]]; then + SETUP_REASON="Frontend build is missing." +fi + +if [[ ! -x "$BACKEND_PATH" ]]; then + SETUP_REASON="No macOS Metal backend binary is installed." +fi + +if [[ -n "$SETUP_REASON" ]]; then + echo "" + echo " ============================================================" + echo " LOCAL AI IMAGE GENERATOR | $PLATFORM_LABEL $SETUP_MODE" + echo " ============================================================" + echo "" + if [[ "$SETUP_MODE" == "First-Time Setup" ]]; then + echo " This looks like your first run on macOS. Setting up automatically..." + else + echo " Local AI Image Generator needs a quick repair before launch." + fi + echo " Reason: $SETUP_REASON" + echo " Models are not downloaded during setup. Download or import them in the app." + echo "" + read -rp " Press Enter to continue, or Ctrl+C to cancel." + + # Clear any existing frontend server process + if command -v fuser >/dev/null 2>&1; then + fuser -k "${FRONTEND_PORT}/tcp" >/dev/null 2>&1 || true + fi + + if ! bash "$SETUP_SCRIPT"; then + echo "" + echo " [ERROR] Setup failed. Please check the output above." + read -rp " Press Enter to close..." + exit 1 + fi +fi + +# ── Launch ───────────────────────────────────────────────────────────────── +clear 2>/dev/null || true +echo "" +echo " ============================================================" +echo " LOCAL AI IMAGE GENERATOR | Launching..." +echo " ============================================================" +echo "" + +# Clear frontend port +if command -v fuser >/dev/null 2>&1; then + fuser -k "${FRONTEND_PORT}/tcp" >/dev/null 2>&1 || true +fi + +# Start the server +echo " Starting Local AI Image Generator..." +export PATH="$NODE_DIR/bin:$PATH" +export FRONTEND_PORT="$FRONTEND_PORT" + +# Run server in background and capture PID +"$NODE_BIN" "$SERVE_SCRIPT" & +SERVER_PID=$! + +# Wait for server to be ready +sleep 2 + +# Open browser +if command -v open >/dev/null 2>&1; then + echo " Opening browser at http://localhost:${FRONTEND_PORT}" + open "http://localhost:${FRONTEND_PORT}" >/dev/null 2>&1 & +else + echo " Open your browser to: http://localhost:${FRONTEND_PORT}" +fi + +echo "" +echo " ============================================================" +echo " Running!" +echo " Web UI: http://localhost:${FRONTEND_PORT}" +echo " GPU API: Auto-selected by the app (starts at 8080)" +echo "" +echo " Press Ctrl+C in this window to stop all services." +echo " ============================================================" +echo "" + +# Cleanup on exit +cleanup() { + echo "" + echo " Shutting down..." + if kill -0 "$SERVER_PID" >/dev/null 2>&1; then + kill -TERM "$SERVER_PID" >/dev/null 2>&1 || true + sleep 1 + kill -KILL "$SERVER_PID" >/dev/null 2>&1 || true + fi + echo " Done. Goodbye!" + exit 0 +} +trap cleanup SIGINT SIGTERM + +# Keep script alive +wait "$SERVER_PID" || true +cleanup diff --git a/scripts/reset.ps1 b/scripts/reset.ps1 index 65d1b01..bdc7515 100644 --- a/scripts/reset.ps1 +++ b/scripts/reset.ps1 @@ -45,6 +45,19 @@ if (Test-Path $nodeModulesDir) { Remove-Item $nodeModulesDir -Recurse -Force -ErrorAction SilentlyContinue } +$nodeModulesMac = Join-Path $appDir "frontend\node_modules_mac" +if (Test-Path $nodeModulesMac) { + Write-Host " >> Removing frontend node_modules_mac..." -ForegroundColor Cyan + Remove-Item $nodeModulesMac -Recurse -Force -ErrorAction SilentlyContinue +} + +$nodeModulesLinux = Join-Path $appDir "frontend\node_modules_linux" +if (Test-Path $nodeModulesLinux) { + Write-Host " >> Removing frontend node_modules_linux..." -ForegroundColor Cyan + Remove-Item $nodeModulesLinux -Recurse -Force -ErrorAction SilentlyContinue +} + + # Delete package-lock.json in frontend $lockFile = Join-Path $appDir "frontend\package-lock.json" if (Test-Path $lockFile) { diff --git a/scripts/reset.sh b/scripts/reset.sh index ca27b07..9334147 100755 --- a/scripts/reset.sh +++ b/scripts/reset.sh @@ -40,11 +40,21 @@ if [[ -d "$APP_DIR/models" ]]; then fi # Delete node_modules in frontend -if [[ -d "$APP_DIR/frontend/node_modules" ]]; then +if [[ -L "$APP_DIR/frontend/node_modules" || -d "$APP_DIR/frontend/node_modules" ]]; then echo " >> Removing frontend node_modules..." rm -rf "$APP_DIR/frontend/node_modules" fi +if [[ -d "$APP_DIR/frontend/node_modules_mac" ]]; then + echo " >> Removing frontend node_modules_mac..." + rm -rf "$APP_DIR/frontend/node_modules_mac" +fi + +if [[ -d "$APP_DIR/frontend/node_modules_linux" ]]; then + echo " >> Removing frontend node_modules_linux..." + rm -rf "$APP_DIR/frontend/node_modules_linux" +fi + # Delete package-lock.json in frontend if [[ -f "$APP_DIR/frontend/package-lock.json" ]]; then echo " >> Removing frontend package-lock.json..." diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 index f3664e2..cfa6836 100644 --- a/scripts/setup.ps1 +++ b/scripts/setup.ps1 @@ -388,6 +388,16 @@ if (-not (Test-Path $npmCmd)) { Read-Host; exit 1 } +# If node_modules is a Unix symlink/junction, remove it so Windows can install natively into a real directory +$nodeModulesDir = Join-Path $frontendDir "node_modules" +if (Test-Path $nodeModulesDir) { + $item = Get-Item $nodeModulesDir + if ($item.Attributes -match "ReparsePoint") { + Print-Info "Removing Unix symlink for node_modules on Windows..." + Remove-Item $nodeModulesDir -Force -ErrorAction SilentlyContinue + } +} + Push-Location $frontendDir $oldPath = $env:PATH try { @@ -419,7 +429,7 @@ try { # ── Done ───────────────────────────────────────────────────────────────────── Write-Host "" Write-Host " ============================================================" -ForegroundColor Green -Write-Host " Setup complete! Just double-click start.bat to launch." -ForegroundColor Green +Write-Host " Setup complete! Just double-click windows.bat to launch." -ForegroundColor Green Write-Host " ============================================================" -ForegroundColor Green Write-Host "" Read-Host " Press Enter to close..." diff --git a/scripts/setup.sh b/scripts/setup.sh index b8dc7f1..0caff8a 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -361,6 +361,25 @@ if [[ ! -x "$NPM_BIN" ]]; then exit 1 fi +# Ensure correct OS-specific node_modules folder is symlinked to avoid conflicts +FRONTEND_NODE_MODULES="$FRONTEND_DIR/node_modules" +if [[ "$PLATFORM" == "Darwin" ]]; then + OS_NODE_MODULES="$FRONTEND_DIR/node_modules_mac" + OS_LABEL="node_modules_mac" +else + OS_NODE_MODULES="$FRONTEND_DIR/node_modules_linux" + OS_LABEL="node_modules_linux" +fi + +if [[ -d "$FRONTEND_NODE_MODULES" && ! -L "$FRONTEND_NODE_MODULES" ]]; then + print_info "Migrating existing node_modules to $OS_LABEL..." + mv "$FRONTEND_NODE_MODULES" "$OS_NODE_MODULES" +fi + +rm -f "$FRONTEND_NODE_MODULES" +mkdir -p "$OS_NODE_MODULES" +ln -sf "$OS_LABEL" "$FRONTEND_NODE_MODULES" + cd "$FRONTEND_DIR" export PATH="$NODE_DIR/bin:$PATH" @@ -384,7 +403,11 @@ fi # ── Done ──────────────────────────────────────────────────────────────────── echo "" echo " ============================================================" -echo " Setup complete! Run ./start.sh to launch." +if [[ "$PLATFORM" == "Darwin" ]]; then + echo " Setup complete! Run ./mac.sh to launch." +else + echo " Setup complete! Run ./linux.sh to launch." +fi echo " ============================================================" echo "" diff --git a/start.bat b/windows.bat similarity index 100% rename from start.bat rename to windows.bat