Some checks failed
Deploy to Production / Trigger production update (push) Has been cancelled
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.
235 lines
18 KiB
Markdown
235 lines
18 KiB
Markdown
# План развития 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`. Нужно решить, форсить ли перестроение.
|