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

217 commits

Author SHA1 Message Date
catborise
87e4de77bb chore(deps): patch and minor updates of pinned and vendored libraries
psycopg 3.3.8, ruff 0.17.4; Chart.js 4.6.1, Ace 1.45.2 (its XML mode and
worker are unchanged) and Bootstrap Icons 1.13.2 (font and stylesheet).
django-bootstrap5 26.4 and coverage 7.17.2 need Python 3.11 and stay at
26.3 and 7.16.2 while Python 3.10 is supported. No known vulnerability was
fixed by them (pip-audit clean before and after).
2026-11-07 18:14:59 +03:00
catborise
f411659f40 fix(deps): libpass instead of crypt-r; gstfsd's python3-libguestfs on a RHEL compute; PyYAML for the tests
- The root password's SHA-512 crypt hash comes from libpass (the maintained
  passlib fork, pure Python) instead of crypt-r, a copy of the stdlib crypt
  module gone in Python 3.13: crypt-r ships no wheels and its build needs
  crypt.h, which Ubuntu 26.04 no longer brings with python3-dev, so the
  installer and the manual installation failed there. The hash is the same
  format ($6$, now with libpass's 656000 rounds), checked with glibc's crypt.
- The README's RHEL compute packages left out python3-libguestfs, which
  gstfsd imports with the system python: setting the password or key of a
  shut-off VM failed there.
- The tests read YAML themselves and had PyYAML only through
  drf-spectacular.
2026-11-07 17:52:35 +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
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
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
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
482f5bc16d fix(backup): TLS client certificates persist in Docker and are backed up
libvirt reads a TLS compute's client certificate from the panel user's
~/.pki/libvirt. In Docker that was /var/www/.pki inside the container,
so `docker compose up -d --build` dropped it and every TLS compute
stopped connecting; no backup held it on either install type, although
"Moving to a new machine" says the backup carries what the computes
accept, and the README never said where the certificates go.

Docker keeps /var/www/.pki in a webvirtcloud-pki volume, owned by
www-data and private like ~/.ssh; the backup copies ~/.pki into pki/;
the moving steps put it back; and "Adding a Compute Node" says where the
files go.
2026-11-06 09:12:34 +03:00
catborise
77f85832c9 fix(docker): the health check covers the worker and the console services
The container's health check only fetched the login page, through nginx
and gunicorn. A job worker, novncd or socketiod that kept failing (runit
starts it again each second) left the container healthy while jobs
waited and consoles failed. The check now also asks runit for each
service and fails when one is down or has run for less than five
seconds.
2026-11-06 09:07:49 +03:00
catborise
26683b28ed fix(console): a serial console waiting for its VM to start checks five times a second
The worker checked its VM's state once a second (34dd61c8); a VM started
while its console was open could lose up to a second of its first output
(the BIOS banner, a boot menu) before the stream opened. A VM that is not
running is checked five times a second again; one that runs stays at once
a second.
2026-11-05 19:39:19 +03:00
catborise
fd60851a28 chore(deps): django-rest-knox 5.1.1
Same code as 5.1.0 (only the version changes); keeps the pin current.
2026-11-05 18:58:13 +03:00
catborise
4cdc31b29a fix(docker): the generated secret key file is private from its creation
The container wrote data/secret_key and made it 600 afterwards; it is
now written under umask 077, as the installer does.
2026-11-05 18:58:13 +03:00
catborise
34dd61c80c perf(console): a serial console asks for its VM's state once a second
Its worker asked libvirt for the VM's state five times a second, up to
100 calls a second with 20 consoles open, only to notice a start or a
stop. Once a second is enough: output and keys do not wait for it.
2026-11-05 18:58:13 +03:00
catborise
51aa712802 fix(console): a serial console whose VM was deleted meanwhile says so
consolecallback read the VM's record with .get(): one deleted between
socketiod's accept and the worker's start ended with a Python traceback
in the user's terminal, which is the worker's stderr. It now says the VM
is no longer in the panel.
2026-11-05 18:58:13 +03:00
catborise
89035038e1 fix(gstfsd): at most 4 connections at a time; one more is told it is busy
Every connection got a thread before any authentication, and every
signed request a guestfs appliance, with no limit: with remote access
allowed, anyone on that network could make the host run out of threads
or memory. A connection over MAX_CLIENTS (4) now gets an unsigned busy
line and is closed before it gets a thread; the panel shows it as the
gstfsd error.
2026-11-05 18:58:13 +03:00
catborise
c62de11147 docs(gstfsd): GSTFSD_PORT is for the tests; the panel always connects to 16510
gstfsd read its port from the environment while the panel has 16510 as a
constant, so a host that set GSTFSD_PORT could no longer be reached. The
documentation already gives 16510 everywhere; the variable now says it
exists for the tests.
2026-11-05 18:08:03 +03:00
catborise
2e4e9c78f8 fix(console): a VNC token works only in its user's session; refusals reach the Logs page
The VNC page's token travels in the websocket URL, so nginx's access log
and anyone who saw the URL had it for 120 s, and it opened the console as
a bearer token. novncd now also wants the session cookie of the token's
user. Its refusals were a warning without a user or an address: they now
name the client address (nginx sends X-Real-IP to novncd too) and leave
a Logs row "VNC console refused: reason".
2026-11-05 18:08:03 +03:00
catborise
458074df82 fix(console): the console daemons hold to what the console page allowed
The token now names the console type it was made for: novncd accepts only
a VNC page's token and socketiod only a serial page's, so a VNC page's
token no longer opens the serial console without the VNC password.

A VNC session re-checks every 10 s, as the serial one did, that its user
may still open the console; a deactivated user or a removed owner loses
it. The check (may_still_open) moves to console.tokens for both daemons.
2026-11-05 16:16:22 +03:00
catborise
f670ee7333 fix(logs): libvirt prints no errors on the processes' stderr
libvirt's default handler printed each error on stderr without saying
where it came from: in Docker those lines went to docker logs, and a
VM page read of a shut-off VM left one there. Every process that talks to
libvirt imports vrtManager.connection, which now registers a silent
handler; the errors still reach their callers as exceptions. The
console callback's own registration is dropped.
2026-11-05 15:34:51 +03:00
catborise
5d90d3fcf9 fix(docker): gunicorn's and the job worker's output go to docker logs
They went to files in the container that were never rotated and were lost
on each recreate. docker logs is rotated (10 MB x 5) and outlives a
restart.
2026-11-05 14:47:37 +03:00
catborise
fdd7310297 feat(logs): each log line names its request, user and client address
A request gets nginx's $request_id (or a new one when it comes without a
valid one) and sends it back in X-Request-ID. The app log's lines carry
the logger, process and thread, the request ID, the signed-in user and the
client address; gunicorn's access lines carry the client address from
X-Real-IP and the same request ID, so the two logs can be joined.
2026-11-05 14:47:37 +03:00
catborise
a81eb79e51 chore(deps): django-otp 1.7.5, paramiko 5.1.0, python-socketio 5.18.1, python-engineio 4.15.1
The updates on the login, SSH and serial console paths:
- django-otp 1.7.5 revokes the session of a device that is no longer
  confirmed.
