implement model management UI and backend integration for image, text, and speech models with platform-specific setup scripts

This commit is contained in:
techjarves committed 2026-06-25 22:04:52 +05:30
1 parent ba987db776
commit 45540aee29
7 files changed
+413 -157

No files matched your search

+105 -1
View File
@@ -587,6 +587,50 @@ function isVirtualOrSoftwareGpu(name) {
lowercase.includes("vmware");
}
let cachedPreferredVulkanBackend = null;
let cachedPreferredVulkanBackendChecked = false;
function getPreferredVulkanBackendName() {
const explicit = String(process.env.SD_VULKAN_DEVICE || process.env.UAIS_VULKAN_DEVICE || "").trim();
if (/^vulkan\d+$/i.test(explicit)) return explicit.toLowerCase();
if (/^\d+$/.test(explicit)) return `vulkan${explicit}`;
if (cachedPreferredVulkanBackendChecked) return cachedPreferredVulkanBackend || "vulkan0";
cachedPreferredVulkanBackendChecked = true;
cachedPreferredVulkanBackend = "vulkan0";
if (osPlatform !== "linux") return cachedPreferredVulkanBackend;
try {
const result = spawnSync("vulkaninfo", ["--summary"], {
encoding: "utf8",
timeout: 5000,
maxBuffer: 1024 * 1024,
});
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
const devices = [];
let current = null;
for (const line of output.split(/\r?\n/)) {
const gpuMatch = line.match(/^\s*GPU(\d+)\s*:/i);
if (gpuMatch) {
current = { index: Number(gpuMatch[1]), name: "", type: "" };
devices.push(current);
continue;
}
if (!current) continue;
const nameMatch = line.match(/deviceName\s*=\s*(.+)$/i);
if (nameMatch) current.name = nameMatch[1].trim();
const typeMatch = line.match(/deviceType\s*=\s*(.+)$/i);
if (typeMatch) current.type = typeMatch[1].trim();
}
const usable = devices.filter((device) => Number.isInteger(device.index) && !isVirtualOrSoftwareGpu(device.name));
const discrete = usable.find((device) => /discrete/i.test(device.type));
const amd = usable.find((device) => /amd|radeon/i.test(device.name));
const selected = discrete || amd || usable[0];
if (selected) cachedPreferredVulkanBackend = `vulkan${selected.index}`;
} catch (_) {}
return cachedPreferredVulkanBackend;
}
let cachedLlamaCudaGpu = null;
let cachedLlamaCudaGpuChecked = false;
function detectLlamaCudaGpu() {
@@ -3285,6 +3329,36 @@ function requestJson(url, payload = null, timeoutMs = 120000) {
});
}
function proxyImageBackendRequest(req, res) {
const headers = { ...req.headers };
delete headers.host;
headers.host = `127.0.0.1:${PORT_BACKEND}`;
const proxyReq = http.request({
hostname: "127.0.0.1",
port: PORT_BACKEND,
path: req.url,
method: req.method,
headers,
timeout: 300000,
}, (proxyRes) => {
res.writeHead(proxyRes.statusCode || 502, {
...proxyRes.headers,
"Access-Control-Allow-Origin": "*",
});
proxyRes.pipe(res);
});
proxyReq.on("error", (err) => {
json(res, 502, { ok: false, error: `Image backend is not reachable: ${err.message}` });
});
proxyReq.on("timeout", () => {
proxyReq.destroy(new Error("Image backend request timed out"));
});
req.pipe(proxyReq);
}
function requestHttpsJson(url, timeoutMs = 30000) {
return new Promise((resolve, reject) => {
const req = https.get(url, {
@@ -4303,6 +4377,7 @@ async function startBackend(settings = {}) {
let args = [];
const requestedBackend = resolveBackendType(currentSettings.useGpu, currentSettings.backendType, currentSettings.model);
const selectedVulkanBackend = requestedBackend === "vulkan" ? getPreferredVulkanBackendName() : "";
const supportsFlags = backendSupportsFlags[backendPath] !== false;
@@ -4331,7 +4406,7 @@ async function startBackend(settings = {}) {
args.push("--rng", "cpu", "--sampler-rng", "cpu");
} else if (requestedBackend === "vulkan") {
if (supportsFlags) {
args.push("--backend", "vulkan0", "--params-backend", "vulkan0");
args.push("--backend", selectedVulkanBackend, "--params-backend", selectedVulkanBackend);
}
args.push("--rng", "cpu", "--sampler-rng", "cpu");
} else if (requestedBackend === "cuda") {
@@ -4350,6 +4425,11 @@ async function startBackend(settings = {}) {
}
if (requestedBackend === "vulkan" && selectedVulkanBackend) {
currentSettings.backendDevice = selectedVulkanBackend;
backendLoadState.device = selectedVulkanBackend;
}
if (requestedBackend !== "apple-npu") {
if (currentSettings.vaeTiling) {
args.push("--vae-tiling");
@@ -4485,6 +4565,11 @@ async function startBackend(settings = {}) {
process.stderr.write(" [sd-err] " + output);
updateCoreMLLoadProgress(output);
const cleanOutput = stripAnsi(output);
const runtimeLinkerError = describeLinuxRuntimeLinkerError(cleanOutput);
if (runtimeLinkerError) {
backendError = runtimeLinkerError;
backendLoadState.phase = "Linux runtime is too old for this backend";
}
const deviceMatch = cleanOutput.match(/Device\s+\d+:\s*([^,\r\n]+)/);
if (deviceMatch) {
backendLoadState.device = deviceMatch[1].trim();
@@ -5217,6 +5302,22 @@ function describeBackendError(rawError, modelPath) {
return `${raw}\n\nThe backend could not create the model context. Common causes are a corrupt/incomplete model file, unsupported checkpoint type, or not enough free RAM/VRAM. Delete and re-download the model, then try CPU or Vulkan mode at 512x512.`;
}
function describeLinuxRuntimeLinkerError(rawError) {
const raw = String(rawError || "").trim();
const lower = raw.toLowerCase();
if (osPlatform !== "linux") return null;
if (!lower.includes("glibc") && !lower.includes("libstdc++") && !lower.includes("libc.so.6")) return null;
const needsGlibc = raw.match(/GLIBC_([0-9.]+)/)?.[1];
const needsGlibcxx = raw.match(/GLIBCXX_([0-9.]+)/)?.[1];
const requirements = [];
if (needsGlibc) requirements.push(`glibc ${needsGlibc}+`);
if (needsGlibcxx) requirements.push(`GLIBCXX_${needsGlibcxx}+`);
const requirementText = requirements.length ? requirements.join(" and ") : "newer glibc/libstdc++ runtime libraries";
return `${raw}\n\nThe selected model is not the problem. The Linux backend binary cannot start because this OS is missing ${requirementText}. The bundled Linux backends are built for Ubuntu 24.04-era systems. Use Ubuntu 24.04+, Fedora 40+, another glibc 2.38+ distro, or build stable-diffusion.cpp from source on this machine.`;
}
function describeBackendExitCode(code, backendPath) {
const numericCode = Number(code);
if (osPlatform === "win32" && numericCode === 3221225781) {
@@ -5483,6 +5584,9 @@ const server = http.createServer(async (req, res) => {
res.end(); return;
}
if ((req.url.startsWith("/v1/") || req.url.startsWith("/sdapi/")) && ["GET", "POST"].includes(req.method)) {
return proxyImageBackendRequest(req, res);
}
// ── Management API ────────────────────────────────────────────────────────
// GET /api/health
if (req.url === "/api/health" && req.method === "GET") {
+66 -41
View File
@@ -34,57 +34,82 @@ function Download-File {
param([string]$Url, [string]$DestPath, [string]$Label)
$barWidth = 48
$lastBytes = [long]0
$lastTime = [DateTime]::Now
Write-Host " >> Downloading $Label..."
try {
Enable-Tls12
$req = [System.Net.HttpWebRequest]::Create($Url)
$req.UserAgent = "Mozilla/5.0"
$resp = $req.GetResponse()
$total = [long]$resp.ContentLength
$stream = $resp.GetResponseStream()
$out = [System.IO.File]::Create($DestPath)
$buf = New-Object byte[] 65536
$done = [long]0
for ($attempt = 1; $attempt -le 3; $attempt++) {
$lastBytes = [long]0
$lastTime = [DateTime]::Now
$resp = $null
$stream = $null
$out = $null
while ($true) {
$read = $stream.Read($buf, 0, $buf.Length)
if ($read -le 0) { break }
$out.Write($buf, 0, $read)
$done += $read
try {
if (Test-Path $DestPath) { Remove-Item $DestPath -Force }
Enable-Tls12
$req = [System.Net.HttpWebRequest]::Create($Url)
$req.UserAgent = "Mozilla/5.0"
$req.Timeout = 300000
$req.ReadWriteTimeout = 300000
$resp = $req.GetResponse()
$total = [long]$resp.ContentLength
$stream = $resp.GetResponseStream()
$out = [System.IO.File]::Create($DestPath)
$buf = New-Object byte[] 65536
$done = [long]0
$now = [DateTime]::Now
$elapsed = ($now - $lastTime).TotalSeconds
if ($elapsed -ge 0.35) {
$speed = ($done - $lastBytes) / $elapsed
$lastBytes = $done
$lastTime = $now
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
$fill = [int](($pct / 100) * $barWidth)
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
while ($true) {
$read = $stream.Read($buf, 0, $buf.Length)
if ($read -le 0) { break }
$out.Write($buf, 0, $read)
$done += $read
$eta = ""
if ($speed -gt 0 -and $total -gt 0) {
$rem = [int](($total - $done) / $speed)
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
$now = [DateTime]::Now
$elapsed = ($now - $lastTime).TotalSeconds
if ($elapsed -ge 0.35) {
$speed = ($done - $lastBytes) / $elapsed
$lastBytes = $done
$lastTime = $now
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
$fill = [int](($pct / 100) * $barWidth)
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
$eta = ""
if ($speed -gt 0 -and $total -gt 0) {
$rem = [int](($total - $done) / $speed)
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
}
$dl = Format-Bytes $done
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
$spd = Format-Speed $speed
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
}
$dl = Format-Bytes $done
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
$spd = Format-Speed $speed
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
}
if ($total -gt 0 -and $done -ne $total) {
throw "Download ended early: $(Format-Bytes $done) of $(Format-Bytes $total) received."
}
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
Write-Host ""
return
} catch {
Write-Host ""
if ($attempt -lt 3) {
Write-Host " !! Download attempt $attempt failed: $_" -ForegroundColor Yellow
Write-Host " >> Retrying download..." -ForegroundColor Cyan
Start-Sleep -Seconds (2 * $attempt)
} else {
throw "Download failed after $attempt attempts: $_"
}
} finally {
if ($out) { $out.Close() }
if ($stream) { $stream.Close() }
if ($resp) { $resp.Close() }
}
$out.Close(); $stream.Close()
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
Write-Host ""
} catch {
Write-Host ""
throw "Download failed: $_"
if (Test-Path $DestPath) { Remove-Item $DestPath -Force -ErrorAction SilentlyContinue }
}
}
+121 -90
View File
@@ -64,58 +64,83 @@ function Invoke-RichDownload {
Write-Host ""
$barWidth = 48
$lastBytes = [long]0
$lastTime = [DateTime]::Now
try {
Enable-Tls12
$req = [System.Net.HttpWebRequest]::Create($Url)
$req.UserAgent = "Mozilla/5.0"
$resp = $req.GetResponse()
$total = [long]$resp.ContentLength
$stream = $resp.GetResponseStream()
$out = [System.IO.File]::Create($Dest)
$buf = New-Object byte[] 65536
$done = [long]0
for ($attempt = 1; $attempt -le 3; $attempt++) {
$lastBytes = [long]0
$lastTime = [DateTime]::Now
$resp = $null
$stream = $null
$out = $null
while ($true) {
$read = $stream.Read($buf, 0, $buf.Length)
if ($read -le 0) { break }
$out.Write($buf, 0, $read)
$done += $read
try {
if (Test-Path $Dest) { Remove-Item $Dest -Force }
Enable-Tls12
$req = [System.Net.HttpWebRequest]::Create($Url)
$req.UserAgent = "Mozilla/5.0"
$req.Timeout = 300000
$req.ReadWriteTimeout = 300000
$resp = $req.GetResponse()
$total = [long]$resp.ContentLength
$stream = $resp.GetResponseStream()
$out = [System.IO.File]::Create($Dest)
$buf = New-Object byte[] 65536
$done = [long]0
$now = [DateTime]::Now
$elapsed = ($now - $lastTime).TotalSeconds
if ($elapsed -ge 0.35) {
$speed = ($done - $lastBytes) / $elapsed
$lastBytes = $done
$lastTime = $now
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
$fill = [int](($pct / 100) * $barWidth)
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
while ($true) {
$read = $stream.Read($buf, 0, $buf.Length)
if ($read -le 0) { break }
$out.Write($buf, 0, $read)
$done += $read
$eta = ""
if ($speed -gt 0 -and $total -gt 0) {
$rem = [int](($total - $done) / $speed)
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
$now = [DateTime]::Now
$elapsed = ($now - $lastTime).TotalSeconds
if ($elapsed -ge 0.35) {
$speed = ($done - $lastBytes) / $elapsed
$lastBytes = $done
$lastTime = $now
$pct = if ($total -gt 0) { [int](($done / $total) * 100) } else { 0 }
$fill = [int](($pct / 100) * $barWidth)
$bar = ("#" * $fill) + ("-" * ($barWidth - $fill))
$eta = ""
if ($speed -gt 0 -and $total -gt 0) {
$rem = [int](($total - $done) / $speed)
$eta = " ETA $([int]($rem/60))m$($rem%60)s"
}
$dl = Format-Bytes $done
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
$spd = Format-Speed $speed
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
}
$dl = Format-Bytes $done
$tot = if ($total -gt 0) { " / " + (Format-Bytes $total) } else { "" }
$spd = Format-Speed $speed
Write-Host -NoNewline "`r [$bar] $pct% $dl$tot $spd$eta "
}
if ($total -gt 0 -and $done -ne $total) {
throw "Download ended early: $(Format-Bytes $done) of $(Format-Bytes $total) received."
}
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
Write-Host ""
return $true
} catch {
Write-Host ""
if ($attempt -lt 3) {
Print-Warn "Download attempt $attempt failed: $_"
Print-Info "Retrying download..."
Start-Sleep -Seconds (2 * $attempt)
} else {
Print-Fail "Download failed after $attempt attempts: $_"
}
} finally {
if ($out) { $out.Close() }
if ($stream) { $stream.Close() }
if ($resp) { $resp.Close() }
}
$out.Close(); $stream.Close()
Write-Host "`r [$("#" * $barWidth)] 100% $(Format-Bytes $done) Done! " -ForegroundColor Green
Write-Host ""
return $true
} catch {
Write-Host ""
Print-Fail "Download failed: $_"
return $false
if (Test-Path $Dest) { Remove-Item $Dest -Force -ErrorAction SilentlyContinue }
}
return $false
}
function Expand-WithProgress {
@@ -297,9 +322,11 @@ if ($hasNvidia) {
$backendDest = Join-Path $appDir "backend\win\cuda"
$backendExe = Join-Path $backendDest "sd-cuda.exe"
$backendDll = Join-Path $backendDest "stable-diffusion.dll"
$cudaBackendReady = $false
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
Print-OK "CUDA GPU backend binaries already ready."
$cudaBackendReady = $true
} else {
$backendZip = Join-Path $toolsDir "sd-cuda.zip"
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
@@ -310,59 +337,63 @@ if ($hasNvidia) {
-Dest $backendZip `
-Label "stable-diffusion.cpp CUDA Backend (Windows x64)"
if (-not $ok) { Print-Fail "Cannot download CUDA backend binaries."; Read-Host; exit 1 }
$tempExt = Join-Path $toolsDir "sd-cuda-temp"
Expand-WithProgress -ZipPath $backendZip -Destination $tempExt -Label "CUDA Backend"
Remove-Item $backendZip -Force
# Move files and rename sd.exe/sd-server.exe to sd-cuda.exe
if (Test-Path $tempExt) {
$extractedExe = Join-Path $tempExt "bin\sd-server.exe"
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" }
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" }
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" }
$extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll"
if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" }
if (Test-Path $extractedExe) { Copy-Item $extractedExe $backendExe -Force }
if (Test-Path $extractedDll) { Copy-Item $extractedDll $backendDll -Force }
# Copy any other DLLs or EXEs
Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $backendDest -Force }
Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object {
if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $backendDest -Force }
}
Remove-Item $tempExt -Recurse -Force
}
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
Print-OK "CUDA GPU backend binaries installed successfully!"
if (-not $ok) {
Print-Warn "Cannot download CUDA backend binaries. Continuing with the Vulkan backend fallback."
} else {
Print-Fail "Failed to copy backend binaries to app/backend/win/cuda/."
Read-Host; exit 1
$tempExt = Join-Path $toolsDir "sd-cuda-temp"
Expand-WithProgress -ZipPath $backendZip -Destination $tempExt -Label "CUDA Backend"
Remove-Item $backendZip -Force
# Move files and rename sd.exe/sd-server.exe to sd-cuda.exe
if (Test-Path $tempExt) {
$extractedExe = Join-Path $tempExt "bin\sd-server.exe"
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd-server.exe" }
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "bin\sd.exe" }
if (-not (Test-Path $extractedExe)) { $extractedExe = Join-Path $tempExt "sd.exe" }
$extractedDll = Join-Path $tempExt "bin\stable-diffusion.dll"
if (-not (Test-Path $extractedDll)) { $extractedDll = Join-Path $tempExt "stable-diffusion.dll" }
if (Test-Path $extractedExe) { Copy-Item $extractedExe $backendExe -Force }
if (Test-Path $extractedDll) { Copy-Item $extractedDll $backendDll -Force }
# Copy any other DLLs or EXEs
Get-ChildItem $tempExt -Filter "*.dll" -Recurse | ForEach-Object { Copy-Item $_.FullName $backendDest -Force }
Get-ChildItem $tempExt -Filter "*.exe" -Recurse | ForEach-Object {
if ($_.FullName -ne $extractedExe) { Copy-Item $_.FullName $backendDest -Force }
}
Remove-Item $tempExt -Recurse -Force
}
if ((Test-Path $backendExe) -and (Test-Path $backendDll)) {
Print-OK "CUDA GPU backend binaries installed successfully!"
$cudaBackendReady = $true
} else {
Print-Warn "Failed to copy CUDA backend binaries. Continuing with the Vulkan backend fallback."
}
}
}
$cudaDllsExist = (Test-Path (Join-Path $backendDest "cublas64_12.dll")) -and `
(Test-Path (Join-Path $backendDest "cublasLt64_12.dll")) -and `
(Test-Path (Join-Path $backendDest "cudart64_12.dll"))
if ($cudaBackendReady) {
$cudaDllsExist = (Test-Path (Join-Path $backendDest "cublas64_12.dll")) -and `
(Test-Path (Join-Path $backendDest "cublasLt64_12.dll")) -and `
(Test-Path (Join-Path $backendDest "cudart64_12.dll"))
if (-not $cudaDllsExist) {
Print-Info "CUDA runtime DLLs are missing from backend folder. Downloading portable CUDA v12 runtime..."
$dllZip = Join-Path $toolsDir "cuda-dlls.zip"
$ok = Invoke-RichDownload `
-Url "https://github.com/ggml-org/llama.cpp/releases/download/b9509/cudart-llama-bin-win-cuda-12.4-x64.zip" `
-Dest $dllZip `
-Label "CUDA v12 Runtime DLLs (llama.cpp)"
if (-not $cudaDllsExist) {
Print-Info "CUDA runtime DLLs are missing from backend folder. Downloading portable CUDA v12 runtime..."
$dllZip = Join-Path $toolsDir "cuda-dlls.zip"
$ok = Invoke-RichDownload `
-Url "https://github.com/ggml-org/llama.cpp/releases/download/b9509/cudart-llama-bin-win-cuda-12.4-x64.zip" `
-Dest $dllZip `
-Label "CUDA v12 Runtime DLLs (llama.cpp)"
if ($ok) {
Expand-WithProgress -ZipPath $dllZip -Destination $backendDest -Label "CUDA Runtime DLLs"
Remove-Item $dllZip -Force
Print-OK "CUDA runtime DLLs set up successfully!"
} else {
Print-Warn "Could not download portable CUDA runtime DLLs automatically. If the app fails to start in CUDA mode, you may need to install the CUDA Toolkit manually."
if ($ok) {
Expand-WithProgress -ZipPath $dllZip -Destination $backendDest -Label "CUDA Runtime DLLs"
Remove-Item $dllZip -Force
Print-OK "CUDA runtime DLLs set up successfully!"
} else {
Print-Warn "Could not download portable CUDA runtime DLLs automatically. If the app fails to start in CUDA mode, you may need to install the CUDA Toolkit manually."
}
}
}
+63 -14
View File
@@ -194,23 +194,69 @@ detect_gpu_vendor() {
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=""
# Official Linux binaries are built on Ubuntu 24.04 and link against glibc 2.38+
# plus libstdc++ with GLIBCXX_3.4.32+. Stop early on older distributions so
# setup does not appear successful when the backend cannot start.
version_at_least() {
local current="$1" required="$2"
[[ "$(printf '%s\n' "$required" "$current" | sort -V | head -n1)" == "$required" ]]
}
detect_glibcxx_version() {
local libstdcpp=""
if command -v ldconfig >/dev/null 2>&1; then
libstdcpp="$(ldconfig -p 2>/dev/null | awk '/libstdc\+\+\.so\.6/{print $NF; exit}')"
fi
if [[ -z "$libstdcpp" ]]; then
for candidate in /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /usr/lib64/libstdc++.so.6 /lib/x86_64-linux-gnu/libstdc++.so.6; do
if [[ -f "$candidate" ]]; then
libstdcpp="$candidate"
break
fi
done
fi
if [[ -n "$libstdcpp" && -f "$libstdcpp" ]] && command -v strings >/dev/null 2>&1; then
strings "$libstdcpp" 2>/dev/null | grep -oE 'GLIBCXX_[0-9]+\.[0-9]+\.[0-9]+' | sed 's/GLIBCXX_//' | sort -V | tail -n1
fi
}
check_linux_runtime_abi() {
local required_glibc="2.38"
local required_glibcxx="3.4.32"
local current_glibc=""
local current_glibcxx=""
local unsupported=0
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)"
current_glibc="$(ldd --version 2>/dev/null | head -n1 | grep -oE '[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
current_glibcxx="$(detect_glibcxx_version || true)"
if [[ -z "$current_glibc" ]]; then
print_warn "Could not detect glibc version. Prebuilt Linux backends require glibc $required_glibc+ (Ubuntu 24.04)."
elif ! version_at_least "$current_glibc" "$required_glibc"; then
print_fail "Detected glibc $current_glibc. Prebuilt Linux backends require glibc $required_glibc+ (Ubuntu 24.04 or newer)."
unsupported=1
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)."
if [[ -z "$current_glibcxx" ]]; then
print_warn "Could not detect GLIBCXX version. Prebuilt Linux backends require GLIBCXX_$required_glibcxx+."
elif ! version_at_least "$current_glibcxx" "$required_glibcxx"; then
print_fail "Detected GLIBCXX_$current_glibcxx. Prebuilt Linux backends require GLIBCXX_$required_glibcxx+."
unsupported=1
fi
if [[ $unsupported -ne 0 ]]; then
print_info "CyberRealistic and other valid models will fail before loading on this OS because the backend binary cannot start."
print_info "Fix: use Ubuntu 24.04+, Fedora 40+, another glibc 2.38+ distro, or build stable-diffusion.cpp from source on this machine."
if [[ "${UAIS_ALLOW_UNSUPPORTED_LINUX:-0}" == "1" ]]; then
print_warn "Continuing anyway because UAIS_ALLOW_UNSUPPORTED_LINUX=1 is set."
return 0
fi
return 1
fi
print_ok "Linux runtime ABI ready: glibc $current_glibc, GLIBCXX_$current_glibcxx"
}
copy_binaries_from_extracted() {
@@ -255,7 +301,10 @@ copy_macos_backend_from_extracted() {
print_header
if [[ "$PLATFORM" == "Linux" ]]; then
check_glibc
if ! check_linux_runtime_abi; then
print_fail "Linux setup stopped before downloading incompatible backend binaries."
exit 1
fi
fi
TOTAL_STEPS=7