From 0372e2c5f19b44d3a306d01d0c75d4059ed595cc Mon Sep 17 00:00:00 2001 From: host Date: Sat, 18 Jul 2026 15:05:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 11 ++++ README.md | 127 +++++++++++++++++++++++++++++++++++++++++++++ config.py | 25 +++++++++ docker-compose.yml | 35 +++++++++++++ index.html | 14 +++++ nginx.conf | 20 +++++++ package.json | 20 +++++++ requirements.txt | 14 +++++ vite.config.js | 18 +++++++ 9 files changed, 284 insertions(+) create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 config.py create mode 100644 docker-compose.yml create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package.json create mode 100644 requirements.txt create mode 100644 vite.config.js diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3a08de2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20-slim AS build +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 5173 diff --git a/README.md b/README.md new file mode 100644 index 0000000..201f1bf --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# Proxmox VPS Panel + +Минимальная self-hosted панель для мульти-тенантного управления VPS (VM и LXC) на вашем +Proxmox VE через API. Backend — FastAPI (Python), frontend — React (Vite), хранилище — Postgres. + +Клиенты регистрируются, выбирают шаблон и разворачивают себе VM или LXC-контейнер одной кнопкой; +дальше могут запускать/останавливать/перезагружать/удалять сервер и открывать веб-консоль. +Первый зарегистрированный пользователь автоматически становится администратором и управляет +шаблонами и списком пользователей. + +## Возможности MVP + +- Регистрация/логин (JWT), роли admin / client +- Шаблоны VPS: админ создаёт тарифы, привязанные к VM-шаблону (клонирование) или CT-шаблону LXC +- Клиент создаёт инстанс из шаблона — панель сама берёт свободный VMID и вызывает Proxmox API +- Действия: start / stop / shutdown / reboot / delete +- Веб-консоль через noVNC (websocket-прокси на backend) +- Всё через docker-compose, разворачивается как Custom App в TrueNAS SCALE + +## Чего нет в MVP (сознательно, чтобы не раздувать первую версию) + +- Поддержки нескольких Proxmox-нод/кластеров одновременно (сейчас один `PVE_NODE` в конфиге) +- Биллинга и лимитов по ресурсам на пользователя +- Очереди задач (Celery/Redis) — создание VPS идёт в фоне через `BackgroundTasks` FastAPI, + для нескольких одновременных заявок этого достаточно, для десятков в секунду — уже нет +- Управления сетью/IP-пулами (LXC получает адрес по DHCP, для VM выдаётся из клонированного шаблона) + +## 1. Подготовка Proxmox + +### API-токен + +Datacenter → Permissions → API Tokens → добавить токен для пользователя (например `root@pam`, +в проде лучше завести отдельного пользователя с ролью `PVEVMAdmin`/кастомной ролью и без +"Privilege Separation", если хотите, чтобы токен имел те же права, что и юзер). + +Права, которые нужны панели: создание/клонирование/удаление VM и CT, управление питанием, +`VM.Console` для консоли, `Sys.Audit` для чтения статусов задач. + +### Шаблон для VM + +Создайте одну VM с cloud-init образом (Ubuntu/Debian cloud image), настройте её и переведите +в шаблон (`qm template `). Полученный VMID указывается в панели как `source_vmid` шаблона. + +### Шаблон для LXC + +Скачайте официальный CT-шаблон через Proxmox (Node → local (storage) → CT Templates → Download), +например `debian-12-standard_12.7-1_amd64.tar.zst`. В панели укажите его как `source_template` +в формате `local:vztmpl/имя_файла.tar.zst`. + +## 2. Настройка backend + +Скопируйте `backend/.env.example` в `backend/.env` и заполните: + +``` +PVE_HOST=https://:8006 +PVE_NODE=pve +PVE_TOKEN_NAME=root@pam!panel +PVE_TOKEN_VALUE=<значение токена> +SECRET_KEY=<случайная строка> +``` + +## 3. Запуск + +```bash +docker compose up -d --build +``` + +Frontend будет на `http://:5173`, backend API — на `http://:8000`. +Первый, кто зарегистрируется на `/register`, станет администратором — заходите первым сами. + +## 4. Установка на TrueNAS SCALE + +TrueNAS SCALE умеет запускать произвольные Docker-приложения ("Apps → Discover Apps → Custom App" +либо через "Launch Docker Compose", если версия TrueNAS это поддерживает). Проще всего: + +1. Скопируйте папку проекта на TrueNAS (например, в датасет `/mnt/tank/apps/vps-panel`). +2. Заполните `backend/.env`, как описано выше. +3. Из этой директории выполните `docker compose up -d --build` через shell TrueNAS + (System Settings → Shell, либо через SSH), либо оформите как Custom App, указав тот же + `docker-compose.yml` в интерфейсе TrueNAS Apps. +4. Откройте порт 5173 наружу через reverse-proxy (например, встроенный в TrueNAS, либо + отдельный Nginx Proxy Manager), если панель должна быть доступна клиентам извне. + +## Консоль VNC — важное примечание + +Веб-консоль реализована как websocket-прокси: backend получает от Proxmox тикет +(`vncproxy`) и порт, затем проксирует бинарный поток на `/console/ws`, а фронтенд рисует +экран через noVNC (`@novnc/novnc`, подключается с CDN). + +Proxmox исторически ожидает на `vncwebsocket` либо cookie-тикет (`PVEAuthCookie`) из обычной +браузерной сессии, либо (в более новых версиях) заголовок `Authorization: PVEAPIToken=...` — +в коде (`backend/app/routers/console.py`) используется второй вариант. Если на вашей версии +Proxmox консоль не подключается, скорее всего понадобится либо обновить PVE, либо переключить +аутентификацию панели на логин/пароль с получением `PVEAuthCookie` вместо API-токена именно +для этого запроса — сама бизнес-логика (создание/старт/стоп VPS) от этого не зависит и будет +работать в любом случае. + +## Безопасность перед продакшеном + +- Смените `SECRET_KEY` и пароль Postgres по умолчанию +- Ограничьте `allow_origins` в `backend/app/main.py` конкретным доменом фронтенда +- Закройте порт 8000 (backend) наружу, оставьте доступ только через nginx фронтенда +- Используйте отдельного Proxmox-пользователя с минимально необходимыми правами для токена + панели, а не `root@pam` + +## Структура проекта + +``` +backend/ + app/ + main.py — точка входа FastAPI + config.py — переменные окружения + models.py — таблицы: User, Template, Instance + proxmox_client.py — вся логика вызовов Proxmox API (proxmoxer) + routers/ + auth.py — регистрация / логин + templates.py — CRUD шаблонов (admin) + instances.py — создание/список/действия/удаление VPS + admin.py — управление пользователями + console.py — websocket-прокси для VNC-консоли +frontend/ + src/ + pages/ — Login, Register, Dashboard, InstanceDetail, AdminTemplates, AdminUsers + components/ — InstanceCard, ConsoleViewer + api.js — обёртка над fetch к backend +docker-compose.yml +``` diff --git a/config.py b/config.py new file mode 100644 index 0000000..00c3738 --- /dev/null +++ b/config.py @@ -0,0 +1,25 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + # Общие + database_url: str = "sqlite:///./panel.db" + secret_key: str = "change-me-please-super-secret" + access_token_expire_minutes: int = 60 * 12 + + # Proxmox + pve_host: str = "https://proxmox.local:8006" + pve_token_name: str = "root@pam!panel" + pve_token_value: str = "" + pve_verify_ssl: bool = False + pve_node: str = "pve" + + # Пул VMID, из которого будут выделяться номера новым машинам + vmid_range_start: int = 9000 + vmid_range_end: int = 9999 + + class Config: + env_file = ".env" + + +settings = Settings() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eddd211 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3.8" + +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: panel + POSTGRES_PASSWORD: panel + POSTGRES_DB: panel + volumes: + - panel_db:/var/lib/postgresql/data + + backend: + build: ./backend + restart: unless-stopped + env_file: + - ./backend/.env + environment: + DATABASE_URL: postgresql://panel:panel@db:5432/panel + depends_on: + - db + ports: + - "8000:8000" # можно убрать наружу, если доступ только через frontend/nginx + + frontend: + build: ./frontend + restart: unless-stopped + depends_on: + - backend + ports: + - "5173:5173" + +volumes: + panel_db: diff --git a/index.html b/index.html new file mode 100644 index 0000000..fc0c31a --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + Panel — VPS на вашем Proxmox + + + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..1acd6fd --- /dev/null +++ b/nginx.conf @@ -0,0 +1,20 @@ +server { + listen 5173; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri /index.html; + } + + location /api/ { + proxy_pass http://backend:8000/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..43c0cba --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "proxmox-vps-panel-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview --host --port 5173" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.24.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.3.3" + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1aa41a9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.1 +sqlalchemy==2.0.30 +psycopg2-binary==2.9.9 +python-jose[cryptography]==3.3.0 +passlib[bcrypt]==1.7.4 +bcrypt==4.0.1 +pydantic==2.7.4 +pydantic-settings==2.3.4 +proxmoxer==2.0.1 +requests==2.32.3 +python-multipart==0.0.9 +websockets==12.0 +httpx==0.27.0 diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..4f89489 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + host: true, + port: 5173, + proxy: { + "/api": { + target: "http://backend:8000", + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ""), + ws: true, + }, + }, + }, +});