- python-engineio 4.15.1 and python-socketio 5.18.1 refuse requests and
  events before the connection completes, HTTP requests after the
  upgrade to WebSocket, and ../ in static file paths.
- paramiko 5.1.0 fixes agent keys and the default RSA signature
  algorithm.

django-rest-knox 5.1.1 changes only its project's references, and
django-auth-ldap 5.4.0 drops Python 3.10, which the panel supports;
both stay.
2026-11-05 12:37:51 +03:00
catborise
2573228569 chore(deps): the icons come from the committed cache, not django-bootstrap-icons
django-bootstrap-icons (last release July 2024) only read the SVG of
each {% bs_icon %} from static/icon_cache; for an icon not there it
fetched the SVG from a CDN while rendering the page, and it brought
defusedxml along. A 20-line template tag in webvirtcloud reads the
same files; the package, its CDN path and the BS_ICONS_CACHE setting
go. requests stays: websockify needs it.
2026-11-05 12:37:51 +03:00
catborise
d70552a734 fix(installer): a failed step stops the installer, and openSUSE gets a working panel
Tested on Ubuntu 22.04, 24.04 and 26.04, Debian 12 and 13, AlmaLinux 10
and openSUSE Leap 15.6. On Leap the panel did not start: the secret key
was written with python3 before the installer looked for Python 3.10+,
and Leap has only python3.11, so the key was empty. The installer still
said "Finished!" and exited 0, as it did for any failed step.

