1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
Commit graph

2123 commits

Author SHA1 Message Date
catborise
aab8dbf1df perf(instances): a VM page does not wait for the guest agent's addresses
A running VM's page read its addresses while it rendered: the guest agent,
when its channel is connected but it does not answer, holds the page (and
a gunicorn worker) for its whole 10 s timeout, on every load. The page now
shows them once it has loaded: vm_addresses.js asks the new
instances/<pk>/addresses/ for each NIC's addresses and fills the header's
IPv4 and each network card's IPv4 and IPv6.
2026-11-07 15:42:49 +03:00
catborise
bf736942eb perf(computes): a host's capacity is one libvirt call, not one per VM
The Computes page, the host overview and the new-VM dialog's capacity line
read each VM's XML for its memory and vCPUs: a round trip a VM, about
200 ms for 150 VMs on a LAN, more with each host. libvirt's domain stats
give them for every VM in one call; a running VM busy with a job is not
waited for (NOWAIT) and its sizes come from its XML as before.
2026-11-07 15:25:49 +03:00
catborise
56a3ca4c99 fix(logs): a lost-OTP mail and a wrong old password leave a row
The "Lost OTP?" page writes a row when it mails a user their QR code link,
or fails to, under the anonymous requester and their address. A password
change refused for a wrong old password writes a failed row.
2026-11-07 14:36:44 +03:00
catborise
c5c6de6ba8 fix(logs): a job's rows carry the address it was asked from, and a cancel its own user
A job keeps the client address of its submission (Operation.client_ip,
migration operations.0003); the rows the worker writes for it, including the
migration's and the VM delete's own rows, carry it. A cancel is written under
the user who asked and from their address, also when an administrator
cancels another user's job; a running job's cancel gets a row when asked,
and its end a row when it stops. A resolution carries the administrator's
address.
2026-11-07 14:36:44 +03:00
catborise
528b4492f6 fix(logs): a row says what happened: a recovered migration, a failed clone step, a device not there
- A migration whose libvirt call failed but which recovery finds moved (or
  cannot tell) gets a row of that end; only a failure already logged by
  migrate_instance is not logged twice.
- A clone made with its resize or start failed, and a recovered migration
  whose autostart or NVRAM step failed, write a failed row of that step, as
  migrate_instance does for its later steps.
- Setting the link state of an interface or the video model of a VM that
  has none is refused: it changed nothing and was logged as done.
- The rows of a removed VM record (deleted or expired) name its host, VM
  and UUID, so the VM's history has them.
2026-11-07 14:36:34 +03:00
catborise
470cc9860e fix(api): a volume's size is GiB to create and bytes to read, each documented
One serializer created volumes and read them: size was GiB in a request
and bytes in an answer, the same field in the schema. A client that sent
back a size it read asked for a volume 2**30 times as large. A new
volume is CreateVolumeSerializer (size in GiB); a volume read is
VolumeSerializer (allocation and size in bytes), and the schema says so,
as for the pools' size and free space.
2026-11-07 13:29:46 +03:00
catborise
0610c23a34 fix(api): an error's status says what it is, and its body is always {"detail"}
- libvirt errors all answered 400. A VM, pool or volume the host does not
  have now answers 404, a volume in use 409 (vrtManager.util.InUse) and a
  host that cannot be reached 503, which a client may retry.
- A template's power on or cycle and a stopped pool answered
  {"status": ...} with 409; they answer {"detail": ...}, as every other
  refusal. The schema documents these 409s (ErrorSerializer).
- The volume list and a volume of a stopped pool answered 200 with an
  empty list or a made-up volume; they answer 409.
- An API URL that names nothing answered the HTML error page; it answers
  a JSON 404 after the API's authentication.
- A session that must change its password was redirected to the HTML
  form; an API request of it gets the JSON 403 a token gets.
2026-11-07 13:29:46 +03:00
catborise
9d22d48a85 fix(vrtManager): an architecture the host does not run is refused, not a 500
The create page and the API's create take the architecture from the
URL. One the host's capabilities do not list raised a KeyError in
get_dom_cap_xml: an HTML 500 for both. It is now "This host has no
architecture noarch": the page shows it, the API answers 400.
2026-11-07 00:10:06 +03:00
catborise
6d22a18ba7 fix(i18n): refusals and checks users read are translated
English-only texts users read, now in their language:
- the serial console's four refusals, which socketiod sends in the
  language LocaleMiddleware would choose (cookie, then browser) and logs
  in English; the texts live in console/refusals.py, as makemessages
  reads only .py files;
