Files
gpu-rent/src/gpu_rent/config.py
T
Leonid Pershin 7ed6a99df2 Enhance LLM and SwarmUI integration with improved configuration options
- Updated `env.example` and `gpu-rent.vars.example` to include new variables for LLM runtime and SwarmUI options.
- Refactored CLI commands to support interactive selection of LLM runtime and workload type (SwarmUI, LLM, or both).
- Improved access link generation to handle cases where SwarmUI is disabled, providing clearer user feedback.
- Enhanced provisioning logic to conditionally bootstrap SwarmUI based on user configuration, allowing for LLM-only setups.
- Updated documentation across multiple files to reflect changes in LLM integration, CLI usage, and configuration management.
2026-08-21 06:44:50 +03:00

250 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Load <project>/.env. No secrets in git."""
from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
from dotenv import load_dotenv
from gpu_rent.errors import ConfigError
from gpu_rent.paths import (
app_root,
default_ssh_key_path,
env_path,
extensions_manifest_path,
llamacpp_models_manifest_path,
migrate_legacy_if_needed,
models_manifest_path,
ollama_models_manifest_path,
runtime_dir,
vars_path,
)
from gpu_rent.varsfile import apply_vars_file
from gpu_rent.llm_runtime import normalize_runtime
def _as_bool(value: str | None, default: bool) -> bool:
if value is None or value.strip() == "":
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
def parse_enable_swarmui(raw: str | None, *, default: bool = True) -> bool:
"""ENABLE_SWARMUI / WORKLOAD=llm|swarm|both → whether to install SwarmUI."""
wl = (os.environ.get("WORKLOAD") or "").strip().lower()
if wl in {"llm", "llm-only", "llm_only", "llama", "ollama-only"}:
return False
if wl in {"swarm", "swarmui", "ui"}:
return True
if wl in {"both", "all", "full"}:
return True
if raw is None or str(raw).strip() == "":
return default
return _as_bool(str(raw), default)
def _as_int(value: str | None, default: int) -> int:
if value is None or value.strip() == "":
return default
return int(value)
def _csv(value: str | None, default: tuple[str, ...]) -> tuple[str, ...]:
if value is None or value.strip() == "":
return default
return tuple(part.strip() for part in value.split(",") if part.strip())
@dataclass
class Config:
os_auth_url: str
os_user_domain_name: str
os_username: str
os_password: str
os_project_id: str
os_region_name: str
gpu_rent_az: str
ssh_private_key_path: Path
ssh_user: str
boot_volume_id: str
data_volume_id: str
data_volume_size_gb: int
boot_snapshot_name: str
civitai_api_token: str
civitai_api_host: str
models_manifest: Path
extensions_manifest: Path
git_token: str
local_models_dir: Path
local_wildcards_dir: Path
local_workflows_dir: Path
local_output_dir: Path
app_root: Path
autocomplete_enabled: bool
autocomplete_github_repo: str
autocomplete_github_path: str
autocomplete_github_ref: str
autocomplete_filename: str
swarmui_local_port: int
swarmui_image: str
update_git: bool
llm_runtime: str
enable_swarmui: bool
ollama_models_manifest: Path
llamacpp_models_manifest: Path
ollama_local_port: int
llamacpp_local_port: int
default_flavor_id: str
flavor_preference: tuple[str, ...]
flavor_fallback: bool
scan_pools: str
default_spot: bool
keep_floating_ip: bool
idle_minutes: int
idle_grace_minutes: int
pull_output: bool
notify_ready: bool
missing: list[str] = field(default_factory=list)
@property
def auth_ok(self) -> bool:
return not self.missing
def _required(name: str) -> str:
return (os.environ.get(name) or "").strip()
def load_config(*, require_auth: bool = True) -> Config:
root = app_root()
runtime_dir().mkdir(parents=True, exist_ok=True)
migrate_legacy_if_needed()
env_file = env_path()
if env_file.is_file():
load_dotenv(env_file, override=False)
# Non-secret launch defaults (gpu-rent.vars). Do not override .env / real env.
apply_vars_file(vars_path(), override=False)
missing: list[str] = []
required = (
"OS_AUTH_URL",
"OS_USER_DOMAIN_NAME",
"OS_USERNAME",
"OS_PASSWORD",
"OS_PROJECT_ID",
"OS_REGION_NAME",
"GPU_RENT_AZ",
)
values = {name: _required(name) for name in required}
for name, value in values.items():
if not value:
missing.append(name)
if require_auth and missing:
raise ConfigError(
f"В {env_file} не хватает: "
+ ", ".join(missing)
+ ". Как заполнить: docs/setup.md (сервисный пользователь, не X-Token)."
)
key_override = (os.environ.get("SSH_PRIVATE_KEY_PATH") or "").strip()
ssh_key = Path(key_override).expanduser() if key_override else default_ssh_key_path()
models_manifest = Path(
(os.environ.get("MODELS_MANIFEST") or "").strip() or str(models_manifest_path())
).expanduser()
extensions_manifest = Path(
(os.environ.get("EXTENSIONS_MANIFEST") or "").strip()
or str(extensions_manifest_path())
).expanduser()
ollama_manifest = Path(
(os.environ.get("OLLAMA_MODELS_MANIFEST") or "").strip()
or str(ollama_models_manifest_path())
).expanduser()
llamacpp_manifest = Path(
(os.environ.get("LLAMACPP_MODELS_MANIFEST") or "").strip()
or str(llamacpp_models_manifest_path())
).expanduser()
try:
llm_runtime = normalize_runtime(os.environ.get("LLM_RUNTIME"))
except ValueError:
llm_runtime = "none"
enable_swarmui = parse_enable_swarmui(os.environ.get("ENABLE_SWARMUI"), default=True)
def _dir(env_name: str, folder: str) -> Path:
raw = (os.environ.get(env_name) or "").strip()
return Path(raw).expanduser() if raw else (root / folder)
return Config(
os_auth_url=values["OS_AUTH_URL"] or "https://cloud.api.selcloud.ru/identity/v3",
os_user_domain_name=values["OS_USER_DOMAIN_NAME"],
os_username=values["OS_USERNAME"],
os_password=values["OS_PASSWORD"],
os_project_id=values["OS_PROJECT_ID"],
os_region_name=values["OS_REGION_NAME"],
gpu_rent_az=values["GPU_RENT_AZ"],
ssh_private_key_path=ssh_key,
ssh_user=(os.environ.get("SSH_USER") or "ubuntu").strip(),
boot_volume_id=(os.environ.get("BOOT_VOLUME_ID") or "").strip(),
data_volume_id=(os.environ.get("DATA_VOLUME_ID") or "").strip(),
data_volume_size_gb=_as_int(os.environ.get("DATA_VOLUME_SIZE_GB"), 100),
boot_snapshot_name=(os.environ.get("BOOT_SNAPSHOT_NAME") or "gpu-rent-boot-ok").strip(),
civitai_api_token=(os.environ.get("CIVITAI_API_TOKEN") or "").strip(),
civitai_api_host=(os.environ.get("CIVITAI_API_HOST") or "civitai.red").strip().lower(),
models_manifest=models_manifest,
extensions_manifest=extensions_manifest,
git_token=(os.environ.get("GIT_TOKEN") or "").strip(),
local_models_dir=_dir("LOCAL_MODELS_DIR", "Models"),
local_wildcards_dir=_dir("LOCAL_WILDCARDS_DIR", "Wildcards"),
local_workflows_dir=_dir("LOCAL_WORKFLOWS_DIR", "CustomWorkflows"),
local_output_dir=_dir("LOCAL_OUTPUT_DIR", "Output"),
app_root=root,
autocomplete_enabled=_as_bool(os.environ.get("AUTOCOMPLETE_ENABLED"), True),
autocomplete_github_repo=(
os.environ.get("AUTOCOMPLETE_GITHUB_REPO") or "DominikDoom/a1111-sd-webui-tagcomplete"
).strip(),
autocomplete_github_path=(
os.environ.get("AUTOCOMPLETE_GITHUB_PATH") or "tags/danbooru.csv"
).strip(),
autocomplete_github_ref=(os.environ.get("AUTOCOMPLETE_GITHUB_REF") or "main").strip(),
autocomplete_filename=(os.environ.get("AUTOCOMPLETE_FILENAME") or "danbooru.csv").strip(),
swarmui_local_port=_as_int(os.environ.get("SWARMUI_LOCAL_PORT"), 17801),
swarmui_image=(os.environ.get("SWARMUI_IMAGE") or "").strip(),
update_git=_as_bool(os.environ.get("UPDATE_GIT"), True),
llm_runtime=llm_runtime,
enable_swarmui=enable_swarmui,
ollama_models_manifest=ollama_manifest,
llamacpp_models_manifest=llamacpp_manifest,
ollama_local_port=_as_int(os.environ.get("OLLAMA_LOCAL_PORT"), 17811),
llamacpp_local_port=_as_int(os.environ.get("LLAMACPP_LOCAL_PORT"), 17812),
default_flavor_id=(os.environ.get("DEFAULT_FLAVOR_ID") or "").strip(),
flavor_preference=_csv(
os.environ.get("FLAVOR_PREFERENCE"),
("4090-24", "4090-48", "a5000", "a100-40"),
),
flavor_fallback=_as_bool(os.environ.get("FLAVOR_FALLBACK"), True),
scan_pools=(os.environ.get("SCAN_POOLS") or "ru-6,ru-7").strip(),
default_spot=_as_bool(os.environ.get("DEFAULT_SPOT"), True),
keep_floating_ip=_as_bool(os.environ.get("KEEP_FLOATING_IP"), False),
idle_minutes=_as_int(os.environ.get("IDLE_MINUTES"), 30),
idle_grace_minutes=_as_int(os.environ.get("IDLE_GRACE_MINUTES"), 45),
pull_output=_as_bool(os.environ.get("PULL_OUTPUT"), False),
notify_ready=_as_bool(os.environ.get("NOTIFY_READY"), True),
missing=missing,
)