Now Python is found first. The package list update, each package, the
clone, the key, pip, migrate and collectstatic stop the installer with
a message when they fail (pipefail makes a logged step's status count).

Also:
- Ubuntu 26.04 is installed; Ubuntu 18/20 and Debian 9-11 are refused
  up front (their Python is older than 3.10, so they failed later).
- The uos and openEuler branches are gone: untested, and their Python
  is too old as well.
- Only `apt-get update`, no upgrade of the whole system.
- Debian no longer asks for smbios-utils (gone in Debian 13) and uuid,
  which nothing uses; Debian and Ubuntu take python3-libvirt.
- RHEL reads the time zone like the others, and installs hostname for
  ALLOWED_HOSTS.
- The unused wvcuser account is no longer created.
- The end of the run says where the admin password is.
2026-11-03 13:29:03 +03:00
catborise
0f7a0ca5bc feat(conf): the panel's backups are copied off site every day
The daily backups stayed on the panel's host, so losing the host lost
them too. webvirt-offsite.timer now copies them at 03:45 with restic
over SFTP to another server, encrypted with a password only the
administrator and the host hold, and keeps 7 daily, 4 weekly and 6
monthly copies. It runs on the host as root, with Docker too, outside
the panel and its image. It fails when the newest backup is older
than 26 hours, so a stopped panel backup shows; an optional heartbeat
URL is called after each good copy.

The README has the setup, how to get a backup back from the copies,
and a fix found by the drill: moving to a new machine stopped when
the backup had no SSH keys (a panel without SSH computes).
2026-11-03 11:30:10 +03:00
catborise
777057cb19 feat(appsettings): the panel backs itself up every day
The only backups of the panel were the ones taken by hand before an
update. Now `manage.py backup` writes a new directory under backups/
with a consistent copy of the sqlite database (the backup API, while
the panel runs, checked with PRAGMA integrity_check), the secret key,
the worker's node_id, settings.py, the SSH keys of the panel's user
and, outside Docker, the commit. It keeps the newest 7 (--keep N) and
deletes older ones only after a good backup; a failed one leaves
nothing, and a second run while one runs fails. Files are mode 600,
directories 700: the database holds compute passwords and keys. With
PostgreSQL the database is left out.

It runs daily at 03:15: in Docker from the image's cron into a new
volume, webvirtcloud-backups; otherwise from webvirt-backup.timer,
which the installer and the manual steps install. A backup has the
layout of the update backups, so the restore steps take either.
2026-11-03 09:40:47 +03:00
catborise
30864b507e fix(console): a serial console reports its VM's stop
When a VM stopped, its console's stream could end before the worker's
next check saw the VM shut off. The check then found no stream to drop and
said nothing, or, seeing the VM still running, opened the console again,
which libvirt refused, and the session ended. The worker now remembers
whether the VM ran at its last check, and a console that cannot be opened
again because the VM stopped meanwhile is a stop: the terminal says the VM
is not running and the console opens again when it starts. A new stream is
the console's before its callback is registered, which may end it at once.

The live serial test's VM now repeats its BIOS banner (it reboots right
after finding no boot disk), so a console attached late still gets one,
and the test waits for the superuser's console to show it before the VM
stops. A run of the whole live suite over the socket missed the banner of
the paused-then-resumed VM before.
2026-10-31 23:23:30 +03:00
catborise
2dab23cd9e fix(gstfsd): only the panel, with the host's key, can change a VM's disk
gstfsd, the root service on each compute that rewrites a shut-off VM's
/etc/shadow and authorized_keys, did what any client on its port asked.
Now it needs GSTFSD_KEY (64 hexadecimal characters, in the unit's
/etc/default/webvirt-gstfsd) and does not start without it. Each
connection gets a new challenge; the panel signs its request for it
with HMAC-SHA256 and gstfsd checks the signature before any change, so
a request recorded on one connection is refused on another. gstfsd
signs its reply for the same challenge and the panel's nonce, and the
panel accepts only that reply.