- a submission key reused for another request, a malformed MAC address,
  the moved-VM refusal of a script's request, the API's "Templates
  cannot be started" (now the page's sentence), "Pool is not active"
  and the ISO upload's checks;
- "Problem occurred with host" was a fragment with the host's name and
  error after it: one sentence with both.
2026-11-06 23:48:53 +03:00
catborise
6abea495f1 fix(logs): failures name their job, VM and user; the new VM form shows a list it could not load
- A failed cancel hook logged "worker task cancel failed": it now names
  the job, its kind, resource and user, with the traceback for a fault
  of the panel.
- novncd logged "Console connection failed" without the VM or the user.
- django-axes wrote four INFO lines a login ("Reset 0 access
  attempts"...); logins are on the Logs page, so it logs warnings only.
- The new VM form's machine types and disk buses answered 200 with an
  empty object when libvirt failed, and nothing was logged: the list
  stayed empty, the form could not go on and said nothing. They answer
  400 with the error, as the pool's volume list does, the warning is
  logged with the compute, and the form's lists show an error they get
  with the page's messages.
2026-11-06 23:48:44 +03:00
catborise
432b1505b3 fix(operations): a job's unexpected error is not shown raw; the log has its traceback
A job that failed on an exception that is not libvirt's or a refusal
showed its raw text to users (a SQL error such as "UNIQUE constraint
failed: instances_instance.uuid", a KeyError's key), and the worker
logged only that text. Users now read "The panel failed unexpectedly;
its log has the details." and the worker logs the exception with its
traceback.

A live clone whose recovery found the clone made but could not thaw the
source's file systems kept that in the error of a succeeded job, which
the job panel does not show. It is a warning of the clone's result, as
when the job itself made the clone.
2026-11-06 23:48:44 +03:00
catborise
d1fb9bd2d4 fix(console,computes): an unreachable host is named as the cause, not an empty list
The console page of a VM whose host does not answer said "Fail to get
console. Please check the console configuration of your VM." and logged
nothing. It now shows the connection error (to users who do not see the
hosts, that the host cannot be reached) and logs it with the VM and host.

A host's Storage, Networks, Interfaces, Filters and Secrets pages showed
the connection error and also "This host has no ...". The list is None
when it was not read, and the page shows only the error.
2026-11-06 23:08:11 +03:00
catborise
39dda65132 docs(readme): a database-only restore keeps the current settings and migrates
Restoring only the database on newer code copied the backup's old
settings.py back and ran no migrations: a backup from v4.3.0 brought back
RemoteUserMiddleware and left the password validators and WAL out, with
seven migrations pending. The restore runs migrate, which changes nothing
on a rollback, and the text names every line that takes a file from the
backup.
2026-11-06 22:28:15 +03:00
catborise
1f9347e148 fix(instances): a new VM and each clone get a VNC password of their own
Since the VM create became a job (6f0c7300) every new VM was defined with
no VNC password, and a clone copied its source's password (or its lack
of one) exactly: every clone of a template shared it. Computes listen
for VNC on all their addresses (dev/libvirt-bootstrap.sh), so anyone who
could reach a compute's VNC ports opened any of these consoles directly,
without the panel; the README's firewall rule was the only guard.

The create job asks for create.py's random 8-character password again,
and a clone's VNC graphics get a new random one. The console page sends
the password to a user with passwordless_console, which the user create
form now ticks by default next to change_password; a user without it is
asked for the password, as the permission intends. Existing VMs keep
their settings.

Live create, clone and VM page tests pass on a TCP compute.
2026-11-06 20:00:43 +03:00
catborise
c5d0672c68 fix(admin): setting a user's password deletes the user's API tokens
An administrator resets a password to close an account someone else
took over, but the API tokens stayed valid: a stolen token kept working
with the new password, for as long as it was made to last. Setting a
password on the user's Password page now deletes that user's tokens,
and the page says so. A user who changes their own password keeps their
tokens (scripts keep working); they can delete them on their profile.
2026-11-06 20:00:43 +03:00
catborise
4f7bca9754 fix(console): socketiod refuses a request without a signed-in session
Engine.IO accepts a connection, and holds one of socketiod's 40 threads
for it, from its first request; the console token is checked only in
Socket.IO's connect(), which a client need never send. Anyone who could
reach the panel, without an account, could open 40 websockets, answer
their pings, and no serial console would open for anybody (checked: the
41st handshake got no answer until they were closed).

The daemon's WSGI app now refuses a request whose session cookie names
no signed-in user, with 403, before Engine.IO sees it; the page always
sends that cookie. A token without its session no longer connects either,
as for novncd. Checked again: 60 such websockets are each answered 403 at
once and hold nothing.

The process tests sign their client in with a session cookie, as a
browser; python-socketio sends a custom Cookie header only with its first
request, so the client gets a cookie jar instead.
2026-11-06 20:00:43 +03:00
catborise
bb490101fd fix(nginx): a request body is at most 64 MiB
Every route accepted a 1 GiB body, and nginx buffers a body to disk
before the panel can refuse it: anyone, without an account, could fill
that disk with large requests sent slowly and never finished. The one
large body is an ISO upload's chunk, 32 MiB (storage_page.js); the limit
is now 64 MiB.

Checked with nginx and this config: 33 and 64 MiB pass to the panel,
65 MiB is answered 413.
2026-11-06 19:50:20 +03:00
catborise
ce8d5d3e17 docs(readme): a PostgreSQL installation keeps its DATABASES across an update
Step 2 rebuilds settings.py from the template, whose DATABASES is sqlite
unless WEBVIRTCLOUD_DB_HOST is set; its list of values to put back did
not name DATABASES. An installation that set PostgreSQL in settings.py
(the only way before the WEBVIRTCLOUD_DB_* variables) would have migrate
make a new, empty sqlite database and the panel start on it.

Checked by upgrading an installation of the upstream release of
September 2026 on PostgreSQL 16 with DATABASES put back: every user,
permission, owner, compute, VM and log row came through.
2026-11-06 15:20:45 +03:00
catborise
901e9a1fac docs(readme): an update from an old installer moves the secret key into a new data/
Installers up to September 2026 wrote SECRET_KEY into settings.py and
made no data/ directory. Step 2 said that leaving the key out signs every
user out; in fact the template then has no key at all: migrate in step 3
stops with "The SECRET_KEY setting must not be empty" and every page
answers 500. The step now says to create data/ first and what happens
without the key.

Checked on an installation of the upstream release of September 2026
upgraded to this version, with data/secret_key moved away and back.
2026-11-06 15:20:45 +03:00
catborise
21bd56a9f9 fix(accounts): an admin still on the password "admin" must change it
Every upstream release and this fork's up to v2.0.0 created the first
user as admin/admin. The first-login password change only covered the
password the panel generates itself (data/admin_password), so an upgraded
installation whose admin never changed the default kept a working
admin/admin login with no prompt, on the pages and through API tokens.

Migration accounts.0013 marks a user named admin whose password is
"admin" with must_change_password: the next login goes to the password
change form, and the API refuses the user's tokens until then. Only that
one name is checked, so the migration hashes one password at most.

Found by upgrading an installation of the upstream release of
September 2026 (installer and Docker) to this version: admin/admin still
signed in.
2026-11-06 15:20:35 +03:00
catborise
402a109633 docs(readme): the update's pull works on an installation of the old installer
Installers up to September 2026 ran collectstatic into the tracked static/
(which changed static/drf-yasg and static/rest_framework files) and deleted
the tracked install.sh and webvirtcloud.sh. Step 1 put back only
static/css and dev/scss, so `git pull` refused with "Your local changes
would be overwritten" on every such installation, and the later steps ran
on the old code. Step 1 now puts back every tracked file under static/ and
the two installer scripts.

Checked by installing the upstream release of September 2026 with its own
installer on Debian 12, Ubuntu 24.04 and AlmaLinux 10, and following the
update steps to this version.
2026-11-06 15:19:40 +03:00
catborise
f996b03ed1 test: the live VM page tests run the delete job
Since deleting a VM became a job (#431), the delete page queues it and
returns to the VM page; the live VM page tests still expected the VM
gone at once and a redirect to the list, so they failed, and the VMs
they left behind failed later tests. They now run the queued job and
check the VM is gone.
2026-11-06 13:48:04 +03:00
catborise
34b9fb21c8 chore: comments say what the code does
- gunicorn.conf.py was gunicorn's sample file: pages of generic
  comments, a broken link and examples of the eventlet worker the panel
  no longer has. Only the panel's own comments stay; every setting keeps
  its value.
- docker-compose.yml: the backups hold the database, keys and
  certificates, not "both".
- accounts' system checks point to settings.py.template, which has the
  middleware they ask for (the README does not list it).
- The authorization matrix test names doc/permissions.md, where the
  roles table is; the keymap list no longer cites a dead page.
2026-11-06 13:48:04 +03:00
catborise
be5e37a616 docs: README and doc/ match the code
- Deleting VMs and moving disks are jobs too (Features, Behavior
  changes, architecture), and the API's delete answers 202.
- Docker keeps the TLS client certificates in webvirtcloud-pki.
- The manual installation covers Ubuntu 26.04 and Debian 13, says lxml
  is reused like libvirt-python, and adds the service user to the
  libvirt and kvm groups, as the installer does, for a socket compute.
- Moving to a new machine starts in the clone's directory (Docker) and
  copies secret_key only when the backup has it (installer); the
  off-site steps run in the panel's checkout; an update's backup also
  holds /etc/default/webvirtcloud and local-migrations.tar.
- A kept settings.py is rebuilt from the template instead of patched by
  a list that had fallen behind (knox, axes, four middlewares).
- gstfsd on 127.0.0.1 serves a panel that adds its compute as localhost;
  LOGIN_TRUSTED_PROXIES matters only for a proxy on another machine;
  socket computes reach a localhost console too.
- New passwords pass Django's validators (Behavior changes).
- permissions: a VM an administrator paused stays paused for owners.
- architecture: nginx listens on 80, on 443 only with your TLS.
- Running tests: the libvirt layer's tests are in vrtManager/tests.
2026-11-06 13:48:04 +03:00
catborise
f3003bb414 test: test files named by their topic, one file per topic
- console settings were split over four files; who sees the form, the
  listen address, and the settings themselves are test_console_settings,
  and who may open a console is test_console_access;
- the three template files are test_templates;
- the quota lock test joins test_quota;
- accounts' test_security_fixes held two topics: the SSH key tests are
  test_ssh_keys, the owner-row delete redirect is in test_views;
- instances' test_security_mutations, who may change a VM, is
  test_permissions.
The suite runs the same 2097 tests.
2026-11-06 13:48:04 +03:00
catborise
bdc8a6755c test: each test is in the tests package of the code it tests
Tests that build vrtManager objects and touch no view, form or model of
an app lived in that app; vrtManager/tests already holds such tests, so
the convention was split. They move to vrtManager/tests: the firmware,
CPU mode and PCIe port definitions of a new VM (test_create), USB and PCI
host devices, the console listener, host CPU reads, interface reads,
deletes and XML, network filters, PCI passthrough rules, node devices,
network changes, addresses and leases, pool XML, secrets, and the live
tests that use vrtManager only (disk resize, ISO, snapshots, XML, live
clone). livetest.py, the live-test helper six apps import, and the
network test driver move with them.

Others go to their app: the quota with a host down to instances, the
401/403 Logs rows (webvirtcloud's middleware) to webvirtcloud, the VM
page's tab script test to instances, novncd's own tests out of the
console views tests, the compute lock and input validation tests out of
the computes views tests, the interface views out of the forms tests.
instances/tests/test_views.py, entirely live, is test_live_views.py.

Test modules imported fixtures from each other; shared ones are now in
vrtManager/tests/fakes.py (migration fakes, node devices, IOMMU groups,
leases, a NAT network, a Ceph secret), interfaces/tests/fakes.py,
console/tests/daemons.py (novncd loaded once), livetest.wait_for and
instances/tests/create_page.BODY. Every live test checks
livetest.enabled(). The suite runs the same 2097 tests.
2026-11-06 13:48:04 +03:00
catborise
4e0952aa50 test: the computes tests share one fake libvirt domain
test_ownership, test_ownership_race and test_reconciliation each defined
the same fake domain (a name and a UUID); it is computes/tests/fakes.py.
2026-11-06 13:48:03 +03:00
catborise
00fb8c1d21 test: the installer library's shell tests run with the suite
conf/test_install_lib.sh tested conf/install-lib.sh, but no CI step or
test ran it. It moves to webvirtcloud/tests/sh/install_lib.test.sh, takes
the library's path as its argument, and test_install_lib runs it in bash,
as the JS tests run in node.
2026-11-06 13:48:03 +03:00
catborise
7be6be78f1 test: the read-write lock tests check who may take the lock
Five tests only acquired and released the lock and asserted nothing.
They now check, from another thread, that a reader lets readers in and
keeps writers out, that a writer keeps everyone out, and that a nested
or upgraded lock is held until its last release.
2026-11-06 13:48:03 +03:00
catborise
42ab293dc5 test: remove code no test run executes
- 25 vrtManager test modules configured settings themselves when Django
  had none (MAC_OUI, and in test_network_ip a fake django module): under
  manage.py test settings are always configured, so it never ran.
- webvirtcloud/tests/test_common_tags.py tested no tag: it fetched an
  unknown URL, as test_error_pages and test_logging already do.
- .gitignore covers the parallel runs' test_db_N.sqlite3 files too.
2026-11-06 13:48:03 +03:00
catborise
03811cc4ab test: classes that use no database are SimpleTestCase
Ten classes ran on TestCase or TransactionTestCase although they read
files, templates or scripts only. SimpleTestCase skips the transaction
and refuses a database query, so a test that starts to use the database
says so. LoggingConfigTestCase also removes the temporary directory it
makes.
2026-11-06 13:48:03 +03:00
catborise
ee6b8d3e10 test: no seeded admin password, settings changed only for a test, no local libvirt by default
- create_admin had a branch for `"test" in sys.argv` that gave the admin
  the password "admin"; 17 places signed in with it, and failed silently
  with ADMIN_PASSWORD set in the environment. The tests now make their
  own superuser and force_login it, and the shipped code has no test
  branch: the test runner gives migrate a password (also when
  ADMIN_PASSWORD is set but empty), so no data/admin_password is written
  into the checkout.
- AccountsTestCase builds its rows in setUpTestData instead of
  setUpClass and deleting them by hand.
- accounts tests set OTP_ENABLED with override_settings instead of
  assigning settings (one assignment was never undone, so later tests in
  the process ran with OTP on), and undo their app_settings change. The
  OTP login class reloads the URLs again only after the setting is off.
- computes/tests/test_views.py reached the machine's own libvirt
  (localhost over the socket) when TEST_LIBVIRT_HOST was unset; its live
  tests now run only with TEST_LIBVIRT_HOST, like the other live tests,
  as the README says.
2026-11-06 13:48:03 +03:00
catborise
a4ea23004f chore(appsettings): one squashed migration for a new database
0001_initial to 0024_log_retention_setting are mostly RunPython steps
that added, renamed, re-chose and described setting rows since 2020. A
squashed migration now creates the table in its final shape and inserts
the 38 rows those steps leave; it replaces all 24, so a new database
runs it alone. The 24 stay beside it, as Django's squashing expects
until every installation has applied them: a database that applied
them all records the squashed one without running anything, and one
that stopped between them (an older release, or an upstream
installation) applies the rest of them first.

Checked:
- on a new database its rows equal the old chain's (name, key, value,
  choices, description of every row);
- on a copy of a running panel's database (all 24 applied) migrate
  plans nothing for appsettings;
- a database migrated by the old code to 0023 applies 0024 and then
  records the squashed migration, with the same 38 rows;
- makemigrations finds no change.

It inserts its rows on the database being migrated, as the replaced steps
do. The tests of the replaced steps stay, since an installation that stopped
between them still runs them; the two that checked a few rows' columns
became one that checks every row's columns and description, and a value is
one of its choices.
2026-11-06 13:47:23 +03:00
catborise
f1f52aac42 refactor: the API's serializers and the new VM form no longer need models
Networks, Interfaces, Storages, Storage, Volume, MigrateInstance and
CreateInstance were unmanaged models with no table, there only so that
ModelSerializers and the new VM ModelForm could take their fields. They
made content types and migrations, and their DO_NOTHING foreign keys
added reverse accessors that meant nothing (Compute.host,
Compute.target_host, Instance.source_host).

The serializers are plain Serializers and NewVMForm a Form, field for
field as the models gave them (types, lengths, required, defaults, the
minimum of 1 for vCPUs, memory and disk size; the form's labels are
still derived from the field names). Compared field by field before and
after, the API schema changes only where the models were wrong:
- no int64 bounds on the listing fields;
- the interface fields lose the titles that named the wrong column
  (type was "Status", state "Device", mac "Forward");
- a pool's volumes is a list of objects (it was described as an
  integer) and a volume's allocation an integer (it was untyped).
One migration per app deletes the models; none touches a table.
2026-11-06 13:46:27 +03:00
catborise
f3bdeddb0b fix(instances): a VM list poll for a host id that is not a number is 404
instances:states?compute=x raised ValueError in the lookup, a 500.
2026-11-06 13:46:27 +03:00
catborise
afe80a9e78 refactor(accounts): UserInstance's uniqueness is a UniqueConstraint
Django documents unique_together as likely to be deprecated in favour of
Meta.constraints; the rule is the same (one row per user and VM).
2026-11-06 13:46:27 +03:00
catborise
8b1df3ab09 fix(admin): the Technicians group migration can run on a database that has it
It created the group with objects.create and had no reverse: it failed
when the group existed and could not be unapplied. It now uses
get_or_create, and its reverse does nothing.
2026-11-06 11:01:58 +03:00
catborise
52cb23eefd chore(settings): settings-dev without obsolete keys
TEMPLATE_DEBUG has been unused since Django 1.10 and debug toolbar no
longer reads INTERCEPT_REDIRECTS; DebugToolbarMiddleware goes first, as
its documentation asks, instead of after django-axes.
2026-11-06 11:01:51 +03:00
catborise
abe3b7ee63 refactor: settings are read through django.conf
instances/forms.py and instances/models.py imported values from the
webvirtcloud.settings module itself, which bypasses DJANGO_SETTINGS_MODULE
(settings-dev, test overrides) and runs the module a second time. They
now read django.conf.settings. webvirtcloud/urls.py also loses its
fallback that set REST_FRAMEWORK's schema class at import for settings.py
copies without it: the template sets it, and an older settings.py is
rebuilt from the template when updating.
2026-11-06 11:01:51 +03:00
catborise
2440c9eaf7 refactor(settings): SQLite's WAL mode is an init_command
The operations app set PRAGMA journal_mode=WAL from a connection_created
receiver because Django's SQLite OPTIONS had no init command; Django 5.1
added init_command, so the template sets it there and the receiver goes.
The mode is stored in the database file, so existing databases keep it.
2026-11-06 11:01:51 +03:00
catborise
2e287f3a7f fix(settings): translations load from any working directory
LOCALE_PATHS was the relative "locale/": a manage.py or cron run outside
the checkout found no catalogs and fell back to English. It is now under
BASE_DIR. Reading data/secret_key also catches only OSError instead of
any exception.
2026-11-06 11:01:44 +03:00
catborise
5b2542c6ed fix(settings): new passwords pass Django's password validators
AUTH_PASSWORD_VALIDATORS was unset, so the user forms and the password
change accepted any password, a one-letter one included. The template
now sets Django's four standard validators: at least 8 characters, not
a common password, not only digits, not like the user's name or e-mail.
The tests that set weak passwords use ones the validators accept.
2026-11-06 11:01:43 +03:00
catborise
0ccdb2adb7 fix(settings): remove RemoteUserMiddleware, which no backend uses
No RemoteUserBackend is configured, so the middleware never logged
anyone in: it only ran on every request and read as if the panel trusted
a REMOTE_USER header.
2026-11-06 11:01:43 +03:00
catborise
97e107ce14 fix(instances): no deprecated crypt module from Python 3.11
The root password hash for a shut-off VM used crypt_r where installed
(Python 3.13) and fell back to the stdlib crypt, which Python 3.11 and
3.12 deprecate: `python -W error::DeprecationWarning manage.py check`
failed there. crypt-r (the same module, it requires Python 3.11) is now
installed from 3.11, and the module is chosen by the Python version;
3.10's crypt is not deprecated.
2026-11-06 10:56:14 +03:00
catborise
c126a8cd66 fix(installer): nginx is enabled and restarted after its config is written
restart_nginx only logged "systemctl enable/restart nginx.service": both
calls had been commented out since the installer was added (2020). On
RHEL and openSUSE nginx is not enabled after its install, so the panel
was not served until someone started it; on Debian and Ubuntu nginx
kept the default site until a reload. The installer now checks the
config, enables nginx and restarts it, and stops if that fails.
2026-11-06 10:54:04 +03:00
catborise
3ceca5f9f3 chore: remove files nothing uses
- noVNC's own web client (app/ except the bell sounds, vnc.html,
  vnc_lite.html, defaults.json, mandatory.json): the console imports
  only noVNC's core, and novncd serves no web pages. AUTHORS, LICENSE.txt
  and package.json (the version) stay.
