diff --git a/docs/cli.md b/docs/cli.md index 2cc2d9a..b5764d7 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -35,7 +35,7 @@ Нет команды `generate`. Нет зеркала каталога локального SwarmUI — только папки приложения, см. [local-folders.md](local-folders.md). -Сейчас в коде: `up` создаёт/unshelve compute, FIP, SSH, идемпотентный bootstrap SwarmUI (без Docker) и systemd. `tunnel` на 17801, Ctrl+C не гасит GPU. Ещё нет: extensions, autocomplete, Civitai seed, push, idle-killer, snapshot, EXPIRED-reconnect туннеля. +Сейчас в коде: `up` создаёт compute, идемпотентный bootstrap (без Docker), **затем** git-расширения, autocomplete, Civitai-seed и push локальных папок, и только потом `systemctl start swarmui`. `tunnel` на 17801. Ещё нет: idle-killer, snapshot, hold, EXPIRED-reconnect туннеля. ## `doctor` diff --git a/docs/roadmap.md b/docs/roadmap.md index b0ef1cb..d742adc 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -54,19 +54,19 @@ - [x] Второй `up` не создаёт второй GPU - [ ] `resize-data` вверх - [x] Генерация `~/.gpu-rent/id_ed25519` + keypair при первом `up` -- [ ] `seed-models` на живом диске (идемпотентно, SHA256) -- [ ] `push` / `push-models`: `Models/`, `Wildcards/`, `CustomWorkflows/` -- [ ] `pull-output` и `PULL_OUTPUT` на `stop` / повторный `up` -- [ ] `seed-extensions` + `systemctl restart swarmui` +- [x] `seed-models` на живом диске (идемпотентно, SHA256) +- [x] `push` / `push-models`: `Models/`, `Wildcards/`, `CustomWorkflows/` +- [x] `pull-output` и `PULL_OUTPUT` на `stop` / повторный `up` +- [x] `seed-extensions` + `systemctl restart swarmui` - [x] `destroy` только с флагом ## 4. Bootstrap SwarmUI + idle-killer - [x] Идемпотентный first-boot, маркер FS (`/mnt/swarm_data/.gpu-rent-ready`) - [x] Bind-mounts data volume → `/opt/swarmui`, systemd `swarmui` (auth SwarmUI ещё на spike) -- [ ] Clone `extensions.yaml` в Extensions / DLNodes до старта UI -- [ ] Autocomplete: danbooru.csv + Settings.fds; на каждом up — GitHub sha -- [ ] Civitai seed по манифесту; без токена — дефолт SwarmUI +- [x] Clone `extensions.yaml` в Extensions / DLNodes до старта UI +- [x] Autocomplete: danbooru.csv + Settings.fds; на каждом up — GitHub sha +- [x] Civitai seed по манифесту; без токена — дефолт SwarmUI - [ ] systemd idle-killer + application credential; льгота 45 мин; hold-файл; качалка в UI = busy; затем 30 мин пустой очереди; не вооружать до конца seed - [ ] Один snapshot `gpu-rent-boot-ok` после первого backend Idle; следующие create — из snapshot если есть - [ ] `gpu-rent hold` / `hold --clear` по SSH diff --git a/src/gpu_rent/remote/clone_ext.py b/src/gpu_rent/remote/clone_ext.py index 7e8018b..de06373 100644 --- a/src/gpu_rent/remote/clone_ext.py +++ b/src/gpu_rent/remote/clone_ext.py @@ -3,7 +3,6 @@ from __future__ import annotations import json -import os import subprocess import sys from pathlib import Path diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index e96b587..910eed0 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -10,6 +10,8 @@ def test_bootstrap_script_is_native_swarmui(): assert "/mnt/swarm_data" in script assert "launch-linux.sh --launch_mode none --host 127.0.0.1 --port 7801" in script assert "systemctl enable swarmui" in script + assert "systemctl restart swarmui" not in script + assert "Data/Autocompletions" in script assert "mkfs.ext4" in script assert "apt-get upgrade" not in script assert ".gpu-rent-ready" in script diff --git a/tests/test_seed.py b/tests/test_seed.py new file mode 100644 index 0000000..cd2d0f6 --- /dev/null +++ b/tests/test_seed.py @@ -0,0 +1,59 @@ +from pathlib import Path + +from gpu_rent.civitai import pick_primary_file +from gpu_rent.manifests import extract_version_id, repo_dirname, parse_models +from gpu_rent.payload import has_payload, model_push_set +from gpu_rent.remote.clone_ext import is_sha, strip_auth, with_token + + +def test_extract_version_id(): + assert extract_version_id("https://civitai.red/models/1?modelVersionId=9") == 9 + assert extract_version_id("https://civitai.com/api/download/models/77") == 77 + + +def test_parse_models_fills_id_from_url(tmp_path: Path): + path = tmp_path / "m.yaml" + path.write_text("lora:\n - url: https://civitai.red/models/1?modelVersionId=42\n", encoding="utf-8") + entries = parse_models(path) + assert entries[0].version_id == 42 + + +def test_repo_dirname(): + from gpu_rent.manifests import GitRepo + + repo = GitRepo(kind="swarmui", url="https://github.com/org/SwarmUI-Foo.git", ref="main", directory=None) + assert repo_dirname(repo) == "SwarmUI-Foo" + + +def test_pick_primary_file(): + version = { + "files": [ + {"name": "a.ckpt", "primary": False}, + {"name": "b.safetensors", "primary": True}, + ] + } + assert pick_primary_file(version)["name"] == "b.safetensors" + + +def test_payload_pairs_sidecar_skips_orphan(tmp_path: Path): + root = tmp_path / "Models" / "Lora" + root.mkdir(parents=True) + (root / ".gitkeep").write_text("x", encoding="utf-8") + (root / "README.md").write_text("x", encoding="utf-8") + (root / "foo.safetensors").write_bytes(b"weights") + (root / "foo.civitai.json").write_text("{}", encoding="utf-8") + (root / "orphan.json").write_text("{}", encoding="utf-8") + names = {p.name for p in model_push_set(tmp_path / "Models")} + assert names == {"foo.safetensors", "foo.civitai.json"} + empty = tmp_path / "Wildcards" + empty.mkdir() + (empty / ".gitkeep").write_text("", encoding="utf-8") + assert not has_payload(empty) + + +def test_git_token_injection(): + url = "https://github.com/org/Ext.git" + assert "x-access-token:secret@" in with_token(url, "secret") + assert strip_auth(with_token(url, "secret")) == url + assert is_sha("a" * 40) + assert not is_sha("main")