- configure_vm: capture resize UPID and call wait_for_task(120s)
before returning; prevents lock conflict when VM is started right
after disk resize
- resize_vm_disk / resize_container_disk (x2): same fix applied
- remove unnecessary restart_vm() after manual QEMU disk resize —
resize is a storage-layer operation and does not require a reboot;
the auto-restart was causing a second lock conflict
Fixes: can't lock file '/var/lock/qemu-server/lock-xxx.conf' - got timeout
Fixes: command '/usr/bin/qemu-img resize' failed: got timeout
- Migration 20: user_servers association table (User ↔ ProxmoxServer)
- API: GET/PUT /api/users/{id}/servers — assign servers to users with workspace-conflict validation
- API: GET /api/users/{id}/server-assignments — enriched server list (assigned + compatible flags)
- Non-admin server filter: intersection of assigned_servers and workspace servers
- Migration 21: dashboard:view = false for user role (new key format)
- Migration 22: proxmox.cluster.manage → cluster:manage for admin role
- API: GET/PUT /api/{server_id}/vm/{vmid}/owner — VM/LXC owner assignment (admin-only)
- Login: smart redirect based on user permissions after authentication
- Login: returnUrl param support for post-login redirect
- Roles API: user_count field per role in GET /api/roles
- node_uptime added to server detail API response
- Remove redundant PermissionChecker from GET /users HTML route
- Extract 1149 translation keys from i18n.py into locales/ru.json and locales/en.json
- Rewrite i18n.py as a lightweight JSON loader (5287 → 128 lines)
- Add I18nService.reload() for runtime hot-reload without restart
- Add I18nService.available_languages() to list loaded language codes
- Public API unchanged: t(), get(), get_all(), add_translation()
- New languages require only a new *.json file in locales/ — no code changes
Bump version: 1.1.6 → 1.1.7
Root cause: docker compose down killed the nohup script inside the
container before rebuild could complete — only git pull was visible
(updated VERSION file).
Changes:
- update_host.sh: new host-side watchdog (poll .update_trigger every 3s)
runs: git pull → docker compose down → build --no-cache app → up -d
PROJECT_DIR and User are dynamic (set at install time, not hardcoded)
- update_service.py: perform_update() now writes .update_trigger instead
of running git/docker commands inside the container
- deploy.sh: added install_update_watchdog() + --watchdog CLI option;
generates /etc/systemd/system/pvemanager-update.service dynamically
with correct User= and WorkingDirectory= for any install path/user
- .gitignore: ignore .update_trigger and generated systemd/ files
- CHANGELOG, VERSION, WIKI: bump to v1.1.1, document new update flow
String comparison caused local 1.0.3 to show "update available"
when GitHub had 1.0.2, since "1.0.3" != "1.0.2".
Added _is_newer() with proper semver tuple comparison so update_available
is only set when the remote version is strictly greater than local.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace hardcoded legacy Gitea URL in settings.py fallback
- Skip legacy Gitea URL even if saved in DB (treat as unset)
- Track last_response in branch loop so errors report the real HTTP
status code instead of always showing "HTTP unknown"
- Unify 401/403/404 into one "private or not accessible" message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fallback repo URL was pointing to the old Gitea instance
(git.tzim.uz/dilshod/pve_manager), causing "Failed to fetch VERSION
file: HTTP unknown" because both main/master branches returned non-200.
Updated to the current GitHub repository.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously seenNodes used only the node name as the dedup key, causing
independent servers that happen to share a default node name (e.g. "pve")
to hide each other's charts.
Fix: /api/{server_id}/nodes now also returns cluster_id — the actual
Proxmox cluster name for clustered servers, or "standalone-{server_id}"
for single-node servers. The dashboard deduplicates by
"cluster_id::node_name", so:
- nodes in the same cluster → deduped correctly (shown once)
- nodes on independent servers with the same name → both shown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /api/auto-import/{server_id} endpoint ran as a background task
and returned {status: "queued"} with no templates field, while the
frontend's executeAutoImport() immediately called data.templates.forEach()
— causing TypeError: can't access property "forEach", data.templates
is undefined.
Replaced the background task with a synchronous handler that imports
templates in-request and returns {templates: [...], imported_count: N},
matching what the frontend expects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- VERSION: 1.0.0 → 1.0.1 (patch — bug fixes only)
- README: full rewrite with modern formatting, accurate badges,
real GitHub URLs, complete feature list and tech stack table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix all `from ..` → `from ...` in app/api/proxmox/*.py after the
proxmox.py split (PR #6) placed modules one level deeper, causing
ModuleNotFoundError on startup
- Broaden Content-Security-Policy to allow CDN sources used in templates:
cdnjs.cloudflare.com (Font Awesome), cdn.jsdelivr.net (Bootstrap,
Chart.js, xterm), fonts.googleapis.com/gstatic.com (Google Fonts),
esm.sh (noVNC) — the overly-strict CSP was blocking all icons and
charts from loading in the browser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/ipam.py: convert 30 async def handlers that had no await to plain def,
letting FastAPI run them in its thread pool (correct for sync SQLAlchemy I/O);
cleanup_orphan_allocations kept async (it has await)
- api/proxmox.py: add _get_proxmox_client(server) helper that encapsulates the
use_password/token branching, replace 58 duplicated 10-line blocks with a
single call; get_proxmox_resources() call sites left unchanged (different sig)
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R
- proxmox_client.py: add ThreadPoolExecutor(max_workers=20) + async wrapper
methods (async_get_nodes, async_get_vms, async_get_containers,
async_get_node_status, async_get_vm_status, async_get_container_status,
async_get_storages, async_get_all_resources) to avoid blocking event loop
- ipam_service.py: replace full-network allocation scan + Python IP loop with
per-pool targeted SQL query — only loads IPs for the specific pool being searched
- api/ipam.py: fix N+1 full-table scans on GET /api/allocations — use IN-filtered
queries on only the network/pool IDs present in the result set
- monitoring_worker.py: wire LoggingService.cleanup_old_logs(days=30) into the
existing run_cleanup_expired() job so audit logs don't grow unboundedly
- api/templates.py: convert auto_import_templates to async endpoint using
BackgroundTasks — returns immediately, heavy Proxmox I/O runs in background
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R
Encrypt Proxmox server passwords and VM cloud-init passwords in the database:
- Add backend/app/crypto.py: Fernet-based encrypt/decrypt helpers + EncryptedString
SQLAlchemy TypeDecorator (transparent to all existing code, no API changes)
- Add FERNET_KEY config field (env var); without it values stay plaintext for
backward compatibility, with a logged warning
- Update models.py: ProxmoxServer.password and VMInstance.cloud_init_password
now use EncryptedString(255) instead of String(255)
- Add migration 14 in migrations/migrations.py: idempotent re-encryption of
existing plaintext rows (skipped when FERNET_KEY is not set)
CSRF analysis:
- /api/auth/change-password is protected by JWT Bearer token (Authorization header)
- Browsers cannot auto-include Authorization headers cross-site → CSRF is not
applicable here; added explanatory comment
Improve migration loading:
- Add migrations/__init__.py to make it a proper Python package
- Replace fragile importlib dynamic loading in main.py with a standard import
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R
Critical fixes:
- init_admin.py: replace hardcoded admin123 password with ADMIN_PASSWORD env var
- proxmox_client.py, models.py: change verify_ssl default from False to True
- api/proxmox.py: use server.verify_ssl instead of hardcoded verify=False in websocket handler
- ssh_client.py: replace AutoAddPolicy (MITM-vulnerable) with WarningPolicy + load_system_host_keys
Security hardening:
- main.py: add security headers middleware (CSP, X-Frame-Options, HSTS, X-XSS-Protection, Referrer-Policy)
- main.py: fix CORS — allow_credentials=True is incompatible with allow_origins=["*"]; read origins from CORS_ORIGINS env var
- auth.py, logging_middleware.py: validate X-Forwarded-For/X-Real-IP to prevent IP spoofing
Performance:
- monitoring_worker.py: wire up cleanup_expired_connections() as hourly scheduled job (was defined but never called)
Code quality:
- logging_service.py: move `import json` from inside function to module level
- ssh_client.py, proxmox_client.py, api/proxmox.py, rbac/migration.py, task_queue_service.py: replace all bare `except:` with `except Exception:`
- .env.example, config.py: document ADMIN_PASSWORD and CORS_ORIGINS settings
https://claude.ai/code/session_019rK2wvaWg5zXFwmAPPEB7R