Each compute has a gstfsd key, the host's GSTFSD_KEY: the compute forms
and the API take it and never show it, and an empty value on an edit
keeps it. The bootstrap script makes the key file once, readable by
root only; the README shows how to make it and where the panel takes
it. The traffic is still not encrypted: a remote panel's port stays
open to the panel's address only.
2026-10-31 01:53:43 +03:00
catborise
6744838850 fix(console): a serial console worker's failure reaches socketiod's log
- The worker told why a console did not open or closed only in the user's
  terminal (as users see errors: a host that cannot be reached is not
  named). It now also logs the full reason to socketiod's own stderr, the
  journal or docker logs, which socketiod hands it as a descriptor.
- A refused token says the link has expired or the user may no longer
  open the console: a valid link for a user who lost access was told only
  that it had expired.
- A send that takes nothing (0 bytes) waits like a busy stream instead of
  spinning.
2026-10-26 23:52:37 +03:00
catborise
060a3cc86b fix(console): a serial paste and its echo no longer wait on each other
- socketiod wrote input to the worker's terminal while holding the
  session's lock, and the write could wait for the worker; the output
  forwarder needs that lock. A large paste into a guest that echoes it
  could stop both for good. The terminal is now non-blocking: input is
  written as far as the worker takes it, and the wait for room is outside
  the lock.
- The terminal is raw from the start. Until the worker made it raw, it
  kept at most one 4 KiB line of a paste, and the worker's own setraw
  dropped what was typed meanwhile; the worker no longer sets it.
- A superuser's takeover ends the old worker, and waits for it, before
  starting the new one: libvirt gives a VM's console to one at a time.
- A resize while the worker ends no longer raises.
2026-10-26 23:52:37 +03:00
catborise
69ddbbdc94 feat(console): the console daemons' systemd units run sandboxed
webvirt-novncd and webvirt-socketiod get read-only system directories, no
access to /home and /root, no new privileges, no kernel tunables, modules
or cgroups, and only Unix, IPv4 and IPv6 sockets; socketiod also a private
/tmp. novncd keeps the shared /tmp, where a VM's VNC Unix socket may be.
They keep what they need: a pty and its console worker, TCP and Unix
sockets, ssh with its configuration, writes under /srv/webvirtcloud and
the service user's ~/.ssh. The units installed by the update steps bring
this.
2026-10-26 23:52:29 +03:00
catborise
3bd9fe63e5 fix(console): the console daemons log where the operator looks
- novncd and socketiod log to stderr: the journal on hosts, docker logs in
  the container (runit no longer sends them to unrotated files under
  /var/log). Django's settings gave them the panel's own log file, which
  every process rotated on its own, and made novncd's -v and -d have no
  effect; both work again.
- A refused console says why in the log: novncd's denials are warnings
  (they were shown only with -v), socketiod logs each refusal and who
  opened which VM's serial console.
- The README's serial console notes say what #228 changed (access
  re-checked, superuser takeover, reasons) and where the logs are.
2026-10-26 20:54:11 +03:00
catborise
68b104dfe4 feat(console): the VNC console uses the panel's own address
The console pages connect to /novncd/ on the address, scheme and port the
page itself was loaded from; nginx proxies it to novncd, which listens on
the loopback only. There is nothing to configure: the console works on
HTTPS (wss), behind another proxy and on any published port.

