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

1608 commits

Author SHA1 Message Date
catborise
ff1508b3c2 feat(instances): external snapshots use libvirt's snapshot API
Create, revert and delete are one libvirt call each. The pflash-to-rom
loader rewrite, the hand-written block commit and the s1./s2. names are
gone, so UEFI VMs can take external snapshots, a revert keeps disks added
later, and deleting an older snapshot keeps the newer one.

Create refuses what libvirt could not revert or delete later: internal
snapshots on the VM, a saved state, a running snapshot job, disks that
are not files, CD-ROM media, a branch after a revert, and memory without
a running VM or room in the first disk's pool. A disk-only revert keeps
the VM running, paused or shut off; one with memory resumes it as saved.
A revert in the same second as the previous one, which libvirt fails
after stopping the VM, is retried once.
A snapshot the old code took of a UEFI VM saved the loader as rom; its
revert is refused (deleting it still works).

The snapshots tab lists internal and external snapshots separately, can
save memory too, shows running snapshot jobs, and the revert confirmation
names the disks it detaches and that NVRAM is not restored.
2026-10-12 15:25:44 +03:00
catborise
c55f52f025 test: each app keeps its tests in a tests package
131 test modules sat next to the code they test; instances alone had
51 of them beside views.py and utils.py. They move into a
tests/ package per app, and the JavaScript checks into
webvirtcloud/tests/js. Each non-empty tests.py becomes tests/test_views.py;
the empty generated stubs are removed.

