Remove the previous owner's SSH keys and apply the new owner's keys
(profile + library) when ownership is reassigned. If the new owner has
no keys, the old keys are simply removed.
QEMU VMs: updates cloud-init sshkeys via Proxmox API.
LXC containers: writes /root/.ssh/authorized_keys via exec.
The swap is best-effort — failures are logged but do not block the
ownership change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only show user-relevant filesystems by skipping snap/loop mounts
and /boot/* partitions in QEMU guest agent fsinfo response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
volume_exists() only checked file presence, not content. A stale image
from a previous download (different URL/version) would be silently reused,
causing templates and VMs to be created from unpatched images even when
the mirror's checksum pointed to a newer patched build.
Add delete_volume() to ProxmoxClient storage mixin, and update both
_do_image_download_sync and _do_image_template_sync to delete the cached
volume and re-download whenever a checksum is provided, letting Proxmox
verify the new download server-side.
Both toasts printed the endpoint's own message field verbatim, so a Russian UI
answered "Terminated 0 sessions". That string is assembled in Python and can
never be translated where it is built, so the endpoints now also return the raw
count and the phrase is composed on the client.
message is kept: it goes to the audit log and may have other consumers, and
adding a field breaks no one.
Russian needs three forms, and zero does not land in the obvious one — 0 is the
"many" category ("сессий", not "сессии") — hence four keys against English's
two.
Fixes both call sites: the per-user button in the table row and Terminate all
sessions on the Sessions tab, which shared the same untranslated string.
setting:view used to gate the whole /settings page, which mixed panel-wide
configuration with a user's own account. Revoking it to keep a standard user
out of the panel settings also took away their profile, password, 2FA and SSH
keys, so the permission could not be used for what it names.
The account half now lives at /profile: its endpoints are self-scoped and only
require a session, so the page carries no permission at all and is reached from
the user menu (and Ctrl+K) rather than a sidebar group. /settings keeps the
panel-wide tabs behind setting:view, with saving gated on setting:update,
security on setting:manage and the SMTP/Telegram channels on is_admin, matching
what each endpoint checks.
The shipped "user" role no longer gets setting:view. Migration 42 drops it from
roles holding it without setting:update/manage, since those grants only existed
to reach the profile page; it runs once (marker in panel_settings) so a later
deliberate grant is not reverted on the next start.
Instances deployed while save_vm_instance() dropped the owner are still sitting
in the database with owner_id = NULL, so their users cannot see them. Recover
the creator from the two places it was recorded:
1. deploy_tasks — user_id is a real foreign key stored when the deploy was
queued. server_id is absent for LXC deploys, so a NULL there is treated as
matching any server instead of discarding the row.
2. audit_logs — vm_create / ct_create / lxc_create entries, which only carry a
username and are matched by name.
Ownership decides who may see, stop and delete an instance, so a wrong guess is
worse than no owner at all: a row is updated only when every applicable piece of
evidence names the same existing user. VMIDs get reused, so conflicting evidence
is expected and those instances deliberately stay ownerless for an operator to
assign with assign_instances_owner.py. Existing owners, soft-deleted rows,
failed create events and logs from deleted users are all left alone, which also
makes the migration idempotent.
Note this recovers who created an instance, not who it was meant for: a VM an
admin created on behalf of a user is attributed to the admin, because the
intended owner was never persisted before the fix.
The attribution rule is a pure function (pick_backfill_owner) so it can be
tested directly. The migration's own statements are portable, so the tests run
it against SQLite with the two information_schema helpers stubbed out.
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.
- 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
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.
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.
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.
/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.
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>
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.
При редактировании ноды (например, при смене имени) форма отправляла
пустые api_token_name/api_token_value, т.к. секреты не возвращаются
с бэкенда. Бэкенд принимал их как явно заданные и затирал реальные
credentials, из-за чего немедленная проверка подключения падала и
сервер помечался offline. Теперь пустые значения при редактировании
трактуются как «не менять» — и на фронтенде, и на бэкенде.
Co-Authored-By: Claude Sonnet 5 <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>
Список инстансов (/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>
Доставка файлов приложения из каталога (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>
Добавлена библиотека скриптов по образцу 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>
Проблема: присоединяемая нода подключалась к кластерной по её адресу из
панели (часто публичный FQDN), из-за чего join падал с
"Can't connect to <fqdn>:8006 (Connection timed out)" при выделенной
corosync-сети. Плюс панель помечала ноду как присоединённую сразу после
старта задачи, показывая ложное членство даже при провале join.
- join: новый опциональный параметр cluster_host — приватный IP кластерной
ноды в corosync-сети; используется как hostname для API-подключения и
для выбора fingerprint из nodelist. Фолбэк — ip_address из панели.
- join: cluster_name пишется в БД только после подтверждения, что нода
реально появилась среди нод кластерной ноды (опрос до ~40с, укладываемся
в nginx proxy_read_timeout 60с). Иначе — confirmed=false и предупреждение.
- UI: поле "Адрес кластерной ноды для подключения" в шаге 3 мастера,
обработка неподтверждённого join (toast.warning, окно не закрывается).
- Локали ru/en.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /cluster/config/join не возвращает fingerprint на верхнем уровне —
он лежит в nodelist[].pve_fp. Добавлен хелпер _extract_fingerprint,
который выбирает pve_fp целевой ноды (по IP), с фолбэком на
preferred_node и первую ноду. Устраняет ошибку 502
"Не удалось получить fingerprint кластера" при присоединении нод.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Сертификаты: методы клиента + endpoints GET/POST/DELETE
/nodes/{node}/certificates, хуки и вкладка «Сертификаты» в
NodeAdminManager (просмотр, загрузка PEM cert+key, удаление кастомного,
бейдж срока действия)
- Bulk migrate: тип bulk_migrate в очереди задач с WS-прогрессом,
target_node/online в BulkOperationItem, диалог выбора целевой ноды и
кнопка в тулбаре инстансов (в пределах одного сервера/кластера)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ядро:
- Reboot/Shutdown ноды (POST /nodes/{node}/status) под новым правом node:power
с подтверждением в UI; меню действий на карточке ноды + пункт «Консоль (Shell)».
Доп. вкладки в «Администрировании ноды»:
- Журнал (syslog ноды), История задач (tasks + лог задачи),
Система (DNS / /etc/hosts / часовой пояс).
Бэкенд: методы node_power_action/get_node_syslog/get_node_tasks/get_node_task_log/
get_node_task_status/DNS/hosts/time в NodeAdminMixin + эндпоинты в node_admin.py.
RBAC: право node:power (admin + backfill server:manage).
Проверено: py_compile, tsc --noEmit, vite build; live — маршруты в openapi,
403 без токена, node:power засеян роли admin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A1: иконки Umbrel тянутся пофайлово с GitHub Pages CDN в пуле из 16 потоков
(<app-id>/icon.svg) вместо тяжёлого tarball галереи со скриншотами. Синк
Umbrel: ~7.5 мин → ~13-17с, 379/379 иконок. Настройка UMBREL_APPSTORE_GALLERY_CDN
заменяет UMBREL_APPSTORE_GALLERY_REPO. 404/сетевой сбой отдельного запроса
просто пропускает иконку, не роняя синк.
A2: на свежем деплое каталог пуст, а IntervalTrigger впервые сработает лишь
через сутки. Теперь при пустом каталоге планировщик ставит разовый синк
немедленно (trigger=date) — каталог наполняется на старте без ручной кнопки.
Проверено на стенде: стартовый синк наполнил каталог 644 (runtipi 265 +
umbrel 379) за 16.5с; эндпоинт логотипа отдаёт 200 image/svg+xml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App Store:
- поле кастомного порта в мастере установки (переопределяет порт каталога)
- валидация диапазона порта 1..65535 (Pydantic Field + EngineError)
- APP_PORT в окружении синхронизируется с выбранным портом
- при удалении приложения инстанс сразу помечается deleted в кэше
vm_instances (soft_delete_vm_instance), не дожидаясь фонового воркера
Также включены правки IPAM (привязка сетей к рабочей области, UI
WorkspaceSelect) и синхронизации каталога App Store.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поле ip_address валидировалось строго как IP, из-за чего подключение ноды по домену (node1.tzim.cloud) падало с 'Invalid IP address format'. Добавлен хелпер validate_host: принимает IP (v4/v6) или корректный hostname/FQDN. IPAM-валидаторы по-прежнему требуют строгий IP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Установка: поле «Хранилище» стало выпадающим списком доступных rootdir-хранилищ
ноды (useLXCStorages) с автовыбором валидного вместо ручного ввода
- Диагностика сбоя: увеличен захват вывода упавшей команды (400 → 2000 символов)
и добавлен 2>&1 к docker compose pull/up — реальная причина (напр. отсутствие
тега образа) больше не обрезается за строками warning
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pipeline: _normalize_compose() объявляет упомянутые в сервисах сети
(tipi_main_network) как external в top-level — иначе docker compose up
падал с "refers to undefined network: invalid compose project"
- pipeline: LOCAL_DOMAIN=tipi.local в .env (стандартная переменная Runtipi)
- pipeline: per-command таймауты (_CMD_TIMEOUT/_DOCKER_TIMEOUT), при таймауте
установка падает с ошибкой, а не зависает на 75%
- ssh_client: execute() ждёт готовности exit-status с жёстким дедлайном
вместо бесконечного recv_exit_status() (ловушка paramiko)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AppOperation.to_task_dict(): операции install/update/delete/… приведены к
общему формату и включены в /api/all-tasks, active-count и clear-completed
- Установка приложения: опциональный выбор сети/пула IPAM — статический IP
вместо DHCP (net0 ip=<addr>/<mask>,gw=…), освобождение адреса при удалении
- Мастер установки: селекторы «Сеть IPAM»/«Пул IPAM» (ru/en), по умолчанию DHCP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При удалении LXC/VM через delete_vm/delete_container чистим таблицу installed_apps
(по server_id+vmid), иначе приложение из каталога оставалось «хвостом» в «Мои приложения».
Шлём WS-эвент appstore_app_deleted для live-обновления списка у владельца.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docs/golden-template.md: шаг 1 разбит на 1a–1d, имя vztmpl берётся из
`pveam available` в переменную и переиспользуется (фикс no such template /
volume does not exist)
- backend/.env.example: добавлены APPSTORE_GOLDEN_TEMPLATE и APPSTORE_HOST_ARCH
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Описания приложений: безопасный рендер markdown + HTML (react-markdown +
remark-gfm + rehype-raw + rehype-sanitize), скриншоты отображаются как картинки
- Фильтр по категории: переведён на надёжную фильтрацию в Python (JSONB-оператор
на generic JSON-колонке отрабатывал ненадёжно)
- Ошибка отсутствия золотого шаблона локализована (i18n appstore.golden_missing)
- Каталог: карточки шире (макс 3 колонки вместо 4), описание в 3 строки
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Добавлена возможность пробросить физический диск ноды в VM по стабильному
пути /dev/disk/by-id/ (scsiN/virtioN/sataN) в карточке «Проброс устройств».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточки на странице «Узлы» дополнены важной информацией:
- версия Proxmox VE (чип «PVE x.y.z»);
- число нод в кластере (для кластерных серверов);
- аптайм узла;
- чипы рабочих областей, в которых состоит сервер.
Бэкенд: эндпоинт /api/{id}/nodes отдаёт version; ответ серверов
дополнен полями cluster_name и workspaces (ServerWorkspaceBrief).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Реальные интервалы циклов: dashboard 2с, server 2с, instance 1с.
В комментариях было устаревшее 10с/5с. Логику не меняем — дашборд
уже обновляется раз в 2 секунды.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>