Before, the page connected to WS_PUBLIC_HOST/PORT/PATH, port 6080 by
default: straight to novncd, past nginx and without TLS, so an HTTPS panel
and a Docker container published on another port had no console until the
public port was set by hand, and novncd listened on every interface.

Gone: WS_PUBLIC_PORT, WS_PUBLIC_HOST, WS_PUBLIC_PATH and WS_CERT, the
installer's two public console questions and its 6080 firewall rule, the
Docker image's WS_PUBLIC_PORT and its init script lines, the manual
install's firewall rule for 6080 and nginx's unused /websockify location.
WS_HOST defaults to 127.0.0.1. A VM with a QEMU websocket port is still
reached on that port of the panel's host name (left for a separate
decision).
2026-10-26 20:14:04 +03:00
catborise
82af555d45 fix(console): a serial session follows its user's access; a superuser takes over
- The console worker learns whose session it is and checks every ten
  seconds that the user may still open the console: an owner removed or a
  user deactivated meanwhile loses it, with a line in the terminal. Access
  was checked only when the session started, which could last for days.
- One console per VM: a console left open in a forgotten tab locked
  everyone out, superusers included. A superuser's connection now takes
  the console over, and the other window is told so.
2026-10-26 19:46:41 +03:00
catborise
1d48cc6881 fix(console): the serial console worker keeps pastes whole and says what failed
- A paste reaches the VM whole: the non-blocking console stream takes it
  in parts, or none while the VM is busy, and the rest was dropped. The
  worker sends the rest until the VM has it, for at most five seconds.
- A libvirt error while the console is open (the host gone, the VM
  undefined or migrated) ends the session with one line in the terminal
  instead of a Python traceback; libvirt no longer prints its errors on
  the terminal. Errors are shown as users see them elsewhere: a host that
  cannot be reached is not named.
- The terminal says when the VM is not running and its console will open
  when it starts. The terminal is raw before the console opens, and the
  stream is dropped once when both threads see the VM stop.
2026-10-26 19:46:41 +03:00
catborise
f004552148 feat(instances): a new VM can get a cloud-init seed with its hostname and SSH keys
Cloud images read their first-boot settings with cloud-init. The panel
served them only over HTTP, which a VM reaches only with the network
and a reverse DNS name for its address. A new VM can now carry them
itself: Cloud-init seed (Advanced) on the create form, or cloud_init in
the API.

The job writes a NoCloud seed, an ISO labelled cidata, into the disk's
directory pool as cidata-<uuid>.iso and puts it in the VM's CD-ROM
drive. Its meta-data holds the VM's UUID as instance-id and its name as
hostname, written as JSON so a VM named 123 or yes keeps a string
hostname; its user-data is what the HTTP datasource serves, with the
creating user's SSH keys. The ISO is built with pycdlib and written
through a libvirt stream, as the NVRAM copy is, now one helper for both.

A seed needs a CD-ROM bus and a directory pool, else the create is
refused. A failed create deletes the seed with the new disk, recovery
names one left behind, and deleting the VM with its disks deletes its
seed, found in the pools by name even when ejected. A clone's drive is
left empty: the seed is the source VM's identity.
2026-10-25 19:57:15 +03:00
catborise
1c60696ea1 feat(accounts): API tokens for scripts and tools
The REST API took only the login session, so a script could not use it
without a browser. A user now makes API tokens on the profile page (API
Tokens tab): a name, and an expiry of 30, 90 or 365 days or none. The
token is shown once, on a page that is not cached, and then kept only as
a SHA-512 digest; the list shows its name, first characters, creation
and expiry, and a token is deleted there. Tools send it as
"Authorization: Token wvc_...", and act as its user, with that user's
permissions. Making and deleting a token is logged, without the token.

A password alone (HTTP Basic) still does not reach the API, as it would
skip the OTP second factor: a token is made in a session that passed the
login and its OTP. The token class comes first, so a refused request gets
401 with its challenge instead of 403.

