Document the new GET /api/backups/scan-nfs/{server_id} endpoint and
the storage-add dialog's export scan button in CHANGELOG(.ru).md and
WIKI(.ru).md.
94 KiB
📖 PVEmanager - Documentation
Complete guide for installation, configuration and usage of PVEmanager v1.16.0
📑 Table of Contents
- Quick Start
- Installation and Deployment
- Main Features
- Notification System
- VM and Container Management
- Bulk Operations
- OS Templates
- Images — Cloud Image Catalog
- LXC CT Template Deployment
- App Store
- Proxmox Clusters
- Snapshots
- Backups
- IPAM
- Remote Command Execution
- Monitoring
- Security (RBAC v2)
- Localization
- Settings
- SSH Keys Management
- Networks (SDN & Node Interfaces)
- pve CLI Tool
- API Reference
- Workspaces
- User → Server Assignment
- VM / LXC Ownership
- Quotas
- Logs & Audit
- Deployment Guide
- Troubleshooting
- FAQ
🚀 Quick Start
Requirements
- Docker and Docker Compose
- 2GB RAM minimum
- Proxmox VE server with API access
Start in 1 minute
# Clone repository
git clone https://github.com/your-repo/pvemanager.git
cd pvemanager
# Copy and configure environment variables
cp .env.example .env
cp backend/.env.example backend/.env
# Start
docker compose up -d
# Open in browser
open http://localhost:3001
Default credentials:
- Login:
admin - Password:
admin123
⚠️ Make sure to change password after first login!
📦 Installation and Deployment
Deployment Options
1. Standalone (Development)
docker compose up -d
- UI port: 3001 (frontend), API port: 8000 (backend)
- Without NGINX reverse proxy
- Suitable for local development
2. With NGINX (HTTP)
./deploy.sh
# Select option 2
3. With NGINX + SSL (Production)
./deploy.sh
# Select option 3
# Specify domain and email for Let's Encrypt
4. One-command bootstrap (bootstrap.sh)
A single script that installs the panel on either a plain Debian/Ubuntu host or directly on a Proxmox VE host — it detects which by checking for /etc/pve:
git clone https://git.tzim.uz/markmorado/pvemanager.git
cd pvemanager
bash bootstrap.sh
- Plain Debian/Ubuntu host — installs Docker and
git/curl/openssl/jqif missing, clones the repo intoPVEMANAGER_DIR(default/opt/pvemanager) and hands off todeploy.sh --standalone. - Proxmox VE host — creates a Debian 12 LXC (DHCP on
vmbr0,nesting=1,keyctl=1for Docker inside), installs the panel inside it via the same flow, then — once the panel answersGET /health— creates aroot@pam!pvemanagerAPI token locally on the PVE host (pvesh create ... --privsep 0) and registers that host as the panel's first node. No Proxmox password is ever transmitted to or through the panel. - Configuration is via environment variables (
PVEMANAGER_REPO,PVEMANAGER_VERSION,PVEMANAGER_DIR, andPVEMANAGER_LXC_*for the container's vmid/storage/bridge/resources) — see the header ofbootstrap.shfor the full list and defaults. - Tolerates
apt-get updatefailures from an unsubscribed PVE host's enterprise repo instead of aborting beforegit/curlare installed.
Environment Variables
Main (.env)
# Database
POSTGRES_PASSWORD=your_secure_password
# Timezone
TZ=Your/Timezone
# Disable update checks (for private repos without token)
DISABLE_UPDATE_CHECK=false
# GitHub token for private repository access (optional)
GITHUB_TOKEN=
Backend (backend/.env)
# Secret key (generate unique! minimum 32 characters)
SECRET_KEY=your-very-long-secret-key-change-me
# Default admin password — MUST be changed before first deployment!
ADMIN_PASSWORD=admin123
# Fernet encryption key for sensitive DB fields (Proxmox/cloud-init passwords).
# Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# WARNING: Keep this key safe — losing it means losing access to encrypted passwords.
# If not set, passwords are stored as plaintext (insecure).
FERNET_KEY=
# JWT token lifetime (minutes). Default: 480 (8 hours)
ACCESS_TOKEN_EXPIRE_MINUTES=480
# CORS origins (comma-separated). Use actual domain(s) in production.
CORS_ORIGINS=*
Email and Telegram Notifications
SMTP and Telegram bot settings are now managed via web interface:
- Go to Settings → Notifications
- In "Notification Channels Configuration" section fill:
- SMTP - your mail server details
- Telegram - bot token from @BotFather
- Click Test button to verify settings
📚 API Reference
This section summarizes key API endpoints. All require JWT authentication.
Authentication
POST /api/auth/login— returnsaccess_token
System Updates
GET /settings/api/version— current versionGET /settings/api/updates/repository— get git repository URLPUT /settings/api/updates/repository— set git repository URLGET /settings/api/updates/check— check for updatesPOST /settings/api/updates/perform— start update
Proxmox Servers
GET /api/servers— list serversPOST /api/servers— add server (auto_create_tokenprovisions an API token from a login/password)PUT /api/servers/{id}— update serverDELETE /api/servers/{id}— delete serverPOST /api/servers/{id}/test— test connectionPOST /api/servers/{id}/provision-token— migrate a password-auth server to an auto-provisioned API token
Virtual Machines
GET /api/servers/{id}/resources— list VMsPOST /api/servers/{id}/vm/{vmid}/start?node={node}— start VMPOST /api/servers/{id}/vm/{vmid}/stop?node={node}— stop VMPOST /api/servers/{id}/vm/{vmid}/restart?node={node}— restart VMGET /api/servers/{id}/vm/{vmid}/status?node={node}— VM status
Containers
GET /api/servers/{id}/resources— list containersPOST /api/servers/{id}/container/{vmid}/start?node={node}— start containerPOST /api/servers/{id}/container/{vmid}/stop?node={node}— stop containerPOST /api/servers/{id}/container/{vmid}/restart?node={node}— restart containerGET /api/servers/{id}/container/{vmid}/status?node={node}— container status
OS Templates
GET /templates/api/groups— list template groupsPOST /templates/api/groups— create template groupGET /templates/api/templates— list templatesPOST /templates/api/templates— create templateDELETE /templates/api/templates/{id}— delete template
IPAM
GET /ipam/api/networks— list networksPOST /ipam/api/networks— create networkDELETE /ipam/api/networks/{id}— delete networkGET /ipam/api/allocations— list IP allocationsPOST /ipam/api/allocations— create IP allocationDELETE /ipam/api/allocations/{id}— release IP
Notifications
GET /api/notifications— list notificationsPATCH /api/notifications/{id}/read— mark as readDELETE /api/notifications/{id}— delete notification
Users & Roles
GET /api/users— list users (admin)POST /api/users— create user (admin)PUT /api/users/{id}— update user (admin)DELETE /api/users/{id}— delete user (admin)GET /api/roles— list roles (admin)POST /api/roles— create role (admin)PUT /api/roles/{id}— update role (admin)DELETE /api/roles/{id}— delete role (admin)
Quotas
GET /api/users/{id}/quota— get a user's quota plus current usage (requiresquota:view)PUT /api/users/{id}/quota— set a user's quota;nullper metric = unlimited (requiresquota:manage)GET /api/quota— get the current user's own quota and usage (self-service)
Settings
GET /settings/api/panel— get panel settingsPUT /settings/api/panel— update panel settingsGET /settings/api/security— get security settingsPUT /settings/api/security— update security settingsGET /settings/api/notifications— get notification settingsPUT /settings/api/notifications— update notification settings
Backups & Storages
GET /api/backups/storages/{server_id}— list storagesGET /api/backups/scan-nfs/{server_id}?server=<ip>— scan an NFS server for available exportsPOST /api/backups/storages/{server_id}— add storagePUT /api/backups/storages/{server_id}/{storage_id}— update storageDELETE /api/backups/storages/{server_id}/{storage_id}— delete storageGET /api/backups/list/{server_id}?node=&storage=— list backup filesDELETE /api/backups/{server_id}/backup— delete backup filePOST /api/backups/create— create VM/LXC backup (vzdump)POST /api/backups/restore— restore VM/LXC from backupGET /api/backups/task/{server_id}/{node}/{upid}— get task status/logGET /api/backups/jobs— list scheduled backup jobsPOST /api/backups/jobs— create scheduled backup jobPUT /api/backups/jobs/{id}— update scheduled backup jobDELETE /api/backups/jobs/{id}— delete scheduled backup job
High Availability (HA)
GET /api/{server_id}/ha/status— get HA status for all VMsGET /api/{server_id}/ha/{vm_type}/{vmid}— get HA status for a VMPOST /api/{server_id}/ha/{vm_type}/{vmid}/add— add VM to HADELETE /api/{server_id}/ha/{vm_type}/{vmid}/remove— remove VM from HA
SDN (Software Defined Networking)
GET /api/servers/{server_id}/sdn/status— check SDN availabilityGET /api/servers/{server_id}/sdn/zones— list zonesPOST /api/servers/{server_id}/sdn/zones— create zonePUT /api/servers/{server_id}/sdn/zones/{zone}— update zoneGET /api/servers/{server_id}/sdn/vnets— list VNetsPOST /api/servers/{server_id}/sdn/vnets— create VNetPUT /api/servers/{server_id}/sdn/vnets/{vnet}— update VNetPOST /api/servers/{server_id}/sdn/vnets/{vnet}/subnets— create subnet (supportscreate_ipam_network)DELETE /api/servers/{server_id}/sdn/vnets/{vnet}/subnets/{cidr}— delete subnetPOST /api/servers/{server_id}/sdn/apply— apply pending SDN changes
Node Networks
GET /api/servers/{server_id}/nodes— list nodes for a server
Misc
POST /api/sync-vms— force immediate VM sync from all Proxmox serversDELETE /proxmox/api/all-tasks/completed— delete all completed/failed/cancelled tasks for the current user (both ProxmoxTask and TaskQueue)
🏠 Main Features
Dashboard
- 5 stat cards — Nodes, VMs, Containers, Clusters, Alerts (real-time)
- Resource cards — CPU, RAM, Storage with mini sparkline charts
- Status chip in header —
All systems operational/ warnings / critical alerts - Live clock in header
- Cluster count computed automatically from database
- Alerts count from AuditLog (errors + criticals in last 24 h)
- Recent audit events (last 5) on the dashboard
- Cluster Selector in sidebar — shows active cluster or standalone name
- Version badge in header
Proxmox Servers
- Add multiple servers
- API Token or password authentication
- Automatic API token creation, with in-place migration for existing password-auth servers
- Server status monitoring, distinguishing an unreachable node from rejected/stale credentials
- Per-node storage availability
VM and LXC Management
- View all virtual machines
- Start / Stop / Restart / Shutdown
- Configuration changes (CPU, RAM, Disk)
- VNC console in browser
- Remote command execution
Bulk Operations
- Mass start/stop/restart/delete VMs and containers
- Task queue system with progress tracking
- Background processing
OS Templates
- Quick VM deployment from templates
- Cross-node template deployment for clusters
- Template groups and icons
- VM reinstall from template
Snapshots
- Create, delete, rollback VM/LXC snapshots
- Snapshot operation queue system
- Snapshot archival on VM deletion
Backups
- Manual backup creation (vzdump) for VMs and containers
- Backup listing and deletion by storage/node
- Restore VM/LXC from backup with optional new VMID
- Scheduled backup jobs with cron expressions
- Backup retention policies (keep_last, keep_daily, keep_weekly, keep_monthly)
- Storage pool management (add, edit, delete)
- Task progress tracking via UPID
High Availability (HA)
- View HA status for all VMs on a server
- Add/remove VMs from Proxmox HA groups
- Per-VM HA status and group information
IPAM (IP Address Management)
- Network and subnet management
- Server and node binding — networks are linked to a specific Proxmox server and node to prevent cross-server IP assignment
- Manual server + network selection modal when linking IP to a VM/LXC
- Dynamic server and node selectors in network creation forms
- Automatic IP allocation
- IP reservation and release
- Allocation history
Notifications
- Real-time alerts about VM events
- Email notifications via SMTP
- Telegram notifications via Bot API
- In-App notifications with bell icon
Security
- RBAC v2 with atomic permissions (
resource:action) - VPS-style user isolation (users see only their instances)
- IP blocking (automatic and manual)
- Session management
- Login protection (lockout, rate limiting)
- Audit logs
Quotas
Per-user resource limits enforced when deploying VMs/LXC:
- Limited metrics — number of instances, total vCPU (cores), total RAM (MB) and total disk (GB), summed across the user's non-deleted, non-template instances
- Unlimited by default — a user with no quota row, or a metric left blank (
NULL), is unrestricted for that metric; admins are never blocked - Enforcement — deploying an instance that would exceed any limit is rejected with HTTP 429 and a bilingual message showing the projected value vs. the limit
- Admin management — set limits per user on the Users page (quota dialog with live usage bars); requires the
quota:managepermission (quota:viewto read) - Self-service — each user can see their own limits and current usage on the Settings page, and the remaining quota is shown as a hint in the Create Instance wizard
- Current usage is derived from the
vm_instancescache (owner_id), where RAM is stored in MB and disk in GB
Monitoring
- Real-time CPU, RAM, Disk, Network metrics
- Historical graphs (hour, day, week, month) for both instances and nodes (node Graphs tab: CPU, IO delay, memory, load average, network, swap, root filesystem)
- Resource alert thresholds
🔔 Notification System
Overview
Notification system provides:
- Real-time alerts about VM events
- Email notifications via SMTP
- Telegram notifications via Bot API
- In-App notifications with bell icon
Notification Types
| Type | Description | Example |
|---|---|---|
vm_status |
VM state changes | VM started/stopped |
resource_alert |
Resource alerts | CPU > 80%, RAM > 85% |
system |
System events | Connection errors |
Severity Levels
| Level | Color | Description |
|---|---|---|
critical |
🔴 Red | Critical issues |
warning |
🟠 Orange | Warnings |
info |
🔵 Blue | Informational |
success |
🟢 Green | Successful operations |
Email Setup
- Go to Settings → Notifications
- In "Notification Channels Configuration" fill SMTP data:
- SMTP server - mail server address
- Port - 465 for SSL, 587 for STARTTLS
- User - your email
- Password - app password
- Sender email - sender address
- In "Notification Settings" check Email notifications
- Click Test to verify
Supported SMTP servers:
| Server | Host | Port | TLS |
|---|---|---|---|
| Yandex | smtp.yandex.ru | 465 | SSL |
| Gmail | smtp.gmail.com | 587 | STARTTLS |
| Mail.ru | smtp.mail.ru | 465 | SSL |
⚠️ For Yandex and Gmail use "App Password", not main password!
Telegram Setup
- Create bot via @BotFather
- In Settings → Notifications enter bot token
- Send
/startto your bot - Get Chat ID via @userinfobot
- In "Notification Settings" enable Telegram and enter Chat ID
- Click Verify to confirm
Notification Settings
| Parameter | Description |
|---|---|
| Enabled | Activate channel |
| Critical only | Send only critical level |
| Quiet hours | Period without notifications (e.g., 23:00 - 07:00) |
Background Monitoring
Automatically tracks:
- VM Status every 30 seconds
- Resources every 60 seconds
- Thresholds: CPU > 80%, RAM > 85%, Disk > 90%
🖥️ VM and Container Management
Command palette (Ctrl+K)
Press Ctrl+K (⌘K on macOS) anywhere in the app to open the command palette — a searchable list of every navigation destination. Start typing to filter, then Enter to jump. The palette and the sidebar are driven by the same unified navigation registry, so they always stay in sync.
VM Actions
The per-instance row menu (⋯) exposes Options, Migrate, Change resources, Create backup and Delete alongside the power actions below. The menu stays open across live list refreshes.
| Action | Description | Hotkey |
|---|---|---|
| Start | Start VM | - |
| Stop | Stop (ACPI shutdown) | - |
| Restart | Reboot | - |
| Force Stop | Force stop | - |
| Delete | Delete VM | - |
Instance tabs: for VMs, Compute resources, CPU options, Disk management and Disk resize live on the Hardware tab; LXC containers (which have no Hardware tab) keep those cards on the Settings tab.
Bulk Operations
Select multiple VMs/containers and perform mass actions:
- Check the checkbox next to each VM you want to select
- Use "Select All" to select all visible VMs
- Click action button in the bulk actions bar:
- Start All — Start all selected
- Stop All — Stop all selected
- Restart All — Restart all selected
- Delete All — Delete all selected (with confirmation)
See Bulk Operations for more details.
Console (VNC / Terminal)
- QEMU VMs — VNC console via noVNC (in-browser graphical display).
- Open VM details → click Console, or right-click VM in the list → VNC Console
- Full keyboard and mouse passthrough; fullscreen supported
- LXC Containers — interactive shell via xterm.js terminal.
- Open container details → click Terminal, or right-click container in the VM list → Terminal
- Full PTY: tab completion, colours, Unicode, terminal resize on window resize
- Uses the Proxmox
termproxyWebSocket protocol (auth handshake + keepalive pings every 2 min)
Configuration Changes
CPU: 1-32 cores
RAM: 512MB - 128GB
Disk: Increase size (decrease not possible)
Note (v1.2.0+): Disk resize is fully asynchronous. The panel waits for the Proxmox resize task to complete before starting the VM, preventing
lock file timeouterrors that occurred when the VM was started immediately after a resize request.
High Availability (HA)
Requires a Proxmox cluster with HA manager configured.
- Open VM or container details
- Go to HA tab
- Click Add to HA and choose HA group (optional)
- To remove — click Remove from HA
Panel operations currently reflect HA status but do not replace Proxmox HA manager configuration.
⚡ Bulk Operations
Overview
Bulk operations allow you to perform the same action on multiple VMs or containers at once. This is useful when you need to start, stop, or restart many virtual machines simultaneously.
Supported Actions
| Action | Description | Confirmation Required |
|---|---|---|
| Bulk Start | Start all selected VMs/containers | No |
| Bulk Stop | Stop all selected (ACPI shutdown) | No |
| Bulk Restart | Restart all selected | No |
| Bulk Shutdown | Graceful shutdown | No |
| Bulk Delete | Delete all selected | Yes (double confirm) |
How to Use
- Navigate to Virtual Machines page
- Select VMs using checkboxes:
- Click individual checkboxes
- Or use "Select All" to select all visible VMs
- Bulk Actions Bar appears at the bottom when VMs are selected
- Click desired action button
- Confirm if prompted (for delete operations)
Task Drawer
When you initiate a bulk operation the Task Drawer opens automatically in the sidebar. It shows real-time progress for each task:
- Running tasks show a spinner and current progress (
completed / total) - Completed tasks are shown in green; Failed in red
- Tasks older than 24 hours are automatically hidden from the drawer (they remain accessible on the
/taskspage) - Use the trash icon (🗑) in the drawer header to remove all finished/failed/cancelled tasks in one click
Proxmox UPID Tracking
Starting from v1.1.5, individual VM/LXC control actions (start / stop / restart) are linked to the real Proxmox task UPID. The UPID is registered as a ProxmoxTask entry and polled in real time, so the Task Drawer reflects the actual Proxmox-side task state.
Limitations
- Only one bulk task runs at a time
- Tasks are processed sequentially
- Maximum items per task: unlimited (but consider server load)
- Tasks cannot be paused (only cancelled if pending)
📋 OS Templates
Concept
OS Templates allow quick VM deployment from preconfigured templates.
Creating Template Group
- OS Templates → Groups → Add Group
- Enter name (e.g., "Linux Servers")
- Add description
Adding Template
- OS Templates → Templates → Add Template
- Or: Scan → select from Proxmox
Deploying VM
- Select template
- Specify:
- VM name
- Target Node (for clusters - select where to deploy)
- CPU/RAM/Disk
- IP address (or auto from IPAM)
- SSH key (optional)
- Click Deploy
Cross-Node Template Deployment (Clusters)
When using Proxmox clusters, templates can be deployed to any cluster node:
How it works
- Create template on any node - Template is initially created on one cluster node
- Select target node - When deploying VM, choose any online cluster node
- Automatic replication - If template doesn't exist on target node:
- System clones template to target node
- Converts clone to template
- Tracks replicated templates in database
- Subsequent deployments - Use already replicated template (fast)
Requirements
- Proxmox cluster (nodes must be in same cluster)
- Shared storage recommended (but not required)
- Template must be accessible from source node
Example
Cluster: pve1, pve2, pve3
Template: Ubuntu-22.04 on pve1
Deploy to pve1: Uses original template (fast)
Deploy to pve2: Replicates template → deploys (first time slower)
Deploy to pve3: Replicates template → deploys (first time slower)
Next deploy to pve2: Uses replicated template (fast)
Reinstalling VM
- Open VM details
- Click Reinstall
- Select template
- VMID preserved
📥 Images — Cloud Image Catalog
Overview
The Images module allows you to browse, download, and manage OS images from cloud repositories. Images can be downloaded directly to Proxmox storage and optionally converted to VM templates automatically.
Features
- Browse cloud images — discover OS images from the default Proxmox repository and custom mirrors
- Download images — download
.qcow2images to any Proxmox storage with progress tracking - Auto-convert to template — optional automatic conversion of downloaded images to Proxmox VM templates
- Architecture-aware filtering — images are filtered by node platform (x86-64, aarch64); default architecture matches the selected node
- Custom mirrors — administrators can add, edit, and manage custom mirror sources for image discovery
- ISO downloads — fetch install ISOs by URL, from a saved mirror (
kind=iso), or straight from the ISO step of the create-instance wizard. The bundled catalog stays.qcow2-only on purpose — install ISOs have no stable "latest" URL, so hardcoded links would rot on every point release - Local file upload — upload an ISO or LXC template (
vztmpl) from your own machine straight to node storage, for images that aren't published anywhere the panel can reach
Accessing Images
- Go to Infrastructure → Images (sidebar)
- Select a Proxmox server and node
- Browse available images filtered by architecture
Downloading an Image
- Click Download next to the desired image
- Select target storage
- Optionally enable Convert to Template (creates a VM template on completion)
- Click Download — progress is tracked in real-time
- Once complete, the image is available in storage for VM deployment
Uploading a Local File
- Go to Infrastructure → Images and pick Upload
- Select an ISO or
vztmplfile and the target storage - The panel streams the file to the node in the background; progress is tracked the same way as a URL download
- Once complete, the file is available in storage for VM/CT deployment
Installing an OS from an ISO
The create-instance wizard offers a blank VM kind for ISO-based installs: it provisions an empty disk with the chosen ISO mounted on ide2 and set first in the boot order, so the VM boots straight into the installer over the built-in noVNC console. Selecting a Windows guest automatically presets OVMF firmware, a SATA disk and an e1000 NIC, since the installer has no virtio drivers loaded yet.
Ejecting and Mounting an ISO
From a VM's detail page, the ISO dialog shows every ISO already on the node's storage — including ones that predate the node joining the panel — and the image currently mounted, if any:
- Mount — pick an ISO from storage; optionally check "boot from this ISO" to put the CD-ROM drive first in the boot order.
- Eject (renamed from "Detach") — remove the mounted ISO; optionally check "boot from disk" to put the primary disk first in the boot order.
Either action that changes boot order applies it via a hybrid reboot: a graceful ACPI shutdown with a forceStop fallback, then start. This avoids the guest-ping timeout that a plain restart hits on a freshly installed OS with no QEMU Guest Agent running yet.
GitHub Mirror with QEMU Guest Agent
The bundled amd64 image catalog points to a GitHub mirror that contains cloud images pre-patched with qemu-guest-agent. This means:
- Live per-VM metrics (CPU, memory, disk fill, I/O rates) work immediately after deploy — no post-install agent setup needed
- Images include a sha256 checksum for integrity verification; if the cached file no longer matches the published checksum, the panel re-downloads it automatically
To rebuild or update the mirror yourself, run the included helper script on a Linux host with QEMU installed:
bash patch-cloud-images.sh
It downloads the upstream .qcow2, installs qemu-guest-agent via virt-customize, and publishes the patched image as a GitHub Release.
Custom Mirrors (Admin Only)
Administrators can configure custom image repositories:
- Go to Settings → Images → Custom Mirrors
- Click Add Mirror
- Enter mirror URL (must be a valid Proxmox repository)
- Save — mirror appears in the image browser immediately
Mirror URL Format:
A valid mirror must serve Proxmox's standard repository structure with an index.json file containing image metadata.
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/images |
List available images |
GET |
/api/images/node-arch/{server_id}/{node} |
Get architecture for a node |
POST |
/api/images/download |
Start image download (returns task UPID) |
GET |
/api/images/mirrors |
List configured custom mirrors (admin) |
POST |
/api/images/mirrors |
Add custom mirror (admin) |
PUT |
/api/images/mirrors/{id} |
Update mirror (admin) |
DELETE |
/api/images/mirrors/{id} |
Delete mirror (admin) |
Image Download Task Tracking
Image downloads are tracked as Proxmox tasks (UPID). Use the standard task endpoint to monitor progress:
GET /api/backups/task/{server_id}/{node}/{upid}
<EFBFBD> LXC CT Template Deployment
Overview
Starting from v1.5.0, LXC containers can be created directly from Proxmox CT template files (.tar.zst, .tar.gz) without needing a pre-configured OS Template entry. This is the standard way to deploy Debian, Ubuntu, Alpine, and other distros from the Proxmox download list.
Browsing Available CT Templates
CT templates are fetched directly from the Proxmox server storage:
- Go to Instances → Create Instance
- Select the target Proxmox server
- Choose type LXC Container
- The wizard fetches available
.tar.zst/.tar.gzfiles from all storages on the selected server
Deploying an LXC Container
- Server step — select Proxmox server
- Type step — select LXC Container
- Template step — select a CT template from the list
- Config step — fill in:
- Container name (hostname)
- CPU cores, RAM (MB), Swap (MB), Disk (GB)
- Storage, network bridge
- IP address / gateway (manual or from IPAM)
- Root password and/or SSH keys
- Owner (admin only)
- Start after create / On-boot options
- Confirm step — review and click Deploy
Deployment runs asynchronously; progress is tracked in the Task Drawer.
SSH Keys in LXC Deployment
- Select one or more keys from the personal SSH key library (see SSH Keys Management)
- Admins deploying on behalf of another user can pick from that user's key library
- Keys are injected into the container's
authorized_keysduring provisioning
Reinstalling an LXC Container (CT template)
Containers created from CT templates support reinstall:
- Open the container's action menu → Reinstall
- Optionally select a different CT template
- The following are preserved automatically:
- Root password
- SSH keys
- IP address and gateway
- The old container is destroyed and a new one is created with the same VMID
Note:
nesting=1is enabled by default on all new LXC containers for systemd 255+ (cgroup v2) compatibility.
🛒 App Store
Catalog of self-hosted applications with one-click install. Each app runs in its own unprivileged LXC container with Docker Compose (1 app = 1 LXC). The catalog is multi-source: runtipi/runtipi-appstore (~265 apps) and, optionally, getumbrel/umbrel-apps (~380 apps). Sources are selected via APPSTORE_SOURCES; Umbrel app ids are namespaced (umbrel-<id>) to avoid collisions.
Prerequisites
- A golden LXC template (Debian 12 + Docker + Compose) per Proxmox server. Build it from App Store → золотой шаблон (
POST /api/appstore/golden-template, picks server/node/storage) — this automates the manual procedure in docs/golden-template.md. Template and rootfs storage are picked from dropdowns populated with the selected server/node's actual storages (filtered byvztmpl/rootdircontent), defaulting tolocal/local-lvmwhen available. The resulting vztmpl volid is stored perserver_id(a template built on server A lives on A's storage only and is never used to install on server B);APPSTORE_GOLDEN_TEMPLATEis just the last-resort fallback for servers that never got their own build. - SSH access from the panel to the Proxmox node (used for
pct exec/pct push). FERNET_KEYset (encrypts app secrets at rest).
Environment settings
| Variable | Default | Purpose |
|---|---|---|
APPSTORE_GOLDEN_TEMPLATE |
— | golden vztmpl volid fallback, used only if a server has no per-server template saved |
APPSTORE_NAMESERVER |
1.1.1.1 8.8.8.8 |
DNS explicitly set on App Store LXCs (and the golden-template build CT) — without it, the container inherits the node's resolver, which can be unreachable from the LXC's network (e.g. a Tailscale-only DNS on the host) and break image pulls |
APPSTORE_SOURCES |
runtipi |
active catalog sources, comma-separated (runtipi, umbrel) |
RUNTIPI_APPSTORE_REPO |
runtipi/runtipi-appstore |
Runtipi catalog source |
RUNTIPI_APPSTORE_REF |
master |
branch/tag/commit (pin recommended) |
UMBREL_APPSTORE_REPO |
getumbrel/umbrel-apps |
Umbrel catalog source (used when umbrel is enabled) |
UMBREL_APPSTORE_REF |
master |
Umbrel branch/tag/commit |
UMBREL_APPSTORE_GALLERY_CDN |
https://getumbrel.github.io/umbrel-apps-gallery |
Umbrel icons CDN (<app-id>/icon.svg, fetched per-file in parallel) |
APPSTORE_DATA_DIR |
/app/data/appstore |
logo cache (mounted volume) |
CATALOG_SYNC_INTERVAL_HOURS |
24 |
auto catalog sync interval |
APPSTORE_HOST_ARCH |
amd64 |
target node arch (marks unsupported apps) |
Catalog
- App Store screen: card grid with search and category filters; Refresh catalog button + last-sync timestamp. When more than one source is enabled, a source filter and a per-card source badge (Runtipi / Umbrel) appear.
- Automatic sync every 24 h; a broken app entry is skipped without failing the whole sync. Each source syncs independently and the disappeared → unavailable pass is scoped per source.
- Umbrel apps: metadata comes from
umbrel-app.yml, icons from the gallery repo; on install the Umbrelapp_proxy/torruntime services are stripped and the container port (app_proxy → APP_PORT) is published on the LXC host. - App page: description, version, source link, install disclaimer, and a compose preview.
Install
Pipeline (each step is journaled and streamed over WebSocket):
- Clone the golden LXC template (default 2 vCPU / 2048 MB / 8 GB, DHCP or advanced overrides).
- Start and wait for IP.
pct pushdocker-compose.yml+.env(form answers + generatedrandomsecrets) + any bundleddata_files.docker compose up -d.- HTTP health-check.
- Show the app URL and one-time credentials.
A failed step sets status error with a step log; Retry does not recreate the LXC (idempotent by VMID) and reuses the original install parameters (saved in installed_apps.install_params), Delete removes the container and the record.
Reliability notes:
- Files bundled with a catalog app (
data_files) are delivered to the container beforedocker compose up; UmbrelAPP_PASSWORD/default_credentialsare honored. - Bind-mount host directories (
volumes:in the app's compose, both${APP_DATA_DIR}/...and relative paths) are pre-created with permissive ownership beforeup— otherwise Docker auto-creates them as root and non-root container users (e.g. mariadb'smysql) getPermission denied. - The VMID search skips already-occupied records —
nextiddoes not reserve the number, so a retry could otherwise grab another container. pct createretries a few times on a transient config-lock error (common right after deleting the same VMID, while the node is still wiping the old disk).- Delete now waits for Proxmox to actually confirm the destroy task finished (up to 30 min, covers slow disk wipes) before marking the app removed — otherwise a VMID could be reused while still busy on the node.
.envvalues containing#are quoted (the dotenv parser would otherwise trim them as an inline comment); a port chosen explicitly in the wizard (including80) is published as-is.
Minimized operations tray
Long-running App Store operations — an app install and a golden-template build — can be minimized instead of keeping the dialog open:
- Minimized operations live in a global store (persisted to
localStorage) and are rendered as a tray in the app layout, so a chip survives page navigation and a full tab reload. The tray polls each operation's progress itself (REST, every 3 s), independently of the dialog. - Clicking a chip returns to the operation's page and reopens the dialog — an install resumes in the wizard, a template build reopens the golden-template dialog with the server preselected.
- Minimize adds the operation to the tray and closes the dialog; the chip's ✕ only removes the chip (the operation keeps running on the server). Chips for operations that no longer exist (a
404after reload) are cleared automatically.
My Apps
Manage installed apps: Start / Stop / Restart / Logs / Delete, live status, and a clickable IP:port.
- Reconciliation runs every 60 s and on screen open: an LXC removed outside PVEmanager is flagged
orphaned. - Update: takes a pre-update Proxmox snapshot →
docker compose pull && up -d→ health-check; keeps only the last pre-update snapshot. - Rollback: restores the snapshot (⚠️ also reverts app data to the snapshot moment) and reverts the version in the database.
Permissions
app:view (catalog + My Apps), app:install (install), app:manage (sync, lifecycle, update, rollback, delete). Admins bypass all checks.
REST API
GET /api/appstore/catalog # list (q, category, source)
GET /api/appstore/catalog/{app_id} # detail
GET /api/appstore/catalog/{app_id}/logo # logo (public)
POST /api/appstore/catalog/sync # manual sync (admin)
POST /api/appstore/apps/install # install
GET /api/appstore/apps # installed list
POST /api/appstore/apps/{id}/action/{start|stop|restart}
GET /api/appstore/apps/{id}/logs
POST /api/appstore/apps/{id}/update
POST /api/appstore/apps/{id}/rollback
POST /api/appstore/apps/reconcile
DELETE /api/appstore/apps/{id}
POST /api/appstore/golden-template # build golden vztmpl for a server
GET /api/appstore/golden-template/status?server_id=… # per-server template + last build op
<EFBFBD>🔗 Proxmox Clusters
Overview
PVEmanager fully supports Proxmox clusters, enabling management of multiple nodes as a single entity.
Adding a Cluster
- Go to Proxmox VE → Add Server
- Enter any node's IP address (e.g., pve1)
- Panel automatically discovers all cluster nodes
- All nodes appear in node selectors
Cluster Benefits
| Feature | Standalone | Cluster |
|---|---|---|
| Node count | 1 | Multiple |
| Cross-node templates | ❌ | ✅ |
| Target node selection | ❌ | ✅ |
| Automatic failover | ❌ | ✅ (Proxmox HA) |
Creating a Proxmox Cluster
If your nodes aren't clustered yet:
# On first node (pve1)
pvecm create my-cluster
# On other nodes (pve2, pve3, etc.)
pvecm add 10.10.10.11 # IP of pve1
Cross-Node Template Deployment
See OS Templates - Cross-Node Template Deployment
Importing an Existing Cluster
If nodes were already joined into a cluster outside the panel (via pvecm or the Proxmox web UI), add each node to the panel as a regular standalone server first, then use Cluster → Import:
- Pick any one of the already-added nodes — the panel reads
cluster/config/joinfrom it to detect the real cluster name and node list - Matching panel servers (by IP or hostname) are pre-selected; nodes not yet added to the panel are listed but can't be linked until you add them as servers
- Confirm — the panel only links the matching server records to the cluster group in its own database; no
pvecmcommand is run and the real cluster is not touched
This is the DB-only counterpart to Create cluster/Join node, which perform the actual pvecm create/pvecm add operations — use Import only when the nodes are already clustered for real.
📸 Snapshots
Overview
Snapshots allow you to save and restore the state of VMs and containers at any point in time.
Creating a Snapshot
- Open VM or container details
- Go to the Snapshots tab
- Click Create Snapshot
- Enter a name and description
- Optionally include RAM state (QEMU VMs only)
Managing Snapshots
| Action | Description |
|---|---|
| Create | Save current VM state |
| Rollback | Restore to a previous snapshot state |
| Delete | Remove an existing snapshot |
⚠️ Rolling back will discard all changes made after the snapshot was created!
Snapshot Queue
Snapshot operations are processed through the task queue system. Progress is tracked and results are saved for each item.
API Endpoints
# List snapshots
GET /api/{server_id}/vm/{vmid}/snapshots?node={node}
# Create snapshot
POST /api/{server_id}/vm/{vmid}/snapshots?node={node}
# Delete snapshot
DELETE /api/{server_id}/vm/{vmid}/snapshots/{snapname}?node={node}
# Rollback to snapshot
POST /api/{server_id}/vm/{vmid}/snapshots/{snapname}/rollback?node={node}
For LXC containers use
/container/instead of/vm/in the endpoints above.
💾 Backups
Overview
PVEmanager provides a full backup management interface powered by Proxmox vzdump. You can create, browse, restore and delete backups, as well as configure automated scheduled jobs.
Storage Management
Before creating backups, make sure the target storage is configured in Proxmox.
| Action | Description |
|---|---|
| List storages | View all storages on a server |
| Add storage | Add a new storage pool (directory, NFS, CIFS, etc.) |
| Edit storage | Modify storage configuration |
| Delete storage | Remove storage from Proxmox |
Creating a Backup
- Navigate to Backups page
- Select Server and Node
- Choose Storage where the backup will be saved
- Select VM/Container (by VMID)
- Configure options:
- Mode:
snapshot(default),suspend, orstop - Compress:
zstd(default),lzo,gzip,none
- Mode:
- Click Create Backup
- A UPID is returned — use it to track task progress
Backup Modes
| Mode | Description | Downtime |
|---|---|---|
snapshot |
Live snapshot (requires QEMU agent or LVM) | None |
suspend |
Suspend VM during backup | Brief |
stop |
Stop VM, backup, start again | Full stop |
Listing & Deleting Backups
- Browse existing backups by server, node and storage
- Filter by VMID
- Delete individual backup files
Restore
- Select a backup file from the list
- Click Restore
- Specify:
- Target storage for restoration
- New VMID (optional, keep original or assign new)
- Start after restore (optional)
- Restoration runs as a Proxmox task (UPID returned for tracking)
⚠️ Restoring to an existing VMID will overwrite it!
Scheduled Backup Jobs
Automate recurring backups using cron-style scheduling.
Creating a Backup Job
Server: select Proxmox server
Node: target node
VMIDs: comma-separated list (e.g., 100,101,102)
Storage: backup destination
Mode: snapshot / suspend / stop
Compress: zstd / lzo / gzip / none
Cron: standard 5-field cron expression
Enabled: on/off toggle
Cron Expression Examples
| Expression | Meaning |
|---|---|
0 2 * * * |
Every day at 02:00 |
0 3 * * 0 |
Every Sunday at 03:00 |
0 1 * * 1-5 |
Weekdays at 01:00 |
30 4 1 * * |
First day of each month at 04:30 |
Retention Policies
Configure how many backups to keep automatically:
| Parameter | Description | Default |
|---|---|---|
keep_last |
Keep N most recent backups | 3 |
keep_daily |
Keep one per day for N days | 7 |
keep_weekly |
Keep one per week for N weeks | 4 |
keep_monthly |
Keep one per month for N months | 6 |
Managing Jobs
- List jobs — All backup jobs (admins see all; users see own)
- Enable/disable — Toggle job without deleting it
- Edit — Change schedule, retention, storage
- Delete — Remove job and its APScheduler entry
Task Tracking
Every backup operation (create / restore / delete) returns a Proxmox UPID. Use it to poll task status and logs:
# Get task status
GET /api/backups/task/{server_id}/{node}/{upid}
🌐 IPAM
Networks
Create networks to organize IP space:
Name: Production Network
CIDR: 192.168.1.0/24
VLAN: 100
Gateway: 192.168.1.1
DNS: 8.8.8.8, 8.8.4.4
Proxmox Server: select server
Proxmox Node: select node (prevents cross-server IP assignment)
IP Pools
Pools define ranges for automatic allocation:
Pool: Web Servers
Start: 192.168.1.10
End: 192.168.1.50
IP Status
| Status | Description |
|---|---|
| 🟢 Available | Free |
| 🔵 Allocated | Assigned to VM |
| 🟠 Reserved | Reserved |
💻 Remote Command Execution
Requirements
- QEMU Guest Agent installed and running in VM
- VM must be running
Installing Guest Agent
Debian/Ubuntu:
apt install qemu-guest-agent
systemctl enable --now qemu-guest-agent
CentOS/RHEL/AlmaLinux:
yum install qemu-guest-agent
systemctl enable --now qemu-guest-agent
Windows: Download virtio-win drivers
Usage
- Open VM details
- Go to Commands tab
- Enter command or select from quick commands
- Click Execute
Quick Commands
| Command | Description |
|---|---|
df -h |
Disk usage |
free -h |
Memory usage |
uptime |
Uptime |
ps aux |
Process list |
systemctl status |
Service status |
Limitations
- Timeout: 30 seconds
- Interactive commands not supported
- Requires Guest Agent
📊 Monitoring
Real-time Metrics
- CPU: Total load + per core
- RAM: Used/Total
- Disk: Used/Total + I/O (per-disk cards on Overview tab)
- Network: In/Out traffic (rates in bytes/sec)
- Disk IOPS: Read/write rates per disk via QEMU guest agent (
get_vm_fsinfo) - Network I/O rates: Live bytes/sec computed from cumulative counters in
metrics_broadcaster
Data Granularity
| Source | Granularity | Used for |
|---|---|---|
cluster/resources (pvestatd) |
10 s | Node and storage overview |
status/current |
1 s | Running VM real-time metrics |
All running VMs are polled via status/current at a 1 s cadence with bounded concurrency. Results are broadcast via WebSocket.
Where Metrics Appear
| Location | Data shown |
|---|---|
| Instance → Overview tab | Per-disk fill cards, Network I/O card, Disk I/O-rate card |
| Instances list | Net I/O and Disk I/O columns; Disk% for LXC |
| Dashboard top-loaded list | CPU, RAM, Disk, Net metrics overlay |
Graph Periods
- Hour
- Day
- Week
- Month
Alerts
Threshold values for notifications:
| Metric | Warning | Critical |
|---|---|---|
| CPU | 80% | 90% |
| RAM | 85% | 95% |
| Disk | 90% | 95% |
🔒 Security
Overview
PVEmanager includes comprehensive security features to protect your infrastructure.
RBAC v2 — Role-Based Access Control
This is the section to read if RBAC feels confusing. It explains the whole model end-to-end: what a permission is, how a request is actually checked, what every built-in role can do, and — crucially — how "each user sees only their own VMs" is a separate mechanism from RBAC and is easy to misconfigure. Backend source of truth:
backend/app/rbac/permissions.py(catalogue of permissions),backend/app/rbac/engine.py(the check logic),backend/app/rbac/migration.py(default roles).
The mental model in one paragraph
A user has exactly one role. A role is just a named JSON map of
permissions → true/false (e.g. {"vm:view": true, "vm:delete": false}).
Every protected API endpoint declares the permission it needs (e.g. "you need
vm:create"). On each request the Permission Engine looks at the caller's
role map and answers yes/no. There are no per-resource ACLs and no permission
"scopes" in practice — a permission is either granted to the role or it is not.
The one apparent exception, "users only see their own instances", is not a
permission at all — it is a separate ownership filter described at the bottom of
this section.
Permission format
Every permission is an atomic string:
resource:action
resource— what you are acting on (vm,backup,user, …).action— what you do to it (view,create,delete, …).
Examples: vm:view (see VMs), server:create (add a Proxmox server),
log:export (download audit logs), role:manage (full role administration).
About
:scope. The permission dataclass technically supports a third:scopesegment, but no built-in permission uses it — every real permission is global (resource:action). Ignore scope when designing roles; tenant isolation is handled by ownership, not by a:ownscope. (Older versions of this wiki documented fictionalvms:view:owncodes — those never existed in the code and have been removed.)
How a permission check actually works (the engine)
When an endpoint asks "does this user have X:Y?", PermissionEngine.has_permission
evaluates these rules in order and returns true on the first match:
- Admin bypass. If the user is an administrator (
is_admin, or role nameadmin), the answer is always yes — admins implicitly hold every permission and are never blocked. - Direct grant. The exact code is present and
truein the role map (vm:createis in the role → yes). - Wildcard grant. The role holds
resource:*(e.g.vm:*grants everyvm:action). Wildcards are not used by the built-in roles but you may add them to custom roles. manageimplies common actions. If the role holdsresource:manage, the engine also grants these actions on that resource:view,list,update,start,stop,restart. Sovm:managealone lets a user view/edit/power VMs without listing those actions separately. ⚠️ Notemanagedoes not implycreateordelete— grant those explicitly.
If none match, access is denied with HTTP 403 Permission denied: X:Y.
Enforcement points. Endpoints gate access via the
PermissionCheckerFastAPI dependency (Depends(PermissionChecker("vm:create"))) or an inlinerequire_permission(user, "vm:create")call. The frontend independently hides sidebar items the user lacks permission for (see the UI-mapping table below), but the backend check is the real security boundary.
requires — permission dependencies
Many permissions declare prerequisites (the requires field in
permissions.py). These express logical dependencies — e.g. vm:create
requires vm:view + template:view; vm:execute requires vm:console.
Important:
requiresis documentation / UI guidance, not runtime enforcement — the engine does not auto-grant prerequisites. When you build a custom role, tick the prerequisites yourself, otherwise the feature may be half-broken (e.g. a user who canvm:createbut lacksvm:viewcannot see the VM they just created).
Full permission catalogue
Grouped by resource. "Implied by manage" means the engine grants it for free
when the role holds <resource>:manage (rule 4 above).
Dashboard
| Permission | Unlocks |
|---|---|
dashboard:view |
Access the Dashboard page and overview stats |
Proxmox Servers & Cluster (server, cluster)
| Permission | Unlocks |
|---|---|
server:view |
See the server/node list and status |
server:create |
Add a new Proxmox server |
server:update |
Edit server connection settings |
server:delete |
Remove a Proxmox server |
server:manage |
Full server management incl. cluster ops (requires server:view) |
cluster:manage |
Create clusters, join/eject nodes (requires server:view) |
Virtual Machines (vm)
| Permission | Unlocks |
|---|---|
vm:view |
See VMs (subject to ownership filter — see below) |
vm:create |
Create VMs (requires vm:view, template:view) |
vm:update |
Change VM config (CPU/RAM/disk) |
vm:delete |
Delete VMs |
vm:start / vm:stop / vm:restart |
Power actions |
vm:console |
noVNC / xterm.js console |
vm:migrate |
Migrate between nodes of the same cluster |
vm:remote_migrate |
Migrate to a different registered cluster (requires vm:migrate) |
vm:execute |
Run commands via QEMU guest agent (requires vm:console) |
vm:manage |
Full VM management and the global "see all instances" flag (see isolation section) |
LXC Containers (lxc) — same shape as VM: view, create, update,
delete, start, stop, restart, console, migrate, remote_migrate.
(There is no separate lxc:manage; container listing/power is governed by the
lxc:* actions and the same ownership filter as VMs.)
Templates & Images (template)
| Permission | Unlocks |
|---|---|
template:view |
Browse OS templates |
template:create / template:update / template:delete |
Manage template entries |
template:manage |
Full template management incl. the Images (cloud-image download) page |
Storage (storage): storage:view, storage:manage.
Backups (backup): backup:view, backup:create, backup:restore, backup:delete, backup:manage (storages + scheduled jobs).
IPAM (ipam): ipam:view, ipam:manage.
Networking (network): network:view, network:manage (node interfaces + SDN zones/vnets/subnets).
Firewall (firewall): firewall:view, firewall:manage (datacenter/node/VM rules, groups, IP sets).
Node administration (node): node:view, node:manage (systemd services, APT), node:upgrade (apt dist-upgrade), node:power (reboot/shutdown the physical node).
Resource pools (pool): pool:view, pool:manage.
PVE access (access): access:view, access:manage (Proxmox auth realms & API tokens).
App Store (app): app:view (catalog + My Apps), app:install, app:manage (sync, update, rollback, delete).
Scripts (script): script:view, script:execute, script:manage (edit scripts & git sources).
Users, Quotas & Roles (user, quota, role)
| Permission | Unlocks |
|---|---|
user:view |
See the user list |
user:create / user:update / user:delete |
Manage user accounts |
user:manage |
Full user admin, incl. managing other users' SSH keys |
quota:view / quota:manage |
Read / set per-user resource quotas (requires user:view) |
role:view |
See roles & their permissions |
role:create / role:update / role:delete |
Manage roles |
role:manage |
Full role administration incl. assigning roles (requires role:view, user:view) |
Logs (log): log:view, log:export, log:delete.
Settings (setting): setting:view, setting:update (panel settings), setting:manage (security & advanced settings). These cover panel-wide configuration only — a user's own account (profile, password, 2FA, SSH keys, notification preferences) lives at /profile, is self-scoped, and needs no permission.
Notifications (notification): notification:view, notification:manage.
Delegated role editing. A non-admin with
role:managecan only grant permissions they themselves already hold (can_assign_permission/validate_role_permissions). This prevents privilege escalation — you cannot hand out access you don't have. Admins are exempt and can grant anything.
Which permission unlocks which sidebar page
The frontend (frontend/src/lib/nav-items.ts) hides a menu entry unless the
user is an admin or holds the listed permission. Items with no permission
are admin-only. The same permission also guards the route itself
(RequirePermission in frontend/src/components/shared/route-guards.tsx), so
a hidden page cannot be reached by typing its URL — denied users are redirected
to the first page they can open.
| Sidebar item | Required permission |
|---|---|
| Dashboard | dashboard:view |
| Instances | vm:view |
| Nodes | server:view |
| Cluster | cluster:manage |
| Templates | template:view |
| Images | template:manage |
| Backups | backup:view |
| Tasks | vm:view |
| App Store / My Apps | app:view |
| Scripts | script:view |
| IPAM | ipam:view |
| Networks | server:manage |
| Users | user:view |
| Workspaces | (admin-only) |
| Logs | log:view |
| Settings (panel-wide) | setting:view |
| Profile (user menu, not the sidebar) | (none — any logged-in user) |
Default (built-in) roles
Four roles are seeded/updated on every startup by
ensure_default_roles_new_format. They are is_system roles. The seeding logic
only adds missing permission keys — it never overwrites keys an admin has
already toggled in the UI — so your customisations to these roles survive
upgrades. New permissions introduced by a version bump are also backfilled
onto custom roles that already hold the matching umbrella permission
(backfill_granular_permissions), so a custom role built on server:manage
automatically gains new node:* / network:* keys instead of silently losing
access.
| Role | Intended use | What it can do |
|---|---|---|
| admin | Platform administrator | Everything. Holds every permission and the is_admin bypass — sees all servers, all instances (regardless of owner), all users, settings, security. |
| moderator | Operator without admin rights | View dashboard/servers; create + power + console VMs and LXC; view templates/storage/firewall/network/node/pool/backups/IPAM/users/logs; create backups; export logs; manage notifications. No delete, no settings changes, no user/role management. Note: without vm:manage a moderator is still subject to the ownership filter (see below). |
| user | Standard tenant (VPS-style) | View + start/stop/restart/console their own VMs and LXC; view templates/storage/firewall/network/node/pool/IPAM; manage own notifications. Their own profile, SSH keys and quota live at /profile and need no permission, so this role deliberately has no setting:view (that would hand out the panel-wide settings page). No create/delete by default, no dashboard. Scoped to owned instances because it lacks vm:manage. |
| demo | Read-only showcase | View-only across dashboard, servers, VMs, LXC, templates, storage, firewall, network, node, pool, IPAM. No power actions, no writes. |
The exact JSON for each role lives in
NEW_DEFAULT_ROLESinbackend/app/rbac/migration.py— consult it if you need the byte-for-byte permission set.
Managing roles in the UI
- Open Users (needs
role:view; editing needsrole:manageor admin). - Switch to the Roles tab → New Role or edit an existing one.
- Permissions are presented grouped by category (Virtual Machines, Backups, User Management, …). Tick the checkboxes you want.
- Remember to also tick the prerequisites (
requires) — the engine does not auto-enable them. - Assign the role to a user on the user's edit dialog.
is_system roles (admin/moderator/user/demo) cannot be deleted, but their
permission sets can be tuned (your changes are preserved across upgrades).
Legacy permission format
Older releases used a dot format (vms.view, proxmox.manage). These are
mapped to the new colon format via LEGACY_PERMISSION_MAP and converted
once in the database at startup, so no runtime translation is needed. You may
still type a legacy code into a custom role — it is resolved transparently — but
new roles should use the resource:action form.
User Isolation — "each user sees only their own instances"
This is the feature most often confused with RBAC. It is not a permission.
It is a separate ownership filter layered on top of the vm:*/lxc:*
permissions. Understanding the split is the key to configuring tenants correctly.
The rule
| Caller | Sees / controls |
|---|---|
Admin, or role named admin |
All instances |
Any role holding vm:manage |
All instances |
Everyone else (incl. stock user role) |
Only instances they own (owner_id == user.id) |
The decision is made by can_view_all_instances in
backend/app/api/proxmox/_helpers.py. Note carefully: the "see everything" key
is vm:manage, not vm:view. The stock user role deliberately carries
vm:view only so the Instances page works at all — vm:view on its own never
reveals other tenants' VMs.
⚠️ The single most common misconfiguration: granting
vm:manageto theuserrole (or a custom "tenant" role) to "let them manage their VMs". That flips on the global view and every user suddenly sees every VM in the panel. For tenants, grant the granular actions (vm:update,vm:delete,vm:start, …) instead ofvm:manage. Reservevm:managefor operators/admins who are supposed to see everything.
How ownership is enforced everywhere
Ownership is checked not just on the instance list but on every surface a non-privileged user can reach:
- Instance list & single-instance actions —
check_vm_access/require_vm_accessverifyowner_idbefore any view/power/console/snapshot/backup/delete op. - Live resource endpoints — the dashboard (
/api/resources/all) and the node/server page (/api/{server_id}/resources) callget_owned_vmidsand filter the live Proxmox listing down to owned VMIDs, so a user with onlyserver:viewcannot enumerate other owners' VMs through the node page.
Assigning an owner
- Automatic — when a user deploys a VM/LXC they become its
owner_id. - Manual (admin) — open the instance → Owner button → pick a user → Save. See VM / LXC Ownership for the REST API.
Instances synced from Proxmox before ownership existed have owner_id = NULL
(visible to privileged users only). Bulk-assign them with:
docker compose exec app python assign_instances_owner.py
RBAC vs Workspaces vs Server-assignment vs Ownership
These four mechanisms compose — a non-admin must pass all applicable filters:
| Layer | Question it answers | Configured in |
|---|---|---|
| RBAC role | What actions may this user perform at all? | Users → Roles |
| Workspaces | Which servers is this user allowed to see? | Workspaces |
| Server assignment | Which specific servers within those workspaces? | User → Server Assignment |
| Ownership | Which individual VMs/LXC (unless vm:manage/admin)? |
VM / LXC Ownership |
Snapshots
Snapshots allow you to save and restore the state of VMs and containers.
VM Snapshots
# List snapshots
GET /api/{server_id}/vm/{vmid}/snapshots?node={node}
# Create snapshot
POST /api/{server_id}/vm/{vmid}/snapshots?node={node}
{
"snapname": "before-update",
"description": "Before system update",
"vmstate": false # Include RAM state (optional)
}
# Delete snapshot
DELETE /api/{server_id}/vm/{vmid}/snapshots/{snapname}?node={node}
# Rollback to snapshot
POST /api/{server_id}/vm/{vmid}/snapshots/{snapname}/rollback?node={node}&start=false
Container Snapshots
Same endpoints, but use /container/ instead of /vm/.
SDN (Software Defined Networking)
Manage Proxmox SDN zones and virtual networks.
Check SDN Availability
GET /api/servers/{server_id}/sdn/status
Returns:
{
"server_id": 1,
"sdn_available": true,
"pending_changes": false
}
Zone Types
| Type | Description |
|---|---|
simple |
Basic isolated network |
vlan |
VLAN-based separation |
vxlan |
VXLAN overlay network |
evpn |
EVPN/VXLAN with BGP |
SDN API Endpoints
# List zones
GET /api/servers/{server_id}/sdn/zones
# Create zone
POST /api/servers/{server_id}/sdn/zones
{
"zone": "myzone",
"type": "simple"
}
# Update zone
PUT /api/servers/{server_id}/sdn/zones/{zone}
{
"mtu": 1500,
"dns": "8.8.8.8"
}
# List VNets
GET /api/servers/{server_id}/sdn/vnets
# Create VNet
POST /api/servers/{server_id}/sdn/vnets
{
"vnet": "vnet1",
"zone": "myzone",
"alias": "Production Network"
}
# Update VNet
PUT /api/servers/{server_id}/sdn/vnets/{vnet}
{
"alias": "Updated Alias",
"tag": 100
}
# Create subnet (with optional IPAM auto-create)
POST /api/servers/{server_id}/sdn/vnets/{vnet}/subnets
{
"subnet": "10.0.0.0/24",
"gateway": "10.0.0.1",
"snat": true,
"create_ipam_network": true
}
# Delete subnet (optionally delete linked IPAM network)
DELETE /api/servers/{server_id}/sdn/vnets/{vnet}/subnets/{subnet_cidr}?delete_ipam_network=true
# Apply changes (required after modifications)
POST /api/servers/{server_id}/sdn/apply
Node Network Interfaces
Manage network interfaces (bridges, bonds, VLANs) directly on Proxmox nodes.
Accessing the Networks Page
- Open Networks from the sidebar (requires
server:managepermission; SDN/interface writes neednetwork:manage) - Select a Server from the dropdown
- The page shows two tabs: SDN and Node Interfaces
Node Interface Management
| Action | Description |
|---|---|
| List interfaces | View all interfaces on a selected node |
| Create interface | Create bridge, bond, VLAN, or alias |
| Edit interface | Modify IP, netmask, gateway, ports, etc. |
| Delete interface | Remove interface configuration |
| Apply config | Activate pending changes |
| Revert config | Roll back to running configuration |
⚠️ Always Apply changes after creating or editing interfaces. Changes are staged until applied.
Node Network API Endpoints
# List nodes for a server
GET /api/servers/{server_id}/nodes
# List interfaces on a node
GET /proxmox/api/servers/{server_id}/nodes/{node}/networks
# Get interface details
GET /proxmox/api/servers/{server_id}/nodes/{node}/networks/{iface}
# Create interface
POST /proxmox/api/servers/{server_id}/nodes/{node}/networks
{
"iface": "vmbr1",
"type": "bridge",
"address": "10.0.0.1",
"netmask": "255.255.255.0",
"bridge_ports": "ens4",
"autostart": true
}
# Update interface
PUT /proxmox/api/servers/{server_id}/nodes/{node}/networks/{iface}
{
"address": "10.0.0.2"
}
# Delete interface
DELETE /proxmox/api/servers/{server_id}/nodes/{node}/networks/{iface}
# Apply pending network config
PUT /proxmox/api/servers/{server_id}/nodes/{node}/networks/apply
# Revert pending changes
DELETE /proxmox/api/servers/{server_id}/nodes/{node}/networks/revert
Legacy Compatibility
Old permission format (vms.view, proxmox.manage) is still supported and automatically converted to new format.
IP Blocking
Automatic and manual IP blocking protects against brute-force attacks.
Automatic Blocking
When a user fails to login multiple times from the same IP:
- After threshold (default: 10) failed attempts
- IP is blocked for configured duration (default: 60 minutes)
- Block can be temporary or permanent
Manual Blocking
Administrators can manually block IPs:
- Block with reason
- Set duration or make permanent
- View blocked IP list
- Unblock as needed
Session Management
Track and control active user sessions.
| Feature | Description |
|---|---|
| Active Sessions | View all currently logged in users |
| Device Info | Browser and device detection |
| IP Tracking | Session IP address logging |
| Single Session | Option to allow only one session per user |
| Force Logout | Terminate any session |
Login Protection
| Setting | Default | Description |
|---|---|---|
| Max Login Attempts | 5 | Attempts before account lockout |
| Lockout Duration | 30 min | How long account stays locked |
| IP Block Threshold | 10 | Failed attempts before IP block |
| IP Block Duration | 60 min | How long IP stays blocked |
Security Settings
Access via Settings → Security:
| Setting | Default | Description |
|---|---|---|
| Session Timeout | 60 min | Auto-logout after inactivity |
| Single Session | Off | Allow only one session per user |
| Password Min Length | 8 | Minimum password characters |
| Require Uppercase | Yes | Password must have uppercase |
| Require Lowercase | Yes | Password must have lowercase |
| Require Numbers | Yes | Password must have digits |
| Require Special | No | Password must have special chars |
| Password Expiry | 0 (never) | Days until password expires |
| API Rate Limit | 60/min | API requests per minute |
Best Practices
- Change default passwords immediately after installation
- Enable SSL/HTTPS in production
- Use strong passwords with complexity requirements
- Monitor login attempts for suspicious activity
- Configure firewall to limit access to panel
- Regular updates to get security patches
<EFBFBD> Logs & Audit
Overview
The Logs page (/logs in the sidebar) gives admins a full searchable view of the panel's audit_logs table. It complements the Dashboard activity feed with deeper filtering, pagination, and aggregated 24-hour stats.
Page Layout
- Stat cards (24h window) — total events, errors count, failed logins, current page row count
- Filters — free-text search (matches message and username) and a level dropdown populated from
GET /logs/api/levels - Logs table — timestamp, level badge, category, username, message
- Pagination — driven by the API (
page,limit)
Log Levels
| Level | Color | Description |
|---|---|---|
INFO |
🔵 Blue | Informational events |
SUCCESS |
🟢 Green | Successful operations |
WARNING |
🟡 Yellow | Non-critical warnings |
ERROR |
🔴 Red | Failures |
CRITICAL |
🟣 Purple | Critical security / system events |
API Reference
| Method | Endpoint | Description |
|---|---|---|
GET |
/logs/api/logs |
List audit log entries (paginated) |
GET |
/logs/api/stats |
Aggregated stats (24h by default) |
GET |
/logs/api/levels |
Distinct levels for the filter dropdown |
GET |
/logs/api/categories |
Distinct categories |
Query Parameters for GET /logs/api/logs
| Name | Type | Description |
|---|---|---|
search |
string | Free-text search over message/username |
level |
string | Filter by level (INFO, ERROR, …) |
category |
string | Filter by category |
username |
string | Filter by username |
page |
int | Page number (1-based) |
limit |
int | Page size (default 50) |
date_from |
ISO date | Inclusive lower bound |
date_to |
ISO date | Inclusive upper bound |
Response Shape
{
"logs": [
{
"id": 1234,
"timestamp": "2026-06-04T10:21:33Z",
"level": "INFO",
"category": "vm",
"username": "admin",
"message": "VM 100 started on node pve1"
}
],
"total": 532,
"page": 1,
"limit": 50,
"pages": 11
}
Access Control
The Logs page is gated by the log:view permission in RBAC v2. The default admin role has it; moderator has read-only access; user and demo roles do not see the page.
Retention
Log retention is controlled via Settings → Panel → Log Retention Days (added in v1.5.2). Entries older than the configured number of days are pruned automatically. Default is 30 days.
<EFBFBD>🔑 SSH Keys Management
Overview
Every user has a personal SSH key library. Keys stored in the library can be selected when deploying VMs or LXC containers, eliminating the need to paste the public key manually each time.
Managing SSH Keys
- Go to Settings → SSH Keys
- Click Add Key
- Fill in:
- Name — a human-readable label (e.g.,
Work laptop) - Public Key — paste the full public key (
ssh-rsa ...,ssh-ed25519 ..., etc.) - Private Key (optional) — stored encrypted in the database
- Comment (optional) — free-form note
- Name — a human-readable label (e.g.,
- Click Save
The SHA-256 fingerprint is computed and displayed automatically.
Using Keys When Deploying
- In the Create Instance Wizard, the Config step shows a key selector
- Regular users see their own keys
- Admins selecting an owner see the owner's keys in addition to their own
SSH Key Sync on Owner Change
When an admin reassigns a VM or LXC to a different user (via the Owner dialog on the instance detail page), the panel automatically:
- Removes the previous owner's SSH public key from the instance's
ciuser/authorized keys - Injects the new owner's SSH public key
This keeps access credentials in sync with whoever currently owns the instance, without any manual key rotation.
Admin Key Management
Admins (users with user:manage permission) can view and manage SSH keys for any user:
GET /api/ssh-keys/user/{user_id} — list keys for a specific user
API Reference
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/ssh-keys |
List own SSH keys |
POST |
/api/ssh-keys |
Add a new key |
PUT |
/api/ssh-keys/{id} |
Update a key |
DELETE |
/api/ssh-keys/{id} |
Delete a key |
GET |
/api/ssh-keys/user/{user_id} |
List keys for any user (admin) |
🌍 Localization
Supported Languages
- 🇷🇺 Russian (full coverage as of v1.16.0)
- 🇺🇸 English (default)
- 🇺🇿 Uzbek (partial)
v1.16.0 completed a full translation sweep: all previously hardcoded or untranslated strings across the dashboard, instances, images, app store, nodes, roles, storages, scripts, certificates, graphs, wizard, bulk migrate, and access realms are now properly translated in both Russian and English.
Switching Language
Since v1.5.2, language is a per-user preference stored in the database.
- Settings → Profile
- Select language in the Language dropdown
- Click Save — the interface switches immediately without a page reload
Adding New Language
Translations are stored as flat JSON files in backend/app/locales/.
To add a new language, simply create a new file — no code changes required:
# Create a new locale file for Uzbek
cp backend/app/locales/en.json backend/app/locales/uz.json
# Then translate the values in uz.json
The file name becomes the language code (uz.json → "uz"). The panel picks
it up automatically on next startup (or call I18nService.reload() at runtime).
Structure: each file is a flat
{"key": "translated string"}JSON object. Keys must match the existing keys inru.json/en.json.
⚙️ Settings
User Profile
- Full name
- Email (for notifications)
- Password change
- Language — per-user UI language (stored in DB column
users.language); applies immediately on save viai18n.changeLanguage(); each account keeps its own preference - SSH Public Key — stored in user profile and automatically injected into VM/LXC during cloud-init deployment
Panel Settings
| Parameter | Description | Default |
|---|---|---|
| Refresh interval | Data refresh frequency | 30 sec |
| Log retention | How many days to keep logs | 30 days |
| Panel name | Display name (read-only in UI, can be overridden in DB) | PANEL_NAME env |
Note: The language setting was moved from the Panel tab to the Profile tab in v1.5.2 — each user now controls their own language independently.
System Updates & Repository Selection
The panel supports automatic updates from a Git repository. You can choose which repository to use:
Available Options:
- git.tzim.uz/markmorado/pvemanager (Default) — Primary repository
- github.com/markmorado/pvemanager — Mirror repository
- Custom Repository — Your own fork or mirror
How to Change Repository:
- Go to Settings → System Updates
- In "Git Repository" dropdown, select your preferred repository
- For custom repository:
- Select "Custom Repository..." from dropdown
- Enter full URL (e.g.,
https://git.example.com/user/repo) - Click Save
The repository setting affects:
- Update checks (version comparison)
- Changelog retrieval
- Panel update process
Note: Make sure the selected repository contains valid
VERSIONandCHANGELOG.mdfiles.
How the Update Process Works
Starting from v1.1.1, updates are executed by a host-side watchdog (pvemanager-update.service) instead of inside the container. This solves the fundamental problem where docker compose down would kill the in-container script before the rebuild could complete.
[UI: "Update Panel"] → perform_update() → writes .update_trigger
↓ (poll every 3 sec)
[pvemanager-update.service on HOST]
detects trigger → removes it → runs:
git pull
docker compose down
docker compose build --no-cache app
docker compose up -d
Logs: ./logs/update_host.log (visible from the panel)
Installing the Update Watchdog
The watchdog must be installed once on the host machine. It is automatically set up when you run deploy.sh, or you can install/reinstall it separately:
# Install or reinstall (requires root for systemd)
sudo ./deploy.sh --watchdog
# Verify it is running
systemctl status pvemanager-update
# Watch live update log
tail -f logs/update_host.log
Requirements:
- systemd on the host
- The user who runs
deploy.sh --watchdog(or$SUDO_USER) must be in thedockergroup (root is always allowed) - The
compose.ymlvolume.:/project:rwmust be present (it is by default)
Note for non-root users: If you run
./deploy.sh --watchdogwithout sudo, it generates ready-to-use files in./systemd/and prints the exactsudocommands needed to install them.
Notification Settings
- In-App: Always enabled
- Email: Requires SMTP setup
- Telegram: Requires Bot Token
🛠️ pve CLI Tool
Starting from v1.1.2, PVEmanager ships with a pve shell script — a handy CLI shortcut for common management tasks.
Installation
# Requires root (copies script to /usr/local/bin/pve and patches project path)
sudo ./deploy.sh --install-cli
After installation the pve command is available system-wide.
Usage
pve help # Show all available commands
pve logs # Tail live app logs
pve restart # Restart all containers
pve update # Trigger panel update (via watchdog)
pve exec <cmd> # Execute command inside app container
pve status # Show docker compose status
pve dev # Start frontend in dev mode (Vite HMR)
pve dev stop # Stop dev mode, switch back to production frontend
How It Works
deploy.sh --install-clireads thepvefile from the project root- Replaces the placeholder
PVE_DIRwith the actual project path - Writes the result to
/usr/local/bin/pveand makes it executable - Re-run
sudo ./deploy.sh --install-cliafter moving the project directory
Note: The
pvebinary reflects the project directory at install time. If you relocate the project, reinstall the CLI.
🔌 API Reference
Authentication
# Get token
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
# Response
{
"access_token": "eyJ...",
"token_type": "bearer"
}
Using Token
curl -H "Authorization: Bearer eyJ..." \
http://localhost:8000/api/notifications
Main Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/servers |
List Proxmox servers |
| GET | /api/servers/{id} |
Get specific server |
| POST | /api/servers |
Add new server |
| PUT | /api/servers/{id} |
Update server |
| DELETE | /api/servers/{id} |
Delete server |
| POST | /api/servers/{id}/test |
Test server connection |
| GET | /api/servers/{id}/resources |
VMs and containers on server |
| POST | /api/servers/{id}/vm/{vmid}/{action} |
Control VM (start/stop/restart) |
| POST | /api/servers/{id}/container/{vmid}/{action} |
Control Container (start/stop/restart) |
| GET | /api/servers/{id}/vm/{vmid}/status |
Get VM status |
| GET | /api/servers/{id}/container/{vmid}/status |
Get Container status |
| GET | /ipam/api/networks |
List IPAM networks |
| POST | /ipam/api/networks |
Create IPAM network |
| GET | /ipam/api/allocations |
List IP allocations |
| POST | /ipam/api/allocations |
Create IP allocation |
| GET | /templates/api/groups |
List template groups |
| GET | /templates/api/templates |
List OS templates |
| POST | /templates/api/templates |
Create OS template |
| GET | /api/notifications |
List notifications |
| PATCH | /api/notifications/{id}/read |
Mark notification as read |
| DELETE | /api/notifications/{id} |
Delete notification |
| GET | /api/users |
List users (admin only) |
| POST | /api/users |
Create user (admin only) |
| GET | /settings/api/panel |
Get panel settings |
| PUT | /settings/api/panel |
Update panel settings |
| GET | /logs/api/logs |
Get audit logs |
| GET | /logs/api/stats |
Get log statistics |
| GET | /api/backups/storages/{server_id} |
List Proxmox storages |
| GET | /api/backups/scan-nfs/{server_id} |
Scan NFS server for exports |
| POST | /api/backups/create |
Create VM/LXC backup |
| POST | /api/backups/restore |
Restore VM/LXC from backup |
| GET | /api/backups/list/{server_id} |
List backup files |
| DELETE | /api/backups/{server_id}/backup |
Delete backup file |
| GET | /api/backups/jobs |
List scheduled backup jobs |
| POST | /api/backups/jobs |
Create scheduled backup job |
| GET | /api/{server_id}/ha/status |
HA status for all VMs |
| POST | /api/{server_id}/ha/{vm_type}/{vmid}/add |
Add VM to HA group |
| DELETE | /api/{server_id}/ha/{vm_type}/{vmid}/remove |
Remove VM from HA |
| POST | /api/sync-vms |
Force VM sync from Proxmox |
| GET | /api/servers/{id}/nodes |
List nodes for a server |
| PUT | /api/servers/{id}/sdn/zones/{zone} |
Update SDN zone |
| PUT | /api/servers/{id}/sdn/vnets/{vnet} |
Update SDN VNet |
| DELETE | /api/servers/{id}/sdn/vnets/{vnet}/subnets/{cidr} |
Delete SDN subnet |
Swagger Documentation
Available at: http://localhost:8000/docs
<EFBFBD>️ Workspaces
Workspaces are named groups of Proxmox servers with scoped user access. They allow administrators to partition the infrastructure and grant users access to only their servers.
Key Concepts
| Concept | Description |
|---|---|
| Workspace | Named group with optional color and description |
| Default Workspace | Created automatically; all servers belong to it by default |
| Server assignment | One server can belong to multiple workspaces |
| User assignment | Users see only workspaces they are assigned to |
Managing Workspaces (Admin)
- Open Settings → Workspaces (sidebar menu item) or navigate to
/workspaces - Click New Workspace to create a workspace
- Assign servers from the Servers tab inside the workspace detail
- Assign users from the Users tab
Switching Workspaces (Sidebar)
- The active workspace is shown in the sidebar below the logo
- Click the workspace name to open the switcher
- Select a workspace — all VM/server lists are immediately filtered
- Select All Workspaces (admin only) to remove the filter
API
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/workspaces |
List workspaces (filtered for non-admins) |
POST |
/api/workspaces |
Create workspace (admin) |
GET |
/api/workspaces/{id} |
Get workspace detail with servers/users |
PUT |
/api/workspaces/{id} |
Update workspace (admin) |
DELETE |
/api/workspaces/{id} |
Delete workspace (admin) |
POST |
/api/workspaces/{id}/servers |
Add server to workspace |
DELETE |
/api/workspaces/{id}/servers/{server_id} |
Remove server from workspace |
POST |
/api/workspaces/{id}/users |
Add user to workspace |
DELETE |
/api/workspaces/{id}/users/{user_id} |
Remove user from workspace |
Pass the X-Active-Workspace: {id} header in API requests to filter responses by workspace.
👤 User → Server Assignment
Server assignment allows admins to directly link specific Proxmox servers to individual users. In combination with Workspaces it gives fine-grained control: a user only sees servers that are both in their workspace and explicitly assigned to them.
How it works
- User belongs to one or more Workspaces
- Admin assigns specific servers to the user (from servers that share at least one workspace with the user)
- The user sees only the intersection: workspace-servers ∩ assigned-servers
Managing assignments (UI)
- Open Users page
- Click Edit for a user
- In the Servers tab — check/uncheck servers; incompatible servers (different workspace) are highlighted
- Save — assignments are applied immediately
REST API
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/users/{id}/servers |
Assigned servers for a user |
GET |
/api/users/{id}/server-assignments |
All servers with assigned and compatible flags |
PUT |
/api/users/{id}/servers |
Set servers (replaces current list); body: {"server_ids": [1,2]} |
Returns 409 workspace_conflict if any server shares no workspace with the user.
🖥️ VM / LXC Ownership
Any VM or LXC container can have an owner — a regular user responsible for that instance. The owner is displayed in the VM list.
Isolation
Ownership is enforced everywhere a non-privileged user sees instances, not just the instance list. The live-resource endpoints — /api/resources/all (dashboard) and /api/{server_id}/resources (node/server page) — filter VMs/LXC by owner (matching each vmid against the vm_instances cache), so a user with only proxmox.view cannot see other owners' instances through the node page. Admins and roles with vm:manage are exempt and see everything.
Owner is set automatically on create (vm_create/ct_create), including when an admin creates an instance on behalf of another user — the deploy flow injects that owner's SSH key (merged with any pasted/library keys) rather than the creator's.
Instances synced from Proxmox before ownership existed, or created before the fix above, have owner_id = NULL. A one-time migration backfills these automatically from deploy_tasks and audit_logs (vm_create/ct_create/lxc_create entries), but only when every applicable piece of evidence names the same existing user — ambiguous cases (e.g. a reused VMID) are deliberately left ownerless. To assign an owner to whatever is left, run the helper script shipped in the backend image:
docker compose exec app python assign_instances_owner.py
Assigning an owner (Admin)
Open the VM detail page → Owner button → select user → Save.
REST API
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/{server_id}/vm/{vmid}/owner |
Current owner + list of users |
PUT |
/api/{server_id}/vm/{vmid}/owner |
Set or clear owner ({"user_id": null} to clear) |
🎯 Quotas
Per-user resource quotas cap how much a user can provision. Four metrics are limited, each summed across the user's non-deleted, non-template instances:
| Metric | Unit | Quota field |
|---|---|---|
| Instances | count | max_instances |
| vCPU | cores | max_cores |
| RAM | MB | max_memory_mb |
| Disk | GB | max_disk_gb |
- Unlimited by default — a user with no quota row, or any metric left blank (
NULL), is unrestricted for that metric. Admins and unconfigured users are never blocked. - Enforcement — deploying a VM/LXC that would push any metric over its limit is rejected with HTTP 429 and a bilingual message (
Превышена квота … / Quota exceeded …) showing the projected value vs. the limit. - Usage source — current usage is computed from the
vm_instancescache (owner_id), where RAM is stored in MB and disk in GB (no conversion needed).
Two levels of client-side blocking
The backend's 429 is the only real enforcement, but the UI now warns before a user reaches it, using the same exhaustedMetrics() logic (features/instances/quota.ts) in both places so the two can't disagree:
- No headroom at all (used ≥ limit on any metric) — the Create Instance entry point itself is disabled, with a tooltip naming exactly which limits are full. No configuration would fit, so the wizard does not open.
- Headroom left, but the chosen configuration would exceed it — the wizard opens normally; from the configuration step onward, the metric that would be exceeded is named and Next/Deploy are disabled until the request is reduced.
Neither block applies to admins — their own quota says nothing about an instance they're creating on behalf of someone else, and the figures shown to them are informational only.
Managing quotas (Admin)
Users page → quota dialog. Live usage bars show current consumption against each limit. Leave a field empty for unlimited. Requires the quota:manage permission (quota:view to read).
Self-service
Each user sees their own limits and current usage on the Profile page. The Create Instance wizard shows the remaining quota as a hint on the configuration step.
REST API
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/users/{id}/quota |
A user's limits + current usage (requires quota:view) |
PUT |
/api/users/{id}/quota |
Set a user's limits; null per metric = unlimited (requires quota:manage) |
GET |
/api/quota |
Current user's own limits + usage (self-service) |
🔧 Troubleshooting
Startup Issues
Container won't start
# Check logs
docker compose logs app
# Check status
docker compose ps
Database connection error
# Check if DB is running
docker compose logs db
# Restart
docker compose restart db
docker compose restart app
Proxmox Issues
"Connection refused"
- Check server IP address
- Check that port 8006 is open
- Check firewall on Proxmox
"Authentication failed"
- Check login/password
- Use format
user@pamoruser@pve - Try creating API Token
Notification Issues
Email not sending
- Check SMTP settings in
backend/.env - For port 465 SSL is required
- For port 587 STARTTLS is required
- Check "App Password" for Yandex/Gmail
# Check variables in container
docker compose exec app env | grep SMTP
Telegram not working
- Check bot token
- Send
/startto bot - Check Chat ID
- Make sure bot is not blocked
"Test" button freezes
- Check logs:
docker compose logs app --tail 50 - SMTP server may be unreachable
- Timeout is 10 seconds
VNC / Terminal Issues
"VNC connection failed" (QEMU)
- Check that the VM is running
- Ensure VNC is enabled in the Proxmox node settings
- Try restarting the VM
- Verify the Proxmox API user has
VM.Consoleprivilege
"Connection closed" immediately on VNC open
- This was a known bug fixed in v1.3.0 (noVNC received a pre-opened WebSocket instead of URL)
- Upgrade to v1.3.0 or later
Terminal not connecting (LXC)
- Check that the container is running (not stopped or paused)
- Verify the Proxmox API user has
VM.Consoleprivilege on the node - Check logs:
docker compose logs app --tail 50
Command Issues
"Guest Agent not running"
# In VM run:
systemctl status qemu-guest-agent
systemctl start qemu-guest-agent
Command hangs
- Timeout is 30 seconds
- Don't use interactive commands
- Check that VM responds
Performance Issues
High memory usage
# Restart containers
docker compose restart
# Clean logs
docker system prune -f
Slow page loading
- Increase refresh interval in settings
- Check network to Proxmox server
Account Issues
Admin forgot password
If the admin forgot their password, you can reset it using the command line:
Option 1: Reset to default password
# Run inside the app container
docker compose exec app python -c "
from app.db import get_db
from app.models import User
from app.auth import get_password_hash
db = next(get_db())
admin = db.query(User).filter(User.username == 'admin').first()
if admin:
admin.hashed_password = get_password_hash('admin123')
db.commit()
print('Admin password reset to: admin123')
else:
print('Admin user not found')
db.close()
"
Option 2: Set a custom password
# Replace YOUR_NEW_PASSWORD with your desired password
docker compose exec app python -c "
from app.db import get_db
from app.models import User
from app.auth import get_password_hash
db = next(get_db())
admin = db.query(User).filter(User.username == 'admin').first()
if admin:
admin.hashed_password = get_password_hash('YOUR_NEW_PASSWORD')
db.commit()
print('Admin password has been reset')
else:
print('Admin user not found')
db.close()
"
Option 3: Reset any user's password
# Replace USERNAME with the target username
docker compose exec app python -c "
from app.db import get_db
from app.models import User
from app.auth import get_password_hash
db = next(get_db())
user = db.query(User).filter(User.username == 'USERNAME').first()
if user:
user.hashed_password = get_password_hash('newpassword123')
db.commit()
print('Password reset for user: USERNAME')
else:
print('User not found')
db.close()
"
⚠️ Security note: After resetting the password, log in immediately and change it to a secure password through the web interface.
❓ FAQ
General Questions
Q: Can I use without Proxmox? A: No, the panel is specifically designed for Proxmox management.
Q: How many servers can I add? A: Unlimited.
Q: Is Proxmox cluster supported? A: Yes, add any cluster node.
Security
Q: Are passwords stored in plain text? A: No, bcrypt hashing is used.
Q: How to change SECRET_KEY?
A: Change in .env and restart. All sessions will be invalidated.
Notifications
Q: Why emails are not arriving? A: Check SMTP settings, especially port and TLS.
Q: Can I disable notifications? A: Yes, in notification settings.
Q: How to set quiet hours? A: In notification settings specify period (e.g., 23:00 - 07:00).
Integrations
Q: Is there an API for integration?
A: Yes, REST API with documentation at /docs.
Q: Can I add webhooks? A: Not yet, but planned for future versions.
📞 Support
- GitHub Issues: Create issue
- Documentation: This file (WIKI.md)
- Changelog: CHANGELOG.md
Last updated: July 24, 2026 Version: 1.13.0