Add package data for GPU rent and update CLI documentation

- Added package data configuration for the 'gpu_rent' package in pyproject.toml.
- Updated README.md to include usage instructions for Windows and Unix launchers.
- Enhanced CLI documentation in cli.md to reflect new commands and their functionalities.
- Revised setup.md to clarify installation steps and environment setup.
- Improved error handling and command descriptions in the CLI implementation.
- Added new functions for model version handling and flavor resolution in the codebase.
- Updated state management to include additional properties for better tracking.
This commit is contained in:
Leonid Pershin
2026-08-21 03:06:51 +03:00
parent 167d07a733
commit 615cf81493
34 changed files with 2733 additions and 117 deletions
+142 -59
View File
@@ -18,7 +18,12 @@ from gpu_rent.config import load_config
from gpu_rent.doctor import blocking_failed, dry_run_plan, run_doctor
from gpu_rent.errors import GpuRentError, NotReadyError
from gpu_rent.os_client import connect, find_snapshot_by_name, find_tagged_servers
from gpu_rent.session import cmd_stop, cmd_up
from gpu_rent.ssh_ops import interactive_ssh, run_ssh
from gpu_rent.state import load_state, preempt_window_end
from gpu_rent.provision import ensure_swarmui_running, seed_civitai, seed_extensions
from gpu_rent.sync_files import pull_tree, push_tree
from gpu_rent.tunnel import run_tunnel
if sys.platform == "win32":
for _stream in (sys.stdout, sys.stderr):
@@ -55,10 +60,8 @@ def _die(exc: BaseException) -> None:
def _nyi(name: str) -> None:
raise NotReadyError(
f"`{name}` ещё не создаёт/не гасит GPU. Сейчас работают: doctor, dry-run, status, open.\n"
"1) Заполни ~/.gpu-rent/.env по docs/setup.md\n"
"2) gpu-rent doctor\n"
"3) Когда doctor зелёный и квота GPU > 0 — можно писать up."
f"`{name}` ещё не готов. Уже работают: doctor, dry-run, status, open, up, stop, destroy, ssh, logs, tunnel, seed-*, push, pull-output.\n"
"Ключи: docs/setup.md"
)
@@ -77,10 +80,18 @@ def _print_checks(checks) -> int:
if failed:
console.print("\n[red]Сессию начинать нельзя.[/red] См. docs/setup.md")
return 1
console.print("\n[green]Можно идти дальше.[/green] mutating up пока не подключён.")
console.print("\n[green]Можно идти дальше.[/green] Дальше: gpu-rent up --yes")
return 0
def _live():
cfg = load_config(require_auth=True)
state = load_state()
if not state.floating_ip:
raise GpuRentError("нет floating IP — сначала gpu-rent up")
return cfg, state.floating_ip
@app.command()
def version() -> None:
"""Версия пакета."""
@@ -185,14 +196,86 @@ def up(
yes: bool = typer.Option(False, "--yes", help="Без вопросов"),
adopt: bool = typer.Option(False, "--adopt", help="Подхватить тег gpu-rent без state"),
) -> None:
"""Create/unshelve GPU. Пока: doctor, затем стоп — mutating ещё не подключён."""
del no_spot, flavor, yes, adopt
"""Create/unshelve GPU и bootstrap SwarmUI (без Docker)."""
try:
checks = run_doctor()
code = _print_checks(checks)
if code != 0:
raise typer.Exit(1)
_nyi("up")
cfg = load_config(require_auth=True)
def confirm(msg: str) -> bool:
return typer.confirm(msg)
cmd_up(
cfg,
no_spot=no_spot,
flavor=flavor,
yes=yes,
adopt=adopt,
confirm=confirm,
log=lambda m: console.print(m),
)
except GpuRentError as exc:
_die(exc)
@app.command()
def stop(
no_pull: bool = typer.Option(False, "--no-pull"),
) -> None:
"""Удалить compute и FIP, диски оставить."""
try:
cfg = load_config(require_auth=True)
cmd_stop(cfg, no_pull=no_pull, log=lambda m: console.print(m))
except GpuRentError as exc:
_die(exc)
@app.command()
def destroy(
i_understand_data_loss: bool = typer.Option(False, "--i-understand-data-loss"),
) -> None:
"""stop + диски."""
if not i_understand_data_loss:
console.print("Нужен флаг --i-understand-data-loss")
raise typer.Exit(1)
try:
cfg = load_config(require_auth=True)
cmd_stop(cfg, destroy_disks=True, log=lambda m: console.print(m))
except GpuRentError as exc:
_die(exc)
@app.command()
def ssh() -> None:
"""Оболочка на VM (нужен живой compute и FIP)."""
try:
cfg = load_config(require_auth=True)
state = load_state()
if not state.floating_ip:
raise GpuRentError("нет floating IP в state — сначала gpu-rent up")
raise typer.Exit(interactive_ssh(cfg, state.floating_ip))
except GpuRentError as exc:
_die(exc)
@app.command()
def logs() -> None:
"""cloud-init / journalctl на VM."""
try:
cfg = load_config(require_auth=True)
state = load_state()
if not state.floating_ip:
raise GpuRentError("нет IP — VM не поднята")
out = run_ssh(
cfg,
state.floating_ip,
"sudo -n tail -n 80 /var/log/cloud-init-output.log 2>/dev/null; "
"systemctl is-active swarmui 2>/dev/null || true",
check=False,
)
console.print(out)
except GpuRentError as exc:
_die(exc)
@@ -202,9 +285,17 @@ def tunnel(
open_browser: bool = typer.Option(False, "--open", help="Открыть браузер на 17801"),
) -> None:
"""SSH localhost:17801 -> VM :7801. Ctrl+C закрывает туннель, GPU оставляет."""
del open_browser
try:
_nyi("tunnel")
cfg = load_config(require_auth=True)
state = load_state()
if not state.floating_ip:
raise GpuRentError("нет floating IP — сначала gpu-rent up")
run_tunnel(
cfg,
state.floating_ip,
open_browser=open_browser,
log=lambda m: console.print(m),
)
except GpuRentError as exc:
_die(exc)
@@ -223,84 +314,76 @@ def hold(
_die(exc)
@app.command()
def stop(
no_pull: bool = typer.Option(False, "--no-pull"),
) -> None:
"""Удалить compute и FIP, диски оставить."""
del no_pull
try:
_nyi("stop")
except GpuRentError as exc:
_die(exc)
@app.command()
def destroy(
i_understand_data_loss: bool = typer.Option(False, "--i-understand-data-loss"),
) -> None:
"""stop + диски."""
if not i_understand_data_loss:
console.print("Нужен флаг --i-understand-data-loss")
raise typer.Exit(1)
try:
_nyi("destroy")
except GpuRentError as exc:
_die(exc)
@app.command()
def logs() -> None:
try:
_nyi("logs")
except GpuRentError as exc:
_die(exc)
@app.command()
def ssh() -> None:
try:
_nyi("ssh")
except GpuRentError as exc:
_die(exc)
@app.command("seed-models")
def seed_models() -> None:
"""Докачать Civitai-манифест на живой диск."""
try:
_nyi("seed-models")
cfg, host = _live()
seed_civitai(cfg, host, log=lambda m: console.print(m))
except GpuRentError as exc:
_die(exc)
@app.command("push")
def push_all() -> None:
"""SFTP Models + Wildcards + CustomWorkflows."""
try:
_nyi("push")
cfg, host = _live()
def log(msg: str) -> None:
console.print(msg)
push_tree(cfg, host, cfg.local_models_dir, "/mnt/swarm_data/Models", log, models=True)
push_tree(cfg, host, cfg.local_wildcards_dir, "/mnt/swarm_data/Data/Wildcards", log, models=False)
push_tree(cfg, host, cfg.local_workflows_dir, "/mnt/swarm_data/CustomWorkflows", log, models=False)
except GpuRentError as exc:
_die(exc)
@app.command("push-models")
def push_models() -> None:
"""SFTP только ./Models."""
try:
_nyi("push-models")
cfg, host = _live()
push_tree(
cfg,
host,
cfg.local_models_dir,
"/mnt/swarm_data/Models",
lambda m: console.print(m),
models=True,
)
except GpuRentError as exc:
_die(exc)
@app.command("pull-output")
def pull_output() -> None:
def pull_output_cmd() -> None:
"""Забрать новые файлы Output/ с VM."""
try:
_nyi("pull-output")
cfg, host = _live()
pull_tree(
cfg,
host,
"/mnt/swarm_data/Output",
cfg.local_output_dir,
lambda m: console.print(m),
)
except GpuRentError as exc:
_die(exc)
@app.command("seed-extensions")
def seed_extensions() -> None:
def seed_extensions_cmd() -> None:
"""Clone/fetch extensions.yaml, затем restart swarmui."""
try:
_nyi("seed-extensions")
cfg, host = _live()
def log(msg: str) -> None:
console.print(msg)
seed_extensions(cfg, host, log)
ensure_swarmui_running(cfg, host, log, restart=True)
except GpuRentError as exc:
_die(exc)