Bump version to 0.2.0 and enhance documentation

- Updated version number in pyproject.toml and __init__.py to 0.2.0.
- Revised README.md to reflect the current state of the project, including usage instructions and setup steps.
- Improved CLI documentation in cli.md, adding details about new commands and their functionalities.
- Enhanced the quick start section in README.md for better clarity on initial setup.
- Updated local folder documentation to clarify file handling and commands.
- Added a new command for listing GPU flavors and improved error handling in the CLI.
- Implemented a watchdog feature in the tunnel to manage server states effectively.
This commit is contained in:
Leonid Pershin
2026-08-21 03:38:01 +03:00
parent 343f741baa
commit a563ae06c4
30 changed files with 1750 additions and 132 deletions
+2 -1
View File
@@ -2,13 +2,14 @@
CLI-оркестратор: поднимает прерываемый GPU-сервер в Selectel, монтирует постоянные сетевые диски и открывает SwarmUI на `localhost` через SSH-туннель. Платим за GPU только пока идёт сессия; модели и ComfyUI живут на диске.
Код в разработке: каркас CLI и `doctor` есть. GPU всё ещё не создаём, пока нет квоты и зелёного `doctor`. Что сделать руками до этого [setup.md](setup.md).
**Код v1 готов.** Живой `up` ждёт квоту GPU и зелёный `doctor`. Старт руками: [setup.md](setup.md), прогон: [spike-notes.md](spike-notes.md).
## Как читать
| Документ | Зачем |
| --- | --- |
| [setup.md](setup.md) | **С чего начать:** квота GPU, сервисный пользователь, Civitai, установка CLI |
| [spike-notes.md](spike-notes.md) | Чеклист первого живого прогона |
| [concept.md](concept.md) | Задача, границы, модель стоимости |
| [decisions.md](decisions.md) | Зафиксированные решения |
| [architecture.md](architecture.md) | Компоненты, диски, стейт-машина, idle-killer |
+4 -4
View File
@@ -8,8 +8,8 @@
| Момент | Действие |
| --- | --- |
| Первый диск / файла ещё нет | Скачать с GitHub на VM в `/mnt/swarm_data/Data/Autocompletions/<filename>`. Прописать `DefaultUser.AutoComplete.Source` в `Settings.fds` **до** первого старта контейнера |
| Каждый последующий `up` (диск уже есть) | Спросить GitHub blob `sha` того же пути. Если sha изменился — скачать заново, заменить файл, перезапустить контейнер SwarmUI если он уже крутится |
| Первый диск / файла ещё нет | Скачать с GitHub на VM в `/mnt/swarm_data/Data/Autocompletions/<filename>`. Прописать `DefaultUser.AutoComplete.Source` в `Settings.fds` **до** первого старта SwarmUI |
| Каждый последующий `up` (диск уже есть) | Спросить GitHub blob `sha` того же пути. Если sha изменился — скачать заново, заменить файл, `systemctl restart swarmui` если UI уже крутится |
| `AUTOCOMPLETE_ENABLED=false` или URL пуст | Ничего не качать, стоковый SwarmUI (только синтаксис `<…>`, без booru-тегов) |
Это **не** одноразовый seed как модели: список тегов на GitHub обновляют. Проверка версии — часть каждого `up`, пока есть SSH.
@@ -35,7 +35,7 @@
/mnt/swarm_data/Data/Autocompletions/danbooru.csv.gpu-rent-meta.json
```
В контейнере: `/SwarmUI/Data/Autocompletions/danbooru.csv`.
На data volume: `/mnt/swarm_data/Data/Autocompletions/danbooru.csv` (bind → `/opt/swarmui/Data/…`).
Sidecar meta (не отдавать в Output):
@@ -66,7 +66,7 @@ GET https://api.github.com/repos/{repo}/contents/{path}?ref={ref}
## Настройки SwarmUI до первого запуска
Положить/дописать в `/mnt/swarm_data/Data/Settings.fds` (Frenetic Data Syntax), пока контейнер ещё не стартовал:
Положить/дописать в `/mnt/swarm_data/Data/Settings.fds` (Frenetic Data Syntax), пока SwarmUI ещё не стартовал:
```text
DefaultUser:
+2 -1
View File
@@ -6,6 +6,7 @@
| Команда | Поведение |
| --- | --- |
| `gpu-rent flavors` | Живой список GPU flavors по `FLAVOR_PREFERENCE` + что выберет фоллбек |
| `gpu-rent doctor` | Preflight **без** create: Keystone, квота GPU, flavor в AZ, диски, Civitai token+`.red`, манифесты, SSH-ключ. Код выхода ≠ 0, если сессию нельзя начать |
| `gpu-rent up` | Preflight → create/unshelve (с фоллбеком flavor) → extensions → autocomplete → Civitai seed → push локальных папок → ждать UI → toast/звук |
| `gpu-rent up --no-spot` | Обычный (не preemptible) сервер |
@@ -35,7 +36,7 @@
Нет команды `generate`. Нет зеркала каталога локального SwarmUI — только папки приложения, см. [local-folders.md](local-folders.md).
Сейчас в коде: `up` создаёт compute, идемпотентный bootstrap (без Docker), **затем** git-расширения, autocomplete, Civitai-seed и push локальных папок, и только потом `systemctl start swarmui`. `tunnel` на 17801. Ещё нет: idle-killer, snapshot, hold, EXPIRED-reconnect туннеля.
Сейчас в коде: после seed/`start swarmui` — idle-killer, Idle backend, boot snapshot, toast/MCP-сниппет, `resize-data`. `tunnel` с watchdog: EXPIRED → unshelve + reconnect FIP/SSH. UX-полировка flavors/цен ещё впереди.
## `doctor`
+1 -1
View File
@@ -17,7 +17,7 @@
Wildcards: `.txt` (одна строка — один вариант), как в доке SwarmUI `Data/Wildcards`.
CustomWorkflows: JSON воркфлоу Comfy/Swarm. После заливки на живой UI — refresh, если API это умеет; иначе рестарт контейнера только если без refresh файлы не видны (spike).
CustomWorkflows: JSON воркфлоу Comfy/Swarm. После заливки на живой UI — refresh, если API это умеет; иначе `systemctl restart swarmui` только если без refresh файлы не видны (spike).
Пока идёт push крупных файлов — idle-killer на паузе.
+4 -4
View File
@@ -4,10 +4,10 @@
Осталось подтвердить на **spike**, не в споре:
- Точный URL/JSON «очередь пуста», «backend Idle» и «идёт скачивание модели в UI» у твоей версии SwarmUI.
- Реальная цена 100 GB сетевого диска в выбранном сегменте и цена 1×4090 preemptible ₽/час.
- Качалка модели в UI: отдельного poll-API нет (`DoModelDownloadWS` только WS) → v1 считает busy через `waiting_gens` / `live_gens` / `loading_models` / backend≠idle; иначе пользователь жмёт `hold`.
- Реальная цена 100 GB сетевого диска в выбранном сегменте и цена 1×4090 preemptible ₽/час (в OpenStack API нет).
- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из списка фоллбека реально есть.
- Хватает ли роли application credential «только delete этого сервера», или Selectel отдаёт более широкую роль.
- Snapshot attached boot volume после bootstrap: время и можно ли сразу create from snapshot.
- Хватает ли application credential с правами сервисного `member`, или Selectel отдаёт более узкую роль «только delete».
- Snapshot attached boot volume после Idle: время и можно ли сразу create from snapshot.
Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — поднять `IDLE_GRACE_MINUTES`, не отключать killer.
+29 -30
View File
@@ -1,6 +1,8 @@
# Roadmap
Код не начинать, пока не закрыты **0.0** (квота) и **0** (spike). Без GPU в квоте остальное — театр.
**Код v1 (§16) готов.** Дальше блокирует только облако: **0.0 квота GPU** и **0 spike** в панели. Без GPU в квоте `up` не создать — это ожидаемо.
Чеклист прогона: [spike-notes.md](spike-notes.md). Ключи: [setup.md](setup.md).
## 0.0 Квота GPU
@@ -10,31 +12,28 @@
- [ ] Тикет в поддержку Selectel: 1× GPU (целевой: RTX 4090 24 GB или что дадут в пуле), проект gpu-rent
- [ ] Сервисный пользователь с правом только на этот проект
Пока квота 0, можно писать каркас CLI с dry-run и моками, но не считать этап 0 закрытым.
Пока квота 0: `doctor` / `dry-run` / `flavors` работают; `up` откажется с текстом про поддержку.
## 0. Ручной spike в панели
- [ ] RC-файл, `openstack token issue`
- [ ] Сегмент с GPU, `flavor list` / `image list` (GPU Driver **580**, без Docker)
- [ ] Сеть + SG :22 + FIP + keypair
- [ ] Boot-from-volume, тег `preemptible`, API 2.72, второй диск в том же AZ
- [ ] `nvidia-smi` на хосте; SwarmUI нативно `launch-linux.sh`
- [ ] SwarmUI на 127.0.0.1:7801, туннель на **17801**, UI + один вызов API
- [ ] `flavor list`: какие 4090 / A5000 / A100 есть; disabled vs capacity
- [ ] Скачать тестовую маленькую модель **через Civitai API** на VM (крошечный embedding) + sidecar json; убедиться, что установщик SwarmUI не качает свой SD
- [ ] Время: ACTIVE / SSH / UI / backend Idle / seed; ₽/час
- [ ] EXPIRED → `unshelve` → те же модели
- [ ] Delete VM, диски живы → create from boot volume
- [ ] Snapshot boot после первого Idle → create from snapshot
- [ ] Прототип idle-killer: скрипт на VM с application credential удаляет **этот** сервер
- [ ] JSON «качалка модели в UI» vs пустая очередь (или нет сигнала → только `hold`)
Заполняй [spike-notes.md](spike-notes.md). В коде уже есть idle-killer, hold, snapshot, tunnel-watchdog — spike **проверяет** на живом GPU, не пишет заново.
Заметки — `docs/spike-notes.md`, без секретов и личных имён файлов моделей.
- [ ] RC-файл, `doctor` зелёный, квота > 0
- [ ] `flavors` / образ Driver **580** без Docker в пуле
- [ ] `up --yes` → Idle backend + seed; время ACTIVE/SSH/Idle
- [ ] `tunnel` → UI :17801 + API; `nvidia-smi`
- [ ] idle-killer timer + `hold`; качалка UI → только hold
- [ ] EXPIRED → tunnel unshelve или `up`
- [ ] `stop` → диски живы → второй `up`
- [ ] Boot snapshot `gpu-rent-boot-ok` → следующий boot из него
- [ ] ₽/час и ₽/мес диска из панели (в API нет)
Заметки — только в spike-notes.md, без секретов.
## 1. Каркас
- [x] `pyproject.toml`, пакет `gpu_rent`, MIT
- [x] config / state / Typer: `doctor` / `dry-run` / `status` / `open` / `up` / `stop` / `destroy` / `ssh` / `logs`; `tunnel` / `hold` / seed ещё `NotReadyError`
- [x] config / state / Typer: doctor, dry-run, status, open, up, stop, destroy, ssh, logs, tunnel, hold, seed-*, push, pull-output, resize-data, flavors
- [x] Windows: `gpu-rent.bat` / `gpu-rent.ps1`; Unix: `gpu-rent.sh`; также `python -m gpu_rent`
- [x] [setup.md](setup.md) — квота, сервисный пользователь, RC, Civitai
@@ -50,9 +49,9 @@
## 3. Сессия без туннеля
- [x] `up` / `stop` / `status` / reconcile (compute+диски+SSH+bootstrap SwarmUI)
- [ ] `status`: диск used/free, ₽/час, TTL preempt 24 ч, killer/hold
- [x] `status`: диск used/free (SSH), TTL preempt 24 ч, killer/hold; ₽/час — «смотри панель»
- [x] Второй `up` не создаёт второй GPU
- [ ] `resize-data` вверх
- [x] `resize-data` вверх
- [x] Генерация `<repo>/.gpu-rent/id_ed25519` + keypair при первом `up`
- [x] `seed-models` на живом диске (идемпотентно, SHA256)
- [x] `push` / `push-models`: `Models/`, `Wildcards/`, `CustomWorkflows/`
@@ -67,24 +66,24 @@
- [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
- [ ] `ready` по HTTP, затем backend Idle
- [x] systemd idle-killer + application credential; льгота 45 мин; hold-файл; очередь/loading = busy; затем 30 мин пустой очереди; вооружение после seed/start (качалка UI = hold)
- [x] Один snapshot `gpu-rent-boot-ok` после первого backend Idle; следующие create — из snapshot если есть
- [x] `gpu-rent hold` / `hold --clear` по SSH
- [x] `ready` по HTTP, затем backend Idle
## 5. Туннель
- [x] `gpu-rent tunnel` → 17801, Ctrl+C не делает `stop`
- [x] `gpu-rent open` / `tunnel --open`
- [ ] EXPIRED → unshelve + reconnect, пока туннель жив
- [ ] Сниппет MCP в stdout
- [x] EXPIRED → unshelve + reconnect, пока туннель жив
- [x] Сниппет MCP в stdout
## 6. UX
- [ ] Живой список flavors, `--no-spot`, печать фоллбека и цены
- [ ] Оценка ₽, предупреждение про диск 24/7 и про idle-killer
- [ ] `NOTIFY_READY`: Windows toast + звук при backend Idle
- [ ] Пользовательский README поверх `docs/`
- [x] Живой список flavors (`gpu-rent flavors` / dry-run / up), `--no-spot`, печать фоллбека
- [x] Оценка ₽ честно («смотри панель»), предупреждение про диск 24/7 и idle-killer
- [x] `NOTIFY_READY`: Windows toast + звук при backend Idle
- [x] Пользовательский README поверх `docs/`
## Вне скоупа v1
+89
View File
@@ -0,0 +1,89 @@
# Spike notes (заполнять руками)
Без секретов, без личных имён файлов моделей. Цель — один раз пройти панель + CLI на живом GPU и зафиксировать факты для v1.
## 0.0 Квота
| Шаг | Дата | Результат |
| --- | --- | --- |
| Квота GPU в панели (лимит) | | 0 / N |
| Тикет в поддержку (номер) | | |
| Лимит поднят | | |
| Сервисный пользователь `member` на проект | | |
Текст тикета: [setup.md §2.3](setup.md).
## Preflight CLI
```text
.\gpu-rent.ps1 doctor
.\gpu-rent.ps1 flavors
.\gpu-rent.ps1 dry-run
```
| Проверка | OK? | Заметка |
| --- | --- | --- |
| Keystone token | | |
| GPU quota > 0 | | |
| Flavor из FLAVOR_PREFERENCE | | id / имя |
| Image Driver 580 (без Docker) | | имя образа |
| Volume type в AZ | | |
| Civitai `.red` + token | | |
## Первый `up`
```text
.\gpu-rent.ps1 up --yes
```
| Метрика | Значение |
| --- | --- |
| Flavor фактически | |
| Spot / preemptible | |
| ACTIVE через (мин) | |
| SSH через (мин) | |
| Backend Idle через (мин) | |
| Seed Civitai (мин / GB) | |
| Boot snapshot создался | да / нет / ошибка |
| ₽/час GPU (панель) | |
| ₽/мес data disk | |
## Туннель и API
```text
.\gpu-rent.ps1 tunnel --open
```
| Шаг | OK? |
| --- | --- |
| UI http://127.0.0.1:17801 | |
| `/API/GetNewSession` | |
| MCP `/mcp` (если нужно) | |
| `nvidia-smi` по `gpu-rent ssh` | |
## Idle-killer / hold
| Шаг | OK? | Заметка |
| --- | --- | --- |
| `systemctl status gpu-rent-idle-killer.timer` | | |
| `gpu-rent hold` пишет hold-until | | |
| Качалка модели в UI → нужен hold? | | нет сигнала API — только hold |
## Preempt / диски
| Шаг | OK? |
| --- | --- |
| EXPIRED → tunnel unshelve или `up` | |
| `stop` — диски живы | |
| Второй `up` — те же модели на data | |
| Boot из snapshot `gpu-rent-boot-ok` | |
## Выводы для кода
- Имя GPU-образа в пуле:
- Реальные flavor id (не в git):
- Хватает ли `IDLE_GRACE_MINUTES=45`:
- Application credential: ок / нужны другие права:
- Прочее:
Когда spike закрыт — отметь пункты в [roadmap.md](roadmap.md) §0 / §0.0.