django-rest-knox 5.1.0 provides the tokens (accounts.ApiToken adds the
name); settings.py takes "knox", KNOX_TOKEN_MODEL and REST_KNOX from the
template.
2026-10-24 23:14:27 +03:00
catborise
a80d093c94 feat(instances): a report of every VM with its addresses and OS, as an Excel file
The Administration menu gets "VM report (.xlsx)", a workbook for
superusers. The VMs sheet has one row per VM on every compute, shut-off
ones and those the panel has not registered too: name,
state, host, OS and OS family, vCPUs, memory, IP addresses and where they
came from, MAC addresses, owners, and whether the panel has registered
it. A Coverage sheet says when it was made and what could not be read.

The addresses come from the same live reading as the address search,
now shared by both. The OS is the guest agent's (libvirt's guest info)
or, without one, the libosinfo id a tool such as virt-install wrote into
the definition. Link-local IPv6 addresses are left out of the report.
Text cells stay text: a VM name starting with "=" is not a formula.

openpyxl 3.1.5 writes the workbook.
2026-10-24 22:30:07 +03:00
catborise
5c68050ce0 build: LDAP login's packages are optional
django-auth-ldap, python-ldap and pyasn1 move to
conf/requirements-ldap.txt. Installs without LDAP no longer need the
openldap and sasl headers that pip builds python-ldap with; the
installer and the README's system packages drop them, and the README's
LDAP section installs them with that file. The settings already work
without the module.

The Docker image and dev/requirements.txt keep LDAP, so CI still builds
and audits python-ldap.
2026-10-18 22:50:11 +03:00
catborise
41468c700d build: the themes ship compiled, libsass is no longer installed
A theme change compiled Bootswatch with libsass (34 MB, upstream
deprecated) into two tracked files, which updates, rollbacks and new
Docker containers then reset. The 27 themes now ship as
static/css/themes/<theme>.min.css, built by "manage.py build_themes";
the Settings page only saves the theme name and every page links that
file (zephyr when no settings are in context, as on the 500 page).

libsass moves to dev/requirements.txt; a test checks that each shipped
file is its theme compiled from dev/scss. The "Theme SASS Path" setting
is removed, and the theme default "flaty", a typo that never applied,
becomes zephyr, the theme those installs showed. The update and
rollback steps restore an older version's rewritten theme files only
where they are modified.
2026-10-18 22:36:29 +03:00
catborise
42359ca2e2 build: computes run gstfsd as a systemd unit instead of under supervisor
The compute bootstrap installs gstfsd and webvirt-gstfsd.service in one
helper and no longer installs or restarts supervisor; the README's
compute step does the same, a remote panel's bind goes into a drop-in,
and a compute moving from supervisor removes its gstfsd program.
2026-10-17 23:42:41 +03:00
catborise
837c5468ec build: systemd runs the panel on hosts instead of supervisor
The installer and the manual installation install four systemd units
(webvirt-cloud, webvirt-novncd, webvirt-socketiod, webvirt-worker) that
run as the web server's user with absolute paths and read
/etc/default/webvirtcloud. gunicorn, socketiod and the worker stop with
KillMode=mixed, so they wind down their own children. supervisor, its
installer code and the contributed systemd files go; the README gets
the update, rollback and a one-time move from supervisor, and the
installer refuses to run over a supervisor install. Docker keeps runit;
novncd's runit script is the one-line form of the others.
2026-10-17 23:18:26 +03:00
catborise
70a7155290 refactor(console): socketiod on gunicorn threads instead of eventlet
eventlet is in maintenance mode. socketiod now runs python-socketio in
threading mode under gunicorn (one gthread worker, which holds the
sessions), with simple-websocket, already a python-engineio dependency.
gunicorn waits 1 s on stop: an open console never finishes, and its
worker ends when the pty closes. The forward loop waits in select
instead of polling, and a stopped worker is reaped by a blocking
waitpid. eventlet leaves the requirements.
2026-10-17 20:36:58 +03:00
catborise
d37aa42392 feat(console): a working serial console
The xterm console never connected: socketiod expected a different token
format, its worker had no compute credentials, one global console was
shared by every client and its output broadcast to all of them, and the
Docker image did not run socketiod at all.

