Update extensions and documentation for LLM integration and CLI enhancements

- Added support for a new extension, `swarm-assistent`, in `extensions.example.yaml` with a requirement for `ollama`.
- Enhanced the README.md to clarify the setup process and provide a quick start guide for using extensions.
- Updated documentation in `llm.md` to reflect the opt-in nature of LLM support and provide clearer instructions for enabling it.
- Improved the `autocomplete.md` to detail the automatic setup of word lists during the initial launch.
- Revised `cli.md` to include new commands and options related to LLM runtime handling and extension management.
- Enhanced the `spike-notes.md` to guide users through the first live run with a focus on LLM integration.
This commit is contained in:
Leonid Pershin
2026-08-21 05:49:34 +03:00
parent dc1fde9e3e
commit 71f4e4c2e3
19 changed files with 713 additions and 394 deletions
+62 -55
View File
@@ -1,75 +1,81 @@
# gpu-rent
Прерываемый GPU в [Selectel](https://selectel.ru) + SwarmUI на `http://127.0.0.1:17801`.
Модели и ComfyUI живут на сетевом диске — платишь за GPU только пока он поднят.
Прерываемый GPU в [Selectel](https://selectel.ru) + [SwarmUI](https://github.com/mcmonkeyprojects/SwarmUI) на `http://127.0.0.1:17801`.
**Сначала ключи и квота GPU, не `up`.** → [`docs/setup.md`](docs/setup.md)
Модели и ComfyUI живут на сетевом диске. **GPU платится только пока жив compute** (`up``stop` или idle-killer). Диск — **всегда**, даже после `stop`.
---
## Быстрый старт
## С нуля → первая картинка
```powershell
.\gpu-rent.ps1 doctor # preflight
.\gpu-rent.ps1 flavors # что выберет фоллбек
.\gpu-rent.ps1 dry-run # план без create
.\gpu-rent.ps1 up --yes # GPU + SwarmUI + seed + туннель :17801
.\gpu-rent.ps1 up --yes --no-tunnel # только облако
.\gpu-rent.ps1 tunnel --open # снова открыть UI, если туннель закрыли
.\gpu-rent.ps1 hold # отложить idle-killer
.\gpu-rent.ps1 stop # гасить compute, диски оставить
```
Делай по порядку. Подробности и скрины панели — в [docs/setup.md](docs/setup.md).
| # | Шаг | Команда / действие |
| --- | --- | --- |
| 1 | Python 3.11+, клон репо | — |
| 2 | Квота GPU в Selectel (> 0) или тикет | [setup §2](docs/setup.md#2-selectel-проект-и-квота-gpu) |
| 3 | Сервисный пользователь + `.env` из RC | [setup §3](docs/setup.md#3-ключи-openstack-это-и-есть-api-key-для-gpu-rent) |
| 4 | (Опц.) Civitai token + `models.yaml` | [setup §4](docs/setup.md#4-civitai-api-token-модели) |
| 5 | Preflight | `.\gpu-rent.ps1 doctor` |
| 6 | Поднять GPU + туннель | `.\gpu-rent.ps1 up --yes` |
| 7 | Открыть UI | браузер сам / `.\gpu-rent.ps1 open` |
| 8 | Закончить сессию | `.\gpu-rent.ps1 stop` |
Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
Лаунчер сам создаёт `.venv` и ставит пакет. Конфиг — **в корне репо**:
| Файл | Что |
| --- | --- |
| `.env` | из `env.example``OS_*`, Civitai |
| `models.yaml` | из `models.example.yaml` — seed с Civitai |
| `extensions.yaml` | из `extensions.example.yaml` |
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
**Не начинай с `up`**, пока `doctor` не зелёный. Ключ панели `X-Token` для этого CLI **не подходит**.
---
## Команды
## Обычный день
```powershell
.\gpu-rent.ps1 up --yes # GPU + SwarmUI + туннель :17801
# работаешь в браузере…
# Ctrl+C в терминале = только туннель off, GPU жив
.\gpu-rent.ps1 tunnel --open # снова UI
.\gpu-rent.ps1 hold # отложить idle-killer
.\gpu-rent.ps1 stop # погасить GPU, диски оставить
```
| Важно | |
| --- | --- |
| `Ctrl+C` на туннеле | **не** гасит GPU |
| Простой ~30 мин | idle-killer сам удалит compute |
| Preemptible | ~24 ч → `EXPIRED`; `tunnel` / `up` поднимут снова |
| Двойной клик лаунчера | задай `GPU_RENT_DEFAULT_ARGS=up --yes` в `gpu-rent.vars` |
---
## Команды (кратко)
| Команда | Смысл |
| --- | --- |
| `doctor` | Keystone, квота GPU, flavor, Civitai, манифесты |
| `flavors` | Живой список по `FLAVOR_PREFERENCE` |
| `up` / `up --yes` | Поднять GPU + туннель :17801 (браузер). Ctrl+C = туннель off |
| `up --no-tunnel` | Только облако |
| `up --no-spot` / `up --flavor ID` | Обычный тариф / явный flavor |
| `tunnel` / `tunnel --open` | Повторный проброс; watchdog на EXPIRED |
| `open` | Браузер на 17801 |
| `hold` / `hold --minutes 90` / `hold --clear` | Пауза idle-killer |
| `status` | Nova + диск + killer |
| `seed-models` / `seed-extensions` | Докачать на живой диск |
| `push` / `push-models` / `pull-output` | Локальные папки ↔ VM |
| `resize-data --gb 400` | Диск только вверх |
| `setup` | Wizard: `.env`, LLM, watchdog |
| `doctor` | Preflight без create |
| `flavors` / `dry-run` | Что выберет / план без денег |
| `up` / `up --yes` | GPU + seed + туннель |
| `up --ollama` | + Ollama (см. [docs/llm.md](docs/llm.md)) |
| `tunnel` / `open` | Снова UI / браузер |
| `hold` / `status` | Пауза killer / состояние |
| `stop` / `destroy --i-understand-data-loss` | Стоп GPU / + диски |
| `push` / `pull-output` / `seed-*` | Файлы ↔ VM |
Полный список: [docs/cli.md](docs/cli.md).
---
## Деньги и риски
## Конфиг в корне репо
- **GPU** — пока жив compute (`up``stop` или idle-killer).
- **Диск data** — **24/7**, даже после `stop`.
- Цены в OpenStack API **нет** — смотри панель Selectel.
- Preemptible по умолчанию (~24 ч) → `EXPIRED`; `tunnel` сам unshelve, иначе `up`.
- Idle-killer: льгота после boot, потом N мин пустой очереди → delete. `hold` откладывает.
- `Ctrl+C` на tunnel **не** гасит GPU.
| Файл | Что |
| --- | --- |
| `.env` | из `env.example``OS_*`, Civitai (не в git) |
| `gpu-rent.vars` | из example — несекретные дефолты, лаунчер |
| `models.yaml` / `extensions.yaml` | манифесты (из `*.example.yaml`) |
| `Models/` … | локальный push на `up` |
| `.gpu-rent/` | state, SSH-ключ (gitignore) |
---
## Модели
1. Civitai-seed по `models.yaml` (нужен `CIVITAI_API_TOKEN`, хост `civitai.red`).
2. Локальные `Models/`, `Wildcards/`, `CustomWorkflows/` — непустые едут на каждый `up`.
Пример в репо уже с Krea 2 + LoRA — скопируй в `models.yaml`.
Лаунчер сам создаёт `.venv` и ставит пакет.
---
@@ -77,10 +83,11 @@ Unix: `./gpu-rent.sh …` (один раз `chmod +x gpu-rent.sh`).
| Док | Зачем |
| --- | --- |
| [docs/setup.md](docs/setup.md) | Ключи, квота, RC |
| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист живого spike |
| [docs/cli.md](docs/cli.md) | Все команды и `.env` |
| [docs/decisions.md](docs/decisions.md) | Продуктовые решения |
| [docs/roadmap.md](docs/roadmap.md) | Что готово / что нет |
| [docs/setup.md](docs/setup.md) | **Пошаговая подготовка** до первого `up` |
| [docs/cli.md](docs/cli.md) | Все команды и переменные |
| [docs/llm.md](docs/llm.md) | Ollama / llama.cpp |
| [docs/models.md](docs/models.md) | Civitai + папка `Models/` |
| [docs/spike-notes.md](docs/spike-notes.md) | Чеклист первого живого прогона |
| [docs/README.md](docs/README.md) | Оглавление всего `docs/` |
MIT. Секреты и веса в git не коммитить.
+32 -26
View File
@@ -1,39 +1,45 @@
# Документация gpu-rent
CLI-оркестратор: поднимает прерываемый GPU-сервер в Selectel, монтирует постоянные сетевые диски и открывает SwarmUI на `localhost` через SSH-туннель. Платим за GPU только пока идёт сессия; модели и ComfyUI живут на диске.
CLI поднимает прерываемый GPU в Selectel, держит SwarmUI/модели на сетевых дисках и открывает UI на `localhost:17801` через SSH-туннель.
**Код v1 готов.** Живой `up` ждёт квоту GPU и зелёный `doctor`. Старт руками: [setup.md](setup.md), прогон: [spike-notes.md](spike-notes.md).
## С чего начать
## Как читать
1. **[setup.md](setup.md)** — квота, ключи, `.env`, первый `doctor``up``stop`
2. Корневой **[README](../README.md)** — быстрый старт и обычный день
3. При первом живом GPU заполни **[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 |
| [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 |
| [llm.md](llm.md) | Opt-in Ollama / llama.cpp, `ollama-models.yaml`, порты 17811/17812 |
| [cli.md](cli.md) | Команды, конфиг, локальный state |
| [roadmap.md](roadmap.md) | Порядок реализации |
| [open-questions.md](open-questions.md) | Ещё не закрыто |
| Понять зачем и сколько стоит | [concept.md](concept.md) |
| Все команды и `.env` | [cli.md](cli.md) |
| Модели с Civitai / `Models/` | [models.md](models.md) |
| Git-расширения SwarmUI/Comfy | [extensions.md](extensions.md) |
| Word-list промптов | [autocomplete.md](autocomplete.md) |
| Push/pull папок | [local-folders.md](local-folders.md) |
| Ollama / llama.cpp | [llm.md](llm.md) |
| Как устроены диски и killer | [architecture.md](architecture.md) |
| Контракт Selectel | [selectel.md](selectel.md) |
| Нативный SwarmUI на VM | [swarmui.md](swarmui.md) |
| Зафиксированные решения | [decisions.md](decisions.md) |
| Статус реализации | [roadmap.md](roadmap.md) |
| Что ещё проверить на spike | [open-questions.md](open-questions.md) |
## Источники
Ревью кода: `docs/reviews/` (для разработки, не для первого запуска).
## Источники (Selectel / SwarmUI / Civitai)
Проверено 21 августа 2026:
- [Selectel: preemptible servers](https://docs.selectel.ru/en/cloud-servers/about/preemptible-servers/)
- [Selectel: preemptible](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: GPU drivers](https://docs.selectel.ru/en/cloud-servers/manage/install-gpu-drivers/)
- [Selectel: 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](https://github.com/mcmonkeyprojects/SwarmUI)
- [Civitai Site API](https://developer.civitai.com/)
- [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)
+3 -2
View File
@@ -17,8 +17,9 @@
┌─ 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)
│ idle-killer: очередь / hold / LLM busy → delete this server
│ app cred: DELETE/GET только этот server_id (fail closed)
│ optional: Ollama :11434 / llama.cpp :8080 (туннель 17811/12)│
│ │
│ boot volume (network) ОС + NVIDIA + SwarmUI нативно + snapshot │
│ data volume (network) Models, Output, Data, workflows │
+3 -1
View File
@@ -2,7 +2,9 @@
Официальная инструкция: [docs/Features/Autocompletions.md](https://github.com/mcmonkeyprojects/SwarmUI/blob/master/docs/Features/Autocompletions.md).
Нужен word-list (`.csv` / `.txt`) в `Data/Autocompletions` и выбранный источник в настройках пользователя. Без файла в UI нечего выбирать.
**По умолчанию включено** (`AUTOCOMPLETE_ENABLED=true`): на bootstrap CLI качает `danbooru.csv` и прописывает источник в Settings **до** первого старта SwarmUI. Ничего руками делать не нужно.
Выключить: `AUTOCOMPLETE_ENABLED=false` в `.env`.
## Поведение gpu-rent
+135 -113
View File
@@ -1,144 +1,153 @@
# CLI и конфигурация
Имя команды: `gpu-rent`. Windows и Linux. Лицензия MIT, в репозитории нет секретов и имён личных чекпоинтов.
Имя команды: `gpu-rent`. Лаунчеры: `gpu-rent.bat` / `.\gpu-rent.ps1` / `./gpu-rent.sh` (создают `.venv` и вызывают `python -m gpu_rent`).
Без аргументов CLI показывает **help**. Поднять GPU с двойного клика: в `gpu-rent.vars``GPU_RENT_DEFAULT_ARGS=up --yes`.
---
## Типичные сценарии
### Первый раз
```text
gpu-rent doctor
gpu-rent up --yes
# … работа …
gpu-rent stop
```
Подготовка ключей: [setup.md](setup.md).
### Обычная сессия
```text
gpu-rent up --yes
# Ctrl+C → туннель off, GPU жив
gpu-rent tunnel --open
gpu-rent hold
gpu-rent stop
```
### Только облако (туннель позже)
```text
gpu-rent up --yes --no-tunnel
gpu-rent tunnel --open
```
### LLM рядом со SwarmUI
```text
gpu-rent setup # или
gpu-rent up --yes --ollama
```
Подробности: [llm.md](llm.md).
---
## Команды
| Команда | Поведение |
| --- | --- |
| `gpu-rent flavors` | Скан `SCAN_POOLS` (ru-6 multizone…) × `FLAVOR_PREFERENCE`, затем список в текущем `OS_REGION_NAME` |
| `gpu-rent doctor` | Preflight **без** create: Keystone, квота GPU, flavor в AZ, диски, Civitai token+`.red`, манифесты, SSH-ключ. Код выхода ≠ 0, если сессию нельзя начать |
| `gpu-rent setup` | Wizard: `.env`/манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog |
| `gpu-rent up` / `up --yes` | Preflight → create/unshelve → bootstrap → git update → optional LLM → **туннель** `localhost:17801`; Ctrl+C закрывает туннель |
| `gpu-rent up --ollama` / `--llamacpp` / `--llm …` | Поднять LLM рядом со SwarmUI (см. [llm.md](llm.md)) |
| `gpu-rent up --no-update` | Без `git pull` SwarmUI и extensions (только недостающие clone) |
| `gpu-rent up --no-tunnel` | Только облако + bootstrap, без локального проброса |
| `gpu-rent up --no-spot` | Обычный (не preemptible) сервер |
| `gpu-rent up --flavor … --yes` | Без вопросов. `--flavor` бьёт список фоллбека. `--yes` без `--flavor` берёт первый доступный из `FLAVOR_PREFERENCE` |
| `gpu-rent tunnel` | Повторный SSH-проброс, если `up --no-tunnel` или туннель уже закрыли. 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 watchdog install` | Локальный тик (Task Scheduler / systemd user / launchd): аварийное закрытие туннеля → `stop` после grace |
| `gpu-rent watchdog uninstall` | Снять локальный сервис |
| `gpu-rent watchdog status` | Установлен ли сервис + local lease |
| `gpu-rent watchdog tick` | Один тик (для планировщика; `--dry-run` без delete) |
| `gpu-rent setup` | Wizard: манифесты, `LLM_RUNTIME`, пресет ollama-models, опционально local-watchdog |
| `gpu-rent doctor` | Preflight **без** create. Exit ≠ 0 → сессию начинать нельзя |
| `gpu-rent flavors` | Скан `SCAN_POOLS` × `FLAVOR_PREFERENCE`, список в текущем регионе |
| `gpu-rent dry-run` | План без mutating-вызовов |
| `gpu-rent up` / `up --yes` | Create/unshelve → bootstrap → optional LLM → **туннель** `:17801`. Ctrl+C = туннель off |
| `gpu-rent up -v` / `--verbose` | Полная таблица doctor на `up` (по умолчанию кратко) |
| `gpu-rent up --ollama` / `--llamacpp` / `--llm …` | LLM рядом со SwarmUI |
| `gpu-rent up --no-update` | Без `git pull` SwarmUI/extensions (только недостающие clone) |
| `gpu-rent up --no-tunnel` | Только облако |
| `gpu-rent up --no-spot` | Не preemptible |
| `gpu-rent up --flavor ID --yes` | Явный flavor; без `--flavor` + `--yes` — первый доступный из `FLAVOR_PREFERENCE` |
| `gpu-rent tunnel` / `tunnel --open` | Повторный проброс; `--open` сразу браузер. Ctrl+C GPU **не** гасит |
| `gpu-rent open` / `open --llm` | Браузер на SwarmUI / LLM-порт (туннель уже должен слушать) |
| `gpu-rent status` | State, Nova, диск, killer/hold, LLM, local-watchdog |
| `gpu-rent hold` / `--minutes N` / `--until ISO` / `--clear` | Пауза idle-killer (нужны живая VM + SSH) |
| `gpu-rent stop` / `stop --no-pull` | Удалить compute (+ FIP), диски оставить; optional pull Output |
| `gpu-rent destroy --i-understand-data-loss` | `stop` + диски |
| `gpu-rent logs` | journalctl swarmui / cloud-init |
| `gpu-rent ssh` | Оболочка на VM |
| `gpu-rent seed-models` | Докачать новые строки Civitai-манифеста на живой диск |
| `gpu-rent seed-extensions` | Доклонировать/обновить git-репы; restart swarmui |
| `gpu-rent push` / `push-models` | Локальные деревья → VM |
| `gpu-rent pull-output` | VM `Output/``./Output` |
| `gpu-rent resize-data --gb 400` | Data volume **только вверх** |
| `gpu-rent watchdog install` / `uninstall` / `status` / `tick` | Локальный safety-net (см. ниже) |
Второй `up` при живой VM: не создавать второй GPU; сделать autocomplete-check и **push** локальных папок; если `PULL_OUTPUT` — подтянуть Output; напомнить про `tunnel` / `stop`. `--adopt` если нашли тег без state.
Второй `up` при живой VM: не создаёт второй GPU; autocomplete-check + push локальных папок; при `PULL_OUTPUT` — подтянуть Output. `--adopt` — подхватить тег без state.
Нет команды `generate`. Нет зеркала каталога локального SwarmUI — только папки приложения, см. [local-folders.md](local-folders.md).
Нет команды `generate`. Локальный SwarmUI целиком не зеркалируем — только папки приложения ([local-folders.md](local-folders.md)).
Сейчас в коде: после seed/`start swarmui` — idle-killer, Idle backend, boot snapshot, toast/MCP-сниппет, `resize-data`. `tunnel` с Nova-watchdog: EXPIRED → unshelve + reconnect. Опционально `watchdog install` — локальный safety net. UX-полировка flavors/цен ещё впереди.
## Local watchdog
Опционально. **Idle-killer на VM** остаётся основным: ноут можно закрыть, GPU живёт до простоя. Local watchdog — если хочешь гасить GPU при «убили окно / ребут» без `stop`.
1. `gpu-rent watchdog install` (раз на машине, в корне репо).
2. Пока крутится `up`/`tunnel`, пишется heartbeat в `.gpu-rent/local-lease.json`.
3. **Ctrl+C** → detach, GPU **не** трогаем (как раньше).
4. Процесс умер / ребут → через `LOCAL_WATCHDOG_GRACE_MINUTES` (дефолт 10) тик вызывает `stop` (диски остаются).
5. `gpu-rent stop` чистит lease сам.
Без `install` поведение прежнее. Без открытого туннеля (`up --no-tunnel`) lease не вооружается — работает только VM idle-killer.
---
## `doctor`
Не создаёт сервер, не тратит GPU. Нужен до первого `up` и когда «вчера работало».
Не создаёт сервер и не тратит GPU. Нужен до первого `up` и когда «вчера работало».
Проверки по порядку, все печатаются (не падать на первой, собрать отчёт):
Проверки (все печатаются в отчёт):
1. Читается `.env`, обязательные `OS_*`.
2. Keystone: токен выдаётся.
3. Квота GPU > 0; иначе текст «напишите в поддержку Selectel».
4. В сегменте есть хотя бы один flavor из `FLAVOR_PREFERENCE` (не `disabled`).
5. Volume type сегмента существует; data 100 GB влезает в квоту дисков.
6. SSH-ключ: есть `<repo>/.gpu-rent/id_ed25519` или CLI сможет его создать.
7. Если задан `CIVITAI_API_TOKEN`HEAD/лёгкий запрос к `CIVITAI_API_HOST` (дефолт `.red`); манифест парсится.
8. `extensions.yaml` парсится, если файл есть.
9. Локальные папки: предупреждение, если `Models/` огромный относительно свободного места на будущем диске.
1. Читается `.env`, обязательные `OS_*`
2. Keystone выдаёт токен
3. Квота GPU > 0 (иначе текст про поддержку Selectel)
4. В сегменте есть flavor из `FLAVOR_PREFERENCE`
5. Volume type / место под data ~100 GB
6. SSH-ключ есть или будет создан в `.gpu-rent/`
7. Если есть `CIVITAI_API_TOKEN`доступ к API-хосту; манифест парсится
8. `extensions.yaml` парсится, если есть
9. Предупреждения по огромным локальным `Models/`
Exit 0 можно `up`. Exit 1 — нельзя, причина в отчёте.
Exit 0 можно `up`. Exit 1 причина в таблице / кратком списке на `up`.
## `status`
---
Без туннеля. Если SSH есть — ещё диск и killer; если нет — только OpenStack.
## `status` и `hold`
| Поле | Откуда |
| --- | --- |
| Фаза 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` |
**status** — без туннеля. При SSH: ещё `df` диска и состояние killer; иначе только OpenStack + локальный state.
## `hold`
**hold** пишет на VM `/mnt/swarm_data/.gpu-rent-hold-until`. Killer не удаляет compute, пока `now < ts`. Без живой VM — ошибка (не «запомню на потом»). После unshelve hold на диске сохраняется.
Пишет на VM `/mnt/swarm_data/.gpu-rent-hold-until` (unix ts). Killer не удаляет compute, пока `now < ts`. Нужен SSH и живой сервер.
---
Без VM — ошибка, не «запомню на потом». После `unshelve` hold-файл на диске сохраняется.
## Local watchdog (опционально)
## Что печатать при `up`
Основной авто-stop — **idle-killer на VM** (ноут можно закрыть). Local watchdog — если хочешь гасить GPU при «убили окно / ребут» без `stop`.
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. Готово:
1. `gpu-rent watchdog install` (раз на машине, из корня репо)
2. Пока крутится `up`/`tunnel`, пишется heartbeat `.gpu-rent/local-lease.json`
3. **Ctrl+C** → detach, GPU **не** трогаем
4. Процесс умер / ребут → через `LOCAL_WATCHDOG_GRACE_MINUTES` (дефолт 10) → `stop`
5. `gpu-rent stop` чистит lease сам
```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
```
Без `install` поведение прежнее. При `up --no-tunnel` lease не вооружается.
Сниппет MCP для Cursor — в stdout, `mcp.json` не редактировать.
---
## Конфигурация
| Файл | Назначение |
| --- | --- |
| `.env` | Секреты и OpenStack (`OS_*`, токены). Не в git |
| `gpu-rent.vars` | Параметры запуска / несекретные дефолты. Читают `gpu-rent.ps1` / `.sh` / `.bat` и CLI. Пример: `gpu-rent.vars.example` |
| `models.yaml` / `extensions.yaml` | Манифесты |
| `gpu-rent.vars` | Несекретные дефолты; читают лаунчеры и CLI. Пример: `gpu-rent.vars.example` |
| `models.yaml` / `extensions.yaml` / `ollama-models.yaml` | Манифесты |
`.env` в корне репозитория (рядом с `env.example`).
В `gpu-rent.vars` (создаётся из example при первом запуске):
### Лаунчер (`gpu-rent.vars`)
```env
# Двойной клик / запуск лаунчера без аргументов (CLI сам по себе показывает help):
# Двойной клик / запуск без аргументов у .ps1/.bat/.sh:
GPU_RENT_DEFAULT_ARGS=up --yes
# Дописать ко всем вызовам:
# GPU_RENT_EXTRA_ARGS=--no-update
UPDATE_GIT=true
LLM_RUNTIME=none
```
### Основные переменные (`.env` / vars)
```env
OS_AUTH_URL=https://cloud.api.selcloud.ru/identity/v3
OS_USER_DOMAIN_NAME=
@@ -150,6 +159,8 @@ GPU_RENT_AZ=ru-7a
SSH_PRIVATE_KEY_PATH=
SSH_USER=ubuntu
# Пусто = авто /32 твоего IP. Для spike/WARP иногда 0.0.0.0/0:
GPU_RENT_SSH_CIDR=
BOOT_VOLUME_ID=
DATA_VOLUME_ID=
@@ -167,46 +178,57 @@ 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
LLM_RUNTIME=none
OLLAMA_LOCAL_PORT=17811
LLAMACPP_LOCAL_PORT=17812
UPDATE_GIT=true
DEFAULT_FLAVOR_ID=
FLAVOR_PREFERENCE=4090-24,4090-48,a5000,a100-40
FLAVOR_FALLBACK=true
SCAN_POOLS=ru-6,ru-7
DEFAULT_SPOT=true
KEEP_FLOATING_IP=false
IDLE_MINUTES=30
IDLE_GRACE_MINUTES=45
# LOCAL_WATCHDOG_GRACE_MINUTES=10
PULL_OUTPUT=false
NOTIFY_READY=true
```
Flavor id в git не хардкодить. `FLAVOR_PREFERENCE` — ярлыки; реальные uuid резолвятся в сегменте. `FLAVOR_FALLBACK=false` — только `DEFAULT_FLAVOR_ID` / `--flavor`, без следующего в списке.
Flavor uuid в git не хардкодить. `FLAVOR_FALLBACK=false` — только `DEFAULT_FLAVOR_ID` / `--flavor`.
Application credential для idle-killer CLI создаёт при bootstrap и кладёт на VM; в `.env` ноутбука его можно не дублировать.
Application credential для idle-killer CLI создаёт на `up` (узко: DELETE/GET **этого** server) и отзывает на `stop`. В `.env` ноутбука дублировать не нужно.
Пути локальных папок по умолчанию — каталоги в корне приложения: `Models/`, `Wildcards/`, `CustomWorkflows/`, `Output/`.
Пути локальных папок по умолчанию: `Models/`, `Wildcards/`, `CustomWorkflows/`, `Output/` в корне репо.
Полный шаблон: [`env.example`](../env.example).
---
## State
`<repo>/.gpu-rent/state.json` — ids, фаза (`idle` / `ready_cloud` / `ready_tunneled` / …), flavor, timestamps create/unshelve (для окна 24 ч). Без паролей. Lockfile там же, чтобы два `up` не создали два сервера.
`<repo>/.gpu-rent/state.json` — ids, фаза, timestamps (окно preempt 24 ч). Без паролей. Lockfile рядом — два `up` не создадут два сервера.
---
## Ожидание готовности
Потолок ~2040 мин на первый bootstrap, ~510 мин на unshelve:
Ориентир: **~2040 мин** первый 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.
2. TCP 22 / SSH
3. HTTP `http://127.0.0.1:7801` **на VM** (через SSH)
4. Backend Idle → toast (если `NOTIFY_READY`) + access-card
## Windows
Локальный порт UI: **17801** (на VM по-прежнему 7801 на loopback).
Лаунчеры в корне: `gpu-rent.bat` (cmd / двойной клик), `.\gpu-rent.ps1` (PowerShell). На Unix — `./gpu-rent.sh`. Они создают `.venv` в репозитории и вызывают `python -m gpu_rent`. Ключ генерирует CLI: `<repo>/.gpu-rent/id_ed25519`.
---
`NOTIFY_READY`: toast через WinRT / `win10toast` (что заработает на 10/11 без админ-прав) + системный звук. Если toast недоступен — только звук и лог, не падать.
## Windows / notify
`NOTIFY_READY`: toast + системный звук, когда backend Idle. Если toast недоступен — только звук и лог, без падения CLI.
+1
View File
@@ -24,6 +24,7 @@ GPU в облаке дорогой. Веса для генерации карт
- Первый 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.
- Opt-in LLM (Ollama / llama.cpp) через `setup` / `--ollama` / `LLM_RUNTIME`.
- `status`: диск, ₽/час, окно preempt 24 ч, состояние killer.
- Snapshot boot-диска после первого удачного bootstrap.
+6 -4
View File
@@ -32,11 +32,13 @@
| 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-ключ |
| Bare CLI | Без аргументов — help. `up` только явно или через `GPU_RENT_DEFAULT_ARGS` у лаунчера |
| Flavor fallback | Список предпочтений (4090 → A5000 → …). Нет первого — предложить следующий с ценой, `--yes` берёт первый доступный из списка |
| Готово | Windows toast + звук (и лог), когда backend Idle |
| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold |
| Готово | Windows toast + звук (и лог), когда backend Idle; access-card с URL/MCP |
| Status | Диск used/free, ₽/час, сколько до 24 ч preempt, состояние killer / hold / LLM |
| Boot snapshot | После первого удачного bootstrap — один snapshot boot volume `gpu-rent-boot-ok`; следующие create могут идти с него |
| Open | `gpu-rent open` / флаг после `tunnel`: браузер на 17801 |
| Open | `gpu-rent open` / `open --llm` / флаг после `tunnel`: браузер на 17801 / LLM-порт |
| App cred idle-killer | Только этот `server_id`; fail closed без unrestricted; revoke на stop |
Следствия, которые из этого вытекают и тоже считаются принятыми:
@@ -44,4 +46,4 @@
2. **Локальный SwarmUI на 7801 не трогаем.** Туннель по умолчанию на **17801** (на VM по-прежнему 7801 на loopback).
3. **`Ctrl+C` на туннеле не удаляет VM.** Иначе «закрыл ноут» невозможно. Чтобы убить GPU — `gpu-rent stop` или простой. При установленном **local-watchdog** Ctrl+C по-прежнему detach; убийство процесса/ребут без detach → stop после grace.
4. Idle-killer на VM **не может** быть `shutdown -h`: у Selectel остановленная изнутри VM часто продолжает тарифицировать ресурсы. Нужен вызов OpenStack: удалить **этот** compute, диски оставить.
5. Для этого на VM — OpenStack **application credential** с правом удалить/shelve сервер в проекте (не пароль владельца аккаунта). Компрометация SwarmUI в худшем случае сносит GPU-сессию, а не создаёт новые дорогие машины, если роль без `compute:create`.
5. Для этого на VM — OpenStack **application credential** с правом DELETE/GET **только этого** compute (не `/servers/*`, не unrestricted fallback). На `stop`/`destroy` cred отзывается. Компрометация SwarmUI в худшем случае сносит текущую GPU-сессию, а не произвольные машины проекта.
+27 -4
View File
@@ -1,4 +1,14 @@
# Расширения: git-репы на первый bootstrap
# Расширения: git-репы на bootstrap
## Быстрый старт
```powershell
copy extensions.example.yaml extensions.yaml
# добавь url реп (публичные — без GIT_TOKEN)
.\gpu-rent.ps1 up --yes
# доклонировать на уже живой VM:
.\gpu-rent.ps1 seed-extensions
```
Срабатывает на каждом `up``seed-extensions`):
@@ -23,15 +33,28 @@
```yaml
swarmui:
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
ref: main
dir: swarm-assistent
requires: ollama # none | ollama | llamacpp | any-llm; default none = always
- url: https://github.com/example/SwarmUI-SomeExt.git
ref: main # ветка, тег или commit SHA; по умолчанию HEAD default branch
dir: SomeExt # имя папки; по умолчанию последний сегмент URL без .git
ref: main
dir: SomeExt
comfy:
- url: https://github.com/example/ComfyUI-Something.git
ref: v1.2.0
```
В git репозитория gpu-rent не коммитить рабочий список с приватными URL. В примере — вымышленные репы.
| `requires` | Когда клонировать |
| --- | --- |
| `none` (или поле отсутствует) | всегда |
| `ollama` | только при `LLM_RUNTIME=ollama` |
| `llamacpp` | только при `LLM_RUNTIME=llamacpp` |
| `any-llm` | при `ollama` или `llamacpp` |
Строки с несовпавшим `requires` пропускаются (лог), остальные ставятся как обычно. В `extensions.example.yaml` по умолчанию — **swarm-assistent** с `requires: ollama` (чат/vision под Krea 2).
В git репозитория gpu-rent не коммитить рабочий список с лишними приватными URL сверх примера.
## Когда в жизненном цикле
+81 -38
View File
@@ -1,79 +1,122 @@
# LLM рядом со SwarmUI (opt-in)
По умолчанию поднимается только SwarmUI. Ollama или llama.cpp — по флагу, `LLM_RUNTIME` в `gpu-rent.vars` / `.env`, или через wizard.
По умолчанию поднимается **только SwarmUI**. Ollama или llama.cpp — отдельно, когда нужны (помощь с промптами и т.п.).
## Включение
---
## Быстрый путь
### Вариант A — wizard
```text
gpu-rent setup # спросит none/ollama/llamacpp + пресет моделей
gpu-rent up --ollama # разово
gpu-rent up --llm llamacpp
# или в gpu-rent.vars:
gpu-rent setup
```
Выбери `ollama` или `llamacpp`, при Ollama — пресет моделей. Значение пишется в `gpu-rent.vars` (`LLM_RUNTIME=…`).
### Вариант B — флаг на `up`
```text
gpu-rent up --yes --ollama
gpu-rent up --yes --llm llamacpp
```
### Вариант C — вручную в vars
```env
LLM_RUNTIME=ollama
```
Без параметров `gpu-rent` показывает help (не `up`). Double-click лаунчеры: `GPU_RENT_DEFAULT_ARGS=up --yes`. `gpu-rent up` без `--yes` спросит про LLM, если в vars ещё `none`. Полный doctor: `gpu-rent up -v`.
Потом обычный `gpu-rent up --yes`.
## Порты (только loopback + туннель)
Чтобы **выключить** LLM на следующих сессиях: `LLM_RUNTIME=none` (на `up` старые unit’ы `gpu-rent-ollama` / `gpu-rent-llamacpp` останавливаются).
| Сервис | VM | localhost |
Голый `gpu-rent` без args — help, не `up`. Двойной клик: `GPU_RENT_DEFAULT_ARGS=up --yes` в `gpu-rent.vars`. Полный doctor на up: `up -v`.
---
## Порты (loopback + туннель)
| Сервис | На VM | На ноутбуке |
| --- | --- | --- |
| SwarmUI | 7801 | 17801 |
| Ollama | 11434 | 17811 |
| llama.cpp | 8080 | 17812 |
| SwarmUI | 7801 | **17801** |
| Ollama | 11434 | **17811** |
| llama.cpp | 8080 | **17812** |
```text
gpu-rent tunnel
gpu-rent open --llm # http://127.0.0.1:17811 (Ollama)
# клиент:
set OLLAMA_HOST=http://127.0.0.1:17811
gpu-rent open --llm
```
## Ollama models
Клиент Ollama:
Как Civitai `models.yaml`:
```text
# Windows PowerShell
$env:OLLAMA_HOST = "http://127.0.0.1:17811"
```
- `ollama-models.example.yaml` — в git
- `ollama-models.yaml` — локальный (gitignore)
После успешного `up`/`tunnel` CLI печатает access-card со всеми URL и MCP-сниппетом.
На `up` при `LLM_RUNTIME=ollama` CLI делает `ollama pull` по списку. Уже скачанные не трогает; лишние на диске не удаляет.
---
### Пресеты setup
## Ollama: модели
| Файл | Роль |
| --- | --- |
| `ollama-models.example.yaml` | шаблон в git |
| `ollama-models.yaml` | твой список (gitignore) |
На `up` при `LLM_RUNTIME=ollama` CLI делает `ollama pull` по списку. Уже скачанные теги **точно** не трогает (`foo:3b``foo:7b`). Лишние модели на диске не удаляет.
### Пресеты `setup`
| preset | tag | зачем |
| --- | --- | --- |
| **recommended** | `huihui_ai/qwen2.5-abliterate:7b` | RU/EN, ~5GB, мало отказов — помощь с промптами |
| **recommended** | `huihui_ai/qwen2.5-abliterate:7b` | RU/EN, ~5 GB, меньше отказов |
| light | `qwen2.5:3b` | быстрее, слабее |
| stock | `qwen2.5:7b` | официальный, больше цензуры |
| stock | `qwen2.5:7b` | официальный |
| alt | `richardyoung/qwen2.5-7b-instruct-abliterated` | другой abliterate |
| empty | `[]` | только runtime |
| empty | `[]` | только runtime, pull руками |
Community abliterate-модели без гарантий безопасности — для личного prompt-help / NSFW-тегов рядом со SwarmUI.
Community abliterate-модели без гарантий безопасности — для личного prompt-help рядом со SwarmUI.
Поле `default: true` в yaml — preferred модель (лог / подсказка); pull идёт по всему списку.
---
## llama.cpp
Ставит `llama-server` и systemd. GGUF клади вручную в `/mnt/swarm_data/llamacpp/models` на data-диске (или через SSH), затем `systemctl restart gpu-rent-llamacpp`.
1. `LLM_RUNTIME=llamacpp` или `up --llamacpp`
2. CLI ставит `llama-server` + systemd `gpu-rent-llamacpp`
3. Положи GGUF в `/mnt/swarm_data/llamacpp/models` (SFTP / `gpu-rent ssh`)
4. `systemctl restart gpu-rent-llamacpp` на VM
## Idle-killer
Без GGUF unit может стартовать, но API бесполезен — смотри `gpu-rent logs` / `journalctl -u gpu-rent-llamacpp`.
Busy также если идёт `ollama pull` (маркер младше ~45 мин), в Ollama есть loaded model, или llama.cpp занимает слоты. Зависший маркер `.gpu-rent-ollama-pulling` старше 45 мин idle-killer сбрасывает.
---
## Supply-chain (install на VM)
## Idle-killer и LLM
Скрипты `install_ollama.sh` / `install_llamacpp.sh` по умолчанию тянут upstream **без pin** (Ollama: `curl|sh`; llama.cpp: GitHub `latest`). Это риск подмены артефакта.
Busy (не гасить GPU), если:
Рекомендуется задать pin через env при bootstrap (или патч vars / future hooks):
- идёт `ollama pull` (маркер младше ~45 мин; старше — сбрасывается);
- в Ollama есть загруженная модель;
- llama.cpp занимает слоты.
Ошибка установки LLM на `up`**fail** (не тихий лог): почини или поставь `LLM_RUNTIME=none`.
---
## Supply-chain (опциональный pin)
По умолчанию install-скрипты тянут upstream **без pin** (Ollama: `curl|sh`; llama.cpp: GitHub `latest`) — риск подмены. В логе будет `WARN`.
Жёстче — задай env на VM / при bootstrap:
```bash
# Ollama — GitHub release + checksum
OLLAMA_VERSION=0.6.5
OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
# llama.cpp — tag или прямой URL + checksum
LLAMACPP_TAG=b4690
# или:
LLAMACPP_ASSET_URL=https://github.com/ggerganov/llama.cpp/releases/download/...
LLAMACPP_SHA256=<sha256 of archive>
# или LLAMACPP_ASSET_URL=... + LLAMACPP_SHA256=...
```
Без этих переменных в логе будет `WARN` про отсутствие pin/checksum.
+9 -1
View File
@@ -1,6 +1,14 @@
# Локальные папки приложения
Все живут в корне gpu-rent. Одно правило: **пусто → ничего не синхронизируем**. Появился файл → на `up` (и по отдельной команде) едет инкремент по SHA256. С сервера лишнее не удаляем.
Все живут в корне gpu-rent.
**Одно правило:** пусто (только README / `.gitkeep`) → ничего не синхронизируем. Появился файл → на `up` (или отдельной командой) едет инкремент по SHA256. С сервера лишнее **не** удаляем.
```powershell
# положил LoRA / wildcard / workflow локально
.\gpu-rent.ps1 push # или просто следующий up
.\gpu-rent.ps1 pull-output # картинки с VM → ./Output
```
| Локально | На VM | Направление | Обязательно |
| --- | --- | --- | --- |
+14 -3
View File
@@ -2,10 +2,21 @@
Два источника на data volume, они **складываются**, не заменяют друг друга:
1. **Civitai-seed** (ниже) — один раз на пустой диск, если есть API-токен и манифест.
2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить на VM только то, что изменилось. `Wildcards/` и `CustomWorkflows/` — то же правило, см. [local-folders.md](local-folders.md).
1. **Civitai-seed** (ниже) — на пустой диск при bootstrap, если есть API-токен и манифест.
2. **Папка `Models/` в корне gpu-rent** — на каждом `up` выгрузить только изменившееся. `Wildcards/` и `CustomWorkflows/` — то же, см. [local-folders.md](local-folders.md).
Локальный SwarmUI на `7801` по-прежнему не зеркалируем. Только дерево `./Models` приложения.
## Быстрый старт моделей
```powershell
copy models.example.yaml models.yaml
# правь URL с modelVersionId=…
# в .env: CIVITAI_API_TOKEN=…
.\gpu-rent.ps1 up --yes
```
Или положи веса в `Models/Stable-Diffusion/` (и т.п.) — уедут на `up` / `gpu-rent push-models`.
Локальный SwarmUI на `7801` не зеркалируем. Только дерево `./Models` приложения.
## Локальная папка → сервер
+7 -7
View File
@@ -1,13 +1,13 @@
# Открытые вопросы
Пока пусто по продуктовым развилкам — см. [decisions.md](decisions.md).
Продуктовые развилки закрыты — см. [decisions.md](decisions.md).
Осталось подтвердить на **spike**, не в споре:
Ниже — что **подтвердить на spike** (живой GPU), не в споре:
- Качалка модели в 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 с access_rules (delete/GET server) на Selectel — при отказе CLI падает назад на unrestricted cred и пишет в лог.
- Качалка модели в UI: отдельного poll-API нет → v1 считает busy через очередь / loading / backend≠idle; иначе пользователь жмёт `hold`.
- Реальная цена 100 GB сетевого диска и 1×4090 preemptible ₽/час в твоём сегменте (в OpenStack API нет — только панель).
- Имя GPU-образа (без Docker) и flavor id в твоём пуле (в git не класть); какие из `FLAVOR_PREFERENCE` реально есть.
- Application credential с **узкими** access_rules (DELETE/GET только этого `server_id`) на Selectel: если Keystone отвергает rules — CLI **fail closed** (idle-killer не вооружается unrestricted). Нужно проверить на spike и при необходимости править права / путь.
- Snapshot attached boot volume после Idle: время и можно ли сразу create from snapshot.
Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — поднять `IDLE_GRACE_MINUTES`, не отключать killer.
Если spike покажет, что 45 минут льготы мало на первую установку ComfyUI — подними `IDLE_GRACE_MINUTES`, не отключай killer.
+7 -6
View File
@@ -1,16 +1,17 @@
# Roadmap
**Код v1 (§1–6) готов.** Дальше блокирует только облако: **0.0 квота GPU** и **0 spike** в панели. Без GPU в квоте `up` не создать — это ожидаемо.
**Код v1 (§1–6) готов** (включая opt-in LLM, local-watchdog, access-card). Дальше — твой облачный прогон.
Чеклист прогона: [spike-notes.md](spike-notes.md). Ключи: [setup.md](setup.md).
Пользовательский путь: [setup.md](setup.md) → `doctor``up`. Чеклист живого GPU: [spike-notes.md](spike-notes.md).
## 0.0 Квота GPU
- [x] Квота GPU в аккаунте в порядке (подтверждено)
- [ ] Тикет в поддержку Selectel — не нужен, если лимит уже > 0
- [ ] Сервисный пользователь с правом `member` на проект + `.env` заполнен
- [ ] Квота GPU в панели ≥ 1 (иначе тикет — текст в setup §2.3)
- [ ] Тикет закрыт / не нужен
- [ ] Сервисный пользователь `member` + `.env` заполнен
- [ ] `gpu-rent doctor` exit 0
`doctor` / `flavors` / `dry-run`дальше; `up` после зелёного doctor.
`flavors` / `dry-run`по желанию перед первым `up`.
## 0. Ручной spike в панели
+158 -104
View File
@@ -1,20 +1,38 @@
# Что сделать до первого `gpu-rent up`
# Подготовка до первого `up`
Код **не создаёт GPU**, пока не пройден `gpu-rent doctor`. Сначала доступ в облако, квота GPU и ключи. Статический ключ панели **`X-Token` для этого CLI не подходит** — им нельзя управлять серверами и дисками.
Цель: зелёный `doctor` → первый `up --yes` → UI на `http://127.0.0.1:17801``stop`.
Панель: [my.selectel.ru](https://my.selectel.ru).
Панель Selectel: [my.selectel.ru](https://my.selectel.ru).
**Важно:** ключ панели **`X-Token` не подходит**. Нужен сервисный пользователь + пароль (OpenStack Keystone). CLI сам получает токен на ~24 ч; в `.env` токен хранить не надо.
---
## 1. Python
## Карта шагов
| # | Что сделать | Готово, когда |
| --- | --- | --- |
| 1 | Python 3.11+ и лаунчер | `.\gpu-rent.ps1 --help` печатает help |
| 2 | Проект + квота GPU > 0 | в панели лимит GPU ≥ 1 |
| 3 | Сервисный пользователь + `.env` | заполнены `OS_*` и `GPU_RENT_AZ` |
| 4 | (Опц.) Civitai + `models.yaml` | токен в `.env`, манифест с `modelVersionId` |
| 5 | (Опц.) `extensions.yaml` / Git | публичные репы без токена |
| 6 | `doctor` | exit 0 |
| 7 | Первый `up --yes` | браузер / :17801 |
| 8 | `stop` | compute удалён, диски на месте |
SSH-ключ руками не нужен — CLI создаст `.gpu-rent/id_ed25519` при первом `up`.
---
## 1. Python и лаунчер
Нужен **Python 3.11+**. На Windows при установке включи «Add python.exe to PATH».
В корне репозитория достаточно лаунчера — venv и `pip install` он сделает сам:
В корне репозитория:
```powershell
.\gpu-rent.ps1 --help
.\gpu-rent.ps1 doctor
```
```bat
@@ -26,11 +44,24 @@ chmod +x gpu-rent.sh
./gpu-rent.sh --help
```
Первый запуск копирует `env.example``.env`, `models.example.yaml``models.yaml`, `extensions.example.yaml``extensions.yaml` в **корне репозитория**, если файлов ещё нет. Заполни `OS_*` в `.env` (шаги ниже). После `git pull`, если изменился `pyproject.toml`, лаунчер переустановит пакет.
Лаунчер сам создаёт `.venv` и ставит пакет. Первый запуск копирует шаблоны **в корень репо**, если файлов нет:
Секреты и runtime не уезжают в `%USERPROFILE%`: только `<repo>/.env` и `<repo>/.gpu-rent/` (state, SSH-ключ). Оба в `.gitignore`. Если раньше лежало в `~\.gpu-rent\`, CLI один раз перенесёт в проект.
- `env.example``.env`
- `models.example.yaml``models.yaml`
- `extensions.example.yaml``extensions.yaml`
- `gpu-rent.vars.example``gpu-rent.vars`
Ручной венв по желанию (для разработки / pytest):
Секреты только в `<repo>/.env` и runtime в `<repo>/.gpu-rent/` (оба в `.gitignore`).
Опционально wizard (после того как `.env` хотя бы частично заполнен):
```powershell
.\gpu-rent.ps1 setup
```
Спросит LLM (none/ollama/llamacpp) и local-watchdog. Можно пропустить и настроить позже.
Для разработки / pytest:
```powershell
python -m venv .venv
@@ -44,171 +75,194 @@ python -m gpu_rent --help
## 2. Selectel: проект и квота GPU
На новых аккаунтах лимит GPU почти всегда **0**. Без тикета в поддержку `up` создать карту не сможет — это норма, не баг CLI.
На новых аккаунтах лимит 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).
1. Панель → **IAM****Projects** (Проекты).
2. Отдельный проект, например `gpu-rent` (не общий «мусорный»).
3. Скопируй **ID проекта** (uuid) → это `OS_PROJECT_ID`.
4. **Номер аккаунта** (правый верхний угол) → это `OS_USER_DOMAIN_NAME`.
### 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-6`** (`ru-6a`/`ru-6b`/`ru-6c`) и `ru-7`. CLI сам сканит `SCAN_POOLS` в `gpu-rent flavors` и подскажет `OS_REGION_NAME` / `GPU_RENT_AZ`.
3. Строка **GPU** (и при необходимости vCPU/RAM/network volumes). Если лимит 0 — дальше тикет.
1. **IAM****Projects** → твой проект → **Quotas and limits** / **Квоты****Cloud platform**.
2. Пул и сегмент с GPU: матрица [GPU availability](https://docs.selectel.ru/en/cloud-servers/create/gpus/). Часто смотрят мультизональный `ru-6` и `ru-7`.
3. Строка **GPU**. Если лимит 0 — тикет (ниже).
Квоту внутри уже выданного лимита можно крутить в панели. **Сам лимит GPU поднимает только поддержка.**
Лимит GPU поднимает **только поддержка**. Внутри уже выданного лимита квоту можно крутить в панели.
Подсказка по пулу после появления ключей: `.\gpu-rent.ps1 flavors` (скан `SCAN_POOLS`).
### 2.3. Тикет в поддержку
**Тикеты** в панели (не email вслепую). Лимит увеличивают **на один конкретный проект**.
Текст можно почти копировать:
**Тикеты** в панели. Лимит увеличивают **на один конкретный проект**.
```text
Прошу увеличить лимит GPU в облачной платформе.
Проект: <имя> (ID: <uuid проекта>)
Пул / сегмент: ru-7 / ru-7a ← подставь свой из матрицы GPU
Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог в этом сегменте: 4090 48 GB или A5000).
Цель: один прерываемый (preemptible) облачный сервер для персональных сессий генерации, диски сетевые.
Нужно: 1× NVIDIA RTX 4090 24 GB (если нет — ближайший аналог: 4090 48 GB или A5000).
Цель: один прерываемый (preemptible) облачный сервер для персональных сессий, диски сетевые.
Сейчас квота/лимит GPU = 0, создать сервер с GPU нельзя.
```
Пока тикет не закрыт, ставишь CLI и гоняешь `doctor` — он как раз покажет «квота 0, напишите в поддержку».
Пока тикет открыт, можно уже заполнять `.env` и гонять `doctor` — он покажет «квота 0».
---
## 3. Ключи OpenStack (это и есть «API key» для gpu-rent)
## 3. Ключи OpenStack
Нужен **сервисный пользователь** + пароль. CLI сам получает IAM-токен на 24 часа (`X-Auth-Token`). В `.env` токен хранить не надо.
**Не используй:** Профиль → Access → API Keys → `X-Token`. Это статический ключ панели, OpenStack (серверы/диски/сети) он **не** двигает.
Нужен **сервисный пользователь** + пароль. Не используй: Профиль → Access → API Keys → `X-Token`.
### 3.1. Сервисный пользователь
Только владелец аккаунта или роль `iam.admin`. На балансе для роли `member` должно быть хотя бы **100 ₽**.
Только владелец аккаунта или `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**.
1. **IAM****Service users** / **Сервисные пользователи****Add**.
2. Имя, например `gpu-rent-api`.
3. Пароль: **≥ 20 символов**, сохрани сразу — потом только сброс.
4. Scope: **Projects** проект `gpu-rent` → роль **`member`** (`reader` для `up` мало).
5. **Add user**.
Официально: [Add user](https://docs.selectel.ru/en/access-control/manage/add-user/), [авторизация API](https://docs.selectel.ru/en/api/authorization/).
Официально: [Add user](https://docs.selectel.ru/en/access-control/manage/add-user/), [API auth](https://docs.selectel.ru/en/api/authorization/).
### 3.2. Скачать RC-файл (готовые `OS_*`)
### 3.2. RC-файл
1. **IAM****Service users** твой пользователь → вкладка **Access**.
2. Блок **RC files**:
- проект `gpu-rent`;
- локация = **пул**, например `ru-7` (это `OS_REGION_NAME`, не сегмент `ru-7a`);
- **Download**.
3. Открой файл (`rc.sh`). Из него в `.env` переносятся:
1. **IAM****Service users** → пользователь → **Access**.
2. **RC files**: проект `gpu-rent`, локация = **пул** (например `ru-7`, не сегмент `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 |
| `OS_PASSWORD` | пароль (в RC часто нет — допиши сам) |
| `OS_REGION_NAME` | пул, например `ru-7` |
| `GPU_RENT_AZ` | **сегмент**, например `ru-7a` (в RC может не быть) |
Официально: [Configure OpenStack CLI](https://docs.selectel.ru/en/cloud-servers/tools/openstack-cli/configure-openstack-cli/).
### 3.3. Куда класть
Создай каталог и файл **вне git**:
### 3.3. Заполнить `.env`
```powershell
copy env.example .env
notepad .env
```
Вставь значения из RC + пароль + `GPU_RENT_AZ`. Никогда не коммить `.env`.
Вставь значения из RC + пароль + `GPU_RENT_AZ`. **Не коммить** `.env`.
Проверка без нашего CLI (необязательно):
```powershell
# после pip install python-openstackclient, если хочешь
openstack token issue
openstack flavor list
```
Наш способ: `gpu-rent doctor`.
Проверка: `.\gpu-rent.ps1 doctor` (не обязательно ставить `openstack` CLI).
---
## 4. Civitai API token (модели)
Нужен, если хочешь seed с Civitai по `models.yaml`. Без токена SwarmUI поставит свою дефолтную модель — это допустимо.
Нужен, если хочешь seed по `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, если не хочешь светить вкусы):
1. Войди на [civitai.com](https://civitai.com) (тот же аккаунт, что для `.red`).
2. [Account settings](https://civitai.com/user/account) → **API Keys****Add**.
3. Токен показывают **один раз** → в `.env`: `CIVITAI_API_TOKEN=...`
4. Хост по умолчанию **`civitai.red`** (полный каталог). С `.com` NSFW часто 404.
5. Манифест:
```powershell
copy models.example.yaml models.yaml
```
`version_id: 0` — заглушка, doctor её игнорирует. Нужен **modelVersionId** из URL, не id карточки модели.
В URL нужен **`modelVersionId=`**, не id карточки модели. Подробности: [models.md](models.md).
---
## 5. Git-токен (только приватные репы расширений)
## 5. Git-токен (только приватные репы)
Публичные GitHub-репы в `extensions.yaml` клонируются без токена.
Публичные репы в `extensions.yaml` клонируются без токена.
Если репа приватная:
1. GitHub → Settings → Developer settings → Personal access tokens.
2. Fine-grained: доступ только к нужным репам, **Contents: Read**.
3. `GIT_TOKEN` в `.env`.
Скопируй шаблон: `extensions.example.yaml``extensions.yaml` в корне репо. Пустой файл = стоковый SwarmUI.
Приватные: GitHub fine-grained PAT, **Contents: Read**`GIT_TOKEN` в `.env`.
Шаблон: `extensions.example.yaml``extensions.yaml`. Пустой файл = стоковый SwarmUI. См. [extensions.md](extensions.md).
---
## 6. SSH
## 6. Чеклист перед `doctor`
Ключ **не надо** делать руками. CLI создаст `<repo>\.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
- [ ] `.env` в корне репо заполнен (`OS_*` + `GPU_RENT_AZ`)
- [ ] Нет `X-Token` вместо пароля сервисного пользователя
- [ ] (опционально) Civitai token + `models.yaml`
- [ ] На балансе хватает на диск 100 GB **даже когда GPU выключен**
Дальше:
- [ ] Python 3.11+, лаунчер отвечает на `--help`
- [ ] Проект Selectel, uuid скопирован
- [ ] Лимит GPU ≥ 1 (или тикет в работе — тогда `doctor` честно скажет «0»)
- [ ] Сервисный пользователь `member`, пароль сохранён
- [ ] RC с того же **пула**, где GPU
- [ ] `.env`: `OS_*` + `GPU_RENT_AZ`, не `X-Token`
- [ ] (опц.) Civitai + `models.yaml`
- [ ] На балансе хватает на **диск ~100 GB 24/7**, даже когда GPU выключен
```powershell
gpu-rent doctor
.\gpu-rent.ps1 doctor
.\gpu-rent.ps1 flavors
.\gpu-rent.ps1 dry-run
```
Exit 0 — облако отвечает, можно идти к spike / `up`, когда команда появится. Exit 1 — в отчёте причина (часто квота GPU = 0).
| Результат | Что делать |
| --- | --- |
| exit 0 | можно `up` |
| exit 1 | читай отчёт: чаще всего квота GPU = 0 или неверный пароль / пул |
`gpu-rent dry-run` печатает план без создания сервера.
---
## 7. Первый `up`
Первый прогон долгий (образ, SwarmUI, Comfy, seed): ориентир **2040 минут**. Не закрывай терминал посередине bootstrap.
```powershell
.\gpu-rent.ps1 up --yes
```
Что произойдёт:
1. Снова короткий doctor (кратко; полный — `up -v`).
2. Create/unshelve preemptible GPU + диски.
3. Bootstrap SwarmUI, extensions, autocomplete, Civitai-seed, push локальных папок.
4. Туннель на `localhost:17801`, access-card с URL / MCP.
5. Процесс ждёт: **Ctrl+C** закрывает только туннель, GPU остаётся.
Полезные флаги:
| Флаг | Зачем |
| --- | --- |
| `--no-tunnel` | только облако; UI потом: `tunnel --open` |
| `--no-update` | не `git pull` SwarmUI/extensions |
| `--ollama` | поднять Ollama рядом ([llm.md](llm.md)) |
| `--no-spot` | обычный (не preemptible) тариф |
| `--flavor ID` | явный flavor, без фоллбека |
Двойной клик без аргументов: в `gpu-rent.vars` задай `GPU_RENT_DEFAULT_ARGS=up --yes`. Сам `gpu-rent` без args показывает **help**, не поднимает GPU.
---
## 8. Закончить сессию
```powershell
.\gpu-rent.ps1 stop
```
Удаляет compute (+ FIP по умолчанию), **диски оставляет**. Модели на месте для следующего `up`.
| Команда | Эффект |
| --- | --- |
| `hold` / `hold --minutes 90` | отложить idle-killer |
| `status` | Nova, диск, killer, LLM |
| `destroy --i-understand-data-loss` | stop + удалить диски |
---
## Деньги и риски (кратко)
- **GPU** — пока жив compute.
- **Data-диск** — тарифицируется **всегда** после создания.
- Preemptible ~**24 ч** → `EXPIRED`; `tunnel` или `up` восстановят.
- Idle-killer: после льготы (~45 мин) + ~30 мин пустой очереди → delete compute.
- Цены в OpenStack API нет — смотри панель Selectel.
Дальше: чеклист живого прогона [spike-notes.md](spike-notes.md), справочник команд [cli.md](cli.md).
+63 -26
View File
@@ -1,21 +1,27 @@
# Spike notes (заполнять руками)
# Spike notes — первый живой прогон
Без секретов, без личных имён файлов моделей. Цель — один раз пройти панель + CLI на живом GPU и зафиксировать факты для v1.
Заполняй **руками** после зелёного `doctor`. Без секретов и без личных имён чекпоинтов.
## 0.0 Квота
Подготовка ключей: [setup.md](setup.md). Цель — один раз пройти панель + CLI на реальном GPU и зафиксировать факты (времена, цены, имя образа).
---
## 0. Перед прогоном
| Шаг | Дата | Результат |
| --- | --- | --- |
| Квота GPU в панели (лимит) | | 0 / N |
| Тикет в поддержку (номер) | | |
| Лимит поднят | | |
| Сервисный пользователь `member` на проект | | |
| Тикет в поддержку (номер), если был | | |
| Сервисный пользователь `member` + `.env` | | да / нет |
| `gpu-rent doctor` exit 0 | | |
Текст тикета: [setup.md §2.3](setup.md).
Текст тикета: [setup.md §2.3](setup.md#23-тикет-в-поддержку).
## Preflight CLI
---
```text
## 1. Preflight
```powershell
.\gpu-rent.ps1 doctor
.\gpu-rent.ps1 flavors
.\gpu-rent.ps1 dry-run
@@ -25,17 +31,21 @@
| --- | --- | --- |
| Keystone token | | |
| GPU quota > 0 | | |
| Flavor из FLAVOR_PREFERENCE | | id / имя |
| Flavor из `FLAVOR_PREFERENCE` | | id / имя |
| Image Driver 580 (без Docker) | | имя образа |
| Volume type в AZ | | |
| Civitai `.red` + token | | |
| Civitai `.red` + token (если используешь) | | |
## Первый `up`
---
```text
## 2. Первый `up`
```powershell
.\gpu-rent.ps1 up --yes
```
Первый bootstrap долгий (ориентир 20–40 мин). Не рви терминал посередине.
| Метрика | Значение |
| --- | --- |
| Flavor фактически | |
@@ -44,13 +54,18 @@
| SSH через (мин) | |
| Backend Idle через (мин) | |
| Seed Civitai (мин / GB) | |
| Boot snapshot создался | да / нет / ошибка |
| Boot snapshot `gpu-rent-boot-ok` | да / нет / ошибка |
| ₽/час GPU (панель) | |
| ₽/мес data disk | |
| ₽/мес data disk (панель) | |
| Access-card / URL :17801 | да / нет |
## Туннель и API
---
```text
## 3. Туннель и API
Если закрыл туннель (Ctrl+C):
```powershell
.\gpu-rent.ps1 tunnel --open
```
@@ -59,31 +74,53 @@
| UI http://127.0.0.1:17801 | |
| `/API/GetNewSession` | |
| MCP `/mcp` (если нужно) | |
| `nvidia-smi` по `gpu-rent ssh` | |
| `nvidia-smi` через `gpu-rent ssh` | |
## Idle-killer / hold
---
## 4. Idle-killer / hold
| Шаг | OK? | Заметка |
| --- | --- | --- |
| `systemctl status gpu-rent-idle-killer.timer` | | |
| `systemctl status gpu-rent-idle-killer.timer` (по SSH) | | |
| `gpu-rent hold` пишет hold-until | | |
| Качалка модели в UI → нужен hold? | | нет сигнала API — только hold |
| Качалка модели в UI | | hold, если killer не видит busy |
## Preempt / диски
---
## 5. Preempt / диски / stop
| Шаг | OK? |
| --- | --- |
| EXPIRED → tunnel unshelve или `up` | |
| EXPIRED → `tunnel` unshelve или `up` | |
| `stop` — диски живы | |
| Второй `up` — те же модели на data | |
| Boot из snapshot `gpu-rent-boot-ok` | |
## Выводы для кода
---
## 6. (Опц.) LLM
```powershell
.\gpu-rent.ps1 up --yes --ollama
```
| Шаг | OK? |
| --- | --- |
| Ollama на :17811 через туннель | |
| `ollama pull` из манифеста | |
| `LLM_RUNTIME=none` гасит unit на следующем up | |
См. [llm.md](llm.md).
---
## Выводы для кода / конфига
- Имя GPU-образа в пуле:
- Реальные flavor id (не в git):
- Хватает ли `IDLE_GRACE_MINUTES=45`:
- Application credential: ок / нужны другие права:
- Application credential с узкими access_rules: ок / ошибка (fail closed):
- Прочее:
Когда spike закрыт — отметь пункты в [roadmap.md](roadmap.md) §0 / §0.0.
Когда spike закрыт — отметь пункты в [roadmap.md](roadmap.md) §0.
+7 -1
View File
@@ -2,8 +2,14 @@
# Empty/missing file → no extra extensions, stock SwarmUI.
# swarmui = C# repos cloned to src/Extensions
# comfy = Python custom nodes cloned to ComfyUI DLNodes
# requires: none (default) | ollama | llamacpp | any-llm
# — clone only when LLM_RUNTIME matches (ollama / llamacpp / either)
swarmui: []
swarmui:
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
ref: main
dir: swarm-assistent
requires: ollama
# - url: https://github.com/org/SwarmUI-MyExt.git
# ref: main
# dir: MyExt
+31
View File
@@ -45,12 +45,16 @@ class ModelEntry:
url: str | None
VALID_REQUIRES = frozenset({"none", "ollama", "llamacpp", "any-llm"})
@dataclass
class GitRepo:
kind: str
url: str
ref: str
directory: str | None
requires: str = "none"
def _load_yaml(path: Path) -> Any:
@@ -96,6 +100,28 @@ def parse_models(path: Path) -> list[ModelEntry]:
return entries
def normalize_requires(value: object | None) -> str:
raw = str(value or "none").strip().lower().replace("_", "-")
if raw in {"", "none", "always", "any"}:
return "none"
if raw in VALID_REQUIRES:
return raw
raise ConfigError(
f"requires={value!r}: жду none|ollama|llamacpp|any-llm"
)
def repo_matches_runtime(repo: GitRepo, llm_runtime: str) -> bool:
"""Whether this extension should be cloned for the active LLM_RUNTIME."""
req = normalize_requires(repo.requires)
runtime = (llm_runtime or "none").strip().lower()
if req == "none":
return True
if req == "any-llm":
return runtime in {"ollama", "llamacpp"}
return runtime == req
def parse_extensions(path: Path) -> list[GitRepo]:
data = _load_yaml(path)
if not data:
@@ -112,12 +138,17 @@ def parse_extensions(path: Path) -> list[GitRepo]:
for item in items:
if not isinstance(item, dict) or not item.get("url"):
raise ConfigError(f"{path}: у {kind} нужен url")
try:
requires = normalize_requires(item.get("requires"))
except ConfigError as exc:
raise ConfigError(f"{path}: {exc}") from exc
repos.append(
GitRepo(
kind=kind,
url=str(item["url"]).strip(),
ref=str(item.get("ref") or "main"),
directory=str(item["dir"]) if item.get("dir") else None,
requires=requires,
)
)
return repos
+16 -1
View File
@@ -20,6 +20,8 @@ from gpu_rent.manifests import (
parse_extensions,
parse_models,
remote_root_for,
repo_dirname,
repo_matches_runtime,
)
from gpu_rent.ssh_ops import put_text, remote_exists, run_python, run_ssh
from gpu_rent.sync_files import pull_tree, push_tree
@@ -34,8 +36,21 @@ def _pkg_text(name: str) -> str:
def seed_extensions(cfg: Config, host: str, log: Log, *, update: bool = True) -> bool:
repos = parse_extensions(cfg.extensions_manifest)
from gpu_rent.llm_runtime import normalize_runtime
runtime = normalize_runtime(cfg.llm_runtime)
all_repos = parse_extensions(cfg.extensions_manifest)
repos = [r for r in all_repos if repo_matches_runtime(r, runtime)]
skipped = [r for r in all_repos if r not in repos]
for repo in skipped:
log(
f"extensions: пропуск {repo_dirname(repo)} "
f"(requires={repo.requires}, LLM_RUNTIME={runtime})"
)
if not repos and not update:
if all_repos:
log("extensions: все строки отфильтрованы по requires — стоковый SwarmUI")
else:
log("extensions.yaml пуст — стоковый SwarmUI")
return False
jobs = []
+50 -1
View File
@@ -1,6 +1,9 @@
from pathlib import Path
from gpu_rent.manifests import parse_extensions, parse_models
import pytest
from gpu_rent.errors import ConfigError
from gpu_rent.manifests import parse_extensions, parse_models, repo_matches_runtime
def test_models_skips_version_zero(tmp_path: Path):
@@ -30,3 +33,49 @@ def test_extensions_repo(tmp_path: Path):
repos = parse_extensions(path)
assert repos[0].kind == "swarmui"
assert repos[0].directory == "Ext"
assert repos[0].requires == "none"
def test_extensions_requires_ollama(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n"
" - url: https://gitea.example/swarm-assistent.git\n"
" ref: main\n"
" dir: swarm-assistent\n"
" requires: ollama\n"
" - url: https://github.com/org/Always.git\n"
" ref: main\n",
encoding="utf-8",
)
repos = parse_extensions(path)
assert repos[0].requires == "ollama"
assert repos[1].requires == "none"
assert repo_matches_runtime(repos[0], "ollama")
assert not repo_matches_runtime(repos[0], "none")
assert not repo_matches_runtime(repos[0], "llamacpp")
assert repo_matches_runtime(repos[1], "none")
assert repo_matches_runtime(repos[1], "ollama")
def test_extensions_requires_any_llm(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n - url: https://example/x.git\n requires: any-llm\n",
encoding="utf-8",
)
repo = parse_extensions(path)[0]
assert repo.requires == "any-llm"
assert repo_matches_runtime(repo, "ollama")
assert repo_matches_runtime(repo, "llamacpp")
assert not repo_matches_runtime(repo, "none")
def test_extensions_requires_invalid(tmp_path: Path):
path = tmp_path / "extensions.yaml"
path.write_text(
"swarmui:\n - url: https://example/x.git\n requires: docker\n",
encoding="utf-8",
)
with pytest.raises(ConfigError, match="requires"):
parse_extensions(path)