Imports of the app's own modules become absolute, paths built from
__file__ go one level up, the live helper is instances.tests.livetest,
and coverage omits */tests/*. The suite discovers the same 960 tests as
before.
2026-10-12 15:17:30 +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
d4f04cbf1e build(deps): Bootstrap and Bootswatch 5.3.8
Bootstrap SCSS, the JS bundle and the Bootswatch themes go from 5.3.3 to
5.3.8 (bug fixes only), and wvc-main.min.css is recompiled from zephyr.
Bootswatch now gives table text the body color, and Lux drops card and
modal header borders.

Tests compile every theme with the pinned libsass and check that the
shipped CSS is zephyr from these sources. The update and rollback steps
put back the two files a theme change rewrites, or git refuses to pull
over them.
2026-10-12 08:44:31 +03:00
catborise
c23521bcf4 fix(appsettings): a theme change reaches the CSS nginx serves
nginx serves STATIC_ROOT (staticfiles/) since collectstatic moved there,
but the theme view wrote only static/css/wvc-main.min.css, so a new theme
showed up only after the next collectstatic. The view now also writes the
collected copy when collectstatic has run; the source copy stays so a
later collectstatic keeps the theme.
2026-10-12 08:36:43 +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
99b9f35826 build(deps): drop linters that nothing runs
pycodestyle, pyflakes, pylint, yapf and black were only installed: CI,
pyproject.toml and the docs use ruff, which covers the pyflakes and
pycodestyle checks.
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
d706d58de4 build(deps): dev tools
coverage 7.16.2, pyflakes 4.0.3, pylint 4.1.3, black 26.10.1, ruff 0.17.0
(no new findings) and django-debug-toolbar 8.0.0; the template profiler
panel is pinned at 2.1.0. Toolbar 8 renders in a shadow DOM: the
profiler panel still opens and shows its data (checked in a browser).
Its script is requested by a relative URL and gets 404 with 6.3.0 too.
2026-10-11 21:09:32 +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
catborise
df4b1f4da6 build(deps): djangorestframework 3.18.3
3.18.2 hardens content negotiation against large Accept headers
(GHSA-33wh-fxxf-88vv) and refuses non-text charsets
(GHSA-3547-9m27-7rxg); the API reads both. 3.18 drops Django < 5.2 and
reports list serializer errors as a dict; the API's many=True
serializers only render output.
2026-10-11 21:09:32 +03:00
catborise
af0f67f712 build(deps): Django 5.2.18
Security release of 2026-10-06. Two of its fixes reach this app: a
quadratic HTTP header parsing DoS reachable without a login
(CVE-2026-84429) and long language codes through LocaleMiddleware
(CVE-2026-77050). The GeoDjango and model formset fixes do not apply.
2026-10-11 20:34:09 +03:00
catborise
87ffed66b9 fix(instances): attach a disk, NIC or guest agent to both definitions in one call
On an active VM a device was attached in two calls, running VM first.
When the persistent attach was refused, for example because a disk target
or an agent channel was already pending there, the device stayed in the
running VM only and was gone at the next start. A NIC added without a
MAC got a different generated MAC in each definition. One call with
AFFECT_LIVE|AFFECT_CONFIG lets QEMU check the persistent copy before the
hotplug and keeps one MAC. Detach and update still go per definition,
each from its own XML.
2026-10-11 20:12:24 +03:00
catborise
e924f3e7a0 fix(instances): a new disk on a running VM does not take a pending disk's name
The device name of a new disk or CD-ROM was chosen from the running VM
only. A name used only in the persistent definition, such as a disk
renamed by a pending bus change, could be chosen again: the disk was
attached to the running VM, the persistent attach was refused, and the
disk was gone after the next start. Names either definition uses are now
taken.
2026-10-11 17:58:19 +03:00
catborise
c1f6620c4b feat(instances): a running VM's settings tab says most settings apply at the next start
On an active VM most edits in the settings tab change the persistent
definition only. The tab now says so above its panes.
2026-10-11 17:47:44 +03:00
catborise
d0bd2ac32b fix(instances): a running VM's boot order, video and console forms show the pending settings
On an active VM the boot order editor, the video model select and the
console form showed the running VM: a pending boot order, video model or
console setting looked unset, and the boot order editor offered devices
of the running VM only. Choosing one of those cleared the pending order,
since the setter dropped the old order and skipped devices it could not
find. They now read the persistent definition (get_bootmenu,
get_bootorder, get_media_devices, get_video_model and the console
getters take config=), and set_bootorder refuses a device that is not in
it before it changes anything.
2026-10-11 14:56:51 +03:00
catborise
042530fbfd fix(instances): a running VM's options and CPU edits keep the pending changes
On an active VM the options and CPU forms write the persistent definition
but showed the running VM's title, description and vCPUs, so saving one
posted the live values back over a pending change. They now read the
persistent definition (get_vcpu, get_cur_vcpu, get_title and
get_description take config=).

Buttons in the settings and resize tabs that were disabled only by class
get the disabled attribute: Enter in a field no longer submits a form
the page shows as unavailable while the VM runs.
2026-10-11 14:09:23 +03:00
catborise
ea9424ee1f fix(instances): a running VM's QoS edit keeps the pending QoS changes
The QoS table showed the running VM's bandwidth while an edit changes the
definition for the next start. Each row posts average, peak and burst,
so an edit wrote the running values back over a pending change, and a
direction added while the VM ran had no row. The table shows the
persistent definition now. An edit also kept only the posted values and
dropped an inbound floor; it now updates them and keeps the rest.
2026-10-11 10:53:16 +03:00
catborise
8d2c9b1217 fix(instances): a running VM's disk edit keeps the other pending disk changes
On a running VM a disk edit changes the definition for the next start,
but the disk list and its edit form showed the running VM's disks. The
form posts every field, so an edit wrote the running values back over
earlier pending changes (cache, io, serial, ...), and a disk only in the
next definition could not be edited. The list now shows the persistent
disks. A volume is deleted only when both definitions use it at that
device, since the list may now show another source than the running VM.
2026-10-10 22:53:43 +03:00
catborise
0bb4337043 fix(interfaces): no create, start, stop or delete where libvirt cannot change interfaces
libvirt's udev interface backend (RHEL 9 and later, Debian 12) only reads
interfaces, so the create form and the start, stop and delete buttons
only failed there. The pages ask the host with an invalid definition,
which defines nothing: a backend without define refuses it as not
supported. There they show the interface state and a note instead, and
posts change nothing; the host's devices, needed only by the create
form, are not read. Any other answer keeps the pages as they were.
2026-10-10 21:38:22 +03:00
catborise
35fea424ee test: read the page scripts with lxml, not a tag regex
CodeQL flags the <script> regex (py/bad-tag-filter); the HTML parser finds the inline scripts as the new-VM dialog test does.
2026-10-10 21:04:38 +03:00
catborise
a57290cdf2 fix(instances): the new-VM dialog shows each host's current CPU load
The CPU bar showed the average since the host booted, read for every
compute on every load of the VM list. The dialog now reads each compute's
graph endpoint (load over one second) when it opens, two computes at a
time so the reads hold at most two workers, and ignores answers of an
earlier opening. A host that cannot be read shows a dash. The since-boot
reading had no other caller and is removed.
2026-10-10 20:54:19 +03:00
catborise
c1d2e9610d fix(instances): enabling vCPU hot plug keeps the current vCPU count
Enabling hot plug on a stopped VM wrote every vCPU up to the maximum as
enabled, so a VM with 2 of 4 vCPUs booted with 4. The current vCPUs stay
enabled now; libvirt adds the rest as disabled hotpluggable vCPUs.
2026-10-10 10:03:25 +03:00
catborise
251ddbbec1 fix(instances): a paused VM's memory is resized as a running VM's
Only a running VM counted as active, so a paused one (or one blocked,
PM-suspended or shutting down) had its maximum and current memory written
for the next boot and nothing applied live. Every active VM now has its
current memory changed live and in its definition and keeps its maximum;
a paused guest takes the new size once resumed. The quota check follows
the same rule. Checked on a real guest: paused, shrunk, resumed, then
grown back.
2026-10-10 09:52:56 +03:00
catborise
7b5c2cd057 perf(storages): ISO chunks to an SSH compute reuse the SSH connection
Each 32 MiB chunk is its own request and opened a new SSH connection and
SFTP session (~200 ms). A worker now keeps its connection to the host for
the next chunk; each request still opens its own SFTP session (~50 ms),
so an SFTP error never reaches the next one. A connection is not reused
after an error, when the host dropped it or once it is 10 minutes old; an
idle one is closed after a minute, and a forked worker leaves the parent's
to the parent. The SFTP start, which paramiko waits for without a deadline,
is ended after 10 s. Live: 256 MiB in 8 chunks 4.5-4.9 s -> 3.7 s.
2026-10-09 23:22:15 +03:00
catborise
ff2f16b226 style(tests): drop an unused import from the device edit tests
CI's Ruff job failed on it.
2026-10-09 22:25:13 +03:00
catborise
4ea002f5a3 perf(interfaces): list only the host's network devices
The interfaces page read the XML of every node device (183 on a test host)
to keep the network ones; libvirt now lists only those. Same names on both
test hosts, 756 -> 261 ms over TCP and 475 -> 173 ms over SSH.
2026-10-09 22:17:00 +03:00
catborise
f1e83455e4 refactor(instances): one way to reach a VM, its console and its devices
The .vv console file is built from the VM found by its UUID, with the
console page's getters, instead of looking the VM up by name five times;
a console without a listen address or password now gives 127.0.0.1 and
an empty password instead of "None". Removed methods nothing calls: the
by-name getters and power methods of wvmInstances, managed save, the vCPU
mode getter (it read the wrong attribute), the old NIC editor and its
bridge lookup. CD-ROM media and NIC hotplug use _definitions() like the
other device edits, the disk editor takes the bus prefix from
util.vol_dev_type, and an unreadable disk is logged, not printed.
2026-10-09 18:13:32 +03:00
catborise
0ea5f0ff18 fix(instances): a UEFI clone gets its own NVRAM without a new pool
Cloning a UEFI VM defined a persistent storage pool on the NVRAM's
directory when no pool held it, and dropped <nvram> from the clone,
which found its copy only because libvirt chose the same file name.
The copy is now made through the NVRAM directory's pool (a transient one
when there is none) or the pool that holds the file, with the original's
format, owner and mode, and the clone's definition names it. A failed
definition removes it with the copied disks. An NVRAM that is not a file
is refused before anything is copied.
2026-10-09 18:13:32 +03:00
catborise
11fe2409f9 fix(instances): device edits fit every definition and device type
A volume of an LVM or iSCSI pool can be attached: by its own path, with
the format its volume reports (raw when the pool knows none). Detaching
a disk takes each definition's own description of it, so a disk only in
the persistent definition goes too. Every NIC type can be a boot device;
bridge and direct NICs were dropped from the boot order. The video model
is a value, never parsed as XML. QoS changes clear the page's read cache.

The NIC model "default" leaves the choice to libvirt instead of writing
model "default", and rt18139 is rtl8139: a VM with either did not start
(checked on a RHEL 10 host). A migration corrects the default NIC type
setting.
2026-10-09 18:13:23 +03:00
catborise
769a0dc96f fix(instances): the VM page shows each device's own settings
A NIC without QoS showed the previous NIC's limits, an empty CD-ROM the
previous drive's pool; a NIC without a source or model broke the page.
The boot order shows the target of block and network disks and of bridge
and direct NICs too. Refreshing the VM's pools before a snapshot or a
delete no longer fails on a disk outside every pool.
2026-10-09 18:13:07 +03:00
catborise
57f592df70 perf(computes): the host's info is read once per request
The overview read the host's info four times, now once (12 libvirt calls).
A connection object keeps it next to the capabilities, and the VM page and
VM stats take it from there; the VM page's cached_reads block holds only
the VM's own data. Free memory is still read each time.
2026-10-09 15:52:11 +03:00
catborise
9adc808d26 perf(computes): the create page reads the host's capabilities once
Measured on the create page: 751 ms and 72 libvirt calls, now 123 ms and
13. Its getters parsed the host's capabilities some 60 times, each read
from libvirt. A connection object (one per request) now reads them once,
and the domain capabilities once per emulator, arch, machine and domain
type. The VM page's cached_reads block no longer needs its own copy.
2026-10-09 15:24:03 +03:00
catborise
a87fbf739f perf(storages): an ISO uploads to an SSH compute without waiting for each write
Each 32 MiB chunk went out as 1024 SFTP writes, each waiting for its
answer: 19 MiB/s on a gigabit LAN, ~1.9 s per chunk with the SSH session.
The writes now go out without waiting and their answers are read as they
come and at the end: ~0.53 s per chunk, the same file (checked by sha256).

paramiko's own pipelined writes (set_pipelined) drop the errors in their
answers, so a full disk would leave a short ISO behind a successful
upload. Each write is registered to its file instead, the way paramiko
does for prefetched reads: every answer, in any order, reaches the file
and an error is raised as soon as one is read.
2026-10-09 13:35:02 +03:00
catborise
24231813d7 perf(instances): the VM stats no longer wait a second per poll
Each stats answer carries a signed sample of the counters, bound to the VM,
its compute and UUID. The next poll sends it back and the rates are the
averages since then; a sample from another run of the VM (domain ID),
another host CPU count or device list, or older than 30 s falls back to
one second as before. Windows are measured on the monotonic clock.

Now that a poll is cheap, the Stats tab polls every 5 s like the host
graph, the first time as soon as it opens (pollWhileVisible takes the
delay of the first request).

A counter the host reports as unsupported (-1) or that went back gives no
value instead of 0 or a jump; a NIC without a target keeps its chart, a
pool volume disk is measured, and a disk or NIC attached after the page
loaded no longer stops the other charts. Disk rates are MB/s (they were
MiB/s labelled Mb/s), network Mbit/s; the CPU tooltip names the window.
2026-10-09 11:59:41 +03:00
catborise
27f4845086 perf(computes): the host CPU graph no longer waits a second per poll
Each poll of a host's overview graph held a worker for one second between
two CPU samples. The answer now carries its sample, signed with the time
and the compute; the next poll sends it back and the usage is taken since
then, so a point is the average since the previous one (normally 5 s, at
most 15 s). Without a sample, or with one that is altered, older or from
another host, or when the host restarted, it is measured over one second
as before.

A sample is one reading; the busy share was mixed from separate readings,
counted guest time twice (it is part of user) and added half a percent.
A host that cannot be read leaves a gap in both graphs instead of a zero
CPU point and a broken memory axis.
2026-10-09 09:53:12 +03:00
catborise
439f24041e perf(instances): the VM page reads each disk, the state and host info once
Measured on a VM with two disks, a CD-ROM and a NIC: 70 libvirt calls per
page, now 43. Within the page's cached_reads block the VM's state, the
host's info and domain capabilities, and each disk path's volume are read
once; the running and the persistent disk lists share a path's volume.
The ISO list takes the active pools in one call instead of looking each up
by name; it still refreshes them, so an image copied in by hand shows up.

The opt-in DRBD status waits at most 10 s for the compute's ssh, then
leaves the status unknown instead of holding the page.
2026-10-09 08:36:04 +03:00
catborise
95b4418c96 fix(instances): a VM without a guest agent shows its ARP address
The ARP lookup passed 3 as the address source; libvirt's
VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP is 2 and libvirt refused 3 as an
unknown source. The error was suppressed, so a VM without a guest agent
never showed an address.
2026-10-09 08:36:00 +03:00
catborise
f094982737 test(destroy): NVRAM deletion and the base of an overlay
Destroy deletes the NVRAM file only when asked (VIR_DOMAIN_UNDEFINE_NVRAM,
otherwise KEEP_NVRAM): a unit test checks the flags, live tests check the
file on the host both ways. A VM whose disk is an overlay with a snapshot
goes with its own image and the snapshot metadata, and the base image
stays, since other images may grow from it.
2026-10-09 07:29:10 +03:00
catborise
975b7f0baf fix(migration): remove the NVRAM file a live migration leaves on the source
libvirt undefines the source domain after a migration but keeps its NVRAM
file. A VM created later on that host with the same name reuses the file
and starts with the old VM's UEFI variables (boot entries, enrolled keys):
checked on libvirt 11.10, where only VIR_DOMAIN_START_RESET_NVRAM
recreates it from the template.

After a successful migration the file is now deleted on the source, as a
volume of the pool on libvirt's NVRAM directory or of a transient one.
Only a file in /var/lib/libvirt/qemu/nvram, and only when the destination
cannot create a marker file the source just created there (an exclusive
create also fails on shared storage whose listing an NFS client caches).
A failed removal is logged with the migration, which stands and is
recorded.
2026-10-09 07:29:10 +03:00
catborise
e14a4cc10d feat(instances): grow a disk while the VM runs or is paused
The resize tab enabled disk growth only for a shut-off VM, although
resize_disk already grows a running domain's disk through blockResize.
The button is now enabled for running and paused VMs too. A live test
grows qcow2 and raw disks of a running and of a paused VM and checks the
size the guest sees, the volume size, the unchanged persistent definition
and the state.
2026-10-09 07:29:01 +03:00
catborise
342f6a7135 fix(interfaces): a bridge over a bond or vlan keeps the device's settings
A new bridge always declared its member as type='ethernet'. On a host
with the netcf backend that rewrote the member's configuration: a bond
lost its BONDING_OPTS and a vlan its VLAN=yes. The member is now taken
from the host's own definition of the device (a bond keeps its mode and
devices, a vlan its tag), without the start mode, MTU and addresses that
only a top-level interface has; a device the host does not define stays
ethernet.
2026-10-08 23:23:22 +03:00
catborise
6ca823725c fix(console): socketiod recovers when the console process ends
When the serial console process exited on its own (a bad token, an
unreachable host, a killed consolecallback), the read task died on EIO
while fd and child_pid stayed set: the child was a zombie and every later
connection wrote to the dead pty until socketiod was restarted. A child
that failed before exec also returned into the server code and sent its
traceback to the client.

Now the read loop treats EOF/EIO as the end: it reaps the child, closes
the pty, tells the clients "[console closed]" and disconnects them, so
the next connection starts a new console. The child always leaves with
os._exit, also without a token; output is decoded incrementally, and the
unused imports are gone.
2026-10-08 22:43:24 +03:00
catborise
4ae3fb2d53 feat(admin): list and remove the owners kept for vanished VMs
A VM that disappears from its compute keeps its owners in a tombstone
until INSTANCE_OWNERSHIP_RETENTION_DAYS pass, but nothing showed them.
Superusers now see them on the "Removed VMs" admin page (name, UUID,
owners, removed, expires) and can remove one, so a VM deleted on
purpose no longer hands its owners to a later VM with the same UUID.
2026-10-08 19:07:02 +03:00
catborise
71db69b294 fix(bootstrap): no "integer expression expected" on every step
echodebug compared _ECHO_DEBUG and BS_TRUE with -eq, but neither is set
anywhere, so each step printed "[: : integer expression expected". It
now prints debug lines only when _ECHO_DEBUG=1 is set.
2026-10-08 17:14:10 +03:00
catborise
8dfe4d7713 fix(gstfsd): a key added to an SELinux guest gets the guest's labels
Files that libguestfs creates have no SELinux label. On an enforcing
guest (RHEL, Rocky, Alma, Fedora) sshd may not read the unlabeled
/root/.ssh/authorized_keys, so a key added from the panel did not let
anyone log in ("avc: denied { read } ... unlabeled_t").

After writing a key, gstfsd now relabels /root/.ssh with the guest's own
file contexts (/etc/selinux/<SELINUXTYPE>/contexts/files/file_contexts),
as virt-customize does. Guests without an SELinux policy or with SELinux
disabled are left as they are. If the guest needs labels but the host's
libguestfs cannot set them, the request fails before anything is written.
A root password needs no labelling: /etc/shadow keeps its file and label.
2026-10-08 16:19:24 +03:00
catborise
6961ff2280 docs: SSH key setup that names the panel user per platform
The host commands hard-coded www-data, which does not exist on RHEL and
openSUSE (the panel runs as nginx there), so the first command failed
with "invalid user". The user is now set once in U and its home is read
with getent, since ~$U is not expanded. The host block also ends with
the same connection check as the Docker one.

Both blocks are labelled, and the step says to use only the one for the
installation type: a key set up on the host is not seen by the panel in
Docker.

AGENTS.md is a local agent file and is ignored.
2026-10-08 13:26:07 +03:00
catborise
bed4903ac6 docs: SSH key setup commands that work in the Docker container
The README gave host commands (sudo, ~www-data) and one sentence for
Docker, where there is no sudo and exec runs as root: a key made that way
lands in /root/.ssh, which the panel (www-data) does not use, and the
first connection fails with "Host key verification failed". Docker now
has its own commands, run as www-data, ending with a connection check.
Both variants accept a new host key once (accept-new) instead of
disabling host key checking.
2026-10-08 10:56:35 +03:00