Files
pvemanager/docs/roadmap.md
markmorado f4f917c73e
Some checks failed
Deploy to Production / Trigger production update (push) Has been cancelled
feat(install): add one-command bootstrap for regular hosts and PVE hosts
Adds bootstrap.sh implementing Stage 3 of the roadmap: it detects whether
it runs on a plain Debian/Ubuntu host or directly on a Proxmox VE host.
On a PVE host it provisions a Debian 12 LXC, installs the panel inside it
via the existing deploy.sh, then creates a root@pam API token locally and
registers the host as the panel's first node — no Proxmox password is
ever transmitted.

Also removes the insecure pvemanager_secure_password fallback for
POSTGRES_PASSWORD in compose.yml, since every deploy path already
generates a real .env before the stack starts.
2026-07-25 00:44:08 +05:00

235 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План развития PVEmanager
Документ описывает шесть направлений развития панели, согласованных 24.07.2026.
Два из них уже реализованы, четыре — предстоят. Для каждого этапа указаны цель,
затрагиваемые файлы, шаги и критерии готовности, чтобы задачу мог взять любой
разработчик без дополнительного контекста.
## Статус
| # | Направление | Статус | Приоритет |
|---|---|---|---|
| 1 | Автосоздание API-токена при подключении ноды | ✅ Сделано | — |
| 2 | Графики метрик нод | ✅ Сделано (не проверено визуально) | — |
| 3 | Одна команда установки (`curl \| bash`) | 🟡 Загрузчик готов (ветка B), не проверен на реальном PVE | 1 |
| 4 | Интеграция с Proxmox Backup Server | ⬜ Запланировано | 2 |
| 5 | Redis: вынос воркера задач и общий кэш | ⬜ Запланировано | 3 |
| 6 | Внешний стек метрик и логов (опционально) | ⬜ Запланировано | 4 |
Порядок выбран так: сначала дистрибуция (от неё зависит рост аудитории), затем
крупная продуктовая фича, затем инфраструктурные улучшения. Этап 6 — самый
дорогой и наименее обязательный, поэтому идёт последним.
---
## ✅ Этап 1. Автосоздание API-токена
**Что сделано.** При добавлении сервера пользователь вводит логин и пароль,
панель подключается ими один раз, создаёт на ноде собственный API-токен и
дальше работает по нему. Токен отзывается из UI Proxmox, не даёт SSH- и
веб-логина и виден в аудите PVE под своим именем.
- `backend/app/proxmox/token_provisioning.py` — создание токена (`privsep=0`),
выбор свободного имени (`pvemanager`, при коллизии `pvemanager-2`), откат
через `revoke_api_token`.
- `POST /api/servers` (`backend/app/api/proxmox/servers.py`) — поле
`auto_create_token`; при сбое коммита созданный токен отзывается, чтобы на
ноде не оставался сирота с потерянным секретом.
- `POST /api/servers/{id}/provision-token` — перевод уже подключённого сервера
с пароля на токен по сохранённому паролю.
- `api_token_value` переведён на `EncryptedString` (`backend/app/models/proxmox.py`) —
раньше токены лежали в БД открытым текстом, в отличие от паролей.
- Фронтенд: выбор из трёх режимов подключения в `NodesPage.tsx`, кнопка
«Перевести на API-токен» на карточке сервера.
**Принятые решения.** Пароль после создания токена остаётся в БД
(зашифрованным) — он нужен `ProxmoxClient._password` для SSH-фолбэков вроде
`pvecm delnode`. Токен создаётся с `privsep=0`, то есть наследует права
пользователя.
## ✅ Этап 2. Графики метрик нод
**Что сделано.** Вкладка «Графики» в `NodeDetailPage`: CPU, IO delay, память,
load average, сеть in/out, swap, корневая ФС. Пресеты `1h/24h/7d/30d/1y`.
- `frontend/src/components/shared/MetricChart.tsx` — общий `ChartCard`,
переиспользуется вкладкой инстансов.
- `useNodeMetrics` в `frontend/src/hooks/use-nodes.ts` — нормализация сырого RRD
(PVE отдаёт `cpu`/`iowait` долями 0..1).
- `frontend/src/features/nodes/NodeGraphs.tsx`.
Бэкенд не менялся — эндпоинт `/api/{server_id}/node/rrddata` существовал ранее.
Дисковых I/O и IOPS в наборе нет: PVE их в node RRD не отдаёт.
**Открытый хвост.** Рендер с реальными данными не проверялся визуально.
---
## 🟡 Этап 3. Установка одной командой
**Что сделано.** `bootstrap.sh` в корне репозитория — единый загрузчик для обеих
веток, определяет среду по наличию `/etc/pve`:
- **Ветка A** (`install_branch_a`): ставит Docker (через `get.docker.com`, если
его ещё нет) и git/curl/openssl/jq, клонирует репозиторий в
`PVEMANAGER_DIR` (`/opt/pvemanager` по умолчанию) на выбранный `git`-реф
(`PVEMANAGER_VERSION`, по умолчанию — последний тег репозитория, иначе
`main`), передаёт управление `deploy.sh --standalone`. Docker ставится
заранее нарочно: интерактивный запрос внутри `check_requirements()` в
`deploy.sh` иначе заблокировал бы неинтерактивный запуск из LXC.
- **Ветка B** (`install_branch_b`): создаёт LXC (Debian 12, DHCP на `vmbr0`,
`nesting=1,keyctl=1` для Docker внутри), параметры (vmid, storage, bridge,
ресурсы) настраиваются переменными `PVEMANAGER_LXC_*`; шаблон скачивается
через `pveam`, если его ещё нет. Затем клонирует репозиторий во временную
директорию **на хосте** только чтобы забрать актуальную версию самого
`bootstrap.sh`, кладёт его в контейнер через `pct push` и выполняет там —
внутри LXC `/etc/pve` не существует, поэтому скрипт сам уходит в ветку A.
После того как панель поднялась (`GET /health`), хост создаёт токен
`root@pam!pvemanager` локально через `pvesh create ... --privsep 0`,
логинится в панель под `admin`/сгенерированным паролем и регистрирует
сам себя (`POST /api/servers`) — пароль от Proxmox никуда не передаётся.
**Принятые решения при реализации.**
- Убран небезопасный дефолт `${POSTGRES_PASSWORD:-pvemanager_secure_password}`
в `compose.yml` (заменён на `${POSTGRES_PASSWORD:?...}`) — раньше стек мог
молча подняться с предсказуемым паролем БД при отсутствии `.env`.
- Реф для клонирования (`resolve_ref()`) уже поддерживает пиннинг по
последнему git-тегу через `git ls-remote --tags`, но в репозитории пока нет
ни одного тега — до первого релиза загрузчик fallback'ится на `main`.
**Не сделано / не проверено.**
- Скрипт **не тестировался на живом Proxmox-хосте** — нет доступного стенда
в этой сессии. Нужна проверка: создание LXC, установка Docker внутри
unprivileged-контейнера, полный проход `deploy.sh --standalone`, логин и
регистрация ноды.
- Инфраструктура раздачи (домен `get.<домен>`, статика с нужными
`Content-Type`/`Cache-Control`, публикация sha256, второй путь установки в
README) — не делалась, требует внешних ресурсов (домен, хостинг).
`bash bootstrap.sh` уже работает как локальный путь установки.
- `pve --help` не проверялся в связке с этим потоком (устанавливается тем же
`deploy.sh` через `install_pve_cli --auto`, логика не менялась).
**Осталось до полного закрытия этапа** (инфраструктурная часть, не код):
- Домен `get.<домен>` под контролем проекта + TLS, раздача статики
(Cloudflare Pages/Worker или R2) с `Content-Type: text/plain` и коротким
`Cache-Control`.
- Публикация sha256 `bootstrap.sh` и второй путь установки в README
(«скачать → прочитать → запустить»).
- Первый git-тег в репозитории, чтобы пиннинг версии в `resolve_ref()` начал
реально на что-то указывать (сейчас fallback на `main`).
- Живой прогон на Debian/Ubuntu-машине и на PVE-хосте — см. «Не сделано /
не проверено» выше.
**Готово, когда:**
- на чистой Debian/Ubuntu-машине одна команда поднимает работающую панель;
- на PVE-хосте та же команда создаёт LXC, ставит панель внутрь и регистрирует
ноду без ввода пароля;
- `pve --help` отвечает;
- повторный запуск обновляет установку, не ломая её.
## ⬜ Этап 4. Интеграция с Proxmox Backup Server
**Цель.** Управлять PBS напрямую, а не только через PVE.
**Что уже есть.** PBS подключается к PVE как storage типа `pbs`, поэтому
бэкап и восстановление уже работают через `backend/app/api/proxmox/backup.py`
(storages, native jobs, restore, bulk restore).
**Чего не хватает** (не видно через PVE API): использование datastore и
дедупликация, задания verify/prune/GC, namespaces, sync- и tape-задания,
браузер снапшотов с восстановлением отдельных файлов.
**Шаги.**
1. Модель `PBSServer`: host, порт (по умолчанию 8007), `api_user`, токен
(`user@pbs!tokenid`), fingerprint сертификата, `verify_ssl`. Секреты — через
`EncryptedString`.
2. Клиент `backend/app/pbs/client.py` по образцу `ProxmoxClient` с миксинами:
datastores, snapshots, verify/prune/GC, namespaces.
3. API-роутер `backend/app/api/pbs/` + права в `backend/app/rbac/permissions.py`.
4. Фронтенд: раздел PBS со списком серверов, datastore-обзором и браузером
снапшотов.
5. Связать с существующим разделом бэкапов: если storage типа `pbs` соответствует
зарегистрированному PBS-серверу — показывать расширенные данные.
**Готово, когда** можно добавить PBS-сервер, увидеть занятость datastore и
коэффициент дедупликации, запустить verify/prune и просмотреть содержимое
снапшота.
**Оценка.** Сопоставимо по объёму со всем текущим модулем бэкапов.
## ⬜ Этап 5. Redis: вынос воркера и общий кэш
**Цель.** Вынести исполнение фоновых задач из процесса `app` в отдельный
контейнер и убрать состояние, которое сейчас живёт только в памяти процесса.
**Текущее состояние.** `TaskQueueService` хранит задачи в Postgres, а
исполняются они внутри `app` через APScheduler при `UVICORN_WORKERS=1`.
Последствия: воркеры не масштабируются, задача в статусе `running` зависает при
рестарте контейнера, нет ретраев.
**Важно:** очередь в Postgres сама по себе не проблема — для домашней нагрузки
её достаточно. Выигрыш даёт именно вынос воркера; Redis нужен как брокер и
попутно закрывает три места, которые сейчас ломаются при масштабировании:
- `connection_cache` в `backend/app/proxmox/client.py` — словарь в памяти;
- `_rrd_cache` в `backend/app/services/metrics_broadcaster.py` — то же;
- `websocket_manager.py` — при более чем одном uvicorn-воркере рассылка не
работает без pub/sub.
**Шаги.**
1. Добавить сервис `redis` в `compose.yml` (alpine-образ, ограничение памяти).
2. Подключить **ARQ** как брокер (asyncio-native; Celery для этой задачи
избыточен). Postgres остаётся источником правды по истории задач.
3. Вынести обработку `TaskQueue` в отдельный контейнер `worker`.
4. Перевести `connection_cache` и `_rrd_cache` на Redis.
5. Перевести WebSocket-рассылку на Redis pub/sub, после чего можно поднять
`UVICORN_WORKERS`.
6. Добавить ретраи и переустановку статуса зависших задач при старте воркера.
**Готово, когда** задачи исполняются в отдельном контейнере, переживают рестарт
`app`, а панель корректно работает при `UVICORN_WORKERS > 1`.
## ⬜ Этап 6. Внешний стек метрик и логов
**Цель.** Снять с панели хранение метрик и дать историю глубже, чем позволяет
RRD, — но не навязывая всем пользователям тяжёлый стек мониторинга.
**Ключевой факт.** Собирать метрики самим не нужно: у Proxmox есть встроенный
external metric server (протоколы InfluxDB и Graphite) — нода push-ит метрики
наружу без всякого кода в панели. Для pull-модели существует
`prometheus-pve-exporter`.
**Решения по составу.**
- **VictoriaMetrics вместо Prometheus** и **VictoriaLogs вместо Loki** — заметно
меньше RAM и диска при совместимом PromQL/LogsQL.
- **Grafana необязательна.** Если панель сама ходит в PromQL, Grafana нужна
только продвинутым пользователям — подключается опционально.
- Весь стек включается через `docker compose --profile monitoring`, а **не**
входит в поставку по умолчанию: для типовой домашней установки это дороже
самой панели.
**Шаги.**
1. Профиль `monitoring` в `compose.yml`.
2. В панели — переключаемый источник метрик: `native` (RRD через PVE API, как
сейчас) либо `victoriametrics` (PromQL). Настройка в разделе Settings.
3. Абстрагировать слой доступа к данным на фронтенде, чтобы `GraphsTab` и
`NodeGraphs` не переписывать при смене источника.
4. Инструкция по настройке external metric server на ноде.
5. Опциональная Grafana с преднастроенными дашбордами.
**Готово, когда** при выключенном профиле всё работает как сейчас, а при
включённом графики строятся из VictoriaMetrics с историей глубже RRD.
**Зависимость.** Этап 2 намеренно сделан на RRD. При выполнении этого этапа
слой доступа к данным нужно спрятать за один хук, а не переписывать компоненты.
---
## Технический долг, замеченный попутно
- **Хардкод русских строк** в `frontend/src/features/instances/tabs/GraphsTab.tsx`:
«IOPS чтение», «IOPS запись», «Заполненность диска», «Все», «Сбросить»,
«Нет данных» — не проходят через i18n.
- **`graphify update .`** отказывается перезаписывать граф: AST-версия даёт 5938
узлов против 5959 в текущем `graph.json`. Нужно решить, форсить ли перестроение.