- 24 cached icons no {% bs_icon %} asks for; the icon cache test now
  also fails on an icon nothing uses.
- Ace's ext-textarea.js, which nothing loads.
- instances/fixtures/initial_data.json, for an app ("create") that no
  longer exists; migration 0006 seeds the flavors.
- webvirtcloud/asgi.py: gunicorn runs the WSGI application only.
- instances/templatetags/tags_active.py: a copy of common_tags'
  class_active that no template loads.
- .github/linters/.hadolint.yaml, from super-linter, which no workflow
  runs anymore.
2026-11-06 10:53:43 +03:00
catborise
764cbb3c43 chore: remove code nothing uses
Each was checked with git grep across every tracked file (templates, JS,
shell, unit files and the extensionless console daemons) and for
dynamic use (URL names, template names, registries):

- vrtManager: host_is_up, is_kvm_supported, get_emulators,
  get_file_extensions, both get_network_forward, get_snapshots,
  get_storages_images, get_os_type, get_host_arch, the get_pretty_* trio
  and, in util, is_kvm_available, randomMAC, get_max_vcpus, compareMAC,
  pretty_mem, pretty_bytes and validate_uuid; with them the port
  constants only host_is_up read. Some were reached only by their tests,
  which go too.
- The flavor edit page: nothing linked to it; flavors are created and
  deleted on the Flavors page and edited through the API.
