| #!/usr/bin/env bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| set -euo pipefail |
|
|
| |
| readonly REPO_A1111="https://github.com/AUTOMATIC1111/stable-diffusion-webui.git" |
| readonly REPO_FORGE="https://github.com/lllyasviel/stable-diffusion-webui-forge.git" |
| readonly REPO_COMFY="https://github.com/comfyanonymous/ComfyUI.git" |
| readonly REPO_COMFY_MANAGER="https://github.com/ltdrdata/ComfyUI-Manager.git" |
|
|
| readonly DIR_A1111="stable-diffusion-webui" |
| readonly DIR_FORGE="stable-diffusion-webui-forge" |
| readonly DIR_COMFY="ComfyUI" |
|
|
| readonly VENV_DIR="venv" |
| readonly REQUIREMENTS_FILE="requirements_versions.txt" |
|
|
| |
| MODELS_ROOT="${HOME}/sd_models" |
|
|
| |
| readonly MODEL_NAME="dvine" |
| readonly MODEL_VERSION_ID="2795248" |
| readonly MODEL_DOWNLOAD_URL="https://civitai.com/api/download/models/${MODEL_VERSION_ID}" |
| readonly MODEL_FILENAME="dvine_v20.safetensors" |
|
|
| |
| |
| declare -A BORNNOOB_MODELS=( |
| ["Dvine"]="2795248" |
| ["NAI"]="CHANGE_ME" |
| ["Matrix Hentai"]="CHANGE_ME" |
| ["Tales Hentai"]="CHANGE_ME" |
| ["IL"]="CHANGE_ME" |
| ["Pornzilla Hentai"]="CHANGE_ME" |
| ) |
|
|
| |
| DEFAULT_MODEL_SUBDIR="Stable-diffusion/SD15" |
|
|
| |
| readonly PYTHON_VERSIONS=("3.11" "3.10" "3.12") |
| PYTHON_CMD="" |
|
|
| |
| readonly RED='\033[0;31m' |
| readonly GREEN='\033[0;32m' |
| readonly YELLOW='\033[1;33m' |
| readonly BLUE='\033[0;34m' |
| readonly NC='\033[0m' |
|
|
| |
| GPU_TYPE="" |
| GPU_VRAM="" |
| GPU_GFX="" |
| GPU_INFO="" |
| GPU_CACHE_FILE="${HOME}/.cache/sd_manager_gpu.cache" |
| GPU_TEMP="" |
| GPU_COMPUTE_CAP="" |
|
|
| |
| ATTENTION_MODE="sdp-no-mem" |
|
|
| |
| VERBOSE=false |
| DEBUG_LOG="/tmp/sd_manager_$(date +%Y%m%d_%H%M%S).log" |
|
|
| |
| THERMAL_MONITOR_PID="" |
|
|
| |
| log() { |
| local level="$1" |
| local msg="$2" |
| local timestamp=$(date '+%Y-%m-%d %H:%M:%S') |
| echo -e "${timestamp} [${level}] ${msg}" >> "$DEBUG_LOG" |
| if [[ "$VERBOSE" == true ]]; then |
| echo -e "${timestamp} [${level}] ${msg}" |
| fi |
| } |
|
|
| show_help() { |
| cat <<EOF |
| Usage: $0 [OPTION] |
| |
| Options: |
| -h, --help Show this help message and exit |
| --install-a1111 Non-interactive install of AUTOMATIC1111 WebUI |
| --install-forge Non-interactive install of Forge WebUI |
| --install-comfy Non-interactive install of ComfyUI + Manager |
| --install-both Non-interactive install of A1111 and Forge (linked) |
| --install-dvine Non-interactive install of the dvine model |
| --install-bornnoob Non-interactive install of ALL BoRnNo0b models (requires version IDs set) |
| --auto-configure Auto-detect hardware and configure existing installations |
| --verbose Enable verbose logging (output to console and /tmp/sd_manager_*.log) |
| |
| Interactive menu (default) when run without options. |
| Requires 'gum' for the menu interface. |
| EOF |
| } |
|
|
| |
| if [[ $# -gt 0 ]]; then |
| case "$1" in |
| -h|--help) |
| show_help |
| exit 0 |
| ;; |
| --install-a1111) |
| NON_INTERACTIVE="a1111" |
| ;; |
| --install-forge) |
| NON_INTERACTIVE="forge" |
| ;; |
| --install-comfy) |
| NON_INTERACTIVE="comfy" |
| ;; |
| --install-both) |
| NON_INTERACTIVE="both" |
| ;; |
| --install-dvine) |
| NON_INTERACTIVE="dvine" |
| ;; |
| --install-bornnoob) |
| NON_INTERACTIVE="bornnoob" |
| ;; |
| --auto-configure) |
| NON_INTERACTIVE="configure" |
| ;; |
| --verbose) |
| VERBOSE=true |
| shift |
| if [[ $# -gt 0 ]]; then |
| case "$1" in |
| --install-a1111) NON_INTERACTIVE="a1111" ;; |
| --install-forge) NON_INTERACTIVE="forge" ;; |
| --install-comfy) NON_INTERACTIVE="comfy" ;; |
| --install-both) NON_INTERACTIVE="both" ;; |
| --install-dvine) NON_INTERACTIVE="dvine" ;; |
| --install-bornnoob) NON_INTERACTIVE="bornnoob" ;; |
| --auto-configure) NON_INTERACTIVE="configure" ;; |
| *) echo "Unknown option: $1"; show_help; exit 1 ;; |
| esac |
| fi |
| ;; |
| *) |
| echo "Unknown option: $1" |
| show_help |
| exit 1 |
| ;; |
| esac |
| fi |
|
|
| log "INFO" "Script started with options: $*" |
|
|
| |
| check_gum() { |
| if [[ -z "${NON_INTERACTIVE:-}" ]] && ! command -v gum &> /dev/null; then |
| echo -e "${RED}gum is required for interactive mode but not installed.${NC}" |
| echo "Please install it using your package manager or run with --help for non-interactive options." |
| if command -v pacman &> /dev/null; then |
| echo " sudo pacman -S gum" |
| elif command -v apt-get &> /dev/null; then |
| echo " # Download the latest .deb from https://github.com/charmbracelet/gum/releases" |
| elif command -v dnf &> /dev/null; then |
| echo " # Download the latest .rpm from https://github.com/charmbracelet/gum/releases" |
| else |
| echo " See https://github.com/charmbracelet/gum#installation" |
| fi |
| log "ERROR" "gum not installed" |
| exit 1 |
| fi |
| log "INFO" "gum check passed" |
| } |
|
|
| |
| pre_install_checks() { |
| echo -e "${BLUE}Running pre‑flight safety checks...${NC}" |
| log "INFO" "Running pre‑flight checks" |
|
|
| if ! ping -c 1 github.com &> /dev/null; then |
| echo -e "${RED}No internet connection to GitHub. Please check your network.${NC}" |
| log "ERROR" "Internet connection failed" |
| exit 1 |
| fi |
|
|
| local free_space=$(df -BG . | awk 'NR==2 {print $4}' | sed 's/G//') |
| if [[ "$free_space" -lt 20 ]]; then |
| echo -e "${RED}Insufficient disk space: ${free_space}GB free (need at least 20GB).${NC}" |
| log "ERROR" "Insufficient disk space: ${free_space}GB" |
| exit 1 |
| fi |
|
|
| if command -v pacman &> /dev/null; then |
| if [[ -f /var/lib/pacman/db.lck ]]; then |
| echo -e "${RED}Pacman database is locked (/var/lib/pacman/db.lck exists). Please wait for other package operations to finish.${NC}" |
| log "ERROR" "Pacman lock file present" |
| exit 1 |
| fi |
| fi |
|
|
| echo -e "${GREEN}Pre‑flight checks passed.${NC}" |
| log "INFO" "Pre‑flight checks passed" |
| } |
|
|
| |
| check_dir_permissions() { |
| local target_dir="$1" |
| local parent_dir=$(dirname "$target_dir") |
| if [[ ! -w "$parent_dir" ]]; then |
| echo -e "${RED}No write permission in ${parent_dir}. Please check permissions.${NC}" |
| log "ERROR" "No write permission in $parent_dir" |
| exit 1 |
| fi |
| log "INFO" "Directory permissions OK for $parent_dir" |
| } |
|
|
| |
| detect_python() { |
| |
| for ver in "${PYTHON_VERSIONS[@]}"; do |
| if command -v "python${ver}" &> /dev/null; then |
| PYTHON_CMD="python${ver}" |
| echo -e "${GREEN}Using Python ${ver}${NC}" |
| log "INFO" "Using Python $ver" |
| return 0 |
| fi |
| done |
|
|
| |
| if command -v python3 &> /dev/null; then |
| local pyver=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') |
| if [[ "$pyver" =~ ^3\.1[3-9] ]]; then |
| echo -e "${YELLOW}System Python is ${pyver} – WebUIs may not be compatible.${NC}" |
| if command -v pacman &> /dev/null; then |
| if pacman -Qq python311 2>/dev/null; then |
| echo -e "${GREEN}Found python311 package. Using python3.11.${NC}" |
| PYTHON_CMD="python3.11" |
| return 0 |
| else |
| echo -e "${YELLOW}Consider installing 'python311' from AUR. Falling back to system python3.${NC}" |
| fi |
| fi |
| fi |
| echo -e "${YELLOW}Warning: Using system python3 (version ${pyver}) – SD WebUIs may not be compatible.${NC}" |
| PYTHON_CMD="python3" |
| log "WARNING" "Using system python3 version $pyver" |
| return 0 |
| fi |
|
|
| echo -e "${RED}No Python interpreter found. Please install Python 3.10, 3.11, or 3.12.${NC}" |
| log "ERROR" "No Python found" |
| exit 1 |
| } |
|
|
| |
| cache_gpu_info() { |
| mkdir -p "$(dirname "$GPU_CACHE_FILE")" || true |
| cat > "$GPU_CACHE_FILE" <<EOF || true |
| GPU_TYPE="$GPU_TYPE" |
| GPU_VRAM="$GPU_VRAM" |
| GPU_GFX="$GPU_GFX" |
| GPU_INFO="$GPU_INFO" |
| GPU_COMPUTE_CAP="$GPU_COMPUTE_CAP" |
| EOF |
| log "INFO" "GPU info cached to $GPU_CACHE_FILE" |
| } |
|
|
| load_gpu_cache() { |
| if [[ -f "$GPU_CACHE_FILE" ]]; then |
| source "$GPU_CACHE_FILE" |
| echo -e "${BLUE}Loaded GPU info from cache: ${GPU_TYPE} (VRAM: ${GPU_VRAM:-unknown} MB)${NC}" |
| log "INFO" "Loaded GPU cache: $GPU_TYPE, VRAM $GPU_VRAM" |
| return 0 |
| fi |
| return 1 |
| } |
|
|
| |
| get_gpu_temp() { |
| local temp="" |
| case "$GPU_TYPE" in |
| "nvidia") |
| if command -v nvidia-smi &> /dev/null; then |
| temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits | head -1) || true |
| echo "$temp °C" |
| else |
| echo "N/A (nvidia-smi missing)" |
| fi |
| ;; |
| "amd") |
| if command -v rocm-smi &> /dev/null; then |
| temp=$(rocm-smi --showtemp 2>/dev/null | grep "GPU[0-9]* temperature" | awk '{print $3}') || true |
| if [[ -n "$temp" ]]; then |
| echo "$temp °C" |
| else |
| echo "N/A (rocm-smi failed)" |
| fi |
| else |
| echo "N/A (rocm-smi missing)" |
| fi |
| ;; |
| "intel") |
| echo "N/A" |
| ;; |
| *) |
| echo "N/A" |
| ;; |
| esac |
| return 0 |
| } |
|
|
| |
| get_gpu_vram_usage() { |
| local usage="" |
| case "$GPU_TYPE" in |
| "nvidia") |
| if command -v nvidia-smi &> /dev/null; then |
| usage=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits | head -1) || true |
| local total=$GPU_VRAM |
| if [[ -n "$usage" && -n "$total" && "$total" -gt 0 ]]; then |
| echo "$((usage * 100 / total))%" |
| else |
| echo "N/A" |
| fi |
| else |
| echo "N/A" |
| fi |
| ;; |
| *) |
| echo "N/A" |
| ;; |
| esac |
| } |
|
|
| |
| get_system_ram_available() { |
| if command -v free &> /dev/null; then |
| free -m | awk 'NR==2 {print $7}' || echo "N/A" |
| else |
| echo "N/A" |
| fi |
| } |
|
|
| |
| show_hardware_dashboard() { |
| detect_gpu |
| echo -e "${BLUE}=== Hardware Status ===${NC}" |
| echo -e "GPU Type: $GPU_TYPE" |
| echo -e "GPU Temperature: $(get_gpu_temp)" |
| echo -e "GPU VRAM Usage: $(get_gpu_vram_usage)" |
| echo -e "System RAM Available: $(get_system_ram_available) MB" |
|
|
| local temp=$(get_gpu_temp | awk '{print $1}') |
| if [[ "$temp" =~ ^[0-9]+$ ]] && [[ "$temp" -gt 85 ]]; then |
| echo -e "${RED}WARNING: GPU temperature is critically high (${temp}°C). Check cooling!${NC}" |
| fi |
|
|
| local usage_pct=$(get_gpu_vram_usage | sed 's/%//') |
| if [[ "$usage_pct" =~ ^[0-9]+$ ]] && [[ "$usage_pct" -gt 90 ]]; then |
| echo -e "${YELLOW}WARNING: VRAM usage is very high (${usage_pct}%). Close some applications.${NC}" |
| fi |
|
|
| local ram_avail=$(get_system_ram_available) |
| if [[ "$ram_avail" =~ ^[0-9]+$ ]] && [[ "$ram_avail" -lt 1024 ]]; then |
| echo -e "${YELLOW}WARNING: System RAM is low (${ram_avail} MB available). Close some applications.${NC}" |
| fi |
| log "INFO" "Hardware dashboard displayed" |
| } |
|
|
| |
| start_thermal_monitor() { |
| if [[ -n "$THERMAL_MONITOR_PID" ]] && kill -0 "$THERMAL_MONITOR_PID" 2>/dev/null; then |
| echo -e "${YELLOW}Thermal monitor already running (PID $THERMAL_MONITOR_PID).${NC}" |
| return |
| fi |
|
|
| echo -e "${BLUE}Starting background thermal monitor (checks every 30s). Use 'Stop thermal monitor' to end.${NC}" |
| ( |
| while true; do |
| sleep 30 |
| local temp=$(get_gpu_temp | awk '{print $1}') |
| if [[ "$temp" =~ ^[0-9]+$ ]] && [[ "$temp" -gt 85 ]]; then |
| notify-send "GPU Thermal Warning" "Temperature reached ${temp}°C" 2>/dev/null || \ |
| echo -e "${RED}GPU temperature is high: ${temp}°C${NC}" >&2 |
| fi |
| done |
| ) & |
| THERMAL_MONITOR_PID=$! |
| log "INFO" "Thermal monitor started with PID $THERMAL_MONITOR_PID" |
| } |
|
|
| stop_thermal_monitor() { |
| if [[ -n "$THERMAL_MONITOR_PID" ]] && kill -0 "$THERMAL_MONITOR_PID" 2>/dev/null; then |
| kill "$THERMAL_MONITOR_PID" |
| wait "$THERMAL_MONITOR_PID" 2>/dev/null || true |
| THERMAL_MONITOR_PID="" |
| echo -e "${GREEN}Thermal monitor stopped.${NC}" |
| log "INFO" "Thermal monitor stopped" |
| else |
| echo -e "${YELLOW}No thermal monitor running.${NC}" |
| fi |
| } |
|
|
| |
| check_dependency_conflicts() { |
| echo -e "${BLUE}Checking for dependency conflicts in installed WebUIs...${NC}" |
| log "INFO" "Running dependency conflict check" |
| local found_conflict=false |
| for dir in "$DIR_A1111" "$DIR_FORGE" "$DIR_COMFY"; do |
| if [[ -d "$dir" && -d "$dir/$VENV_DIR" ]]; then |
| echo -e "\n${YELLOW}Checking $dir...${NC}" |
| source "$dir/$VENV_DIR/bin/activate" |
| if pip list 2>/dev/null | grep -q "onnxruntime" && pip list 2>/dev/null | grep -q "onnxruntime-gpu"; then |
| echo -e "${RED}Conflict: Both onnxruntime and onnxruntime-gpu are installed in $dir. This can cause issues.${NC}" |
| found_conflict=true |
| fi |
| local torch_ver=$(pip show torch 2>/dev/null | grep Version | awk '{print $2}') |
| if [[ -n "$torch_ver" ]]; then |
| echo " PyTorch version: $torch_ver" |
| fi |
| deactivate |
| fi |
| done |
| if [[ "$found_conflict" == false ]]; then |
| echo -e "${GREEN}No major dependency conflicts found.${NC}" |
| fi |
| log "INFO" "Dependency conflict check completed" |
| } |
|
|
| |
| detect_gpu() { |
| if load_gpu_cache; then |
| return |
| fi |
|
|
| echo -e "${BLUE}Detecting hardware...${NC}" |
| log "INFO" "Starting hardware detection" |
|
|
| |
| if command -v lspci &> /dev/null; then |
| GPU_INFO=$(lspci 2>/dev/null | grep -E "VGA compatible controller|Display controller" | head -1) || true |
| else |
| GPU_INFO="" |
| log "WARNING" "lspci not found" |
| fi |
|
|
| log "INFO" "Primary GPU info: $GPU_INFO" |
|
|
| if [[ -z "$GPU_INFO" ]]; then |
| |
| GPU_TYPE="cpu" |
| echo -e "${YELLOW}No GPU detected via lspci, falling back to CPU mode.${NC}" |
| cache_gpu_info |
| return |
| fi |
|
|
| if echo "$GPU_INFO" | grep -iq "nvidia"; then |
| GPU_TYPE="nvidia" |
| echo -e "${GREEN}NVIDIA GPU detected.${NC}" |
| log "INFO" "NVIDIA GPU detected" |
|
|
| if command -v nvidia-smi &> /dev/null; then |
| GPU_VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1) || true |
| if [[ -n "$GPU_VRAM" ]]; then |
| echo -e " VRAM: ${GPU_VRAM} MB" |
| log "INFO" "VRAM: $GPU_VRAM MB" |
| fi |
|
|
| GPU_COMPUTE_CAP=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -1 | tr -d '.') || true |
| if [[ -n "$GPU_COMPUTE_CAP" ]]; then |
| echo -e " Compute Capability: ${GPU_COMPUTE_CAP}" |
| log "INFO" "Compute capability: $GPU_COMPUTE_CAP" |
|
|
| if [[ "$GPU_COMPUTE_CAP" -ge 120 ]]; then |
| echo -e "${YELLOW} Note: RTX 50-series (Blackwell) detected. For optimal performance, use PyTorch 2.7+ with CUDA 12.8.${NC}" |
| log "INFO" "RTX 50-series detected, may need PyTorch 2.7+" |
| fi |
| fi |
| else |
| echo -e "${YELLOW} nvidia-smi not found. Install NVIDIA drivers for VRAM detection.${NC}" |
| log "WARNING" "nvidia-smi not found" |
| fi |
| elif echo "$GPU_INFO" | grep -iq "amd"; then |
| GPU_TYPE="amd" |
| echo -e "${GREEN}AMD GPU detected.${NC}" |
| log "INFO" "AMD GPU detected" |
|
|
| if command -v rocminfo &> /dev/null; then |
| GPU_GFX=$(rocminfo 2>/dev/null | awk '/^Agent 2/,/^$/ {if ($1 == "Name:" && $2 ~ /^gfx/) {print $2; exit}}') || true |
| if [[ -n "$GPU_GFX" ]]; then |
| echo -e " GFX architecture: ${GPU_GFX}" |
| log "INFO" "GFX architecture: $GPU_GFX" |
| if [[ "$GPU_GFX" == gfx9* ]]; then |
| export HSA_OVERRIDE_GFX_VERSION=9.0.0 |
| echo -e "${YELLOW} Setting HSA_OVERRIDE_GFX_VERSION=9.0.0 for compatibility${NC}" |
| fi |
| fi |
| else |
| echo -e "${YELLOW} rocminfo not found. Install ROCm for detailed detection.${NC}" |
| log "WARNING" "rocminfo not found" |
| fi |
|
|
| if [[ -d /sys/class/drm/ ]]; then |
| for card in /sys/class/drm/card?/device/mem_info_vram_total; do |
| if [[ -f "$card" ]]; then |
| vram_bytes=$(cat "$card" 2>/dev/null) || true |
| if [[ -n "$vram_bytes" ]]; then |
| GPU_VRAM=$((vram_bytes / 1024 / 1024)) |
| echo -e " VRAM: ${GPU_VRAM} MB" |
| log "INFO" "VRAM: $GPU_VRAM MB (from sysfs)" |
| fi |
| break |
| fi |
| done |
| fi |
| if [[ -z "$GPU_VRAM" ]] && command -v glxinfo &> /dev/null; then |
| GPU_VRAM=$(glxinfo -B 2>/dev/null | awk '/Video memory:/ {print $3}') || true |
| if [[ -n "$GPU_VRAM" ]]; then |
| echo -e " VRAM: ${GPU_VRAM} MB" |
| log "INFO" "VRAM: $GPU_VRAM MB (from glxinfo)" |
| fi |
| fi |
| elif echo "$GPU_INFO" | grep -iq "intel"; then |
| GPU_TYPE="intel" |
| echo -e "${GREEN}Intel GPU detected.${NC}" |
| log "INFO" "Intel GPU detected" |
|
|
| if [[ -d /sys/class/drm/ ]]; then |
| for card in /sys/class/drm/card?/device/mem_info_vram_total; do |
| if [[ -f "$card" ]]; then |
| vram_bytes=$(cat "$card" 2>/dev/null) || true |
| if [[ -n "$vram_bytes" ]]; then |
| GPU_VRAM=$((vram_bytes / 1024 / 1024)) |
| echo -e " VRAM: ${GPU_VRAM} MB" |
| log "INFO" "VRAM: $GPU_VRAM MB (from sysfs)" |
| fi |
| break |
| fi |
| done |
| fi |
| if [[ -z "$GPU_VRAM" ]] && command -v glxinfo &> /dev/null; then |
| GPU_VRAM=$(glxinfo -B 2>/dev/null | awk '/Video memory:/ {print $3}') || true |
| if [[ -n "$GPU_VRAM" ]]; then |
| echo -e " VRAM: ${GPU_VRAM} MB" |
| log "INFO" "VRAM: $GPU_VRAM MB (from glxinfo)" |
| fi |
| fi |
| else |
| GPU_TYPE="cpu" |
| echo -e "${YELLOW}No supported GPU found, will use CPU mode.${NC}" |
| log "WARNING" "No supported GPU, falling back to CPU" |
| fi |
|
|
| GPU_TEMP=$(get_gpu_temp) |
| echo -e " Temperature: $GPU_TEMP" |
| log "INFO" "GPU temperature: $GPU_TEMP" |
|
|
| cache_gpu_info |
| } |
|
|
| |
| get_torch_command() { |
| local target_gpu="${1:-$GPU_TYPE}" |
| case "$target_gpu" in |
| "nvidia") |
| if [[ -n "$GPU_COMPUTE_CAP" && "$GPU_COMPUTE_CAP" -ge 120 ]]; then |
| echo "pip install torch torchvision torchaudio xformers --index-url https://download.pytorch.org/whl/cu128" |
| else |
| echo "pip install torch torchvision torchaudio xformers --index-url https://download.pytorch.org/whl/cu124" |
| fi |
| ;; |
| "amd") |
| if [[ -n "$GPU_GFX" ]]; then |
| if [[ "$GPU_GFX" == gfx9* ]]; then |
| echo "pip install torch==1.13.1+rocm5.2 torchvision==0.14.1+rocm5.2 --index-url https://download.pytorch.org/whl/rocm5.2" |
| elif [[ "$GPU_GFX" == gfx10* ]]; then |
| echo "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm5.7" |
| elif [[ "$GPU_GFX" == gfx11* ]]; then |
| echo "pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm6.0" |
| else |
| echo "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm7.1" |
| fi |
| else |
| echo "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm7.1" |
| fi |
| ;; |
| "intel") |
| echo "pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/xpu && pip install intel-extension-for-pytorch==2.1.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/" |
| ;; |
| *) |
| echo "pip install torch torchvision torchaudio" |
| ;; |
| esac |
| } |
|
|
| |
| configure_webui_user_sh() { |
| local target_dir="$1" |
| local config_file="${target_dir}/webui-user.sh" |
|
|
| if [[ ! -f "$config_file" ]]; then |
| |
| cat > "$config_file" <<'EOF' |
| |
| |
| |
| |
|
|
| |
| export COMMANDLINE_ARGS="" |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| exec "${0/sh/bat}" "$@" |
| EOF |
| fi |
|
|
| local backup="${config_file}.bak.$(date +%Y%m%d_%H%M%S)" |
| cp "$config_file" "$backup" |
| log "INFO" "Backup of webui-user.sh created at $backup" |
|
|
| |
| if grep -q "^python_cmd=" "$config_file"; then |
| sed -i "s|^python_cmd=.*|python_cmd=\"$PYTHON_CMD\"|" "$config_file" |
| else |
| |
| sed -i "2i python_cmd=\"$PYTHON_CMD\"" "$config_file" |
| fi |
|
|
| |
| local torch_cmd=$(get_torch_command) |
| if grep -q "^export TORCH_COMMAND=" "$config_file"; then |
| sed -i "s|^export TORCH_COMMAND=.*|export TORCH_COMMAND=\"$torch_cmd\"|" "$config_file" |
| else |
| echo "export TORCH_COMMAND=\"$torch_cmd\"" >> "$config_file" |
| fi |
|
|
| |
| if grep -q "^export REQS_FILE=" "$config_file"; then |
| sed -i "s|^export REQS_FILE=.*|export REQS_FILE=\"$REQUIREMENTS_FILE\"|" "$config_file" |
| else |
| echo "export REQS_FILE=\"$REQUIREMENTS_FILE\"" >> "$config_file" |
| fi |
|
|
| log "INFO" "Configured $config_file with python_cmd=$PYTHON_CMD, TORCH_COMMAND, REQS_FILE" |
| } |
|
|
| |
| install_system_deps() { |
| echo -e "${YELLOW}Installing system dependencies (Python, git, wget, pciutils, coreutils, ffmpeg)...${NC}" |
| log "INFO" "Installing system dependencies" |
|
|
| if command -v pacman &> /dev/null; then |
| if ! sudo pacman -S --needed python python-pip python-virtualenv git wget pciutils coreutils ffmpeg; then |
| echo -e "${RED}Failed to install dependencies with pacman. Please check your internet connection and package database.${NC}" |
| log "ERROR" "pacman installation failed" |
| exit 1 |
| fi |
| elif command -v apt-get &> /dev/null; then |
| sudo apt-get update || { echo -e "${RED}apt-get update failed.${NC}"; log "ERROR" "apt-get update failed"; exit 1; } |
| if ! sudo apt-get install -y git wget pciutils coreutils ffmpeg; then |
| echo -e "${RED}Failed to install base dependencies with apt-get.${NC}" |
| log "ERROR" "apt-get install failed" |
| exit 1 |
| fi |
| if ! grep -q ^deb .*deadsnakes /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null; then |
| sudo add-apt-repository -y ppa:deadsnakes/ppa || { echo -e "${RED}Failed to add deadsnakes PPA.${NC}"; log "ERROR" "add-apt-repository failed"; exit 1; } |
| sudo apt-get update || { echo -e "${RED}apt-get update after PPA failed.${NC}"; log "ERROR" "apt-get update after PPA failed"; exit 1; } |
| fi |
| sudo apt-get install -y python3.11 python3.11-venv python3-pip || \ |
| sudo apt-get install -y python3.10 python3.10-venv python3-pip || \ |
| sudo apt-get install -y python3 python3-venv python3-pip || { echo -e "${RED}Failed to install Python.${NC}"; log "ERROR" "Python installation failed"; exit 1; } |
| elif command -v dnf &> /dev/null; then |
| sudo dnf makecache --refresh || { echo -e "${RED}dnf makecache failed.${NC}"; log "ERROR" "dnf makecache failed"; exit 1; } |
| if ! sudo dnf install -y python3.11 python3-pip git wget pciutils coreutils ffmpeg-free; then |
| |
| if ! sudo dnf install -y python3 python3-pip git wget pciutils coreutils ffmpeg-free; then |
| echo -e "${RED}Failed to install dependencies with dnf.${NC}" |
| log "ERROR" "dnf install failed" |
| exit 1 |
| fi |
| fi |
| else |
| echo -e "${RED}Unsupported package manager. Please install dependencies manually:${NC}" |
| echo " - Python 3.10/3.11/3.12" |
| echo " - git" |
| echo " - wget" |
| echo " - pciutils (for hardware detection)" |
| echo " - coreutils (for realpath)" |
| echo " - ffmpeg (for video workflows)" |
| log "ERROR" "Unsupported package manager" |
| exit 1 |
| fi |
|
|
| echo -e "${GREEN}System dependencies installed.${NC}" |
| log "INFO" "System dependencies installed" |
|
|
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| echo -e "${YELLOW}Performing system cleanup...${NC}" |
| if command -v apt-get &> /dev/null; then |
| sudo apt autoremove -y && sudo apt clean |
| elif command -v dnf &> /dev/null; then |
| sudo dnf autoremove -y && sudo dnf clean all |
| elif command -v pacman &> /dev/null; then |
| sudo pacman -Sc --noconfirm |
| fi |
| fi |
| } |
|
|
| |
| create_venv() { |
| local repo_dir="$1" |
| local venv_path="${repo_dir}/${VENV_DIR}" |
| if [[ -d "$venv_path" ]]; then |
| echo -e "${YELLOW}Virtual environment already exists in ${repo_dir}. Skipping creation.${NC}" |
| log "INFO" "Venv already exists in $repo_dir" |
| else |
| echo -e "${GREEN}Creating virtual environment in ${repo_dir} ...${NC}" |
| log "INFO" "Creating venv in $repo_dir with $PYTHON_CMD" |
| (cd "$repo_dir" && "$PYTHON_CMD" -m venv "$VENV_DIR") |
| fi |
| } |
|
|
| |
| install_requirements() { |
| local repo_dir="$1" |
| local req_file="$2" |
| local upgrade_flag="${3:-}" |
|
|
| echo -e "${GREEN}Installing requirements from ${req_file} ...${NC}" |
| log "INFO" "Installing requirements from $req_file in $repo_dir" |
| ( |
| cd "$repo_dir" || exit 1 |
| source "${VENV_DIR}/bin/activate" |
| pip install --upgrade pip |
| pip install $upgrade_flag -r "$req_file" |
| ) |
| } |
|
|
| |
| install_pytorch() { |
| local repo_dir="$1" |
| local target_gpu="${2:-$GPU_TYPE}" |
|
|
| if [[ ! -d "${repo_dir}/${VENV_DIR}" ]]; then |
| echo -e "${RED}Virtual environment not found in ${repo_dir}. Please create it first.${NC}" |
| log "ERROR" "Venv not found in $repo_dir" |
| return 1 |
| fi |
|
|
| echo -e "${GREEN}Installing PyTorch for ${target_gpu} GPU in ${repo_dir} ...${NC}" |
| log "INFO" "Installing PyTorch for $target_gpu in $repo_dir" |
|
|
| ( |
| cd "$repo_dir" || exit 1 |
| source "${VENV_DIR}/bin/activate" |
| eval "$(get_torch_command "$target_gpu")" |
| ) |
|
|
| log "INFO" "PyTorch installation completed in $repo_dir" |
| } |
|
|
| |
| verify_repo_url() { |
| local repo_url="$1" |
| local repo_dir="$2" |
| if [[ -d "$repo_dir/.git" ]]; then |
| local remote_url=$(git -C "$repo_dir" config --get remote.origin.url) |
| local norm_repo="${repo_url%.git}" |
| local norm_remote="${remote_url%.git}" |
| if [[ "$norm_remote" != "$norm_repo" ]]; then |
| echo -e "${RED}Security warning: existing repository remote URL (${remote_url}) does not match expected (${repo_url}).${NC}" |
| echo "Please check the repository manually." |
| log "ERROR" "Repository URL mismatch for $repo_dir" |
| exit 1 |
| fi |
| log "INFO" "Repository URL verified for $repo_dir" |
| fi |
| } |
|
|
| |
| clone_repo() { |
| local repo_url="$1" |
| local repo_dir="$2" |
| check_dir_permissions "$repo_dir" |
| if [[ -d "$repo_dir" ]]; then |
| verify_repo_url "$repo_url" "$repo_dir" |
| echo -e "${YELLOW}Repository ${repo_dir} already exists. Pulling latest changes...${NC}" |
| log "INFO" "Pulling latest changes for $repo_dir" |
| (cd "$repo_dir" && git pull) |
| else |
| echo -e "${GREEN}Cloning ${repo_url} ...${NC}" |
| log "INFO" "Cloning $repo_url into $repo_dir" |
| git clone "$repo_url" "$repo_dir" |
| fi |
| } |
|
|
| |
| reset_venv() { |
| local repo_dir="$1" |
| local venv_path="${repo_dir}/${VENV_DIR}" |
| if [[ -d "$venv_path" ]]; then |
| if [[ -z "${NON_INTERACTIVE:-}" ]]; then |
| gum confirm "Are you sure you want to reset the virtual environment in ${repo_dir}? This will delete the current venv." || return |
| fi |
| echo -e "${YELLOW}Removing existing virtual environment in ${repo_dir} ...${NC}" |
| log "INFO" "Removing venv in $repo_dir" |
| rm -rf "$venv_path" |
| fi |
| create_venv "$repo_dir" |
| echo -e "${GREEN}Virtual environment reset complete. You can now install PyTorch and requirements.${NC}" |
| log "INFO" "Venv reset in $repo_dir" |
| } |
|
|
| |
| setup_central_models() { |
| echo -e "${BLUE}Setting up unified model hub at ${MODELS_ROOT}...${NC}" |
| mkdir -p "$MODELS_ROOT"/{Stable-diffusion/SD15,Stable-diffusion/SDXL,Stable-diffusion/Flux,Lora,VAE,embeddings,ControlNet,ESRGAN,RealESRGAN,SwinIR,GFPGAN} |
|
|
| echo -e "${YELLOW}Do you want to move existing models from your installations to the hub?${NC}" |
| if gum confirm "This will move (not copy) model files to $MODELS_ROOT and symlink them back. Proceed?"; then |
| for dir in "$DIR_A1111" "$DIR_FORGE" "$DIR_COMFY"; do |
| if [[ -d "$dir" ]]; then |
| |
| for sub in "models/Stable-diffusion" "models/Lora" "models/VAE" "embeddings" "models/ControlNet" "models/ESRGAN" "models/RealESRGAN" "models/SwinIR" "models/GFPGAN"; do |
| local src="$dir/$sub" |
| local dst_base=$(basename "$sub") |
| if [[ "$sub" == models/* ]]; then |
| dst_base="${sub#models/}" |
| else |
| dst_base="$sub" |
| fi |
| local dst="$MODELS_ROOT/$dst_base" |
| if [[ -d "$src" && ! -L "$src" ]]; then |
| echo "Moving $src to $dst..." |
| mkdir -p "$dst" |
| |
| if [[ "$dst_base" == "Stable-diffusion" ]]; then |
| for arch in "SD15" "SDXL" "Flux"; do |
| if [[ -d "$src/$arch" ]]; then |
| mv "$src/$arch"/* "$dst/$arch"/ 2>/dev/null || true |
| rmdir "$src/$arch" 2>/dev/null || true |
| fi |
| done |
| |
| find "$src" -maxdepth 1 -type f -name "*.safetensors" -o -name "*.ckpt" -exec mv {} "$dst/SD15"/ \; |
| else |
| mv "$src"/* "$dst"/ 2>/dev/null || true |
| fi |
| rm -rf "$src" |
| ln -s "$dst" "$src" |
| fi |
| done |
| fi |
| done |
| echo -e "${GREEN}Models moved and symlinked.${NC}" |
| fi |
|
|
| for ui in "$DIR_A1111" "$DIR_FORGE"; do |
| if [[ -d "$ui" ]]; then |
| local config="$ui/webui-user.sh" |
| if [[ -f "$config" ]]; then |
| cp "$config" "$config.bak.central" |
| local current_args="" |
| if grep -q "^export COMMANDLINE_ARGS=" "$config"; then |
| current_args=$(grep "^export COMMANDLINE_ARGS=" "$config" | sed 's/^export COMMANDLINE_ARGS=//' | sed 's/"//g') |
| fi |
| local new_args="$current_args" |
| for arch in "SD15" "SDXL" "Flux"; do |
| new_args="$new_args --ckpt-dir $MODELS_ROOT/Stable-diffusion/$arch" |
| done |
| new_args="$new_args --lora-dir $MODELS_ROOT/Lora --vae-dir $MODELS_ROOT/VAE --embeddings-dir $MODELS_ROOT/embeddings" |
| if grep -q "^export COMMANDLINE_ARGS=" "$config"; then |
| sed -i "s|^export COMMANDLINE_ARGS=.*|export COMMANDLINE_ARGS=\"${new_args}\"|" "$config" |
| else |
| echo "export COMMANDLINE_ARGS=\"${new_args}\"" >> "$config" |
| fi |
| fi |
| fi |
| done |
|
|
| if [[ -d "$DIR_COMFY" ]]; then |
| local comfy_config="$DIR_COMFY/extra_model_paths.yaml" |
| cat > "$comfy_config" <<EOF |
| # Unified model hub configuration |
| hub: |
| base_path: $MODELS_ROOT |
| checkpoints: |
| SD15: Stable-diffusion/SD15 |
| SDXL: Stable-diffusion/SDXL |
| Flux: Stable-diffusion/Flux |
| loras: Lora |
| vae: VAE |
| embeddings: embeddings |
| controlnet: ControlNet |
| upscale_models: ESRGAN |
| EOF |
| fi |
|
|
| echo -e "${GREEN}Unified model hub configured.${NC}" |
| log "INFO" "Unified hub setup completed" |
| } |
|
|
| |
| link_models() { |
| local master_dir="$1" |
| local target_dir="$2" |
|
|
| local folders=("models/Stable-diffusion/SD15" "models/Stable-diffusion/SDXL" "models/Stable-diffusion/Flux" "models/Lora" "models/VAE" "embeddings" "models/ControlNet" "models/ESRGAN" "models/RealESRGAN" "models/SwinIR" "models/GFPGAN") |
|
|
| echo -e "${BLUE}Linking models from ${master_dir} to ${target_dir}...${NC}" |
| log "INFO" "Linking models from $master_dir to $target_dir" |
|
|
| for folder in "${folders[@]}"; do |
| local src="${master_dir}/${folder}" |
| local dst="${target_dir}/${folder}" |
|
|
| mkdir -p "$src" |
|
|
| if [[ -d "$dst" && ! -L "$dst" ]]; then |
| echo "Moving existing files from ${dst} to ${src}..." |
| log "INFO" "Moving $dst contents to $src" |
| cp -rn "$dst"/* "$src"/ 2>/dev/null || true |
| rm -rf "$dst" |
| fi |
|
|
| if [[ ! -L "$dst" ]]; then |
| ln -s "$(realpath "$src")" "$dst" |
| echo -e "${GREEN}Linked: ${folder}${NC}" |
| log "INFO" "Linked $folder" |
| else |
| echo -e "${YELLOW}Link already exists for: ${folder}${NC}" |
| fi |
| done |
| } |
|
|
| |
| generate_extra_model_paths() { |
| local comfy_dir="$1" |
| local source_ui="$2" |
| local source_dir="" |
|
|
| if [[ "$source_ui" == "A1111" ]]; then |
| source_dir="$DIR_A1111" |
| elif [[ "$source_ui" == "Forge" ]]; then |
| source_dir="$DIR_FORGE" |
| else |
| echo -e "${RED}Invalid source UI.${NC}" |
| log "ERROR" "Invalid source UI: $source_ui" |
| return 1 |
| fi |
|
|
| if [[ ! -d "$source_dir" ]]; then |
| echo -e "${RED}Source directory ${source_dir} does not exist. Please install it first.${NC}" |
| log "ERROR" "Source dir $source_dir not found" |
| return 1 |
| fi |
|
|
| local config_file="${comfy_dir}/extra_model_paths.yaml" |
| local base_path |
| base_path=$(realpath "$source_dir") |
|
|
| cat > "$config_file" <<EOF |
| # This file was auto-generated by sd_manager.sh |
| # It allows ComfyUI to use models from your existing ${source_ui} installation. |
| |
| ${source_ui,,}: |
| base_path: ${base_path} |
| checkpoints: models/Stable-diffusion |
| configs: models/Stable-diffusion |
| vae: models/VAE |
| loras: models/Lora |
| upscale_models: models/ESRGAN |
| embeddings: embeddings |
| controlnet: models/ControlNet |
| EOF |
| echo -e "${GREEN}Generated ${config_file} pointing to ${source_ui} models.${NC}" |
| log "INFO" "Generated $config_file" |
| } |
|
|
| |
| fix_paths() { |
| echo -e "${BLUE}Checking and fixing paths...${NC}" |
| log "INFO" "Running fix_paths" |
|
|
| if [[ -d "$DIR_A1111" && -d "$DIR_FORGE" ]]; then |
| local master="" |
| for folder in "models/Stable-diffusion" "models/Lora" "models/VAE"; do |
| if [[ -d "$DIR_A1111/$folder" && ! -L "$DIR_A1111/$folder" ]]; then |
| master="$DIR_A1111" |
| break |
| elif [[ -d "$DIR_FORGE/$folder" && ! -L "$DIR_FORGE/$folder" ]]; then |
| master="$DIR_FORGE" |
| break |
| fi |
| done |
| if [[ -n "$master" ]]; then |
| if [[ "$master" == "$DIR_A1111" ]]; then |
| echo "Master appears to be AUTOMATIC1111, relinking Forge..." |
| link_models "$DIR_A1111" "$DIR_FORGE" |
| else |
| echo "Master appears to be Forge, relinking AUTOMATIC1111..." |
| link_models "$DIR_FORGE" "$DIR_A1111" |
| fi |
| else |
| echo -e "${YELLOW}No clear master found, skipping symlink fix.${NC}" |
| fi |
| fi |
|
|
| if [[ -d "$DIR_COMFY" ]]; then |
| local config="${DIR_COMFY}/extra_model_paths.yaml" |
| if [[ -f "$config" ]]; then |
| if grep -q "base_path:" "$config"; then |
| local old_path |
| old_path=$(grep "base_path:" "$config" | awk '{print $2}') |
| if [[ -n "$old_path" && ! -d "$old_path" ]]; then |
| echo -e "${YELLOW}extra_model_paths.yaml points to missing directory: $old_path${NC}" |
| log "WARNING" "extra_model_paths.yaml points to missing $old_path" |
| if [[ -d "$DIR_A1111" ]]; then |
| generate_extra_model_paths "$DIR_COMFY" "A1111" |
| elif [[ -d "$DIR_FORGE" ]]; then |
| generate_extra_model_paths "$DIR_COMFY" "Forge" |
| fi |
| fi |
| fi |
| fi |
| fi |
|
|
| echo -e "${GREEN}Path fixing complete.${NC}" |
| log "INFO" "fix_paths completed" |
| } |
|
|
| |
| apply_best_tips() { |
| local target_dir="$1" |
| local config_file="${target_dir}/webui-user.sh" |
| local args="--theme=dark --no-half-vae --api --listen" |
|
|
| echo -e "${BLUE}Optimizing ${config_file} for ${GPU_TYPE} (VRAM: ${GPU_VRAM:-unknown} MB)...${NC}" |
| log "INFO" "Optimizing $config_file for $GPU_TYPE" |
|
|
| case "$GPU_TYPE" in |
| "nvidia") |
| if [[ "$ATTENTION_MODE" == "xformers" ]]; then |
| args="$args --xformers" |
| else |
| args="$args --opt-sdp-no-mem-attention" |
| fi |
| args="$args --pin-shared-memory --opt-channelslast" |
|
|
| if [[ -n "$GPU_VRAM" ]]; then |
| if [[ "$GPU_VRAM" -lt 4000 ]]; then |
| args="$args --lowvram" |
| elif [[ "$GPU_VRAM" -lt 8000 ]]; then |
| args="$args --medvram" |
| elif [[ "$GPU_VRAM" -ge 16000 ]]; then |
| args="$args --highvram" |
| export TORCH_DISTRIBUTED_BACKEND="nccl" |
| fi |
| fi |
| if [[ -n "$GPU_VRAM" && "$GPU_VRAM" -ge 24000 ]]; then |
| args="$args --use-uncached-memory" |
| fi |
|
|
| if [[ -n "$GPU_COMPUTE_CAP" && "$GPU_COMPUTE_CAP" -ge 120 ]]; then |
| args="$args --opt-sdp-attention" |
| fi |
| ;; |
| "amd") |
| args="$args --opt-sdp-no-mem-attention --precision autocast --no-half --opt-sub-quad-attention --disable-nan-check" |
| args="$args --use-gpu-memory-pool" |
| export TORCH_DISTRIBUTED_BACKEND="gloo" |
|
|
| if [[ -n "$GPU_GFX" && "$GPU_GFX" == gfx9* ]]; then |
| export HSA_OVERRIDE_GFX_VERSION=9.0.0 |
| fi |
| if [[ -n "$GPU_VRAM" && "$GPU_VRAM" -lt 8000 ]]; then |
| args="$args --lowvram" |
| fi |
| ;; |
| "intel") |
| args="$args --opt-sdp-no-mem-attention --use-ipex --precision full --no-half --opt-sub-quad-attention" |
| if [[ "$GPU_INFO" == *"Arc"* ]]; then |
| args="$args --lowvram" |
| fi |
| export TORCH_DISTRIBUTED_BACKEND="gloo" |
| ;; |
| *) |
| args="$args --skip-torch-cuda-test --precision full --no-half --use-cpu all" |
| ;; |
| esac |
|
|
| if [[ "$target_dir" == "$DIR_FORGE" ]]; then |
| args=$(echo "$args" | sed -E 's/--(low|med|high)vram//g') |
| echo -e "${YELLOW}Forge detected: stripped explicit memory flags (Forge uses dynamic memory management).${NC}" |
| fi |
|
|
| args="$args --realtime-monitor" |
|
|
| if [[ ! -f "$config_file" ]]; then |
| |
| cat > "$config_file" <<'EOF' |
| |
| |
| |
| |
|
|
| |
| export COMMANDLINE_ARGS="" |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| |
|
|
| |
| exec "${0/sh/bat}" "$@" |
| EOF |
| fi |
|
|
| |
| local backup="${config_file}.bak.$(date +%Y%m%d_%H%M%S)" |
| cp "$config_file" "$backup" |
| log "INFO" "Backup created at $backup" |
|
|
| |
| if grep -q "^export COMMANDLINE_ARGS=" "$config_file"; then |
| sed -i "s|^export COMMANDLINE_ARGS=.*|export COMMANDLINE_ARGS=\"${args}\"|" "$config_file" |
| else |
| echo "export COMMANDLINE_ARGS=\"${args}\"" >> "$config_file" |
| fi |
|
|
| echo -e "${GREEN}Applied optimal arguments: ${args}${NC}" |
| log "INFO" "Applied args: $args" |
| } |
|
|
| |
| apply_comfy_tips() { |
| local target_dir="$1" |
| local launch_script="${target_dir}/launch_comfy.sh" |
| local args="" |
|
|
| echo -e "${BLUE}Generating optimized launch script for ComfyUI (${GPU_TYPE})...${NC}" |
| log "INFO" "Generating ComfyUI launch script for $GPU_TYPE" |
|
|
| case "$GPU_TYPE" in |
| "nvidia") |
| args="--preview-method taesd" |
| if [[ -n "$GPU_VRAM" ]]; then |
| if [[ "$GPU_VRAM" -lt 4000 ]]; then |
| args="--lowvram $args" |
| elif [[ "$GPU_VRAM" -lt 8000 ]]; then |
| args="--normalvram $args" |
| elif [[ "$GPU_VRAM" -ge 16000 ]]; then |
| args="--highvram $args" |
| fi |
| fi |
| ;; |
| "amd"|"intel") |
| args="--use-split-cross-attention --preview-method taesd" |
| if [[ -n "$GPU_VRAM" && "$GPU_VRAM" -lt 8000 ]]; then |
| args="--lowvram $args" |
| fi |
| ;; |
| *) |
| args="--cpu --preview-method taesd" |
| ;; |
| esac |
|
|
| cat > "$launch_script" <<EOF |
| #!/bin/bash |
| # Optimized launch script for ComfyUI |
| cd "$(realpath "$target_dir")" |
| source "${VENV_DIR}/bin/activate" |
| python main.py $args "\$@" |
| EOF |
| chmod +x "$launch_script" |
| echo -e "${GREEN}Created ${launch_script} with arguments: ${args}${NC}" |
| echo "You can run ComfyUI with: $launch_script" |
| log "INFO" "ComfyUI launch script created with args: $args" |
| } |
|
|
| |
| toggle_attention() { |
| local current="$ATTENTION_MODE" |
| if [[ "$current" == "xformers" ]]; then |
| ATTENTION_MODE="sdp-no-mem" |
| echo -e "${GREEN}Attention mechanism set to: --opt-sdp-no-mem-attention${NC}" |
| log "INFO" "Attention mode set to sdp-no-mem" |
| else |
| ATTENTION_MODE="xformers" |
| echo -e "${GREEN}Attention mechanism set to: --xformers${NC}" |
| log "INFO" "Attention mode set to xformers" |
| fi |
| for dir in "$DIR_A1111" "$DIR_FORGE"; do |
| if [[ -d "$dir" ]]; then |
| apply_best_tips "$dir" |
| fi |
| done |
| } |
|
|
| |
| update_all_deps() { |
| echo -e "${BLUE}Updating dependencies for all installed WebUIs...${NC}" |
| log "INFO" "Starting dependency update" |
| for dir in "$DIR_A1111" "$DIR_FORGE" "$DIR_COMFY"; do |
| if [[ -d "$dir" && -d "$dir/$VENV_DIR" ]]; then |
| echo -e "${YELLOW}Updating dependencies in $dir...${NC}" |
| log "INFO" "Updating deps in $dir" |
| (cd "$dir" && source "$VENV_DIR/bin/activate" && pip install --upgrade pip && pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install --upgrade || true) |
| fi |
| done |
| echo -e "${GREEN}Dependency update completed.${NC}" |
| log "INFO" "Dependency update completed" |
| } |
|
|
| |
| download_civitai_model() { |
| local model_name="$1" |
| local version_id="$2" |
| local download_url="https://civitai.com/api/download/models/${version_id}" |
| local target_dir="" |
|
|
| |
| if [[ -d "$MODELS_ROOT" ]]; then |
| |
| target_dir="$MODELS_ROOT/$DEFAULT_MODEL_SUBDIR" |
| else |
| |
| local ui_dir="" |
| [[ -d "$DIR_A1111" ]] && ui_dir="$DIR_A1111" |
| [[ -d "$DIR_FORGE" && -z "$ui_dir" ]] && ui_dir="$DIR_FORGE" |
| [[ -d "$DIR_COMFY" && -z "$ui_dir" ]] && ui_dir="$DIR_COMFY" |
|
|
| if [[ -z "$ui_dir" ]]; then |
| echo -e "${RED}No WebUI installation found. Please install a WebUI first.${NC}" |
| return 1 |
| fi |
|
|
| if [[ "$ui_dir" == "$DIR_COMFY" ]]; then |
| target_dir="$ui_dir/models/checkpoints" |
| else |
| target_dir="$ui_dir/models/Stable-diffusion" |
| fi |
| fi |
|
|
| mkdir -p "$target_dir" |
| local output_file="${target_dir}/${model_name// /_}.safetensors" |
|
|
| if [[ -f "$output_file" ]]; then |
| echo -e "${YELLOW}Model already exists at $output_file. Skipping.${NC}" |
| return |
| fi |
|
|
| echo -e "Downloading $model_name..." |
| if command -v wget &> /dev/null; then |
| wget --show-progress -qO "$output_file" "$download_url" |
| elif command -v curl &> /dev/null; then |
| curl -L --progress-bar -o "$output_file" "$download_url" |
| else |
| echo -e "${RED}No downloader found.${NC}" |
| return 1 |
| fi |
|
|
| if [[ $? -eq 0 ]]; then |
| echo -e "${GREEN}Successfully downloaded $model_name${NC}" |
| else |
| echo -e "${RED}Failed to download $model_name${NC}" |
| fi |
| } |
|
|
| |
| install_dvine_model() { |
| echo -e "${BLUE}Installing recommended model: ${MODEL_NAME}...${NC}" |
| log "INFO" "Starting dvine model installation" |
| download_civitai_model "$MODEL_NAME" "$MODEL_VERSION_ID" |
| } |
|
|
| |
| install_bornnoob_models() { |
| echo -e "${BLUE}Select models to install from BoRnNo0b's collection:${NC}" |
|
|
| local model_names=() |
| for name in "${!BORNNOOB_MODELS[@]}"; do |
| model_names+=("$name") |
| done |
|
|
| |
| local selected |
| selected=$(gum choose --no-limit --header "Use space to select, enter to confirm" "${model_names[@]}") |
|
|
| if [[ -z "$selected" ]]; then |
| echo -e "${YELLOW}No models selected.${NC}" |
| return |
| fi |
|
|
| |
| echo "$selected" | while read -r model; do |
| local version_id="${BORNNOOB_MODELS[$model]}" |
| if [[ "$version_id" == "CHANGE_ME" ]]; then |
| echo -e "${RED}Version ID for '$model' is not set. Please edit the script and add the correct version ID from CivitAI.${NC}" |
| log "WARNING" "Skipping $model: version ID not set" |
| continue |
| fi |
| download_civitai_model "$model" "$version_id" |
| done |
| } |
|
|
| |
| install_repo() { |
| local repo_url="$1" |
| local repo_dir="$2" |
| local req_file="$3" |
| local upgrade_flag="${4:-}" |
|
|
| clone_repo "$repo_url" "$repo_dir" |
| create_venv "$repo_dir" |
| |
| configure_webui_user_sh "$repo_dir" |
| install_pytorch "$repo_dir" |
| install_requirements "$repo_dir" "$req_file" "$upgrade_flag" |
| apply_best_tips "$repo_dir" |
| echo -e "${GREEN}Installation complete for ${repo_dir}.${NC}" |
| log "INFO" "Installation complete for $repo_dir" |
|
|
| if [[ -z "${NON_INTERACTIVE:-}" ]]; then |
| if gum confirm "Would you like to install the recommended 'dvine' model now?"; then |
| install_dvine_model |
| fi |
| fi |
| } |
|
|
| install_comfy() { |
| mkdir -p "$DIR_COMFY" |
| clone_repo "$REPO_COMFY" "$DIR_COMFY" |
| create_venv "$DIR_COMFY" |
| install_pytorch "$DIR_COMFY" |
|
|
| local manager_dir="${DIR_COMFY}/custom_nodes/ComfyUI-Manager" |
| if [[ -d "$manager_dir" ]]; then |
| verify_repo_url "$REPO_COMFY_MANAGER" "$manager_dir" |
| echo -e "${YELLOW}ComfyUI-Manager already exists, pulling updates...${NC}" |
| log "INFO" "Pulling ComfyUI-Manager updates" |
| (cd "$manager_dir" && git pull) |
| else |
| echo -e "${GREEN}Cloning ComfyUI-Manager...${NC}" |
| log "INFO" "Cloning ComfyUI-Manager" |
| git clone "$REPO_COMFY_MANAGER" "$manager_dir" |
| fi |
|
|
| if [[ -f "${manager_dir}/requirements.txt" ]]; then |
| echo -e "${GREEN}Installing ComfyUI-Manager dependencies...${NC}" |
| log "INFO" "Installing ComfyUI-Manager requirements" |
| (cd "$DIR_COMFY" && source "${VENV_DIR}/bin/activate" && pip install -r "${manager_dir}/requirements.txt") |
| fi |
|
|
| if [[ -f "$DIR_COMFY/extra_model_paths.yaml" ]]; then |
| sed -i '/base_path:/c\ base_path: /usr/local/share/comfyui' "$DIR_COMFY/extra_model_paths.yaml" 2>/dev/null || true |
| fi |
|
|
| apply_comfy_tips "$DIR_COMFY" |
| echo -e "${GREEN}ComfyUI installation complete.${NC}" |
| log "INFO" "ComfyUI installation complete" |
|
|
| if [[ -z "${NON_INTERACTIVE:-}" ]]; then |
| if gum confirm "Would you like to install the recommended 'dvine' model now?"; then |
| install_dvine_model |
| fi |
| fi |
| } |
|
|
| auto_configure() { |
| local repo_dir="$1" |
| if [[ ! -d "$repo_dir" ]]; then |
| echo -e "${RED}Repository ${repo_dir} not found.${NC}" |
| log "ERROR" "auto_configure: $repo_dir not found" |
| return 1 |
| fi |
| detect_gpu |
| |
| if [[ "$repo_dir" != "$DIR_COMFY" ]]; then |
| configure_webui_user_sh "$repo_dir" |
| fi |
| install_pytorch "$repo_dir" |
| if [[ "$repo_dir" == "$DIR_COMFY" ]]; then |
| apply_comfy_tips "$repo_dir" |
| if [[ "$GPU_TYPE" == "nvidia" ]]; then |
| echo -e "${GREEN}NVIDIA GPU with ${GPU_VRAM}MB VRAM${NC}" |
| elif [[ "$GPU_TYPE" == "amd" ]]; then |
| echo -e "${GREEN}AMD GPU with ${GPU_GFX} architecture${NC}" |
| fi |
| else |
| apply_best_tips "$repo_dir" |
| fi |
| echo -e "${GREEN}Hardware configuration complete for ${repo_dir}.${NC}" |
| log "INFO" "Hardware configuration complete for $repo_dir" |
| } |
|
|
| |
| main_menu() { |
| local temp=$(get_gpu_temp) |
| local header="Stable Diffusion WebUI Manager v1.1.6 | Attention: $ATTENTION_MODE | GPU Temp: $temp" |
| local choice |
| choice=$(gum choose --header "$header" \ |
| "Install AUTOMATIC1111 WebUI" \ |
| "Install Forge WebUI" \ |
| "Install ComfyUI + Manager" \ |
| "Install BOTH A1111 & Forge (with model linking)" \ |
| "Link A1111/Forge models (symlinks)" \ |
| "Link models to ComfyUI (extra_model_paths.yaml)" \ |
| "Set up unified model hub (central storage)" \ |
| "Install recommended model (dvine)" \ |
| "Install multiple models from BoRnNo0b" \ |
| "Auto-optimize configs for installed UIs" \ |
| "Fix Paths (repair broken links/configs)" \ |
| "Toggle attention mechanism (xformers ↔ sdp-no-mem)" \ |
| "Hardware status dashboard" \ |
| "Start thermal monitor (background)" \ |
| "Stop thermal monitor" \ |
| "Check for dependency conflicts" \ |
| "Update all dependencies (pip upgrade)" \ |
| "Reset venv for AUTOMATIC1111" \ |
| "Reset venv for Forge" \ |
| "Reset venv for ComfyUI" \ |
| "Install/Reinstall PyTorch (choose GPU type)" \ |
| "Install system dependencies" \ |
| "Exit") |
|
|
| case "$choice" in |
| "Install AUTOMATIC1111 WebUI") |
| pre_install_checks |
| detect_gpu |
| install_repo "$REPO_A1111" "$DIR_A1111" "$REQUIREMENTS_FILE" |
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| pip cache purge || true |
| fi |
| ;; |
| "Install Forge WebUI") |
| pre_install_checks |
| detect_gpu |
| install_repo "$REPO_FORGE" "$DIR_FORGE" "$REQUIREMENTS_FILE" |
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| pip cache purge || true |
| fi |
| ;; |
| "Install ComfyUI + Manager") |
| pre_install_checks |
| detect_gpu |
| install_comfy |
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| pip cache purge || true |
| fi |
| if gum confirm "Do you want to link models from an existing A1111/Forge installation?"; then |
| source_ui=$(gum choose "A1111" "Forge") |
| generate_extra_model_paths "$DIR_COMFY" "$source_ui" |
| fi |
| ;; |
| "Install BOTH A1111 & Forge (with model linking)") |
| pre_install_checks |
| detect_gpu |
| install_repo "$REPO_A1111" "$DIR_A1111" "$REQUIREMENTS_FILE" |
| install_repo "$REPO_FORGE" "$DIR_FORGE" "$REQUIREMENTS_FILE" |
| master=$(gum choose --header "Choose the master directory (where models will be physically stored)" "AUTOMATIC1111" "Forge") |
| if [[ "$master" == "AUTOMATIC1111" ]]; then |
| link_models "$DIR_A1111" "$DIR_FORGE" |
| else |
| link_models "$DIR_FORGE" "$DIR_A1111" |
| fi |
| gum toast --title "Success" "Both WebUIs installed and models linked!" |
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| pip cache purge || true |
| fi |
| ;; |
| "Link A1111/Forge models (symlinks)") |
| master=$(gum choose --header "Choose the MASTER directory (where your models are actually stored)" "AUTOMATIC1111" "Forge") |
| if [[ "$master" == "AUTOMATIC1111" ]]; then |
| link_models "$DIR_A1111" "$DIR_FORGE" |
| else |
| link_models "$DIR_FORGE" "$DIR_A1111" |
| fi |
| gum toast --status success "Models linked successfully!" |
| ;; |
| "Link models to ComfyUI (extra_model_paths.yaml)") |
| if [[ ! -d "$DIR_COMFY" ]]; then |
| gum style --foreground 196 "ComfyUI not installed. Please install it first." |
| else |
| source_ui=$(gum choose "A1111" "Forge") |
| generate_extra_model_paths "$DIR_COMFY" "$source_ui" |
| fi |
| ;; |
| "Set up unified model hub (central storage)") |
| setup_central_models |
| gum toast --status success "Unified model hub configured!" |
| ;; |
| "Install recommended model (dvine)") |
| install_dvine_model |
| gum toast --status success "Model installation attempted!" |
| ;; |
| "Install multiple models from BoRnNo0b") |
| install_bornnoob_models |
| gum toast --status success "Batch installation attempted!" |
| ;; |
| "Auto-optimize configs for installed UIs") |
| detect_gpu |
| for dir in "$DIR_A1111" "$DIR_FORGE" "$DIR_COMFY"; do |
| if [[ -d "$dir" ]]; then |
| if [[ "$dir" == "$DIR_COMFY" ]]; then |
| apply_comfy_tips "$dir" |
| else |
| |
| configure_webui_user_sh "$dir" |
| apply_best_tips "$dir" |
| fi |
| fi |
| done |
| gum toast --status success "Optimal settings applied based on your $GPU_TYPE GPU!" |
| ;; |
| "Fix Paths (repair broken links/configs)") |
| fix_paths |
| gum toast --status success "Paths fixed!" |
| ;; |
| "Toggle attention mechanism (xformers ↔ sdp-no-mem)") |
| toggle_attention |
| ;; |
| "Hardware status dashboard") |
| show_hardware_dashboard |
| gum confirm "Press Enter to continue" |
| ;; |
| "Start thermal monitor (background)") |
| start_thermal_monitor |
| ;; |
| "Stop thermal monitor") |
| stop_thermal_monitor |
| ;; |
| "Check for dependency conflicts") |
| check_dependency_conflicts |
| gum confirm "Press Enter to continue" |
| ;; |
| "Update all dependencies (pip upgrade)") |
| update_all_deps |
| gum toast --status success "Dependencies updated!" |
| ;; |
| "Reset venv for AUTOMATIC1111") |
| reset_venv "$DIR_A1111" |
| |
| if gum confirm "Do you want to reinstall PyTorch now?"; then |
| install_pytorch "$DIR_A1111" |
| fi |
| ;; |
| "Reset venv for Forge") |
| reset_venv "$DIR_FORGE" |
| if gum confirm "Do you want to reinstall PyTorch now?"; then |
| install_pytorch "$DIR_FORGE" |
| fi |
| ;; |
| "Reset venv for ComfyUI") |
| reset_venv "$DIR_COMFY" |
| if gum confirm "Do you want to reinstall PyTorch now?"; then |
| install_pytorch "$DIR_COMFY" |
| fi |
| ;; |
| "Install/Reinstall PyTorch (choose GPU type)") |
| dir_choice=$(gum choose "AUTOMATIC1111" "Forge" "ComfyUI") |
| case "$dir_choice" in |
| "AUTOMATIC1111") target="$DIR_A1111" ;; |
| "Forge") target="$DIR_FORGE" ;; |
| "ComfyUI") target="$DIR_COMFY" ;; |
| esac |
|
|
| gpu_choice=$(gum choose "Auto-detect (recommended)" "NVIDIA (CUDA)" "AMD (ROCm)" "Intel (IPEX)" "CPU only") |
| case "$gpu_choice" in |
| "Auto-detect (recommended)") |
| detect_gpu |
| install_pytorch "$target" |
| ;; |
| "NVIDIA (CUDA)") |
| install_pytorch "$target" "nvidia" |
| ;; |
| "AMD (ROCm)") |
| install_pytorch "$target" "amd" |
| ;; |
| "Intel (IPEX)") |
| install_pytorch "$target" "intel" |
| ;; |
| "CPU only") |
| install_pytorch "$target" "cpu" |
| ;; |
| esac |
| ;; |
| "Install system dependencies") |
| pre_install_checks |
| install_system_deps |
| ;; |
| "Exit") |
| stop_thermal_monitor |
| echo "Goodbye!" |
| log "INFO" "Script terminated by user" |
| exit 0 |
| ;; |
| *) |
| echo -e "${RED}Invalid option.${NC}" |
| ;; |
| esac |
| } |
|
|
| |
| handle_non_interactive() { |
| local upgrade_flag="--upgrade" |
| pre_install_checks |
| case "${NON_INTERACTIVE:-}" in |
| "a1111") |
| detect_gpu |
| install_repo "$REPO_A1111" "$DIR_A1111" "$REQUIREMENTS_FILE" "$upgrade_flag" |
| ;; |
| "forge") |
| detect_gpu |
| install_repo "$REPO_FORGE" "$DIR_FORGE" "$REQUIREMENTS_FILE" "$upgrade_flag" |
| ;; |
| "comfy") |
| detect_gpu |
| install_comfy |
| if [[ -d "$DIR_A1111" ]]; then |
| generate_extra_model_paths "$DIR_COMFY" "A1111" |
| elif [[ -d "$DIR_FORGE" ]]; then |
| generate_extra_model_paths "$DIR_COMFY" "Forge" |
| fi |
| ;; |
| "both") |
| detect_gpu |
| install_repo "$REPO_A1111" "$DIR_A1111" "$REQUIREMENTS_FILE" "$upgrade_flag" |
| install_repo "$REPO_FORGE" "$DIR_FORGE" "$REQUIREMENTS_FILE" "$upgrade_flag" |
| link_models "$DIR_A1111" "$DIR_FORGE" |
| ;; |
| "dvine") |
| install_dvine_model |
| ;; |
| "bornnoob") |
| for name in "${!BORNNOOB_MODELS[@]}"; do |
| local version_id="${BORNNOOB_MODELS[$name]}" |
| if [[ "$version_id" != "CHANGE_ME" ]]; then |
| download_civitai_model "$name" "$version_id" |
| fi |
| done |
| ;; |
| "configure") |
| detect_gpu |
| for dir in "$DIR_A1111" "$DIR_FORGE" "$DIR_COMFY"; do |
| if [[ -d "$dir" ]]; then |
| auto_configure "$dir" |
| fi |
| done |
| ;; |
| esac |
|
|
| pip cache purge || true |
|
|
| if [[ ! -d "$DIR_A1111" && ! -d "$DIR_FORGE" ]]; then |
| echo "Cleaning up unused repositories..." |
| if [[ -d "$REPO_A1111" ]]; then |
| rm -rf "$REPO_A1111" |
| fi |
| if [[ -d "$REPO_FORGE" ]]; then |
| rm -rf "$REPO_FORGE" |
| fi |
| fi |
|
|
| if [[ ! -d "$DIR_COMFY" ]]; then |
| if [[ -d "$REPO_COMFY_MANAGER" ]]; then |
| rm -rf "$REPO_COMFY_MANAGER" |
| fi |
| fi |
| } |
|
|
| |
| check_gum |
| detect_python |
|
|
| |
| if [[ -n "${NON_INTERACTIVE:-}" ]]; then |
| handle_non_interactive |
| else |
| while true; do |
| main_menu |
| echo |
| gum confirm "Return to main menu?" --affirmative="Yes" --negative="Exit" || break |
| done |
| fi |
|
|
| log "INFO" "Script finished successfully" |
|
|