Add an email-based "forgot password" flow:
- PasswordResetToken model (stores only a SHA-256 hash, single-use, TTL)
and migration 27 for the password_reset_tokens table.
- POST /api/auth/forgot-password — generic response (no account
enumeration), 60s throttle, emails a one-time reset link via the
existing EmailChannel as a background task.
- GET /api/auth/reset-password/validate — token validity check for the UI.
- POST /api/auth/reset-password — validates the token, enforces the
password policy, sets the new password, clears lock state, invalidates
outstanding tokens and terminates all sessions.
- Configurable link lifetime via PASSWORD_RESET_TOKEN_EXPIRE_MINUTES (30m).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retrying a template build failed at download because the qcow2 from a prior
attempt already existed and download-url refuses to overwrite. Now skip the
download when the volume already exists (volume_exists), and surface the PVE
task-log tail on download failure instead of a generic message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
create_vm_with_import got the display name ('Ubuntu 22.04 LTS (Jammy) Cloud')
which PVE rejects (Parameter verification failed on 'name'). Sanitize to a
DNS-safe name (e.g. ubuntu-22-04-lts-jammy-cloud). Also stop attributing every
create failure to 'import unsupported' — only say that when the error mentions
import; otherwise surface the real error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Proxmox content=import rejects Ubuntu cloud '*.img' with 'invalid filename
or wrong extension' though the format is qcow2. Rename non-import-extension
files to .qcow2 before download-url; import-from auto-detects the format.
Applied to both download and template workers (download filename + import volid).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /proxmox/api/{server_id}/images/node-arch derives amd64/arm64 from
node status current-kernel.machine; returns null when node unreachable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET/POST/PUT/DELETE /proxmox/api/images/mirrors (perm template:manage).
Enabled mirrors merge into the catalog feed automatically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend fixes for VM/LXC disk resize:
- resize_vm_disk/resize_container_disk now honor wait_for_task and raise
the real Proxmox exitstatus (e.g. "disk 'scsi0' does not exist") instead
of always returning success.
- add grow_vm_filesystem: after a VM resize, rescan the disk (retry loop,
needed for SATA/SCSI), growpart the partition and resize the FS
(ext*/xfs/btrfs) via the guest agent; surface its output to the API.
- execute_script normalizes CRLF/CR to LF so pasted scripts and growpart
don't break on trailing \r ("partition-number must be a number").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- _do_clone_sync now tails the PVE clone task log and maps the disk
transfer percentage to deploy progress 50→95% instead of a static 70%.
- Add owner_id to CloneRequest; admins may assign the clone to another
user (mirrors LXC deploy). Register the cloned instance via
save_vm_instance so it has an owner and is visible to limited users.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Store UI language per user (users.language column, migration 26).
Language selector moves from Panel tab to Profile tab — each account
picks its own language, saved to DB and applied via i18n.changeLanguage()
on save so the interface switches immediately.
Panel tab: panel_name read-only (falls back to env PANEL_NAME),
log_retention_days field exposed in UI, language selector removed.
Panel API accepts panel_name in PUT so it can be overridden in DB
without touching the env var.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename SMTP request/response fields (host/port/username/password/from_email/tls)
and point security settings hooks at /admin/api/security/settings with a typed
SecuritySettings shape. Add minutes / security_saved i18n keys.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace all legacy dotted permission codes (vms.view, proxmox.manage, ...)
in PermissionChecker/has_permission across app/api with the new
resource:action format (vm:view, server:manage, ...), per LEGACY_PERMISSION_MAP.
All 33 codes used by the API are now registered in the permission registry.
Also switch the /api/auth/me admin branch to get_all_permissions_v2 so the
endpoint emits permissions in the new format only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collapse four duplicated host+auth client-building blocks into a single
ProxmoxClient.from_server() classmethod (the canonical source of truth):
- _helpers._get_proxmox_client now delegates to from_server (107 callers
untouched)
- ipam._build_proxmox_client removed; its one call site uses from_server
- task_queue_service keeps its per-server cache pool, builds via from_server
- monitoring_worker keeps its ValueError-on-missing-auth contract, builds
via from_server
Behaviour preserved (token-first, then password; non-default port appended).
Verified: containers rebuild healthy, smoke test of both auth paths passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the /api/servers/auto-setup endpoint, the AutoSetupDialog UI and the
useAutoSetupServer hook. Rename the auto_setup locale strings to
auto_create_token. Also normalize the storages response to an array.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop dead route handlers that have an active replacement and zero callers
(frontend, backend, tests):
- create-lxc, create-lxc-smart -> superseded by POST /lxc/deploy
- clone-lxc -> superseded by container/{vmid}/clone
- {server_id}/lxc-templates -> duplicate of /lxc-templates/{server_id}
- dashboard/stats -> dashboard uses resources/all + alerts
- permissions (v1) -> superseded by permissions/v2
The identically-named ProxmoxClient/SecurityService methods are kept (they
back the surviving endpoints).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add target node and storage overrides to single and bulk restore flows, and
track Proxmox task progress (parse_task_progress in the monitoring worker,
new ProxmoxTask progress column + migration, enriched task API responses).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- get_vm_status and get_container_status were missing require_vm_access(),
allowing any user with vms.view to see any VM status regardless of ownership.
Now users with vms.view.own can only access their own VMs (403 otherwise).
- Downgrade upload-artifact@v4 → @v3: v4 is not supported on Gitea (GHES)
and caused CI failures on every run.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The ssh_keys module was imported in main.py but missing from api/__init__.py,
potentially causing import issues. Added to ensure all API routers are
explicitly declared and available.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- restore_vm/restore_lxc: add force=True param (needed for in-place overwrite)
- QEMU restore: remove `restore: 1` from params (breaks Proxmox API — restore
is triggered by archive presence alone, unlike LXC where restore: 1 required).
This fixes both single and bulk QEMU restores.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Critical security and robustness fixes from a code audit:
- WS terminal/VNC now require a JWT (?token=), validate the session,
vms.console permission and VM ownership before bridging to Proxmox
(previously the LXC terminal gave unauthenticated root shell access).
Frontend ConsolePage passes the token in both WS URLs.
- Fix SSH fallback for `pvecm delnode`: SSHClient now supports password
auth (it previously crashed with TypeError); ProxmoxClient stores the
password for SSH fallbacks and the host port suffix is stripped.
- Add production startup warnings for default ADMIN_PASSWORD and wildcard
CORS_ORIGINS.
- dashboard: use tz-aware utcnow() instead of naive datetime.utcnow().
- crypto: narrow decrypt_value except to InvalidToken and log failures.
- ssh_client: guard the shared connection cache with a lock; make
unknown-host-key rejection opt-in via SSH_REJECT_UNKNOWN_HOSTS.
- logging: send loguru console output to stderr instead of stdout.
- Sync version fallback to 1.5.1; remove one-off refactor scripts.
- Add CODE_REVIEW.md audit report.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend:
- servers.py: instant connectivity check + VM cache sync after create/update;
broadcast server_added/updated/deleted events via WebSocket
- websocket_manager: add broadcast_event() helper for global fire-and-forget events
Frontend:
- use-nodes: invalidate topology, VM list and resources after server create/update/delete
- use-realtime-sync: new hook with useGlobalRealtimeSync subscribing to server_*
WS events and invalidating relevant React Query caches
- AppLayout: mount useGlobalRealtimeSync globally
Deploy:
- deploy.sh: auto-install pve CLI during main/quick deploy (idempotent, --auto mode)
The destroy endpoints returned 400 'Cannot delete a running container.
Stop it first.' when the user clicked Destroy on a running instance.
Since the destroy dialog already requires typing the instance name to
confirm, the action is unambiguously intentional — now both endpoints
issue stop_container/stop_vm with force=True, wait up to 15s for the
status to leave 'running', and only then proceed with deletion.
Three related fixes so login/password/SSH key/IP survive a reinstall:
1. backend/app/api/proxmox/lxc.py — on initial LXC deploy save
cloud_init_user='root' and cloud_init_password=req.password to VMInstance
(previously both were None, so reinstall could not restore root password).
2. backend/app/api/proxmox/async_ops.py — _do_reinstall_sync passes
cached.cloud_init_password to create_lxc_container so the new container
gets the same root password as the original.
3. backend/app/workers/monitoring_worker.py — VM sync worker no longer
overwrites VMInstance.ip_address with NULL when no IPAM allocation
exists for that (server_id, vmid). It only updates the field when
IPAM has a value, otherwise the IP saved at deploy/wizard time is
preserved.
Previously the reinstall endpoint required cached.template_id (clone-from-VM-
template flow), so LXC instances created via the wizard from an OS template
file (e.g. local:vztmpl/debian-13-...tar.zst) returned 'VM has no associated
template; reinstall is not available'.
Now reinstall works in two modes:
1) clone-from-template (existing): VM/LXC with cached.template_id pointing
to OSTemplate.vmid
2) ostemplate-file (new, LXC only): cached.template_name like
'storage:vztmpl/...tar.zst'. Reinstall stops, deletes, then re-creates
the container with the same VMID using create_lxc_container, preserving
network config, storage, rootfs size, swap, features, ssh_keys, etc.
pulled from the previous container config + VMInstance cache.
Backend:
- Add DeployTask.kind column ('deploy'|'reinstall'|'clone'|'change_password')
+ Migration 23: ALTER TABLE deploy_tasks ADD kind, drop NOT NULL on template_id
- Update DeployTask.to_dict() with kind-aware Russian descriptions
- New backend/app/api/proxmox/async_ops.py with three workers:
_do_reinstall_sync, _do_clone_sync, _do_change_password_sync
All run on shared _deploy_executor thread pool from templates.py and
report progress through _update_deploy_task (DB + WebSocket broadcast)
- Rewrite five vms.py endpoints to return 202 + DeployTaskStartResponse:
POST /vm/{vmid}/clone, /container/{vmid}/clone,
POST /vm/{vmid}/reinstall,
POST /vm/{vmid}/change-password, /container/{vmid}/change-password
Frontend:
- DeployTask: add 'kind' and 'server_id' fields
- useCloneInstance/useReinstallInstance/useChangePassword now return
AsyncTaskResponse {task_id, status, name}; remove invalidateQueries
- InstanceActionDialogs: each onSuccess pushes task to deploy-tasks-store
with appropriate kind, shows 'launched in background' toast
- InstancesPage:
* ghostRows only for kind in {deploy, clone}, deduped by (server_id, vmid)
* reinstall / change_password tasks shown as inline overlay (badge + ring)
on the existing VM row's status column
- AttributeError: OSTemplate has no 'template_vmid' field; the actual column is 'vmid'.
- Use OSTemplate.get_vmid_for_node() to support cross-node replicated templates,
falling back to source_node/vmid otherwise.
- VMInstance.memory is stored in bytes, but Proxmox config expects MB.
Specific endpoints (clone, reinstall, change-password) were being intercepted
by the generic /{action} handler because they were registered AFTER it.
Fix: move CloneRequest, ChangePasswordRequest models, _resolve_server,
_get_client_or_503 helpers, and all 5 specific endpoint functions BEFORE
the generic @router.post('.../{action}') handlers.
Remove duplicate definitions from the old 'Additional Instance Operations'
section at the end of the file.
Root cause of 'Connection closed' / VNCAuth failure:
- vncproxy was called without generate-password=1
- vnc_data.password was None → noVNC sent empty DES-encrypted challenge → Proxmox rejected
Fix:
- Add generate-password=1 to both qemu and lxc vncproxy requests
- If Proxmox returns password field (Proxmox 8.x VNCAuth): use it
- If not (Proxmox 9.x NoAuth): fall back to ticket as password
- Add diagnostic logging (password_len, has_generate_password)
Remove the 'onFirstMessage' hack where backend sent VNC password as
first text WebSocket message and frontend passed the already-open WS
to noVNC's RFBClass. This caused RFB handshake failures because noVNC
couldn't properly manage a WebSocket it didn't create.
Changes:
- Backend: remove send_text(password) before proxy start
- Backend: improve proxy error logging from debug to info level
- Frontend (virtual_machines.html, instance_detail.html): pass WebSocket
URL string to RFBClass constructor - noVNC creates and manages its own
WebSocket connection, allowing proper RFB protocol handshake
- 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