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
+37
View File
@@ -0,0 +1,37 @@
# Документация gpu-rent
CLI-оркестратор: поднимает прерываемый GPU-сервер в Selectel, монтирует постоянные сетевые диски и открывает SwarmUI на `localhost` через SSH-туннель. Платим за GPU только пока идёт сессия; модели и ComfyUI живут на диске.
Код в разработке: каркас CLI и `doctor` есть. GPU всё ещё не создаём, пока нет квоты и зелёного `doctor`. Что сделать руками до этого — [setup.md](setup.md).
## Как читать
| Документ | Зачем |
| --- | --- |
| [setup.md](setup.md) | **С чего начать:** квота GPU, сервисный пользователь, Civitai, установка CLI |
| [concept.md](concept.md) | Задача, границы, модель стоимости |
| [decisions.md](decisions.md) | Зафиксированные решения |
| [architecture.md](architecture.md) | Компоненты, диски, стейт-машина, idle-killer |
| [selectel.md](selectel.md) | Контракт с облаком: auth, API, GPU, preemptible, сеть |
| [models.md](models.md) | Первый seed с Civitai, манифест, пропуск дефолта SwarmUI |
| [extensions.md](extensions.md) | Git-репы SwarmUI-расширений и ComfyUI nodes |
| [autocomplete.md](autocomplete.md) | Word-list промптов, Settings.fds, проверка версии |
| [local-folders.md](local-folders.md) | `Models/` / `Wildcards/` / `CustomWorkflows/` push, optional `Output/` pull |
| [swarmui.md](swarmui.md) | Нативный install, порт 7801, API, MCP |
| [cli.md](cli.md) | Команды, конфиг, локальный state |
| [roadmap.md](roadmap.md) | Порядок реализации |
| [open-questions.md](open-questions.md) | Ещё не закрыто |
## Источники
Проверено 21 августа 2026:
- [Selectel: preemptible servers](https://docs.selectel.ru/en/cloud-servers/about/preemptible-servers/)
- [Selectel: GPU](https://docs.selectel.ru/en/cloud-servers/create/gpus/)
- [Selectel: GPU images / drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/)
- [Selectel: IAM / API auth](https://docs.selectel.ru/en/api/authorization/)
- [Selectel: restore preemptible](https://docs.selectel.ru/en/cloud-servers/manage/restore-preemptible-server/)
- [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) (нативный Linux, порт 7801)
- [Civitai Site API](https://developer.civitai.com/) (download + metadata; API есть и на `.red`)
- [Civitai: два входа .com / .red](https://civitai.com/articles/28369/two-front-doors-civitaicom-civitaired-and-whats-next)
- [SwarmUI Autocompletions](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md)
+173
View File
@@ -0,0 +1,173 @@
# Архитектура
## Обзор
```
┌─ локальная машина (Windows / Linux) ─────────────────────────┐
│ gpu-rent CLI │
│ up / stop / status / doctor / hold │
│ tunnel / open — SSH localhost:17801 → VM :7801 │
│ state ~/.gpu-rent/state.json │
│ │
│ браузер / MCP / curl API → http://127.0.0.1:17801 │
│ локальный SwarmUI → http://127.0.0.1:7801 (не трогаем)
└───────────────────────────────┬──────────────────────────────┘
│ SSH :22 и OpenStack API
┌─ Selectel, сегмент пула (например ru-7a) ────────────────────┐
│ GPU VM (tag preemptible + gpu-rent) │
│ SwarmUI : 127.0.0.1:7801 │
│ idle-killer: очередь / hold / качалка в UI → delete self │
│ application credential (compute delete/shelve only) │
│ │
│ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │
│ data volume (network) Models, Output, Data, workflows │
│ floating IP только SSH, удаляется на stop │
└──────────────────────────────────────────────────────────────┘
```
Два сетевых диска. После удаления VM boot-диск снова указывают как `--volume` при create.
Разделение **жизни GPU** и **локального туннеля** — следствие [decisions.md](decisions.md): ноут можно закрыть.
## Слои CLI
| Модуль | Ответственность |
| --- | --- |
| `cli` | Typer: `up`, `tunnel`, `open`, `status`, `stop`, `destroy`, `logs`, `ssh`, `doctor`, `hold`, `seed-*`, `push-models`, `pull-output`, `resize-data`, `dry-run` |
| `config` | `.env`, валидация, пути к ключам |
| `state` | JSON сессии: ids, фаза, timestamps |
| `os_client` | `openstacksdk`, refresh IAM-токена |
| `inventory` | Flavors/images в сегменте, квоты, **фоллбек flavor** |
| `bootstrap` | Идемпотентный first-boot; после успеха — snapshot boot volume |
| `doctor` | Preflight без mutating compute |
| `civitai_seed` | Манифест + Civitai API на `.red` |
| `models_push` | SFTP `./Models`, `./Wildcards`, `./CustomWorkflows` |
| `output_pull` | Опциональный SFTP с VM `Output/` |
| `git_seed` | Clone `extensions.yaml` |
| `autocomplete_seed` | Word-list + Settings.fds |
| `notify` | Toast/звук при backend Idle |
| `tunnel` | paramiko / sshtunnel, порт **17801** |
| `watchdog` | EXPIRED → unshelve, пока туннель жив |
| `idle_killer` | systemd на VM + hold-файл + «качалка занята» |
| `reconcile` | Сироты по state и тегу `gpu-rent` |
Каталог сервисов — из Keystone, не из выдуманного `api.selectel.ru/v3/`.
## Два диска
### Boot volume
Сетевой, тот же сегмент, что VM. Первый раз — GPU-optimized образ **без Docker**. `delete_on_termination=false`. Локальный boot запрещён (preemptible сотрёт ОС).
После bootstrap: NVIDIA из образа, `/opt/swarmui` + systemd, unit idle-killer, application credential в `/root/.gpu-rent/` (mode 600).
После **первого** успешного `waiting_ui` + backend Idle: один snapshot boot-диска `gpu-rent-boot-ok` (если ещё нет). Следующий create VM может идти из snapshot, не из сырого GPU-образа. Старый snapshot не плодить каждый `up`.
### Data volume
Второй BDM при create (не attach после ACTIVE). FS один раз, маркер `/mnt/swarm_data/.gpu-rent-ready`. Первый seed: [extensions.md](extensions.md), [autocomplete.md](autocomplete.md), [models.md](models.md), [local-folders.md](local-folders.md).
| На хосте (data volume) | В дереве SwarmUI (`/opt/swarmui`, bind) |
| --- | --- |
| `/mnt/swarm_data/Models` | `/opt/swarmui/Models` |
| `/mnt/swarm_data/Output` | `/opt/swarmui/Output` |
| `/mnt/swarm_data/Data` | `/opt/swarmui/Data` |
| `/mnt/swarm_data/dlbackend` | `/opt/swarmui/dlbackend` |
| `/mnt/swarm_data/Extensions` | `/opt/swarmui/src/Extensions` |
| `/mnt/swarm_data/DLNodes` | `/opt/swarmui/src/BuiltinExtensions/ComfyUIBackend/DLNodes` |
| `/mnt/swarm_data/CustomWorkflows` | `/opt/swarmui/src/BuiltinExtensions/ComfyUIBackend/CustomWorkflows` |
`mkfs` только если нет маркера **и** `blkid` подтвердил пустое устройство по serial/by-id. Не хардкодить `scsi-0Selectel_Volume_…`.
## Стейт-машина
```
idle
│ gpu-rent up
provisioning → bootstrapping → seeding_extensions → seeding_autocomplete → seeding_models → waiting_ui
ready_cloud ← compute жив, idle-killer вооружён
│ gpu-rent tunnel (опционально, пока ноут онлайн)
ready_tunneled ← localhost:17801
│ хостер: EXPIRED
restoring (unshelve) → ready_cloud / ready_tunneled
│ stop | idle-killer | destroy
stopping → idle
```
`ready_cloud` ≠ Nova `ACTIVE`. ACTIVE бывает раньше SSH и UI.
## Idle-killer (на VM)
Пока очередь SwarmUI пуста дольше **30 минут**, скрипт удаляет **этот** compute через OpenStack (диски не трогать). Открытый браузер без джобы жизнь **не** продлевает.
Killer молчит:
- clone расширений, Civitai-seed, push локальных папок;
- первые **45 минут** после ACTIVE или unshelve;
- пока backend/ComfyUI ещё не Idle;
- пока существует hold: файл `/mnt/swarm_data/.gpu-rent-hold-until` с unix ts (пишет `gpu-rent hold`);
- пока SwarmUI качает модель в UI (Model Downloader / активный download — точный JSON на spike). Нет сигнала — пользователь жмёт `hold`.
`gpu-rent hold` без аргументов = +`IDLE_MINUTES` от сейчас. `--minutes 90` — абсолютное продление. `--until` ISO опционально. `hold --clear` снимает.
Потом счётчик 30 минут пустой очереди.
Почему не `shutdown -h now`: у Selectel останов из гостя не обязан снять GPU с биллинга. Нужен API delete/shelve.
Почему не только локальный CLI: ноут спит — процесса нет — GPU продолжает тарифицироваться.
Учётные данные на VM: application credential (или роль без `compute:create`), только delete/shelve. Файл не попадает в Output/Models. Истечение кредов = killer слеп; тогда спасает `gpu-rent status` с ноутбука.
Дефолты: `IDLE_MINUTES=30`, `IDLE_GRACE_MINUTES=45`.
## Watchdog туннеля (на ноутбуке)
Работает только пока открыт туннель:
1. Refresh IAM-токена (TTL 24 ч, как у preemptible).
2. `EXPIRED` → unshelve → переоткрыть туннель.
3. `ERROR` / нет GPU → выход, не бесконечный recreate.
4. Туннель мёртв при ACTIVE → reconnect.
`Ctrl+C` здесь закрывает туннель, **не** вызывает `stop`.
## Teardown (`gpu-rent stop`)
Compute удаляется через OpenStack без SSH. Optional pull Output — только если VM ещё отвечает по SSH:
1. Если `PULL_OUTPUT` и SSH жив — забрать новые файлы в `./Output` (`--no-pull` пропускает).
2. Закрыть туннель этого процесса, если открыт.
3. Удалить compute. Volumes оставить.
4. Дождаться исчезновения сервера.
5. Удалить floating IP (`KEEP_FLOATING_IP=false`).
6. State → `idle`, сохранить volume ids.
`destroy` — то же + диски после `--i-understand-data-loss`.
Reconcile: сервер с тегом `gpu-rent` есть, локального процесса нет — это норма (`ready_cloud`). Сирота = нет тега в state и наоборот; `status` показывает «жив, туннеля нет, idle-killer: …».
## Сеть на VM
- Private net + subnet + router — один раз на пул, переиспользовать.
- SG: ingress TCP/22 с IP оператора. **Не** открывать 7801 наружу.
- SwarmUI: `--host 127.0.0.1 --port 7801` (systemd `swarmui`).
- Туннель: `127.0.0.1:17801``127.0.0.1:7801` на VM.
## Стек
| Слой | Выбор |
| --- | --- |
| Python 3.11+ | openstacksdk, paramiko, Typer, Rich, questionary, dotenv |
| SSH | paramiko + sshtunnel (Windows без системного `ssh -L`) |
| Конфиг | `%USERPROFILE%\.gpu-rent\.env` |
| Лицензия | MIT |
+94
View File
@@ -0,0 +1,94 @@
# Autocomplete промптов SwarmUI
Официальная инструкция: [docs/Features/Autocompletions.md](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md).
Нужен word-list (`.csv` / `.txt`) в `Data/Autocompletions` и выбранный источник в настройках пользователя. Без файла в UI нечего выбирать.
## Поведение gpu-rent
| Момент | Действие |
| --- | --- |
| Первый диск / файла ещё нет | Скачать с GitHub на VM в `/mnt/swarm_data/Data/Autocompletions/<filename>`. Прописать `DefaultUser.AutoComplete.Source` в `Settings.fds` **до** первого старта контейнера |
| Каждый последующий `up` (диск уже есть) | Спросить GitHub blob `sha` того же пути. Если sha изменился — скачать заново, заменить файл, перезапустить контейнер SwarmUI если он уже крутится |
| `AUTOCOMPLETE_ENABLED=false` или URL пуст | Ничего не качать, стоковый SwarmUI (только синтаксис `<…>`, без booru-тегов) |
Это **не** одноразовый seed как модели: список тегов на GitHub обновляют. Проверка версии — часть каждого `up`, пока есть SSH.
Файл маленький (danbooru.csv ≈ 34 MB). Killer из‑за него не откладываем, но на первом bootstrap качаем **до** старта UI, вместе с extensions.
## Откуда файл
Дефолт — то, на что ссылается сама документация SwarmUI:
- репозиторий [DominikDoom/a1111-sd-webui-tagcomplete](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/tree/main/tags)
- путь `tags/danbooru.csv`
- raw: `https://raw.githubusercontent.com/DominikDoom/a1111-sd-webui-tagcomplete/main/tags/danbooru.csv`
Альтернативы из той же папки (`e621.csv`, `danbooru_e621_merged.csv`, …) или релизы [BetaDoggo/danbooru-tag-list](https://github.com/BetaDoggo/danbooru-tag-list/releases) — через конфиг, не хардкод в коде кроме дефолта.
## Куда класть
На data volume (уже примонтирован как `/SwarmUI/Data`):
```text
/mnt/swarm_data/Data/Autocompletions/danbooru.csv
/mnt/swarm_data/Data/Autocompletions/danbooru.csv.gpu-rent-meta.json
```
В контейнере: `/SwarmUI/Data/Autocompletions/danbooru.csv`.
Sidecar meta (не отдавать в Output):
```json
{
"repo": "DominikDoom/a1111-sd-webui-tagcomplete",
"path": "tags/danbooru.csv",
"ref": "main",
"github_blob_sha": "7a3cba59…",
"filename": "danbooru.csv",
"fetched_at": "2026-08-21T00:00:00Z"
}
```
## Как узнать, что файл новый
Не сравнивать «дату файла на диске». GitHub Contents API:
```http
GET https://api.github.com/repos/{repo}/contents/{path}?ref={ref}
```
В ответе поле `sha` — blob git. Сравнить с `github_blob_sha` в sidecar. Совпало → ничего не качать. Другое → GET `download_url` (или raw), атомарно заменить файл, обновить sidecar.
Без токена GitHub лимит 60 запросов/час с IP VM — на один `up` хватает. Если 403 rate limit — пропустить обновление, не валить `up`.
Для URL не с GitHub (произвольный raw): `HEAD`/`GET` с `If-None-Match` / `Last-Modified`, хранить ETag в sidecar.
## Настройки SwarmUI до первого запуска
Положить/дописать в `/mnt/swarm_data/Data/Settings.fds` (Frenetic Data Syntax), пока контейнер ещё не стартовал:
```text
DefaultUser:
AutoComplete:
Source: danbooru.csv
EscapeParens: true
```
`Source` — имя файла **относительно** `Data/Autocompletions`, как в UI `AutoCompletionsSource`.
Если `Settings.fds` уже есть (повторный диск) и пользователь сменил список в UI — **не** перезаписывать `Source` на каждом `up`. Ставить Source только когда он пустой или мы его ещё ни разу не ставили (флаг в meta `settings_applied: true`).
После замены csv на уже живой VM: `systemctl restart swarmui` (хелпер `Reload()` при старте перечитает каталог).
## Конфиг
```env
AUTOCOMPLETE_ENABLED=true
AUTOCOMPLETE_GITHUB_REPO=DominikDoom/a1111-sd-webui-tagcomplete
AUTOCOMPLETE_GITHUB_PATH=tags/danbooru.csv
AUTOCOMPLETE_GITHUB_REF=main
AUTOCOMPLETE_FILENAME=danbooru.csv
```
Или один `AUTOCOMPLETE_URL=` для raw без GitHub sha (тогда версия по ETag).
+171
View File
@@ -0,0 +1,171 @@
# CLI и конфигурация
Имя команды: `gpu-rent`. Windows и Linux. Лицензия MIT, в репозитории нет секретов и имён личных чекпоинтов.
## Команды
| Команда | Поведение |
| --- | --- |
| `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) сервер |
| `gpu-rent up --flavor … --yes` | Без вопросов. `--flavor` бьёт список фоллбека. `--yes` без `--flavor` берёт первый доступный из `FLAVOR_PREFERENCE` |
| `gpu-rent tunnel` | SSH-проброс `localhost:17801` → VM `:7801`. Блокируется. Ctrl+C = закрыть туннель, GPU **оставить** |
| `gpu-rent tunnel --open` | Туннель + сразу открыть браузер на 17801 |
| `gpu-rent open` | Открыть браузер на `http://127.0.0.1:17801`. Туннель уже должен слушать порт |
| `gpu-rent status` | State + Nova + диск used/free + ₽/час + сколько до 24 ч preempt + killer/hold. Туннель не обязателен |
| `gpu-rent hold` | Отложить idle-killer на `IDLE_MINUTES` от сейчас |
| `gpu-rent hold --minutes 90` | Hold до now+90 мин (заменяет предыдущий, не складывает) |
| `gpu-rent hold --until <ISO>` | Hold до абсолютного времени |
| `gpu-rent hold --clear` | Снять hold |
| `gpu-rent stop` | Optional pull Output → удалить compute и FIP, диски оставить. С любой машины с `.env` |
| `gpu-rent stop --no-pull` | Не тянуть Output, даже если `PULL_OUTPUT=true` |
| `gpu-rent destroy` | `stop` + диски, только `--i-understand-data-loss` |
| `gpu-rent logs` | journalctl swarmui / cloud-init по SSH |
| `gpu-rent ssh` | Оболочка на VM |
| `gpu-rent seed-models` | Докачать новые строки манифеста Civitai на уже существующий диск |
| `gpu-rent push` | Инкремент `Models/` + `Wildcards/` + `CustomWorkflows/` (пустые skip) |
| `gpu-rent push-models` | Только `./Models` |
| `gpu-rent pull-output` | Забрать новые файлы с VM `Output/` в `./Output` (сервер не чистим) |
| `gpu-rent seed-extensions` | Доклонировать/обновить git-репы; если VM жива — `systemctl restart swarmui` |
| `gpu-rent resize-data --gb 400` | Увеличить data volume вверх (Selectel online resize). Вниз нельзя |
| `gpu-rent dry-run` | План без mutating-вызовов |
Второй `up` при живой VM: не создавать второй GPU; сделать autocomplete-check и **push** локальных папок; если `PULL_OUTPUT` — подтянуть Output; напомнить про `tunnel` / `stop`. `--adopt` если нашли тег без state.
Нет команды `generate`. Нет зеркала каталога локального SwarmUI — только папки приложения, см. [local-folders.md](local-folders.md).
## `doctor`
Не создаёт сервер, не тратит GPU. Нужен до первого `up` и когда «вчера работало».
Проверки по порядку, все печатаются (не падать на первой, собрать отчёт):
1. Читается `.env`, обязательные `OS_*`.
2. Keystone: токен выдаётся.
3. Квота GPU > 0; иначе текст «напишите в поддержку Selectel».
4. В сегменте есть хотя бы один flavor из `FLAVOR_PREFERENCE` (не `disabled`).
5. Volume type сегмента существует; data 100 GB влезает в квоту дисков.
6. SSH-ключ: есть `~/.gpu-rent/id_ed25519` или CLI сможет его создать.
7. Если задан `CIVITAI_API_TOKEN` — HEAD/лёгкий запрос к `CIVITAI_API_HOST` (дефолт `.red`); манифест парсится.
8. `extensions.yaml` парсится, если файл есть.
9. Локальные папки: предупреждение, если `Models/` огромный относительно свободного места на будущем диске.
Exit 0 — можно `up`. Exit 1 — нельзя, причина в отчёте.
## `status`
Без туннеля. Если SSH есть — ещё диск и killer; если нет — только OpenStack.
| Поле | Откуда |
| --- | --- |
| Фаза state / Nova status | `state.json` + compute |
| Flavor, ₽/час | extra specs / оценка с spike; нет цены в API — «см. панель», не выдумывать |
| Preempt window | create/unshelve timestamp + 24 ч; «осталось Hh Mm» или «не preemptible» |
| Data disk used/free | SSH `df` на `/mnt/swarm_data` |
| Idle-killer | armed / grace до … / hold до … / busy (очередь или качалка) / слеп (нет кредов) |
| Туннель | слушает ли локальный 17801 |
| Snapshot boot | есть ли `gpu-rent-boot-ok` |
## `hold`
Пишет на VM `/mnt/swarm_data/.gpu-rent-hold-until` (unix ts). Killer не удаляет compute, пока `now < ts`. Нужен SSH и живой сервер.
Без VM — ошибка, не «запомню на потом». После `unshelve` hold-файл на диске сохраняется.
## Что печатать при `up`
1. Preflight (тот же набор, что `doctor`, можно вызвать его внутри).
2. Если квота 0 — стоп с текстом «напишите в поддержку Selectel», не traceback.
3. Выбранный flavor (или фоллбек) + оценка: GPU ₽/час, диск ₽/мес, preemptible 24 ч, idle-killer через N мин.
4. Фазы: provisioning → bootstrap → clone extensions → autocomplete → seed Civitai → push локальных папок → waiting UI → backend Idle.
5. Если `NOTIFY_READY` — Windows toast + звук (и строка в лог).
6. Готово:
```text
SwarmUI на VM: 127.0.0.1:7801 (только через туннель)
Локально: gpu-rent tunnel
Браузер: gpu-rent open → http://127.0.0.1:17801
API: http://127.0.0.1:17801/API/
MCP: http://127.0.0.1:17801/mcp
Hold killer: gpu-rent hold
Стоп GPU: gpu-rent stop
```
Сниппет MCP для Cursor — в stdout, `mcp.json` не редактировать.
## Конфигурация
`%USERPROFILE%\.gpu-rent\.env` / `~/.gpu-rent/.env`:
```env
OS_AUTH_URL=https://cloud.api.selcloud.ru/identity/v3
OS_USER_DOMAIN_NAME=
OS_USERNAME=
OS_PASSWORD=
OS_PROJECT_ID=
OS_REGION_NAME=ru-7
GPU_RENT_AZ=ru-7a
SSH_PRIVATE_KEY_PATH=
SSH_USER=ubuntu
BOOT_VOLUME_ID=
DATA_VOLUME_ID=
DATA_VOLUME_SIZE_GB=100
BOOT_SNAPSHOT_NAME=gpu-rent-boot-ok
CIVITAI_API_TOKEN=
CIVITAI_API_HOST=civitai.red
MODELS_MANIFEST=
LOCAL_MODELS_DIR=
LOCAL_WILDCARDS_DIR=
LOCAL_WORKFLOWS_DIR=
LOCAL_OUTPUT_DIR=
EXTENSIONS_MANIFEST=
GIT_TOKEN=
AUTOCOMPLETE_ENABLED=true
AUTOCOMPLETE_GITHUB_REPO=DominikDoom/a1111-sd-webui-tagcomplete
AUTOCOMPLETE_GITHUB_PATH=tags/danbooru.csv
AUTOCOMPLETE_GITHUB_REF=main
AUTOCOMPLETE_FILENAME=danbooru.csv
SWARMUI_LOCAL_PORT=17801
DEFAULT_FLAVOR_ID=
FLAVOR_PREFERENCE=4090-24,4090-48,a5000,a100-40
FLAVOR_FALLBACK=true
DEFAULT_SPOT=true
KEEP_FLOATING_IP=false
IDLE_MINUTES=30
IDLE_GRACE_MINUTES=45
PULL_OUTPUT=false
NOTIFY_READY=true
```
Flavor id в git не хардкодить. `FLAVOR_PREFERENCE` — ярлыки; реальные uuid резолвятся в сегменте. `FLAVOR_FALLBACK=false` — только `DEFAULT_FLAVOR_ID` / `--flavor`, без следующего в списке.
Application credential для idle-killer CLI создаёт при bootstrap и кладёт на VM; в `.env` ноутбука его можно не дублировать.
Пути локальных папок по умолчанию — каталоги в корне приложения: `Models/`, `Wildcards/`, `CustomWorkflows/`, `Output/`.
## State
`~/.gpu-rent/state.json` — ids, фаза (`idle` / `ready_cloud` / `ready_tunneled` / …), flavor, timestamps create/unshelve (для окна 24 ч). Без паролей. Lockfile, чтобы два `up` не создали два сервера.
## Ожидание готовности
Потолок ~20–40 мин на первый bootstrap, ~510 мин на unshelve:
1. Nova `ACTIVE`
2. TCP 22
3. SSH: cloud-init или `systemctl is-active swarmui`
4. HTTP `http://127.0.0.1:7801` **на VM** (через SSH), не путать с локальным 7801
5. Для MCP/API / toast — дождаться Idle backend.
## Windows
Пути через `Path.expanduser`. Венв: `.venv\Scripts\activate`. Ключ генерирует CLI: `~/.gpu-rent/id_ed25519`.
`NOTIFY_READY`: toast через WinRT / `win10toast` (что заработает на 10/11 без админ-прав) + системный звук. Если toast недоступен — только звук и лог, не падать.
+57
View File
@@ -0,0 +1,57 @@
# Концепция
## Проблема
GPU в облаке дорогой. Веса для генерации картинок — десятки гигабайт. Поднимать сервер и заново качать модели на каждый сеанс долго и бессмысленно. Держать GPU включённым «на всякий случай» ещё дороже.
## Решение
1. Арендовать **прерываемый** облачный сервер Selectel с GPU (~70% дешевле обычного, без SLA).
2. Хранить ОС, бинарники SwarmUI, модели, ComfyUI и результаты на **сетевых дисках**. Они переживают удаление и остановку VM.
3. Локальный CLI создаёт (или восстанавливает) сервер и отдельно, когда нужно, пробрасывает SwarmUI на `localhost:17801`.
4. GPU гасится командой `stop` **или** idle-killer на самой VM (пустая очередь). Диски остаются. Ноут можно закрыть — compute от этого не умирает.
Пользователь открывает браузер на `http://127.0.0.1:17801`, переключает Cursor MCP на этот URL или бьёт в HTTP API SwarmUI. Локальный SwarmUI на `7801` не трогаем.
## Что входит в продукт
- Одна GPU-сессия на проект gpu-rent (тег), не «по экземпляру CLI».
- Интерактивный запуск и запуск с флагами. Preemptible по умолчанию (`--no-spot` если нужна гарантия).
- Автовосстановление после прерывания хостером (`EXPIRED``unshelve`).
- Idle-killer на VM и `stop` с любой машины, где есть `.env` — даже без туннеля.
- Первый seed моделей с Civitai по манифесту (если есть API-токен); иначе дефолт SwarmUI.
- Push из `./Models`, `./Wildcards`, `./CustomWorkflows`, если папки не пустые; optional pull `./Output`.
- Первый clone git-реп расширений SwarmUI и ComfyUI nodes из `extensions.yaml`.
- Autocomplete: word-list в `Data/Autocompletions` до старта UI, на каждом `up` проверка новой версии.
- `doctor` до create; фоллбек flavor; `hold` для idle-killer; toast когда backend Idle; `open` на 17801.
- `status`: диск, ₽/час, окно preempt 24 ч, состояние killer.
- Snapshot boot-диска после первого удачного bootstrap.
## Что не входит (пока)
- Несколько одновременных GPU из этого CLI.
- Мультипользовательский доступ и публичный URL SwarmUI.
- Зеркало локального SwarmUI целиком, S3 как источник правды, команда `generate` в CLI.
- Автопатч `mcp.json` в Cursor (только печать сниппета).
- Облачный биллинг-дашборд Selectel: оценка ₽/час перед create (цифры с spike / панели, не отдельный биллинг API).
## Модель стоимости (как есть у Selectel)
| Ресурс | Пока VM работает | После прерывания / удаления compute |
| --- | --- | --- |
| vCPU, RAM, GPU, локальные диски | тарифицируются | с следующего часа — нет |
| Сетевые диски | тарифицируются | **тарифицируются всегда** |
| Публичный IP / публичная подсеть | тарифицируются | **тарифицируются, пока не удалены** |
Прерываемый сервер живёт **не больше 24 часов** с момента создания или restore и может быть остановлен в любой момент. Это не AWS-spot с «может повезти неделями»: суточный потолок — часть контракта.
Сетевой диск тарифицируется всегда: даже 100 GB — постоянный месячный расход при нуле сессий. Перед первым запуском CLI должен сказать это вслух.
Ориентир публичных цен (не договор): облачный сервер с 2×RTX 4090 у Selectel начинается примерно от 210 ₽/час за обычный тариф; preemptible в среднем на ~70% ниже. Точную цифру для выбранного flavor считать в калькуляторе/панели на этапе spike.
## Принципы
- **Не оставлять GPU без хозяина.** Падение CLI и сон ноутбука не гасят compute сами — это делает idle-killer на VM и команда `stop`. State на диске + reconcile, если killer не сработал.
- **Не ставить стек с нуля каждый раз.** Cloud-init «apt + dotnet + git clone SwarmUI» на каждый preempt съедает дешёвые часы. Первый bootstrap — на сетевой boot-диск, дальше `unshelve` или create from volume.
- **Не форматировать диск дважды.** Маркер файловой системы, никогда `mkfs` «если blkid не ответил».
- **SwarmUI не торчит в интернет.** На VM порт только на loopback, снаружи — SSH.
+43
View File
@@ -0,0 +1,43 @@
# Принятые решения
Зафиксировано 21 августа 2026. Менять только явно.
| Тема | Решение |
| --- | --- |
| Аудитория | Личный инструмент, репозиторий можно показать другим: MIT, без аккаунтных id и без имён локальных чекпоинтов |
| Сессия | GPU живёт до `stop` **или** простоя (idle-killer **на VM**). Ноут можно закрыть — compute не обязан умереть |
| Модели | Пустой `./Models` → не грузим ничего. Появились веса + метадата → на `up` выгрузить эту пару. Civitai-seed отдельно. С сервера не удаляем |
| Civitai хост | Дефолт API `civitai.red` (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Ссылки `.com`/`.red`/`.green` в манифесте принимаем. 404 → один retry на второй хост. Один токен на оба домена |
| Манифест моделей | `~/.gpu-rent/models.yaml`, типы: checkpoint / lora / vae / embedding / controlnet / upscaler. В git только `models.example.yaml` |
| Расширения | `~/.gpu-rent/extensions.yaml`: git-репы `swarmui``src/Extensions`, `comfy` → DLNodes. Клон на первом bootstrap до старта UI. Пустой файл — стоковый SwarmUI |
| Autocomplete | До первого старта: скачать word-list в `Data/Autocompletions`, прописать `DefaultUser.AutoComplete.Source`. На каждом `up` сверить GitHub blob sha и обновить файл, если изменился. Дефолт: `tags/danbooru.csv` из a1111-sd-webui-tagcomplete (как в доке SwarmUI) |
| Доступ | Браузер на туннеле; MCP переключается на облако, пока оно живо; HTTP API SwarmUI через тот же туннель |
| Preemptible | По умолчанию всегда. Обычный сервер — только `--no-spot` |
| Selectel сейчас | Аккаунт есть, GPU-квоту не проверяли — скорее 0. Сначала тикет в поддержку |
| Образ ОС | **Ubuntu 24.04 LTS GPU Driver 580** (Selectel, **без Docker**). SwarmUI нативно: `launch-linux.sh` + systemd. Нет образа 580 — фоллбек 24.04 Driver 535 без Docker |
| Floating IP | `KEEP_FLOATING_IP=false`: выделить на `up`, удалить на `stop` |
| Idle-killer | 30 минут пустой очереди генерации. Открытый браузер не продлевает жизнь |
| Льгота после boot | Killer молчит во время clone/seed/push, hold, качалки в UI SwarmUI, пока backend не Idle, и 45 мин после ACTIVE/unshelve |
| `up` / `tunnel` | Разделены: `up` только облако, `tunnel` отдельно. Ctrl+C на туннеле не удаляет VM |
| Data-диск | Старт **100 GB**, рост через resize вверх (вниз Selectel не умеет) |
| SSH | CLI генерирует `~/.gpu-rent/id_ed25519` без passphrase и сам регистрирует keypair |
| Локальный порт | **17801** |
| MCP-конфиг Cursor | Только сниппет в stdout, файл не трогаем |
| `ready` для API | Ждать Idle backend, не только HTTP UI |
| Output | Pull в `./Output` **опционален** (`PULL_OUTPUT=false`). Новые файлы с VM, с сервера не удаляем |
| Wildcards / workflows | Как Models: пустые папки не трогаем; появился файл — push на `up` |
| Idle hold | `gpu-rent hold [--minutes N]` сдвигает дедлайн killer. Качалка моделей в UI SwarmUI тоже считается занятостью (сигнал уточнить на spike) |
| Doctor | `gpu-rent doctor` без create: Keystone, квота GPU, flavor, диск, Civitai token+`.red`, манифесты, SSH-ключ |
| Flavor fallback | Список предпочтений (4090 → A5000 → …). Нет первого — предложить следующий с ценой, `--yes` берёт первый доступный из списка |
| Готово | Windows toast + звук (и лог), когда backend Idle |
| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold |
| Boot snapshot | После первого удачного bootstrap — один snapshot boot volume `gpu-rent-boot-ok`; следующие create могут идти с него |
| Open | `gpu-rent open` / флаг после `tunnel`: браузер на 17801 |
Следствия, которые из этого вытекают и тоже считаются принятыми:
1. **Туннель ≠ жизнь GPU.** `stop` и idle-killer не зависят от того, открыт ли SSH с ноутбука.
2. **Локальный SwarmUI на 7801 не трогаем.** Туннель по умолчанию на **17801** (на VM по-прежнему 7801 на loopback).
3. **`Ctrl+C` на туннеле не удаляет VM.** Иначе «закрыл ноут» невозможно. Чтобы убить GPU — `gpu-rent stop` или простой.
4. Idle-killer на VM **не может** быть `shutdown -h`: у Selectel остановленная изнутри VM часто продолжает тарифицировать ресурсы. Нужен вызов OpenStack: удалить **этот** compute, диски оставить.
5. Для этого на VM — OpenStack **application credential** с правом удалить/shelve сервер в проекте (не пароль владельца аккаунта). Компрометация SwarmUI в худшем случае сносит GPU-сессию, а не создаёт новые дорогие машины, если роль без `compute:create`.
+68
View File
@@ -0,0 +1,68 @@
# Расширения: git-репы на первый bootstrap
Срабатывает вместе с первым диском и первым инстансом, **до** первого старта SwarmUI (C# extensions компилируются при запуске). Повторный `up` на уже засеянный диск только догоняет новые строки манифеста (идемпотентный `git fetch`).
Два разных мира — не путать:
| Вид | Куда клонировать | Что это |
| --- | --- | --- |
| `swarmui` | `/mnt/swarm_data/Extensions/<dir>``/opt/swarmui/src/Extensions/<dir>` | C# extension SwarmUI (класс `: Extension`). После рестарта подхватывается сам |
| `comfy` | `/mnt/swarm_data/DLNodes/<dir>``…/ComfyUIBackend/DLNodes/<dir>` | Custom nodes ComfyUI (Python). `requirements.txt` ставит SwarmUI/Comfy при первом старте backend |
Пустой манифест или его нет — ничего не клонируем, SwarmUI как из коробки. Это не связано с Civitai-токеном.
## Манифест
Пример: [`extensions.example.yaml`](../extensions.example.yaml). Рабочая копия: `~/.gpu-rent/extensions.yaml` (или `EXTENSIONS_MANIFEST`).
```yaml
swarmui:
- url: https://github.com/example/SwarmUI-SomeExt.git
ref: main # ветка, тег или commit SHA; по умолчанию HEAD default branch
dir: SomeExt # имя папки; по умолчанию последний сегмент URL без .git
comfy:
- url: https://github.com/example/ComfyUI-Something.git
ref: v1.2.0
```
В git репозитория gpu-rent не коммитить рабочий список с приватными URL. В примере — вымышленные репы.
## Когда в жизненном цикле
Idle-killer ещё не вооружён. Git нужен на VM (`apt` в bootstrap, если нет в образе).
1. Маркер FS data volume.
2. **Clone extensions** (этот документ). Маркер `/mnt/swarm_data/.gpu-rent-extensions-seeded` после успешного прохода всех строк.
3. Civitai-seed моделей ([models.md](models.md)), если есть токен.
4. Первый `systemctl start swarmui`: видит `src/Extensions`, собирает их; ComfyUI ставит pip из DLNodes.
5. Backend Idle → idle-killer.
Порядок 2→3 важен: расширения до старта UI, модели тоже до старта (чтобы не качать дефолт SwarmUI).
Позже: `gpu-rent seed-extensions` — те же clone/fetch на живом диске, затем **рестарт** SwarmUI (иначе новый C# extension не подхватится). CLI предупреждает и делает `systemctl restart swarmui`, если VM жива.
## Clone на VM
- `git clone --recurse-submodules`.
- Если `ref` — ветка/тег: `--depth 1` + `checkout`.
- Если `ref` похож на SHA: fetch этого commit (shallow может не хватить).
- Каталог уже есть и `remote origin` совпадает с `url``fetch` + `checkout ref`, не `rm -rf`.
- Каталог есть, origin другой → fail этой строки, не молча перезаписывать.
Публичный HTTPS без секретов. Приватный репозиторий:
- `GIT_TOKEN` в `.env` (GitHub: `x-access-token`; GitLab: `oauth2` / PAT). CLI подставляет в URL **только на VM**, не пишет токен в yaml и не в cloud-init.
- Либо отдельный deploy key `~/.gpu-rent/git_deploy` — v1.1, не обязательно сейчас.
Не использовать `git://`. Только `https://` и `git@` + ключ.
## Сбой
Одна строка упала (404, auth, сеть) — остальные можно продолжить; `up` в конце ненулевой, маркер seeded не ставить, повтор доклонирует. Не оставлять полупустой `.git`.
## Конфиг
```env
EXTENSIONS_MANIFEST= # пусто = ~/.gpu-rent/extensions.yaml
GIT_TOKEN= # опционально, для приватных https
```
+37
View File
@@ -0,0 +1,37 @@
# Локальные папки приложения
Все живут в корне gpu-rent. Одно правило: **пусто → ничего не синхронизируем**. Появился файл → на `up` (и по отдельной команде) едет инкремент по SHA256. С сервера лишнее не удаляем.
| Локально | На VM | Направление | Обязательно |
| --- | --- | --- | --- |
| `Models/` | `/mnt/swarm_data/Models/` | push | да, если не пусто; см. [models.md](models.md) |
| `Wildcards/` | `/mnt/swarm_data/Data/Wildcards/` | push | да, если не пусто |
| `CustomWorkflows/` | `/mnt/swarm_data/CustomWorkflows/` → bind в ComfyUI CustomWorkflows | push | да, если не пусто |
| `Output/` | `/mnt/swarm_data/Output/` | **pull**, опционально | нет; флаг `PULL_OUTPUT` |
Веса, картинки, воркфлоу в git не коммитить (`.gitignore` + `.gitkeep`).
## Push (Models / Wildcards / CustomWorkflows)
Как у моделей: нет содержимого кроме README/`.gitkeep` — skip. Новый или изменённый файл — SFTP. Метадата рядом с весами едет парой. Команда на все три дерева: `gpu-rent push`.
Wildcards: `.txt` (одна строка — один вариант), как в доке SwarmUI `Data/Wildcards`.
CustomWorkflows: JSON воркфлоу Comfy/Swarm. После заливки на живой UI — refresh, если API это умеет; иначе рестарт контейнера только если без refresh файлы не видны (spike).
Пока идёт push крупных файлов — idle-killer на паузе.
## Pull Output (опционально)
`PULL_OUTPUT=false` по умолчанию. Включить: `PULL_OUTPUT=true` или `gpu-rent pull-output`.
Когда включено:
- на **`stop`** — забрать с VM файлы, которых нет локально или чей sha другой;
- на **`up`**, если VM уже жива и флаг включён — то же (подтянуть картинки с прошлой сессии, не дожидаясь stop).
Пустой локальный `Output/` — норма. С сервера ничего не стираем. Не тащить гигабайты видео без нужды: только новые/изменённые.
`gpu-rent stop --no-pull` — выключить pull на этот раз.
Отдельная команда всегда: `gpu-rent pull-output` (нужны живая VM и SSH).
+174
View File
@@ -0,0 +1,174 @@
# Модели
Два источника на data volume, они **складываются**, не заменяют друг друга:
1. **Civitai-seed** (ниже) — один раз на пустой диск, если есть API-токен и манифест.
2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить на VM только то, что изменилось. `Wildcards/` и `CustomWorkflows/` — то же правило, см. [local-folders.md](local-folders.md).
Локальный SwarmUI на `7801` по-прежнему не зеркалируем. Только дерево `./Models` приложения.
## Локальная папка → сервер
Каталог: `<корень приложения>/Models/` (или `LOCAL_MODELS_DIR`). Раскладка как у SwarmUI: `Stable-Diffusion/`, `Lora/`, `VAE/`, … Описание: [`Models/README.md`](../Models/README.md). Веса в git не коммитятся.
Правило одно:
| Локально | На `up` |
| --- | --- |
| Папки нет, или только `.gitkeep` / README | **Ничего не выгружаем**, тихий лог |
| Появился файл весов (`.safetensors` / `.ckpt` / …) | Залить его **вместе с соседней метадатой** |
| Веса уже на VM с тем же SHA256 | Пропуск (нет «обновления») |
| Веса изменились | Залить веса заново и актуальную метадату |
Метадата — файлы рядом с тем же stem: `.json`, `.civitai.json`, `.swarm.json`, превью (`.preview.png` / `.png` / `.webp`), если лежат в той же папке. Веса без sidecar тоже можно залить (предупреждение в лог: «метадаты нет»). Метадату без весов **не** шлём отдельно.
С диска в облаке **ничего не удаляем**. Civitai-seed и то, чего нет в `./Models`, остаётся.
Технически: SHA256 локального файла vs тот же относительный путь под `/mnt/swarm_data/Models/`. SFTP/paramiko, resume `.partial`. Preflight свободного места. Если залили и SwarmUI уже жив — refresh списка моделей.
Повторный `up` при живой VM тоже смотрит папку (положил новую LoRA дома → следующий `up` её увезёт). Явно: `gpu-rent push-models` или `gpu-rent push` (все локальные деревья).
Пока идёт аплоад, idle-killer на паузе.
---
# Civitai: первый seed
Срабатывает **один раз**: пустой data-диск + первый инстанс. Повторный `up` к уже засеянному диску строки манифеста с совпавшим SHA256 пропускает.
## Две ветки
| Условие | Что происходит |
| --- | --- |
| В `.env` есть `CIVITAI_API_TOKEN` **и** в манифесте есть хотя бы одна запись | Скачать перечисленные модели с Civitai **на VM** (не через ноут). Рядом положить метаданные. **Не** качать дефолтный чекпоинт установщика SwarmUI |
| Токена нет, или манифест пуст/отсутствует | Обычный первый запуск SwarmUI: пусть ставит свою стандартную модель. В лог — почему Civitai-seed пропущен |
Токен без манифеста = предупреждение и ветка SwarmUI default, не «пустой диск без моделей».
ComfyUI в `dlbackend` качается всегда. Речь только о **модели весов** установщика SwarmUI, не о backend.
## Когда в жизненном цикле
Пока idle-killer ещё **не вооружён**:
1. Создать/примонтировать data volume, маркер FS.
2. Clone расширений из `extensions.yaml` ([extensions.md](extensions.md)).
3. Autocomplete word-list ([autocomplete.md](autocomplete.md)): скачать, если нет; иначе проверить GitHub sha.
4. Если ветка Civitai: залить на VM манифест (scp, не cloud-init — токен не должен светиться в Nova user_data).
5. Скачать файлы в `/mnt/swarm_data/Models/…`, проверить SHA256, записать sidecar.
6. Маркер `/mnt/swarm_data/.gpu-rent-models-seeded`.
7. **Push** из корневой `Models/` приложения (новые/изменённые, см. выше).
8. Первый старт SwarmUI (папки чекпоинтов уже не пустые → установщик не предлагает свой SD; Extensions и Autocompletions уже на месте).
9. Ждать backend Idle, затем вооружить idle-killer.
Качание нескольких GB легко длиннее льготы 45 минут. Поэтому killer не живёт, пока не закончился seed.
Позже тот же код: `gpu-rent seed-models` — докачать новые строки манифеста на уже живой диск (идемпотентно).
## Манифест
Пример в репозитории: [`models.example.yaml`](../models.example.yaml). Рабочая копия: `~/.gpu-rent/models.yaml` (или путь `MODELS_MANIFEST`).
Идентификатор — **Civitai model version id**, не имя файла и не «последняя версия модели» (она плывёт).
```yaml
# ~/.gpu-rent/models.yaml
checkpoint:
- version_id: 2514310
lora:
- version_id: 123456
vae:
- version_id: 789012
embedding:
- version_id: 111
controlnet:
- version_id: 222
upscaler:
- version_id: 333
```
Допустимо вместо `version_id` поле `url` страницы модели/версии — CLI вынимает id. Хосты `civitai.com`, `civitai.red`, `civitai.green` равнозначны (зелёный редиректит на `.com`). Несколько файлов у версии: берём `files[]` с `"primary": true`, иначе SafeTensor / fp16.
Типы манифеста → каталоги SwarmUI (`ModelRoot` = `Models`):
| Ключ yaml | Civitai `model.type` (ориентир) | Каталог на диске |
| --- | --- | --- |
| `checkpoint` | Checkpoint | `Models/Stable-Diffusion` |
| `lora` | LORA, LoCon, DoRA | `Models/Lora` |
| `vae` | VAE | `Models/VAE` |
| `embedding` | TextualInversion | `Models/Embeddings` |
| `controlnet` | Controlnet | `Models/controlnet` |
| `upscaler` | Upscaler | `Models/upscale_models` |
| `clip` | — | `Models/clip` |
Тип в yaml задаёт **куда класть**, даже если Civitai назвал иначе. Если API-тип совсем другой — предупреждение, кладём по yaml.
В git не коммитить рабочий манифест с личными чекпоинтами. В примере — вымышленные id.
## Civitai API (.com / .red)
С апреля 2026 у Civitai два входа в **одну** базу ([анонс](https://civitai.com/articles/28369/two-front-doors-civitaicom-civitaired-and-whats-next)):
| Домен | Что видно |
| --- | --- |
| `civitai.com` | SFW / PG (бывший green). NSFW-карточки с этого хоста часто **не находятся** (404) или без `files[]` |
| `civitai.red` | Полный каталог, включая NSFW |
| `civitai.green` | 301 на `.com` |
REST не раздвоился: те же `/api/v1/...` на обоих хостах, один аккаунт, один API-токен. Официальная дока по-прежнему пишет base `https://civitai.com/api/v1/`, но там же: ответы с «зелёного» домена и из части регионов **молча режутся до SFW**. Нам нужны **любые** `version_id` из манифеста → **дефолтный API-хост `civitai.red`**.
Клиент:
1. Доверять только `civitai.com`, `civitai.red` и их поддоменам. Токен на чужой хост (CDN после редиректа) **не** отправлять.
2. URL из yaml нормализовать: вытащить `modelVersionId` / `/models/{id}?modelVersionId=` с любого из трёх доменов.
3. Метаданные: `GET https://{CIVITAI_API_HOST}/api/v1/model-versions/{id}` с `Authorization: Bearer`.
4. Если 404, пустой `files[]` или нет `downloadUrl`**один** повтор на второй хост (`.red``.com`). Не крутить бесконечно.
5. Качать: `GET https://{хост_успешного_GET}/api/download/models/{id}` с Bearer, следовать редиректам на presigned S3. Имя — `Content-Disposition`. `downloadUrl` в JSON часто указывает на `.com` — для NSFW его переписать на хост, который отдал файлы.
6. Поле `nsfwLevel` / `model.nsfw` в sidecar сохранить как есть, фильтром не пользоваться.
Без токена скачивание часто 401 даже на публичные веса. `requireAuth: true` в mini-endpoint — токен обязателен.
После ответа version:
1. Primary file, `hashes.SHA256`, `sizeKB`.
2. Preflight места: сумма `sizeKB` × 1.2 < свободно на data volume.
3. Качать на VM. Прогресс в логах.
4. Сверить SHA256. Несовпадение — удалить файл, fail строки.
Рядом с весами:
| Файл | Содержимое |
| --- | --- |
| `{name}.safetensors` | веса |
| `{name}.civitai.json` | сырой JSON version (+ при желании `GET /models/{modelId}` для автора/тегов) |
| `{name}.swarm.json` | маппинг под SwarmUI: `name`, `title`, `description`, `trigger_phrase` из `trainedWords`, `author`, `license`, `tags` |
Превью-картинки в base64 не обязательны в v1 (раздувают json). SwarmUI подтянет превью сам, если умеет по civitai id.
Не класть API-токен в sidecar и не в Output.
## Пропуск дефолта SwarmUI
Когда seed успешен (есть хотя бы один checkpoint **или** пользователь явно согласен на «только LoRA»):
- первый старт SwarmUI — **после** появления файлов в `Models/`;
- на spike проверить: установщик не качает Z-Image / SDXL Base, если `Stable-Diffusion` не пуст;
- если всё же качает — выключить автозагрузку настройкой/LaunchArg (имя ключа зафиксировать на spike, не выдумывать сейчас).
Если в манифесте нет ни одного `checkpoint` — предупреждение «без базовой модели генерация может не стартовать», не жёсткий fail.
## Сбой
- 401 → «токен неверный или скачивание требует логина».
- 404 на обоих хостах → версии нет / снята / не published.
- Early access / 403 → строка fail, остальные можно продолжить; маркер seeded не ставить.
- Обрыв сети — resume по `.partial` + повтор SHA256.
## Конфиг
```env
CIVITAI_API_TOKEN=
CIVITAI_API_HOST=civitai.red # полный каталог; civitai.com = только SFW
MODELS_MANIFEST= # пусто = ~/.gpu-rent/models.yaml
LOCAL_MODELS_DIR= # пусто = <корень приложения>/Models
```
+13
View File
@@ -0,0 +1,13 @@
# Открытые вопросы
Пока пусто по продуктовым развилкам — см. [decisions.md](decisions.md).
Осталось подтвердить на **spike**, не в споре:
- Точный URL/JSON «очередь пуста», «backend Idle» и «идёт скачивание модели в UI» у твоей версии SwarmUI.
- Реальная цена 100 GB сетевого диска в выбранном сегменте и цена 1×4090 preemptible ₽/час.
- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из списка фоллбека реально есть.
- Хватает ли роли application credential «только delete этого сервера», или Selectel отдаёт более широкую роль.
- Snapshot attached boot volume после bootstrap: время и можно ли сразу create from snapshot.
Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — поднять `IDLE_GRACE_MINUTES`, не отключать killer.
+94
View File
@@ -0,0 +1,94 @@
# Roadmap
Код не начинать, пока не закрыты **0.0** (квота) и **0** (spike). Без GPU в квоте остальное — театр.
## 0.0 Квота GPU
Аккаунт есть, квота скорее 0.
- [ ] В панели: Облачные серверы → квоты / создание сервера с GPU
- [ ] Тикет в поддержку Selectel: 1× GPU (целевой: RTX 4090 24 GB или что дадут в пуле), проект gpu-rent
- [ ] Сервисный пользователь с правом только на этот проект
Пока квота 0, можно писать каркас CLI с dry-run и моками, но не считать этап 0 закрытым.
## 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`)
Заметки — `docs/spike-notes.md`, без секретов и личных имён файлов моделей.
## 1. Каркас
- [x] `pyproject.toml`, пакет `gpu_rent`, MIT
- [x] config / state / Typer по [cli.md](cli.md): `doctor` / `dry-run` / `status` / `open` живые; остальные команды есть и честно говорят, что mutating ещё нет
- [x] Windows: `python -m gpu_rent`
- [x] [setup.md](setup.md) — квота, сервисный пользователь, RC, Civitai
## 2. OpenStack-клиент
- [x] Token через openstacksdk (`authorize`), inventory flavors/квота/volume type
- [ ] Сеть find-or-create, SG, FIP, volumes, server, unshelve
- [ ] Preemptible tag 2.72, BDM boot+data, `delete_on_termination=false`
- [x] Flavor fallback по `FLAVOR_PREFERENCE` (ранжирование; create ещё нет)
- [x] `doctor`: Keystone, квота, flavor, диск, Civitai `.red`, манифесты
- [x] Тесты с моками / без облака
## 3. Сессия без туннеля
- [ ] `up` / `stop` / `status` / reconcile
- [ ] `status`: диск used/free, ₽/час, TTL preempt 24 ч, killer/hold
- [ ] Второй `up` не создаёт второй GPU
- [ ] `resize-data` вверх
- [ ] Генерация `~/.gpu-rent/id_ed25519` + keypair при первом `up`
- [ ] `seed-models` на живом диске (идемпотентно, SHA256)
- [ ] `push` / `push-models`: `Models/`, `Wildcards/`, `CustomWorkflows/`
- [ ] `pull-output` и `PULL_OUTPUT` на `stop` / повторный `up`
- [ ] `seed-extensions` + restart контейнера
- [ ] `destroy` только с флагом
## 4. Bootstrap SwarmUI + idle-killer
- [ ] Идемпотентный first-boot, маркер FS
- [ ] Bind-mounts data volume → `/opt/swarmui`, systemd `swarmui`, auth
- [ ] Clone `extensions.yaml` в Extensions / DLNodes до старта UI
- [ ] Autocomplete: danbooru.csv + Settings.fds; на каждом up — GitHub sha
- [ ] 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
## 5. Туннель
- [ ] `gpu-rent tunnel` → 17801, Ctrl+C не делает `stop`
- [ ] `gpu-rent open` / `tunnel --open`
- [ ] EXPIRED → unshelve + reconnect, пока туннель жив
- [ ] Сниппет MCP в stdout
## 6. UX
- [ ] Живой список flavors, `--no-spot`, печать фоллбека и цены
- [ ] Оценка ₽, предупреждение про диск 24/7 и про idle-killer
- [ ] `NOTIFY_READY`: Windows toast + звук при backend Idle
- [ ] Пользовательский README поверх `docs/`
## Вне скоупа v1
- `generate` в CLI, S3 как источник моделей
- Автоправка Cursor `mcp.json`
- Несколько GPU, публичный URL, Terraform как основной путь
- Tailscale (туннеля достаточно; к сети с телефона не подключались)
+154
View File
@@ -0,0 +1,154 @@
# Контракт с Selectel
Ниже — то, на что опирается код. Не выдуманные URL и не «примерно OpenStack».
## Иерархия размещения
| Термин | Пример | Смысл |
| --- | --- | --- |
| Аккаунт | число в углу панели | Domain в Keystone (`OS_USER_DOMAIN_NAME`) |
| Проект | uuid / имя | Scope токена |
| Пул (region) | `ru-7`, `ru-9` | Куда ходит compute API: `https://<pool>.cloud.api.selcloud.ru/compute/v2.1/` |
| Сегмент пула (AZ) | `ru-7a`, `ru-9a` | Куда ставятся VM и **диски**. Должны совпадать |
`SELECTEL_REGION=ru-1` из старого черновика недостаточно. Flavor, image, volume type (`fast.ru-7a`) и availability zone берутся из **одного сегмента**. Диск из `ru-7a` нельзя приаттачить к VM в `ru-9a`.
GPU есть не во всех сегментах. Перед create — [матрица доступности GPU](https://docs.selectel.ru/en/cloud-servers/create/gpus/) и живой `flavor list` в выбранном AZ.
## Авторизация
Статический API-ключ панели (`X-Token`) **не управляет** объектами OpenStack (серверы, диски, сети).
Нужен **сервисный пользователь** с правом на проект.
1. CLI хранит: account id, username, password, project id/name, pool, pool segment.
2. Запрос токена: `POST https://cloud.api.selcloud.ru/identity/v3/auth/tokens` (password + project scope).
3. Заголовок ответа `X-Subject-Token` — IAM-токен. TTL **24 часа**.
4. Дальнейшие вызовы: `X-Auth-Token` на endpoints из service catalog (compute, volume, network, image).
Токен нельзя один раз прописать в `.env` и забыть. Клиент обновляет его до истечения. Совпадение «24 часа токена» и «24 часа preemptible» легко пропустить: длинная сессия умрёт с 401, VM при этом останется.
Учётные данные сервисного пользователя — секрет. Роль — только нужный проект (compute + block storage + networking), не владелец аккаунта.
Практичный клиент: `openstacksdk.Connection` с теми же полями, что в RC-файле панели (`OS_AUTH_URL`, `OS_PROJECT_ID`, `OS_USERNAME`, `OS_PASSWORD`, `OS_USER_DOMAIN_NAME`, `OS_REGION_NAME`).
## GPU
Актуальный список в панели и в документации Selectel. Для генерации картинок из черновика плана:
| В старом плане | В облаке Selectel |
| --- | --- |
| RTX 3090 | **нет** |
| RTX 4090 | 24 GB и 48 GB |
| A100 40 GB | есть |
Ближайшие бытовые аналоги 3090: **A5000** (как 3080, 24 GB) или **RTX 4090 24 GB**.
Фиксированный flavor связывает GPU + vCPU + RAM. Три независимых вопроса «какая карта / сколько RAM / сколько CPU» допустимы только если CLI создаёт **custom GPU flavor** через Compute API (`gpu: "4090:1"` и т.п.). Для MVP — живой список фиксированных flavors в сегменте.
Custom GPU flavor создаётся только через API, не через `openstack flavor create` в CLI, и живёт в конкретном пуле/проекте.
### Фоллбек, если предпочтительный GPU занят
Квота «есть GPU» ≠ flavor свободен в сегменте. `up` идёт по списку предпочтений (`FLAVOR_PREFERENCE`), не падает на первом `disabled` / нет capacity.
Дефолт (подстроки в имени или extra spec, живые id — из `flavor list` на spike):
1. RTX 4090 24 GB
2. RTX 4090 48 GB
3. A5000
4. A100 40 GB
Поведение:
- Предпочтительный доступен — берём его, печатаем ₽/час.
- Нет — показать следующий доступный из списка с ценой и отличием (VRAM / ₽). Интерактив: подтвердить. `--yes` — взять первый доступный из списка без вопроса, цену всё равно напечатать.
- Ни один из списка не доступен — стоп с текстом «нет GPU в сегменте», не молчаливый CPU-flavor.
- Явный `--flavor <id>` бьёт список: нет этого id → ошибка, без автофоллбека.
## Образы
Не ставить драйверы NVIDIA руками на каждом boot и не брать Windows.
Канонический boot-образ:
**`Ubuntu 24.04 LTS 64-bit GPU Driver 580`**
Без слова **Docker** в имени. SwarmUI и Comfy крутятся на хосте (`launch-linux.sh`, systemd), не в контейнере.
| Вариант | Почему нет |
| --- | --- |
| Тот же образ **с Docker** | Лишний слой, nvidia-ctk, `docker build` на первом bootstrap. Для одной VM не нужен |
| Windows Server | Лицензия, хуже стек под SwarmUI/Comfy |
| Ubuntu без «GPU optimized» | Сами ставите драйвер + kernel pin — съедает preempt-часы |
| Ubuntu 22.04 GPU | Работает, но старше ядро |
| Driver **535** | Для 4090/свежего PyTorch лучше host driver **580** |
| Data Science / Analytics VM | Jupyter/conda, 22.04, ядро не так же pinned |
Фоллбек, если в Glance пула нет 580: `Ubuntu 24.04 LTS 64-bit GPU Driver 535` (тоже без Docker). Образ «… Docker» — только если не-Docker GPU-образа в пуле нет; `doctor` предупредит.
Имена уточнять через Glance (`image list`) — id плавают, в git не класть. Ядро уже pinned: `apt upgrade` ядра ломает `nvidia-smi`. Не трогать `linux-image-*` / `nvidia-*` в unattended-upgrades.
Образ без GPU optimization = сами ставите драйверы, это не наш путь.
## Прерываемые серверы
Документация: [preemptible servers](https://docs.selectel.ru/en/cloud-servers/about/preemptible-servers/), [create](https://docs.selectel.ru/en/cloud-servers/create/create-preemptible-server/), [restore](https://docs.selectel.ru/en/cloud-servers/manage/restore-preemptible-server/).
- Создаются тегом `preemptible` и микроверсией compute **2.72** (`openstack server create --tag preemptible --os-compute-api-version 2.72`).
- Могут быть остановлены в любой момент **в пределах 24 часов** после create или restore.
- При прерывании сервер **не удаляется**, статус **`EXPIRED`**.
- Restore: `openstack server unshelve <server>`. После restore снова preemptible, снова 24 часа.
- Сетевой boot-диск: VM поднимается в том состоянии, в каком остановилась.
- Локальный диск: данные ОС/локали **теряются**, создаётся новая VM из исходного образа.
- SLA облака на preemptible не действует.
- После остановки GPU/CPU/RAM не тарифицируются со следующего часа; сетевые диски и публичные IP — тарифицируются.
Watchdog, который ждёт `SHUTOFF_BY_HOST` и делает delete+create, к Selectel не применим.
## Сеть
Минимальный набор, которого не было в черновике:
1. Приватная сеть + subnet.
2. Router с gateway на внешнюю сеть.
3. Port VM в приватной сети.
4. Security group (если на сети включён port security: без группы трафик **запрещён**).
5. Floating IP (или порт в public subnet) + association.
6. Keypair (публичный ключ пользователя), заведённый на **того же** сервисного пользователя / проекта.
Сеть и router создаются один раз и переиспользуются. Floating IP либо переиспользовать (платим между сессиями), либо выделять на `up` и удалять на `stop`.
Ingress SG: TCP/22 с адреса оператора. 7801 снаружи не открывать.
## Диски
- Сетевой диск создаётся в **том же сегменте**, тип вида `fast.ru-9a` (точное имя — `volume type list`).
- Boot: из GPU-образа, `delete_on_termination=false`.
- Data: пустой, подключается вторым блочным устройством **в том же `server create`**, не отдельным attach после ACTIVE (гонка с cloud-init).
- Удалить VM с приаттаченным диском в OpenStack часто нельзя: detach → wait → delete, либо delete server с корректным BDM.
- Online resize вверх у Selectel есть; вниз — нет. Заложить команду увеличения data-диска, не «создать новый на 100 GB и надеяться».
- После **первого** удачного bootstrap (backend Idle) — один snapshot boot volume `gpu-rent-boot-ok`, если его ещё нет. Следующий create может идти из snapshot, не из сырого GPU-образа. Каждый `up` новый snapshot не плодит. Attached snapshot в OpenStack допустим для v1 (без fsfreeze).
Путь устройства в Linux: чаще virtio (`/dev/vdb`), не `scsi-0Selectel_Volume_<uuid>`. Определять по serial/by-id после проверки, не хардкодить SCSI-имя из другого провайдера.
## Квоты и лимиты
На новых аккаунтах квота GPU часто **0**. Preflight до create:
- квота compute/GPU;
- наличие flavor в сегменте (`OS-FLV-DISABLED:disabled`, extra specs);
- баланс / возможность создать ресурс (ошибка 402/403 — понятное сообщение, не traceback).
Письмо в поддержку — ручной шаг один раз, не часть CLI.
## Теги и имена ресурсов
Чтобы reconcile нашёл сирот без локального state:
- сервер: имя `gpu-rent` (или `gpu-rent-<short>`), теги `gpu-rent`, `preemptible`;
- диски: `gpu-rent-boot`, `gpu-rent-data`;
- snapshot boot: `gpu-rent-boot-ok` (один, не на каждый `up`);
- SG / сеть: `gpu-rent`, если создаём сами.
Не плодить вторую сеть на каждый `up`.
+201
View File
@@ -0,0 +1,201 @@
# Что сделать до первого `gpu-rent up`
Код **не создаёт GPU**, пока не пройден `gpu-rent doctor`. Сначала доступ в облако, квота GPU и ключи. Статический ключ панели **`X-Token` для этого CLI не подходит** — им нельзя управлять серверами и дисками.
Панель: [my.selectel.ru](https://my.selectel.ru).
---
## 1. Python
Нужен **Python 3.11+**. На Windows при установке включи «Add python.exe to PATH».
В корне репозитория:
```powershell
python -m venv .venv
.\.venv\Scripts\activate
python -m pip install -U pip
python -m pip install -e .
```
Проверка:
```powershell
python -m gpu_rent --help
gpu-rent --help
```
---
## 2. Selectel: проект и квота GPU
На новых аккаунтах лимит GPU почти всегда **0**. Без тикета в поддержку `up` создать карту не сможет — это норма, не баг CLI.
### 2.1. Проект
1. Панель → сверху **IAM****Projects** (Проекты).
2. Отдельный проект, например `gpu-rent`. Не клади GPU в общий «мусорный» проект.
3. Скопируй **ID проекта** (uuid). Он же `OS_PROJECT_ID`.
4. **Номер аккаунта** — в правом верхнем углу панели. Он же `OS_USER_DOMAIN_NAME` (domain в Keystone).
### 2.2. Есть ли GPU в квоте
1. **IAM****Projects** → твой проект → вкладка **Quotas and limits** / **Квоты и лимиты****Cloud platform**.
2. Выбери пул и сегмент, где есть GPU (матрица: [GPU availability](https://docs.selectel.ru/en/cloud-servers/create/gpus/)). Для Москвы часто смотрят `ru-7` / `ru-7a`**проверь матрицу**, GPU есть не везде.
3. Строка **GPU** (и при необходимости vCPU/RAM/network volumes). Если лимит 0 — дальше тикет.
Квоту внутри уже выданного лимита можно крутить в панели. **Сам лимит GPU поднимает только поддержка.**
### 2.3. Тикет в поддержку
**Тикеты** в панели (не email вслепую). Лимит увеличивают **на один конкретный проект**.
Текст можно почти копировать:
```text
Прошу увеличить лимит GPU в облачной платформе.
Проект: <имя> (ID: <uuid проекта>)
Пул / сегмент: ru-7 / ru-7a ← подставь свой из матрицы GPU
Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог в этом сегменте: 4090 48 GB или A5000).
Цель: один прерываемый (preemptible) облачный сервер для персональных сессий генерации, диски сетевые.
Сейчас квота/лимит GPU = 0, создать сервер с GPU нельзя.
```
Пока тикет не закрыт, ставишь CLI и гоняешь `doctor` — он как раз покажет «квота 0, напишите в поддержку».
---
## 3. Ключи OpenStack (это и есть «API key» для gpu-rent)
Нужен **сервисный пользователь** + пароль. CLI сам получает IAM-токен на 24 часа (`X-Auth-Token`). В `.env` токен хранить не надо.
**Не используй:** Профиль → Access → API Keys → `X-Token`. Это статический ключ панели, OpenStack (серверы/диски/сети) он **не** двигает.
### 3.1. Сервисный пользователь
Только владелец аккаунта или роль `iam.admin`. На балансе для роли `member` должно быть хотя бы **100 ₽**.
1. Сверху **IAM****Service users** / **Сервисные пользователи**.
2. **Add service user**.
3. Имя, например `gpu-rent-api`.
4. Пароль: **минимум 20 символов**, сохрани в менеджер паролей. После создания пароль **больше не показывают** — только сброс.
5. Права:
- **Scope: Projects** (не весь аккаунт);
- проект `gpu-rent`;
- роль **`member`** (создание серверов/дисков/сетей). Роль `reader` для `up` не хватит.
6. **Add user**.
Официально: [Add user](https://docs.selectel.ru/en/access-control/manage/add-user/), [авторизация API](https://docs.selectel.ru/en/api/authorization/).
### 3.2. Скачать RC-файл (готовые `OS_*`)
1. **IAM****Service users** → твой пользователь → вкладка **Access**.
2. Блок **RC files**:
- проект `gpu-rent`;
- локация = **пул**, например `ru-7` (это `OS_REGION_NAME`, не сегмент `ru-7a`);
- **Download**.
3. Открой файл (`rc.sh`). Из него в `.env` переносятся:
| Переменная | Откуда |
| --- | --- |
| `OS_AUTH_URL` | обычно `https://cloud.api.selcloud.ru/identity/v3` |
| `OS_USER_DOMAIN_NAME` | номер аккаунта |
| `OS_PROJECT_DOMAIN_NAME` | тот же номер (можно не дублировать в нашем `.env`) |
| `OS_PROJECT_ID` | uuid проекта |
| `OS_USERNAME` | имя сервисного пользователя |
| `OS_PASSWORD` | пароль, который ты сохранил (в RC его часто нет — дописываешь сам) |
| `OS_REGION_NAME` | пул, `ru-7` |
| `GPU_RENT_AZ` | **сегмент** пула, `ru-7a` — в RC его может не быть, смотри матрицу GPU |
Официально: [Configure OpenStack CLI](https://docs.selectel.ru/en/cloud-servers/tools/openstack-cli/configure-openstack-cli/).
### 3.3. Куда класть
Создай каталог и файл **вне git**:
```powershell
mkdir $env:USERPROFILE\.gpu-rent
copy env.example $env:USERPROFILE\.gpu-rent\.env
notepad $env:USERPROFILE\.gpu-rent\.env
```
Вставь значения из RC + пароль + `GPU_RENT_AZ`. Никогда не коммить `.env`.
Проверка без нашего CLI (необязательно):
```powershell
# после pip install python-openstackclient, если хочешь
openstack token issue
openstack flavor list
```
Наш способ: `gpu-rent doctor`.
---
## 4. Civitai API token (модели)
Нужен, если хочешь seed с Civitai по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это допустимо.
1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что и для `.red`).
2. [Account settings](https://civitai.com/user/account) → блок **API Keys****Add API key**.
3. Имя, например `gpu-rent`. Токен показывают **один раз**.
4. В `.env`: `CIVITAI_API_TOKEN=...`
5. Хост API по умолчанию **`civitai.red`** (полный каталог). `.com` — SFW-витрина, NSFW с неё часто 404. Один токен на оба домена.
Не клади токен в query-string в скриптах «на память» — в логах светится. CLI шлёт `Authorization: Bearer …` только на `civitai.com` / `civitai.red` / `civitai.green`, не на CDN.
Манифест (не в git со своими id, если не хочешь светить вкусы):
```powershell
copy models.example.yaml $env:USERPROFILE\.gpu-rent\models.yaml
```
`version_id: 0` — заглушка, doctor её игнорирует. Нужен **modelVersionId** из URL, не id карточки модели.
---
## 5. Git-токен (только приватные репы расширений)
Публичные GitHub-репы в `extensions.yaml` клонируются без токена.
Если репа приватная:
1. GitHub → Settings → Developer settings → Personal access tokens.
2. Fine-grained: доступ только к нужным репам, **Contents: Read**.
3. `GIT_TOKEN` в `.env`.
Скопируй шаблон: `extensions.example.yaml``~\.gpu-rent\extensions.yaml`. Пустой файл = стоковый SwarmUI.
---
## 6. SSH
Ключ **не надо** делать руками. CLI создаст `~\.gpu-rent\id_ed25519` без passphrase и зарегистрирует keypair в OpenStack при первом `up`. `doctor` только проверяет, что это получится.
---
## 7. Чеклист перед `doctor`
- [ ] Python 3.11+, `pip install -e .`
- [ ] Проект Selectel, скопирован uuid
- [ ] Тикет на лимит **1× GPU** в нужном сегменте (или квота уже > 0)
- [ ] Сервисный пользователь `member` на этот проект, пароль сохранён
- [ ] RC скачан на **тот же пул**, где GPU
- [ ] `~\.gpu-rent\.env` заполнен (`OS_*` + `GPU_RENT_AZ`)
- [ ] Нет `X-Token` вместо пароля сервисного пользователя
- [ ] (опционально) Civitai token + `models.yaml`
- [ ] На балансе хватает на диск 100 GB **даже когда GPU выключен**
Дальше:
```powershell
gpu-rent doctor
```
Exit 0 — облако отвечает, можно идти к spike / `up`, когда команда появится. Exit 1 — в отчёте причина (часто квота GPU = 0).
`gpu-rent dry-run` печатает план без создания сервера.
+55
View File
@@ -0,0 +1,55 @@
# SwarmUI на GPU-сервере
Репозиторий: [mcmonkeyprojects/SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI).
**Без Docker.** На VM процесс слушает **7801** на loopback (`launch-linux.sh`). На ноутбуке туннель — **17801**.
## Чего нет
- Docker, nvidia-container-toolkit, образа на Docker Hub.
- Порта 7860 (это Automatic1111).
- Зеркала **локального** SwarmUI. С ноутбука едут только деревья приложения: [local-folders.md](local-folders.md). Первый Civitai-seed — [models.md](models.md).
## Bootstrap (один раз на boot volume)
1. Образ Selectel **Ubuntu 24.04 LTS GPU Driver 580** (без Docker в имени, см. [selectel.md](selectel.md)).
2. Git clone SwarmUI в `/opt/swarmui`. .NET — `launchtools/linux-dotnet-install.sh` (SDK 8 и 10, как в доке SwarmUI).
3. Bind-mounts с data volume в дерево `/opt/swarmui` — таблица в [architecture.md](architecture.md).
4. Clone git-расширений из манифеста ([extensions.md](extensions.md)), если файл не пустой.
5. Word-list autocomplete в `Data/Autocompletions` и `DefaultUser.AutoComplete.Source` ([autocomplete.md](autocomplete.md)).
6. Если есть Civitai-токен и манифест моделей: seed весов **до** первого старта SwarmUI; иначе — дефолтная модель установщика.
7. Push непустых `Models/` / `Wildcards/` / `CustomWorkflows/`.
8. systemd unit `swarmui`: `./launch-linux.sh --launch_mode none --host 127.0.0.1 --port 7801`.
9. Авторизация SwarmUI включена, токен в `Data` на диске.
10. systemd unit idle-killer **после** seed и backend Idle.
11. Один snapshot boot volume `gpu-rent-boot-ok`, если ещё нет.
Рестарт UI: `systemctl restart swarmui`, не `docker restart`.
## ComfyUI
Первый запуск качает backend в `/opt/swarmui/dlbackend` (это bind на data volume). Иначе каждый recreate потеряет часы.
Пока CUDA/ComfyUI поднимаются, UI уже может отвечать. Для MCP и `/API/` — рано: `ready` после Idle backend (подтвердить на spike).
## Доступ с ноутбука
Пока запущен `gpu-rent tunnel`:
| Что | URL |
| --- | --- |
| UI | `http://127.0.0.1:17801` |
| HTTP API | `http://127.0.0.1:17801/API/` |
| MCP | `http://127.0.0.1:17801/mcp` |
Локальный инстанс на 7801 продолжает жить. В Cursor MCP на время сессии переключают на 17801 (вручную по сниппету CLI).
Без туннеля API с ноутбука недоступен: 7801 на VM не опубликован в интернет.
## Чего не делать
- `chmod -R 777` на Models.
- Слушать 7801 на `0.0.0.0`.
- Ставить Docker «на всякий случай».
- Считать Nova `ACTIVE` = можно генерировать.
- `apt upgrade` ядра (ломает nvidia-smi).