- console/tokens.py: one authorize() for noVNC and socketiod; the signed
  token is the only credential, checked at connect time.
- socketiod: one pty worker per client (consolecallback with the
  instance id), one session per VM, a session cap, input and resize
  checked; the parent never opens libvirt. The engine.io same-origin
  check is on.
- consolecallback: opens the console with the compute's credentials and
  exits with a message when it cannot.
- Docker runs socketiod as a runit service; nginx proxies /socket.io/
  with Host and the scheme for the origin check. Supervisor and systemd
  start it as well; WEBVIRTCLOUD_SERIAL_CONSOLE=1 enables it.
- Tests: token and session unit tests, socketiod as a process, and a
  live test that reads SeaBIOS from a wvc-test VM on the TCP and SSH
  hosts.
2026-10-17 19:43:09 +03:00
catborise
b59f30138a build: run the job worker in every install type 2026-10-14 23:27:57 +03:00
catborise
0906838193 fix(nginx): browsers revalidate static files
Static URLs carry no version, but nginx served them with expires max, so
after an update or a theme change browsers kept the old CSS and JS. They
now revalidate each load (304 when unchanged). CI checks the header, the
304 and that a changed file is sent again.
2026-10-12 08:52:41 +03:00
catborise
72681ab54e build(deps): the OTP e-mail draws its QR code with segno
django-qr-code was used for one thing: the QR code in the OTP e-mail,
as an inline SVG and a PNG. It brought pydantic, pydantic-core,
annotated-types and typing-inspection (about 10 MB) and, from 5.0 on,
Pillow. segno, which it used underneath, now draws both images with the
same options (error correction M, 4-module border, 1.8 mm SVG modules,
12 px PNG modules); the SVG, the PNG and the alt text are byte for byte
what django-qr-code 4.2 produced. account.html loaded the tag library
without using it.
2026-10-12 07:24:56 +03:00
catborise
9030b910f5 build(deps): idna, requests and urllib3 above their advisories
django-bootstrap-icons (template tags; it downloads an icon missing from
static/icon_cache) and websockify depend on requests, unpinned. On a
host that already has python3-requests, the installer's venv kept the
distro's idna, requests and urllib3 (Rocky 10: idna 3.7, urllib3
1.26.19; Ubuntu 24.04: idna 3.6, requests 2.31.0, urllib3 2.0.7), all
with advisories, and the panel loads them with its templates. Checked
in fresh Rocky 10 and Ubuntu 24.04 containers with python3-requests
installed first: all three come from the venv.
2026-10-11 22:45:52 +03:00
catborise
3be3b4b2cc fix(install): openSUSE installs get python-ldap and pyasn1 without known flaws
The installer's venv sees the system site packages, and pip keeps a
system package that meets a requirement. On openSUSE Leap 15.6 the LDAP
login thus ran on the distro's python-ldap 3.4.4 (CVE-2025-61911, a
filter-escaping bypass, and CVE-2025-61912) and pyasn1 0.5.0
(CVE-2026-30922 and others), which django-auth-ldap loads through
ldap.controls. python-ldap>=3.4.5 and pyasn1>=0.6.4 make pip install
fixed copies in the venv. python-ldap builds there, so openldap2-devel
joins the openSUSE package lists (installer, manual and development
setup) and the update steps. Checked in a fresh Leap 15.6 container
with the installer's package list: 3.4.8 and 0.6.4 from the venv,
ldap.controls imports.
2026-10-11 22:45:52 +03:00
catborise
d3f3065e30 build(deps): django-otp 1.7.4
1.7.4 no longer preselects a device in the otp_device field. The login
form chooses the user's confirmed TOTP device itself and ignores that
field, so the login is unchanged (OTP login tests pass).
2026-10-11 21:09:32 +03:00
catborise
d86ab7d881 build(deps): django-bootstrap5 26.3
26.3 drops Django 4.2 and adds Django 6.1. The forms render the same
(compared on six pages). 26.4 needs Python 3.11; the app supports 3.10.
2026-10-11 21:09:32 +03:00