first commit

This commit is contained in:
Leonid Pershin
2026-08-21 02:42:48 +03:00
commit 167d07a733
46 changed files with 3334 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
"""Load ~/.gpu-rent/.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 (
default_ssh_key_path,
detect_app_root,
env_path,
home_dir,
)
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 _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
default_flavor_id: str
flavor_preference: tuple[str, ...]
flavor_fallback: bool
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:
home_dir().mkdir(parents=True, exist_ok=True)
env_file = env_path()
if env_file.is_file():
load_dotenv(env_file, override=False)
app_root = detect_app_root()
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(
"В ~/.gpu-rent/.env не хватает: "
+ ", ".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 (home_dir() / "models.yaml")
).expanduser()
extensions_manifest = Path(
(os.environ.get("EXTENSIONS_MANIFEST") or "").strip()
or (home_dir() / "extensions.yaml")
).expanduser()
def _dir(env_name: str, folder: str) -> Path:
raw = (os.environ.get(env_name) or "").strip()
return Path(raw).expanduser() if raw else (app_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=app_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(),
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),
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,
)