feat: add portable backend manager and static file server with hardware telemetry support

This commit is contained in:
techjarves committed 2026-06-09 17:02:34 +05:30
1 parent 25b9a5c965
commit bbe4fe5f09
8 files changed
+250 -63

No files matched your search

+28 -8
View File
@@ -1,6 +1,6 @@
# 🖼️ Local AI Image Generator
### An easy, zero-setup Stable Diffusion GUI for Windows and Linux. Run GGUF & Safetensors models offline without Python configuration.
### An easy, zero-setup Stable Diffusion GUI for Windows, Linux, and macOS. Run GGUF & Safetensors models offline without Python configuration.
@@ -22,7 +22,7 @@
---
## 📖 Overview
**Local AI Image Generator** is a zero-configuration, portable desktop environment for running Stable Diffusion (Safetensors/GGUF/CKPT) offline on Windows and Linux. Double-clicking `start.bat` (Windows) or running `./start.sh` (Linux) 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. 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.
---
@@ -40,11 +40,17 @@
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).
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.
---
## ✨ Features
* **100% Offline & Private:** Inference runs completely locally on your hardware.
* **Auto-Detected GPU Acceleration:** Configures **CUDA** for Nvidia cards, **ROCm** for AMD cards, and **Vulkan** for AMD/Intel/NVIDIA fallback.
* **Auto-Detected GPU Acceleration:** Configures **CUDA** for Nvidia cards, **ROCm** for AMD cards, **Vulkan** for AMD/Intel/NVIDIA fallback, and **Metal** for Apple Silicon Macs.
* **Zero System Footprint:** Node.js is sandboxed inside the folder. No global environment paths are altered.
* **Integrated Model Manager:** Paste a Hugging Face URL to download weights directly, or drag-and-drop local weight files to import them.
* **Real-time Telemetry:** Monitor RAM, VRAM, CPU, and GPU load directly in the UI.
@@ -56,16 +62,16 @@
```
local-ai-image-generator/
├── start.bat # Main double-click entrypoint (Windows)
├── start.sh # Main terminal entrypoint (Linux)
├── start.sh # Main terminal entrypoint (Linux/macOS)
├── PLAN.md # Linux port implementation plan
├── LICENSE # MIT Open Source license
├── .gitignore
├── README.md
├── scripts/
│ ├── setup.ps1 # Automated GPU-detect and environment installer (Windows)
│ ├── setup.sh # Automated GPU-detect and environment installer (Linux)
│ ├── setup.sh # Automated GPU-detect and environment installer (Linux/macOS)
│ ├── reset.ps1 # Cleans runtime environments (Windows)
│ ├── reset.sh # Cleans runtime environments (Linux)
│ ├── reset.sh # Cleans runtime environments (Linux/macOS)
│ └── serve.cjs # UI web server and backend lifecycle manager
└── app/
├── frontend/ # UI source code (Vite + React)
@@ -95,9 +101,17 @@ local-ai-image-generator/
| **Intel Arc / integrated** | Vulkan | CPU | Cross-vendor Vulkan support. |
| **Integrated / None** | CPU | — | Runs on logical CPU threads (slow). |
### macOS
| Hardware | Primary | Fallback | Notes |
| :--- | :--- | :--- | :--- |
| **Apple Silicon (M1 or newer)** | Metal | CPU | Uses the official Darwin arm64 stable-diffusion.cpp backend. |
| **Intel Mac** | Source build required | CPU | Official prebuilt macOS backend is Apple Silicon only. |
**System requirements:**
- **glibc 2.38 or newer** is required for the prebuilt Linux backends (Ubuntu 24.04, Fedora 40+, etc.).
- The setup script will warn you if your glibc is older. You can still run setup, but the prebuilt backends will not start.
- **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).
@@ -118,7 +132,7 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
---
## 🛠️ Troubleshooting
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1` (Windows) or `scripts/reset.sh` (Linux). (This preserves your models and generated images).
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1` (Windows) or `scripts/reset.sh` (Linux/macOS). (This preserves your models and generated images).
* **Port Conflicts:** The frontend uses `1420` by default. The backend tries `8080` first, then automatically falls back to a free port if `8080` is already busy.
* **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.
@@ -130,11 +144,14 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
If your distribution has an older glibc than 2.38, or you want a CUDA backend on Linux, you can build `stable-diffusion.cpp` directly on your machine. The resulting binary will be linked against your system's glibc and will not have the compatibility issues of the prebuilt releases.
For macOS, the included `scripts/build_from_source.sh` builds the Metal backend and copies it to `app/backend/mac/sd`.
### Requirements
- `git`, `cmake`, `make` (or `ninja`), and a C++17 compiler (`g++` / `clang++`).
- For **CUDA**: the NVIDIA CUDA toolkit (`nvcc`) must be on your `PATH`.
- For **Vulkan**: the Vulkan SDK / loader and a compatible driver.
- For **ROCm**: AMD ROCm development libraries.
- For **macOS Metal**: Apple Command Line Tools or Xcode.
### Build commands
@@ -157,8 +174,11 @@ cmake .. -DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# ROCm
cmake .. -DSD_HIPBLAS=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# macOS Metal
cmake .. -DSD_METAL=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
# 3. Build
cmake --build . --config Release -j$(nproc)
cmake --build . --config Release -j$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)
# 4. Copy the binaries into this project
cp bin/sd* /path/to/Local-AI-Image-Generator/app/backend/linux/<backend>/
+16 -16
View File
@@ -1,18 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<title>Local AI Image Generator</title>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<title>Local AI Image Generator</title>
<script type="module" crossorigin src="/assets/index-CG3WZqfX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DW6G7wc2.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
</head>
<body>
<div id="root"></div>␍
</body>
</html>
+3 -3
View File
@@ -1554,9 +1554,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.369",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.369.tgz",
"integrity": "sha512-XM22K9FNaaCOvMMrBn1caIc8v0g6+pKt660ZbfQqUZvfil0hEzr8ZoiY7VcSLGM3L/x3rz5PqZrk+bKOOmVM9w==",
"version": "1.5.370",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.370.tgz",
"integrity": "sha512-D5tSHJReAb/Kf3Hu9F/GO4lJuSWzEWHwvQ/kKSUP7pimNgvxkSKj+gUQhHpKKACwrin7rS3byU7IxreF56rl5g==",
"dev": true,
"license": "ISC"
},
+35 -5
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Compile backends from source
# For systems with GLIBC < 2.38 (e.g. Ubuntu 22.04)
# For Linux systems with GLIBC < 2.38 or macOS systems that need a local Metal build.
#
set -euo pipefail
@@ -9,8 +9,14 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
BACKEND_DIR="$APP_DIR/backend/linux"
PLATFORM="$(uname -s)"
if [ "$PLATFORM" = "Darwin" ]; then
BACKEND_DIR="$APP_DIR/backend/mac"
else
BACKEND_DIR="$APP_DIR/backend/linux"
fi
BUILD_DIR="$APP_DIR/tools/build-sd"
JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
# Pinned release tag
PINNED_TAG="master-685-19bdfe2"
@@ -35,12 +41,36 @@ git fetch origin
git checkout -f "$PINNED_TAG"
git submodule update --init --recursive
if [ "$PLATFORM" = "Darwin" ]; then
echo ""
echo "=== Building macOS Metal Backend ==="
rm -rf build-metal && mkdir build-metal && cd build-metal
cmake .. -DSD_METAL=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j"$JOBS"
echo "Copying macOS Metal backend..."
mkdir -p "$BACKEND_DIR"
if [ -f bin/sd-server ]; then
cp bin/sd-server "$BACKEND_DIR/sd"
elif [ -f bin/sd ]; then
cp bin/sd "$BACKEND_DIR/sd"
else
echo "ERROR: bin/sd-server or bin/sd was not found!"
exit 1
fi
find . -name "*.dylib" -exec cp {} "$BACKEND_DIR/" \; 2>/dev/null || true
chmod +x "$BACKEND_DIR/sd"
echo ""
echo "=== macOS Metal build completed successfully! ==="
exit 0
fi
# 2. Build CPU Backend
echo ""
echo "=== Building CPU Backend ==="
rm -rf build-cpu && mkdir build-cpu && cd build-cpu
cmake .. -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j$(nproc)
cmake --build . --config Release -j"$JOBS"
echo "Copying CPU binaries..."
mkdir -p "$BACKEND_DIR/cpu"
@@ -71,7 +101,7 @@ echo ""
echo "=== Building Vulkan Backend ==="
if rm -rf build-vulkan && mkdir build-vulkan && cd build-vulkan && \
cmake .. -DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release && \
cmake --build . --config Release -j$(nproc); then
cmake --build . --config Release -j"$JOBS"; then
echo "Copying Vulkan binaries..."
mkdir -p "$BACKEND_DIR/vulkan"
@@ -102,7 +132,7 @@ echo ""
echo "=== Building CUDA Backend ==="
rm -rf build-cuda && mkdir build-cuda && cd build-cuda
cmake .. -DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j$(nproc)
cmake --build . --config Release -j"$JOBS"
echo "Copying CUDA binaries..."
mkdir -p "$BACKEND_DIR/cuda"
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux Reset Script
# Local AI Image Generator - Linux/macOS Reset Script
# Resets portable app dependencies/builds while preserving user models and outputs.
#
+59 -9
View File
@@ -28,10 +28,10 @@ const BACKEND_PATHS = {
cuda: path.join(ROOT, "app", "backend", "win", "cuda", "sd-cuda.exe"),
vulkan: path.join(ROOT, "app", "backend", "win", "vulkan", "sd-vulkan.exe"),
mac: path.join(ROOT, "app", "backend", "mac", "sd"),
linuxCpu: path.join(ROOT, "app", "backend", "linux", "cpu", "sd-cpu"),
linuxVulkan: path.join(ROOT, "app", "backend", "linux", "vulkan", "sd-vulkan"),
linuxRocm: path.join(ROOT, "app", "backend", "linux", "rocm", "sd-rocm"),
linuxCuda: path.join(ROOT, "app", "backend", "linux", "cuda", "sd-cuda"),
linuxCpu: path.join(ROOT, "app", "backend", "linux", "cpu", "sd-server-cpu"),
linuxVulkan: path.join(ROOT, "app", "backend", "linux", "vulkan", "sd-server-vulkan"),
linuxRocm: path.join(ROOT, "app", "backend", "linux", "rocm", "sd-server-rocm"),
linuxCuda: path.join(ROOT, "app", "backend", "linux", "cuda", "sd-server-cuda"),
};
let BACKEND_PATH = "";
if (osPlatform === "win32") {
@@ -188,6 +188,21 @@ function getGpuInfo() {
}
}
if (osPlatform === "darwin") {
try {
const output = execSync(
"system_profiler SPDisplaysDataType | awk -F': ' '/Chipset Model|Vendor/ {print $2; exit}'",
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
).trim();
if (output) {
cachedGpuInfo = { name: output };
return cachedGpuInfo;
}
} catch (_) {}
cachedGpuInfo = { name: "Apple Metal GPU" };
return cachedGpuInfo;
}
cachedGpuInfo = { name: "Unavailable" };
return cachedGpuInfo;
}
@@ -588,8 +603,11 @@ function getBackendOptions() {
const rocmInstalled = osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxRocm);
const rocmAvailable = rocmInstalled && hasAmdGpu() && backendAccepts(BACKEND_PATHS.linuxRocm, "rocm");
const cpuInstalled = osPlatform === "linux" && fs.existsSync(BACKEND_PATHS.linuxCpu);
const metalInstalled = osPlatform === "darwin" && fs.existsSync(BACKEND_PATHS.mac);
const metalAvailable = metalInstalled && backendAccepts(BACKEND_PATHS.mac, "metal");
const options = [{ id: "cpu", label: "CPU", available: true }];
if (metalAvailable) options.push({ id: "metal", label: "Metal GPU", available: true });
if (vulkanAvailable) options.push({ id: "vulkan", label: "Vulkan GPU", available: true });
if (rocmAvailable) options.push({ id: "rocm", label: "ROCm GPU (AMD)", available: true });
if (cudaAvailable) options.push({ id: "cuda", label: "CUDA GPU", available: true });
@@ -604,9 +622,14 @@ function getBackendOptions() {
if (rocmInstalled && !rocmAvailable) {
unavailable.push({ id: "rocm", label: "ROCm GPU (AMD)", reason: "Installed, but ROCm backend validation failed." });
}
if (metalInstalled && !metalAvailable) {
unavailable.push({ id: "metal", label: "Metal GPU", reason: "Installed, but Metal backend validation failed." });
}
let defaultBackend = "cpu";
if (cudaAvailable) {
if (metalAvailable) {
defaultBackend = "metal";
} else if (cudaAvailable) {
const gpuName = String(getGpuInfo().name).toLowerCase();
const isGtxCard = gpuName.includes("gtx");
if (isGtxCard && vulkanAvailable) {
@@ -626,6 +649,7 @@ function getBackendOptions() {
cudaAvailable,
vulkanAvailable,
rocmAvailable,
metalAvailable,
defaultBackendType: defaultBackend,
};
return cachedBackendOptions;
@@ -640,7 +664,17 @@ function backendAccepts(binaryPath, backendName) {
"--model", path.join(MODELS, "__backend_probe_missing__.safetensors"),
"--listen-port", "18082",
];
let result = spawnSync(binaryPath, probeArgs, { encoding: "utf8", timeout: 5000 });
const spawnEnv = { ...process.env };
if (osPlatform === "linux") {
const dir = path.dirname(binaryPath);
const existing = spawnEnv.LD_LIBRARY_PATH || "";
spawnEnv.LD_LIBRARY_PATH = dir + (existing ? ":" + existing : "");
} else if (osPlatform === "darwin") {
const dir = path.dirname(binaryPath);
const existing = spawnEnv.DYLD_LIBRARY_PATH || "";
spawnEnv.DYLD_LIBRARY_PATH = dir + (existing ? ":" + existing : "");
}
let result = spawnSync(binaryPath, probeArgs, { encoding: "utf8", timeout: 5000, env: spawnEnv });
let output = `${result.stdout || ""}\n${result.stderr || ""}`;
// Some binaries do not support --backend. If we see "unknown argument",
@@ -650,7 +684,7 @@ function backendAccepts(binaryPath, backendName) {
"--model", path.join(MODELS, "__backend_probe_missing__.safetensors"),
"--listen-port", "18082",
];
result = spawnSync(binaryPath, fallbackArgs, { encoding: "utf8", timeout: 5000 });
result = spawnSync(binaryPath, fallbackArgs, { encoding: "utf8", timeout: 5000, env: spawnEnv });
output = `${result.stdout || ""}\n${result.stderr || ""}`;
}
@@ -691,7 +725,11 @@ function selectBackendPath(useGpu, backendType = "auto") {
if (fs.existsSync(BACKEND_PATHS.linuxCpu)) return BACKEND_PATHS.linuxCpu;
return BACKEND_PATH;
}
// macOS fallback
if (osPlatform === "darwin") {
if (fs.existsSync(BACKEND_PATHS.mac)) return BACKEND_PATHS.mac;
return BACKEND_PATH;
}
// generic fallback
if (fs.existsSync(BACKEND_PATHS.mac)) return BACKEND_PATHS.mac;
return BACKEND_PATH;
}
@@ -709,6 +747,7 @@ function getBackendMode(backendPath, useGpu, backendType = "auto") {
if (name.includes("cuda")) return "CUDA GPU";
if (name.includes("rocm")) return "ROCm GPU";
if (name.includes("vulkan")) return "Vulkan GPU";
if (osPlatform === "darwin" || backendType === "metal") return "Metal GPU";
return "GPU";
}
@@ -882,6 +921,13 @@ async function startBackend(settings = {}) {
"--rng", "cpu",
"--sampler-rng", "cpu",
);
} else if (requestedBackend === "metal") {
args.push(
"--backend", "metal",
"--params-backend", "metal",
"--rng", "cpu",
"--sampler-rng", "cpu",
);
}
if (currentSettings.vaeTiling) {
@@ -909,6 +955,10 @@ async function startBackend(settings = {}) {
const existing = spawnEnv.LD_LIBRARY_PATH || "";
spawnEnv.LD_LIBRARY_PATH = extraLibs.join(":") + (existing ? ":" + existing : "");
}
} else if (osPlatform === "darwin") {
const existing = spawnEnv.DYLD_LIBRARY_PATH || "";
const backendDir = path.dirname(BACKEND_PATHS.mac);
spawnEnv.DYLD_LIBRARY_PATH = backendDir + (existing ? ":" + existing : "");
}
console.log(" [backend] Starting:", path.basename(backendPath), args.join(" "));
@@ -1825,7 +1875,7 @@ const server = http.createServer(async (req, res) => {
});
});
server.listen(PORT_FRONTEND, "127.0.0.1", () => {
server.listen(PORT_FRONTEND, "0.0.0.0", () => {
console.log("");
console.log(" ============================================================");
console.log(" LOCAL AI IMAGE GENERATOR | Running");
+81 -13
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux Setup Script
# Local AI Image Generator - Linux/macOS Setup Script
# Self-contained: no apt/yum/pacman, no global Node.js install.
#
@@ -11,18 +11,40 @@ ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="$ROOT_DIR/app"
FRONTEND_DIR="$APP_DIR/frontend"
TOOLS_DIR="$APP_DIR/tools"
NODE_DIR="$TOOLS_DIR/node-linux"
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"
DIST_DIR="$APP_DIR/dist"
BACKEND_DIR="$APP_DIR/backend/linux"
# 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"
NODE_TARBALL="node-v${NODE_VERSION}-linux-x64.tar.xz"
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
@@ -36,7 +58,7 @@ print_header() {
clear 2>/dev/null || true
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR - Linux First-Time Setup"
echo " LOCAL AI IMAGE GENERATOR - $PLATFORM_LABEL First-Time Setup"
echo " 100% Self-Contained | No System Install Required"
echo " ============================================================"
echo ""
@@ -180,15 +202,39 @@ copy_binaries_from_extracted() {
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
check_glibc
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 (app/tools/node-linux/)"
print_step 1 $TOTAL_STEPS "Setting up portable Node.js ($NODE_DIR/)"
if [[ -x "$NODE_BIN" && -x "$NPM_BIN" ]]; then
VERSION=$("$NODE_BIN" --version)
@@ -201,7 +247,7 @@ else
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*-linux-x64' | head -n 1)"
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"
@@ -217,11 +263,32 @@ else
fi
# ── Step 2: stable-diffusion.cpp Backends ───────────────────────────────────
VENDOR="$(detect_gpu_vendor)"
print_step 2 $TOTAL_STEPS "Detecting GPU vendor: ${VENDOR:-none}"
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 "Intel Macs need a local source build with Metal/OpenBLAS and a matching app/backend/mac/sd binary."
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
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" ]]; then
@@ -284,6 +351,7 @@ if [[ $MAX_PERF -eq 1 ]] && [[ "$VENDOR" == "nvidia" ]]; then
print_info "Linux CUDA backend is disabled pending a reliable upstream binary."
print_info "NVIDIA GPUs on Linux will use the Vulkan backend instead."
fi
fi
# ── Step 3: npm install ─────────────────────────────────────────────────────
print_step 3 $TOTAL_STEPS "Installing frontend dependencies (app/frontend/)"
@@ -320,7 +388,7 @@ echo " Setup complete! Run ./start.sh to launch."
echo " ============================================================"
echo ""
if [[ $MAX_PERF -eq 0 ]] && [[ "$VENDOR" == "nvidia" || "$VENDOR" == "amd" ]]; then
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 ""
+27 -8
View File
@@ -1,15 +1,25 @@
#!/usr/bin/env bash
#
# Local AI Image Generator - Linux Launcher
# Local AI Image Generator - Linux/macOS Launcher
# Double-click or run: ./start.sh
# Use --max-perf to enable ROCm/CUDA backend downloads on first setup.
# Use --max-perf to enable ROCm backend downloads on Linux first setup.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="$SCRIPT_DIR/app"
NODE_DIR="$APP_DIR/tools/node-linux"
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"
fi
NODE_BIN="$NODE_DIR/bin/node"
DIST_INDEX="$APP_DIR/dist/index.html"
SETUP_SCRIPT="$SCRIPT_DIR/scripts/setup.sh"
@@ -38,15 +48,21 @@ if [[ ! -f "$DIST_INDEX" ]]; then
SETUP_REASON="Frontend build is missing."
fi
# At minimum we need CPU or Vulkan backend on Linux
if [[ ! -f "$APP_DIR/backend/linux/cpu/sd-cpu" && ! -f "$APP_DIR/backend/linux/vulkan/sd-vulkan" ]]; then
SETUP_REASON="No Linux backend binary is installed."
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
if [[ ! -f "$CPU_BACKEND_PATH" && ! -f "$BACKEND_PATH" ]]; then
SETUP_REASON="No Linux backend binary is installed."
fi
fi
if [[ -n "$SETUP_REASON" ]]; then
echo ""
echo " ============================================================"
echo " LOCAL AI IMAGE GENERATOR | $SETUP_MODE"
echo " LOCAL AI IMAGE GENERATOR | $PLATFORM_LABEL $SETUP_MODE"
echo " ============================================================"
echo ""
if [[ "$SETUP_MODE" == "First-Time Setup" ]]; then
@@ -98,7 +114,10 @@ SERVER_PID=$!
sleep 2
# Open browser
if command -v xdg-open >/dev/null 2>&1; then
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
echo " Opening browser at http://localhost:${FRONTEND_PORT}"
xdg-open "http://localhost:${FRONTEND_PORT}" >/dev/null 2>&1 &
else