Derive a human-readable OS name (e.g. Debian 13, Ubuntu 24.04) from the
vztmpl volid instead of showing the raw template path like
local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst in the instances list.
Applied at the display layer, so existing instances are fixed too.
Newly created VMs/LXCs ended up with owner_id = NULL, and a VM created by an
admin on behalf of a user received the admin's SSH key instead of the user's.
Ownership: save_vm_instance() only set owner_id when inserting a new row. The
row practically always existed by then — sync_vm_cache runs every 10s and
registers VMs it sees on Proxmox with owner_id = NULL while the clone is still
running, whereas the deploy saves the owner minutes later at 95% progress. So
every deploy landed in the update branch and silently dropped the owner. Set
owner_id there too, but only when it is passed explicitly: callers such as
save-before-delete do not know the owner and must not blank it out.
SSH keys: the profile key was taken from whoever pressed "Create" rather than
from the instance owner, and the LXC path injected no profile key at all. Add
build_deploy_ssh_keys(), which merges pasted keys, keys picked from the library
(same authorization rules as before) and the owner's profile key, deduplicated.
Both the template and LXC deploy endpoints now use it, so the key material is
resolved in one place instead of inside the deploy worker.
ISO VMs are left alone on purpose: they are blank VMs without cloud-init, so
there is nowhere to inject keys.
Covered by backend/tests/test_instance_ownership.py, including the sync race.
/api/cluster/topology and /api/resources/all read every ProxmoxServer row
regardless of the caller access or the X-Active-Workspace header, so the
Cluster page listed all servers and dashboard aggregates counted the whole
panel instead of the active workspace. Extract the access + workspace filter
out of /api/servers into get_visible_server_ids() and apply it in all three.
Live metrics were also gathered server by server with the cache lock held
across the network calls, so a single unreachable cluster stalled the request
(21-31s in the logs) and concurrent requests queued behind it. Fetch in
parallel, skip servers already known offline, drop the redundant
is_connected() probe, and hold the lock only around the cache dict.
/api/resources/all now orders by server id: without it Postgres returned heap
order, which shifts whenever update_status rewrites a row, so the server list
reshuffled between requests.
Список инстансов (/api/virtual-machines) уже фильтровался по owner_id, но
эндпоинты живых ресурсов Proxmox — /api/resources/all (дашборд) и
/api/{server_id}/resources (страница ноды/сервера) — отдавали все VM/LXC без
фильтра. Обычный пользователь (proxmox.view) через страницу ноды видел чужие
инстансы.
- _helpers: get_owned_vmids() — VMID, которыми владеет пользователь
- servers: get_server_resources / get_all_resources фильтруют VM/LXC по
владельцу для не-привилегированных (сверка vmid с кэшем vm_instances);
админы и роли с vm:manage видят всё
- assign_instances_owner.py: скрипт массового назначения владельца
существующим инстансам (owner_id синхронизированных из Proxmox = NULL)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App Store install/update pipeline reliability fixes:
- explicit nameserver (APPSTORE_NAMESERVER) on App Store LXCs and the golden
template build CT, instead of inheriting a node resolver that may be
unreachable from the container network (e.g. Tailscale-only DNS)
- golden template storage/resolution moved from one global setting to
per-server_id (migration 37: app_operations.server_id) — building it on one
Proxmox server no longer overwrites the template configured for another
- golden template automated build fixed: vzdump export step no longer fails
on "unable to parse directory volume name 'vztmpl/probe'"
- bind-mount host directories are pre-created with permissive ownership
before `docker compose up`, fixing Permission denied for non-root
containers (e.g. MariaDB) writing to Docker-auto-created root-owned dirs
- delete/teardown now waits for Proxmox to actually confirm the destroy task
finished (was silently proceeding after a short timeout, letting a new
install reuse the VMID while the node was still wiping the old disk)
- pct create retries a few times on transient "got lock" config errors
- failed pipeline steps now report the tail of command output (4000 chars)
instead of the head, so the real error isn't buried under pull/progress noise
- install error box in the UI wraps long text and scrolls instead of
stretching the dialog
Also carries forward in-progress script catalog / instances / RBAC changes
that were already uncommitted in the working tree before this session.
Bump to v1.9.1, update WIKI.md/docs/CHANGELOG accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5. UPID-синк: транзитная ошибка опроса статуса больше не помечает живую
задачу failed — get_task_status(raise_on_error=True) + счётчик из 3
последовательных неудач; в exit_status пишется реальная причина.
6. Дашборд и бэкапы для серверов на порту != 8006: metrics_broadcaster и
backup_scheduler строят клиент через ProxmoxClient.from_server (учёт
порта и token/password) вместо ручной сборки по ip_address.
7. Бэкап "все VM": create_backup при vmid in (None, 0, "all") передаёт
vzdump параметр all=1 — раньше уходил vmid=0 и PVE отвергал задание.
8. IPAM: get_next_available_ip собирает занятые IP глобально (как проверяет
allocate_ip), а не только по pool_id — авто-выдача больше не предлагает
занятый адрес и не срывает создание инстанса.
9. IPAM: fallback в release_ip_by_vmid ищет только записи без
proxmox_server_id — удаление VM не освобождает IP одноимённого vmid
на другом сервере.
10. _get_proxmox_client: сервер без валидных кредов теперь возвращает
понятную 503-ошибку вместо молчаливо пустых списков; перечисление
серверов в метриках защищено per-server try/except.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add per-user quotas (max instances, vCPU, RAM, disk) enforced at deploy
time and surfaced to users self-service.
Backend:
- UserQuota model (one-to-one, NULL = unlimited) + migration #28
- quota_service: usage from vm_instances cache, check_quota raises 429
- enforce in VM (templates.py) and LXC (lxc.py) deploy paths
- RBAC quota:view / quota:manage, granted to admin
- admin API GET/PUT /admin/api/users/{id}/quota
- self-service GET /settings/api/quota
- tighten VM list isolation via can_view_all_instances
Frontend:
- admin quota dialog in UsersPage with usage bars
- quota card on Settings page (own limits)
- remaining-quota hint in CreateInstanceWizard config step
- useUserQuota/useSetUserQuota, useMyQuota hooks; RU/EN locales
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>
- 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>