- computes.validate_name, UserForm.identity_has_errors, the has_perm
  template tag, the ALLOW_EMPTY_PASSWORD setting nothing reads,
  hidePleaseWaitDialog, default_app_config (ignored since Django 4.1)
  and the Python < 3.6 lines of secret_generator.py.
2026-11-06 10:53:37 +03:00
catborise
02412b7ea5 feat(jobs): deleting a VM is a job that recovery finishes
The delete ran in the request: undefine, then the VM's record, then its
disk files. A request that died in between (a gunicorn worker past its
timeout, OOM, a restart), or a database write that failed after the
undefine, left a record that later showed as removed outside the panel,
and the disk files the user asked to delete were never deleted.

Deleting a VM is now an instance.delete job. It holds the compute
exclusively, as the request did, so no other VM attaches a file it is
about to delete. The job chooses the files before it stops the VM (a host
that cannot be checked still stops nothing) and records them first. It
then stops and undefines the VM, removes its record, and deletes the
files, each with its own Logs row. Recovery decides from libvirt: a VM
still on its host means the delete failed; a VM gone means libvirt did
it, so recovery removes the record and deletes the recorded files that
are still there.

The page queues the job and returns to the VM page, where the job panel
follows it and opens the VM list when it ends; disks that were kept are
the result's warning. The API's delete answers 202 with the job and
needs a submission_key. backing_path moves to wvmConnect, as recovery
has no domain to ask.
2026-11-06 09:49:31 +03:00
catborise
ce066052d9 docs(readme): an update's database copy is named db.sqlite3 only once it is complete
The update steps wrote the copy straight to $b/db.sqlite3: the shell
creates the file before the copy runs, so a copy that failed left an
empty or partial db.sqlite3 next to the commit, which looks like a good
backup. Restoring from it put an empty database in place, and the panel
came back with no users, computes or VMs. The copy is now written to
db.sqlite3.part and renamed when it succeeds, in the Docker and the
installer steps.
2026-11-06 09:12:34 +03:00
catborise
90fed8160f docs(readme): moving to a new machine works with a SECRET_KEY from the compose file
A panel that takes SECRET_KEY from its environment has no data/secret_key,
so its backup has none. The Docker moving steps copied it
unconditionally: the copy failed after the database had already been
replaced, and the chain stopped before node_id, the SSH keys and `up -d`.
The copy is now skipped when the backup has no key, and the section
says to set the same value on the new machine. It also says to stop the
old panel first when both use one PostgreSQL database: the moved worker
id would let both workers take each other's lease and jobs.
2026-11-06 09:12:34 +03:00