On an unsubscribed Proxmox host, apt's enterprise repo answers 401 and
apt-get update exits non-zero even though every other source succeeded,
aborting the whole bootstrap under set -e before git/curl were ever
installed. The subsequent apt-get install still fails loudly if a package
genuinely can't be found, so tolerating this specific failure is safe.
Found by running bootstrap.sh against a real Proxmox host.
Server card used Math.max over all cluster nodes' uptime, so
rebooting one node still showed another node's higher uptime,
making it look like the reboot had no effect.
Adds bootstrap.sh implementing Stage 3 of the roadmap: it detects whether
it runs on a plain Debian/Ubuntu host or directly on a Proxmox VE host.
On a PVE host it provisions a Debian 12 LXC, installs the panel inside it
via the existing deploy.sh, then creates a root@pam API token locally and
registers the host as the panel's first node — no Proxmox password is
ever transmitted.
Also removes the insecure pvemanager_secure_password fallback for
POSTGRES_PASSWORD in compose.yml, since every deploy path already
generates a real .env before the stack starts.
- Images: show real ISO files already on the node storage (incl. those
present before the node joined the panel) via existing node/isos endpoint
- ISO dialog: rename "detach" to "Eject", show current image inside the
select (drop overflowing side text), add "boot from disk" checkbox
- Backend: on eject, put disk first in boot order; on mount, optional
"boot from this ISO" puts CD-ROM first
- Reboot to apply the new boot order via hybrid restart: graceful ACPI
shutdown with forceStop fallback, then start (fixes guest-ping timeout
on freshly installed OS without QEMU Guest Agent)
- Update iso_next_steps hint to reflect the automated flow
The per-column "Node" filter memoized its option list against TanStack's
Column object, which keeps the same identity across data updates, so the
checklist stayed frozen at whatever nodes were visible when the popover
first mounted instead of reflecting the current (workspace-scoped) rows.
Switching the active workspace also left the server/node/column filters
holding values from the previous workspace, which could filter the newly
scoped VM list down to nothing.
Replace free-text storage inputs with dropdowns populated from the
server/node's actual storages (filtered by vztmpl/rootdir content),
matching the pattern used elsewhere in the app.
Add detect/import flow for nodes already clustered on the Proxmox side
(outside the panel). Matches nodelist entries from cluster/config/join
against known panel servers by IP/hostname and links their panel
records to the cluster_name group, without running any pvecm operation.
Adds a local file upload path alongside the existing URL download flow:
a new /images/upload endpoint streams the file to a temp path and hands
it to a background worker that proxies it into Proxmox storage via the
upload API (requests-toolbelt handles the large multipart stream). Also
adds /images/resolve-filename to recover the real filename (Content-
Disposition) for URL-based downloads with opaque paths.
Raise nginx client_max_body_size and timeouts on both the frontend proxy
and the standalone nginx configs so multi-GB ISOs don't get cut off.
- Invalidate workspace list cache after assigning servers/users so
server/user counts on workspace cards update immediately.
- Invalidate node/VM caches after workspace server/user assignment so
the Nodes page reflects visibility changes right away.
- Soft-delete the cached VM/LXC row and broadcast vm_deleted the moment
a VM/container is destroyed, instead of waiting for the 10s
background sync_vm_cache tick.
- Invalidate the VM list on the frontend when a deploy task completes,
since the backend already persists the new instance to cache
synchronously before marking the task done.
Extract ServerStatusBadge so every server list renders the auth-error
state identically instead of repeating the online/offline ternary, and
expose last_error_kind from the cluster topology and user-assignment
endpoints, which build their payloads by hand and so did not inherit it
from the server schema.
Add a third instance kind to the create wizard: a blank KVM VM with an
empty disk, the installation ISO mounted on ide2 and put first in the
boot order, so the guest boots straight into the installer over noVNC.
Picking a Windows guest presets OVMF, a SATA disk and an e1000 NIC,
which the installer sees without virtio drivers loaded.
Also teach the image downloader content=iso, so ISOs can be fetched
onto a node from the panel: by URL from the images page or from a saved
mirror (mirrors now accept kind=iso), and straight from the ISO step of
the wizard.
The bundled catalog stays qcow2-only on purpose — install ISOs have no
stable "latest" URLs, so hardcoded links would rot on every point release.
deploy.sh already covers most of the installer (OS detection, Docker setup,
random secret generation, nginx, TLS, image build, pve CLI), so the stage is
about publishing a thin bootstrap rather than writing one from scratch.
Records the decisions taken: the bootstrap detects its environment and
creates an LXC when run on a PVE host, the repository is public, and images
keep being built locally. On a PVE host the node can register itself without
a password via pvesh, reusing the token provisioning from stage 1.
An offline server used to report a single "Failed to connect" state, but
"the node does not answer" and "the node answered 401" are fixed in
completely different ways — the first is power or network, the second is a
stale token, which is what happens when a node joins a cluster and its
user.cfg and priv/token.cfg are replaced by the cluster ones.
- add ProxmoxClient.check_connection() returning (ok, error_kind, error);
token auth surfaces 401 as ResourceException rather than
AuthenticationError, since proxmoxer validates the token on first use
- store the reason in proxmox_servers.last_error_kind (migration 40) and
expose it through the server response
- surface an auth_error state in the UI with a hint explaining the usual
cause
Storage listings now also report per-node availability: the cluster config
declares a storage for the whole cluster but says nothing about whether it
actually came up on a given node, so a missing volume group or pool stayed
invisible. Availability comes from a single /cluster/resources call.
Records the six agreed directions with their status, the files each one
touches, the steps involved and the decisions already made, so the work can
be picked up without prior context.
Servers can now be connected with a login and password: the panel
authenticates once, creates its own API token on the node (privsep=0) and
uses it from then on. The token can be revoked from the Proxmox UI, cannot
be used for SSH or web login, and appears in the PVE audit log under its
own name. The password stays stored (encrypted) because ProxmoxClient needs
it for SSH fallbacks such as pvecm delnode.
- add proxmox/token_provisioning.py: token creation with free-name
selection and revoke-on-failure so a failed registration does not leave
an orphaned token whose secret was never persisted
- POST /api/servers accepts auto_create_token
- add POST /api/servers/{id}/provision-token to migrate servers that are
still on password auth
- store api_token_value as EncryptedString; it was kept in plaintext while
the password next to it was already encrypted
- drop the unused ProxmoxServerBase schema, a stale copy of the create
schema whose validator rejected the new auth mode
Node detail pages gain a Graphs tab (CPU, IO delay, memory, load average,
network, swap, root filesystem) built on the existing node rrddata
endpoint. The chart card is extracted from the instance graphs tab so both
views share it, and X axis ticks now switch to dates on multi-day ranges.
The overview tab rendered "no data" while the VM list was still loading, so a
server with instances looked empty until the request came back. Gate the empty
state on isLoading and show skeleton rows instead.
Also add the missing nodes.instances_on_server key to both locales — the raw
key was rendered in place of the heading.
/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.
Shift dark-theme background, card, popover, sidebar and border/input
tokens from neutral hue 286 (near-zero chroma) to a blue-graphite
hue 258 with higher chroma, so the panel reads as midnight-blue
instead of flat black.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the RBAC/Security section with a detailed model: permission
format, engine evaluation order (admin bypass, wildcard, manage-implies),
requires dependencies, full permission catalogue, per-page permission
map, built-in role breakdown, and the ownership-based user isolation
(vm:manage, not vm:view, grants the global view). Fix stale references
(version 1.10.2, Networks needs server:manage, SSH keys user:manage).
Add WIKI.ru.md as a complete Russian translation mirroring WIKI.md,
including the expanded RBAC section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The panel update status lived only in-memory and was cleared solely by the
host watchdog restarting the container. If the rebuild failed mid-way (e.g. the
build OOMs the host when no swap is configured), the container never restarted
and the UI stayed at "restarting - 80%" forever.
- update_service: get_update_status() now self-heals — after UPDATE_TIMEOUT_SECONDS
(default 900s) of "is_updating" it transitions to a terminal "failed" state with
a clear error pointing at the host update service / logs.
- use-settings: poll update status while the server reports is_updating (not only
right after the click in the same tab), so reloading a stuck page keeps refreshing.
- SettingsPage: banner now renders the "failed" state with the error message.
- update_host.sh: build images sequentially (app, then frontend) to lower peak
memory and avoid the OOM that killed the build mid-run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend read res.count but the auto-import endpoint returns imported_count,
so the toast showed "Импортировано: undefined". Read imported_count and show
a distinct message when no new templates were imported.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the command palette, reorganized instance menu/tabs, global
minimized-operations tray, owner-scoped live Proxmox resources, and the
React Query cache clear on login/logout; bump VERSION and README badges.
Community scripts (build.func) install packages like Java/OpenSearch and
can take minutes, well past the nginx/proxy HTTP timeout. execute_script
now returns immediately with status "running" and finishes the job on a
background thread with its own DB session; the frontend polls
/api/scripts/executions/{id} every 3s while running.
Also set TERM=xterm for script execution over SSH — community scripts
call `clear` and print colored output, which fails with "TERM
environment variable not set" when run without a PTY.
Non-privileged users previously needed a server to be both directly
assigned AND inside one of their workspaces (intersection) to see or use
it. Adding a user to a workspace should be sufficient on its own to let
them create instances on that workspace's servers, so the two access
paths are now unioned instead of intersected.
The workspace edit dialog reused useServers(), which always sends the
X-Active-Workspace header, so admins assigning servers to a workspace
only ever saw servers already in the currently active workspace instead
of the full list. apiClient now lets a caller override the header, and
useServers(allWorkspaces) requests '0' (admin "show all") for that dialog.
При редактировании ноды (например, при смене имени) форма отправляла
пустые api_token_name/api_token_value, т.к. секреты не возвращаются
с бэкенда. Бэкенд принимал их как явно заданные и затирал реальные
credentials, из-за чего немедленная проверка подключения падала и
сервер помечался offline. Теперь пустые значения при редактировании
трактуются как «не менять» — и на фронтенде, и на бэкенде.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
При смене токена (вход под пользователем / возврат к админу) сбрасываем
выбранную рабочую область через setActiveWorkspace(null): выбор
предыдущей личности передавался в хедере X-Active-Workspace и мог не
принадлежать новой. Дополняет уже существующий сброс кэша React Query.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend:
- POST /api/auth/impersonate/{user_id} — только для админа, выдаёт JWT
с клеймом impersonator; запрещает вход под собой, отключённым,
другим админом и вложенную имперсонацию
- POST /api/auth/stop-impersonate — по клейму impersonator завершает
сессию имперсонации и перевыпускает токен исходного админа
- /api/auth/me отдаёт поле impersonator для баннера в SPA
- аудит-лог: impersonate_start / impersonate_stop
Frontend:
- auth-store: действия impersonate/stopImpersonate со сбросом кэша
React Query при смене токена
- UsersPage: кнопка "войти под пользователем" (только админу,
скрыта для себя/админов/неактивных и во время имперсонации)
- AppLayout: баннер имперсонации с кнопкой "вернуться к администратору"
- локали ru/en
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При входе под другим пользователем в той же вкладке React Query отдавал
закэшированные данные предыдущего пользователя (список инстансов и др.) до
жёсткой перезагрузки, из-за staleTime и отсутствия сброса кэша при смене сессии.
- queryClient вынесен из App.tsx в lib/query-client.ts (доступен вне React)
- auth-store вызывает queryClient.clear() при успешном login и при logout
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Список инстансов (/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>
Свёрнутые долгие операции (установка приложения, сборка золотого шаблона)
теперь живут в глобальном zustand-сторе (persist в localStorage) и
рендерятся треем MinimizedOpsTray в AppLayout, а не самим диалогом:
- плашка переживает навигацию между страницами и перезагрузку вкладки;
прогресс опрашивает сам трей (REST, 3с) независимо от диалога
- клик по плашке возвращает на страницу операции и разворачивает диалог:
установка — resume-режим InstallWizard (resumeInstall в navigation
state), шаблон — openGolden с предвыбранным сервером
- «Свернуть» добавляет операцию в трей и закрывает диалог; крестик на
плашке только убирает её (операция продолжается на сервере)
- плашки несуществующих операций (404 после перезагрузки) трей убирает сам
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Доставка файлов приложения из каталога (data_files) до compose up,
APP_PASSWORD/default_credentials для Umbrel, сворачивание диалогов
долгих операций (MinimizableDialog), фильтрация pull-шума в ошибках.
Исправления багов:
- teardown: отсутствующий в Proxmox CT считается удалённым — orphaned-приложение
теперь можно удалить из БД
- install: сдвиг VMID мимо занятых записей (nextid не резервирует номер,
retry мог подхватить чужой контейнер)
- retry: параметры установки сохраняются в installed_apps.install_params
(cores/memory/disk/storage/bridge/ip_config/port_explicit, миграция JSONB)
вместо жёстких дефолтов
- reconcile: недоступный сервер больше не помечает все приложения orphaned
- update: реально опубликованный порт фиксируется в БД (url/health)
- .env: значения с '#' кавычатся (dotenv-парсер обрезал их как inline-комментарий)
- порт 80, выбранный явно в мастере, публикуется как есть
- useCatalog: available_only включён в queryKey (коллизия кэша)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docker compose up failed for apps that bind-mount a single file (e.g.
AnythingLLM's .env) because _prepare_bind_mounts blindly mkdir -p'd every
bind-mount source, turning the file's path into a directory. Docker then
refused to mount a file onto that directory ("not a directory: ... mount
a directory onto a file"). File-looking sources (by name) now get touch'd
instead, with self-healing rm -rf for directories left over from prior
failed installs.
Co-Authored-By: Claude Sonnet 5 <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>
- Командная палитра (Ctrl+K) с общим реестром nav-items и его store
- Новые общие UI-примитивы: alert, breadcrumb, checkbox, progress, switch, textarea
- Меню инстанса: добавлены «Параметры», «Мигрировать», «Изменить ресурсы»,
«Создать резервную копию» (новый BackupDialog) и «Удалить»; исправлено
закрытие меню при живом обновлении списка (стабильные columns + getRowId)
- Вкладки инстанса: «Вычислительные ресурсы», «Опции CPU», «Управление
дисками» и «Изменение размера диска» перенесены с «Настройки» на
«Оборудование» для VM (для LXC, у которых нет вкладки «Оборудование»,
ресурсы и ресайз диска остаются в «Настройках»); общие карточки вынесены
в ResourceCards.tsx
- Локализация: новые ключи ru/en для добавленных пунктов меню и бэкапа
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Добавлена библиотека скриптов по образцу VMmanager: свои скрипты (CRUD в UI)
и синхронизация из git-репозитория, параметризация через env-переменные
(без конкатенации в shell), выполнение на нодах Proxmox (SSH), в LXC (pct
exec) и в VM (QEMU guest agent, переиспользует существующий execute_script).
Журнал запусков встроен в общий список задач /api/all-tasks.
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>
1. vms.py: catch-all маршруты /vm/{vmid}/{action} и /container/{vmid}/{action}
перенесены в конец файла — раньше они перехватывали POST /unlock,
/cloud-init и /exec, и эти операции всегда падали с 400 "Invalid action".
2. task_queue_service: зависшие после рестарта running-задачи помечаются
failed при старте — иначе очередь bulk-операций блокировалась навсегда.
3. api-client.ts: 401 от /api/auth/login больше не вызывает перезагрузку
страницы — пользователь видит сообщение "неверный логин/пароль".
4. task_queue_service: bulk shutdown теперь вызывает shutdown_vm/
shutdown_container (graceful) вместо жёсткого stop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Список серверов терял порядок при фоновом рефетче (каждые 30с),
т.к. запрос с WHERE id IN (...) не гарантирует стабильный порядок строк.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
При провале join нода остаётся стейл-записью с cluster_name в панели, хотя
в Proxmox она standalone. Обычный eject пытается pvecm delnode через SSH и
падает ("SSH connection failed"), т.к. нода не член кластера и/или узел
недоступен по SSH.
- eject-эндпоинт: новый параметр panel_only — отвязывает панельную запись
(cluster_name=None) без обращения к Proxmox/SSH.
- UI: чекбокс "Только отвязать запись в панели" в диалоге исключения,
блокирует поле пароля при выборе.
- Локали ru/en.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>