From a0657d768f9be3d2aae3fd92099949ebe2b6f585 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 20 Oct 2026 08:03:36 -0400 Subject: [PATCH 1/6] Add a recommends rsync Some functionality requires rsync. However some want the flexibility in skipping it. Split the difference with a recommends --- confluent_server/confluent_server.spec.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index dc87e8e8..8bf0a705 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -17,6 +17,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch Requires: confluent_vtbufferd +Recommends: rsync %if 0%{?python3_version_nodots} >= 313 # 3.13 removed crypt from the stdlib, and stored credentials are hashed with # it. Recommends rather than Requires: some 3.13 distros package no candidate From 337dc6c6380c80be2d174169b57ddefebfde5654 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 20 Oct 2026 13:39:56 -0400 Subject: [PATCH 2/6] Adjust debian recommends --- confluent_server/builddeb | 1 + 1 file changed, 1 insertion(+) diff --git a/confluent_server/builddeb b/confluent_server/builddeb index 69d7ee01..9b06d6de 100755 --- a/confluent_server/builddeb +++ b/confluent_server/builddeb @@ -39,6 +39,7 @@ if [ "$OPKGNAME" = "confluent-server" ]; then else sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-cryptography, python3-websocket, python3-msgpack, python3-aiohttp, python3-pyparsing, python3-asyncssh, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil, python3-crypt-r, python3-numpy, python3-pillow/' debian/control fi + sed -i '/^Depends:/a Recommends: rsync' debian/control echo 'confluent_client confluent-client' >> debian/pydist-overrides fi From 37f6a0b061d5738c05a534d982a0ebc8fa390157 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 20 Oct 2026 15:21:46 -0400 Subject: [PATCH 3/6] Slide unrecognized create requests to 'update' in proxmox --- .../confluent/plugins/hardwaremanagement/proxmox.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/confluent_server/confluent/plugins/hardwaremanagement/proxmox.py b/confluent_server/confluent/plugins/hardwaremanagement/proxmox.py index 03bd1149..fe62ab1a 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/proxmox.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/proxmox.py @@ -708,6 +708,10 @@ async def create(nodes, element, configmanager, inputdata): return yield msg.ChildCollection(url) return + elif '_console' not in element: + async for rsp in update([node], element, configmanager, inputdata): + yield rsp + return serialdata = await clientsbynode[node].get_vm_serial(node) yield PmxConsole(serialdata, node, configmanager, clientsbynode[node]) return From 91174e4a5c1a1e2fb972c950cb0ef8cc840c9790 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 20 Oct 2026 15:42:10 -0400 Subject: [PATCH 4/6] Fix getaddrinfo call in redirect calls --- confluent_server/confluent/httpapi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 9ecb4395..4852e678 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -855,7 +855,9 @@ async def resourcehandler_backend(req, make_response): if default: try: loop = asyncio.get_running_loop() - ip_info = await loop.getaddrinfo(targip, 0, 0, socket.SOCK_STREAM) + ip_info = await loop.getaddrinfo( + targip, 0, family=socket.AF_UNSPEC, + type=socket.SOCK_STREAM) except socket.gaierror: rsp = await make_response('text/plain', 404) await rsp.write(b'hardwaremanagement.manager definition could not be resolved') From 1777c80969bfb1bb46c863cb1fd170898791a0ce Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 21 Oct 2026 08:54:51 -0400 Subject: [PATCH 5/6] Add retry to image fetch --- .../el10-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../el7-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../el8-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../el9-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../suse15-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../suse16-diskless/profiles/default/scripts/imageboot.sh | 5 ++++- .../profiles/default/scripts/imageboot.sh | 5 ++++- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/confluent_osdeploy/el10-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/el10-diskless/profiles/default/scripts/imageboot.sh index aeb6f601..208a4e4c 100644 --- a/confluent_osdeploy/el10-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/el10-diskless/profiles/default/scripts/imageboot.sh @@ -7,7 +7,10 @@ mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot set_selinux_context system_u:object_r:bin_t:s0 /opt/confluent/bin/urlmount if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs" /opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg diff --git a/confluent_osdeploy/el7-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/el7-diskless/profiles/default/scripts/imageboot.sh index 406dc01b..ff254d2c 100644 --- a/confluent_osdeploy/el7-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/el7-diskless/profiles/default/scripts/imageboot.sh @@ -2,7 +2,10 @@ mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs" modprobe fuse diff --git a/confluent_osdeploy/el8-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/el8-diskless/profiles/default/scripts/imageboot.sh index ac9dd67d..322cd988 100644 --- a/confluent_osdeploy/el8-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/el8-diskless/profiles/default/scripts/imageboot.sh @@ -7,7 +7,10 @@ mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay TETHERED=0 if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else TETHERED=1 confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs" diff --git a/confluent_osdeploy/el9-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/el9-diskless/profiles/default/scripts/imageboot.sh index cac45184..7ca1f1e9 100644 --- a/confluent_osdeploy/el9-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/el9-diskless/profiles/default/scripts/imageboot.sh @@ -6,7 +6,10 @@ fi mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs" /opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg diff --git a/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh index 558510e3..79fc55e5 100644 --- a/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/suse15-diskless/profiles/default/scripts/imageboot.sh @@ -2,7 +2,10 @@ mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs" /opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg diff --git a/confluent_osdeploy/suse16-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/suse16-diskless/profiles/default/scripts/imageboot.sh index 876ecef9..7add1718 100644 --- a/confluent_osdeploy/suse16-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/suse16-diskless/profiles/default/scripts/imageboot.sh @@ -2,7 +2,10 @@ mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done else confluent_urls="$confluent_urls https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs" /opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg diff --git a/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/imageboot.sh b/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/imageboot.sh index e0e03879..b09a9e9b 100644 --- a/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/imageboot.sh +++ b/confluent_osdeploy/ubuntu20.04-diskless/profiles/default/scripts/imageboot.sh @@ -23,7 +23,10 @@ TETHERED=1 if grep -q confluent_imagemethod=untethered /proc/cmdline || grep -q confluent_imagemethod=uncompressed /proc/cmdline; then TETHERED=0 mount -t tmpfs untethered /mnt/remoteimg - curl https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs + while ! curl -f https://$confluent_mgr/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs; do + echo "Failed downloading image, retrying..." + sleep $((RANDOM%3)).$((RANDOM)) + done elif [ -n "$rootimg" ]; then ln -s "$rootimg" /mnt/remoteimg/rootimg.sfs else From 220da3987762abbcbb8005bdbdc92a0cd288f24a Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 21 Oct 2026 13:29:49 -0400 Subject: [PATCH 6/6] Request cursor encoding OpenBMC iKVM injects nonsensical cursor without it. For now store the cursor, but currently not used. It's a rare payload so not worth skipping the processing. --- confluent_client/confluent/vnc.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/confluent_client/confluent/vnc.py b/confluent_client/confluent/vnc.py index 360e1ebe..44c0f5d5 100644 --- a/confluent_client/confluent/vnc.py +++ b/confluent_client/confluent/vnc.py @@ -202,9 +202,10 @@ class VNCClient: self.receiver = asyncio.create_task(self._receive_loop()) payload.add_number(2, 1) # Set encodings payload.add_number(0, 1) # Padding - payload.add_number(4, 2) # Number of encodings + payload.add_number(5, 2) # Number of encodings payload.add_number(6, 4) # zlib payload.add_number(7, 4) # tight + payload.add_number(-239, 4) # cursor payload.add_number(-223, 4) # desktopsize payload.add_number(-308, 4) # extended desktopsize payload.flush(self.writer) @@ -339,8 +340,22 @@ class VNCClient: img = Image.open(jpgimg) img.load() self.framebuffer.paste(img, (x, y)) + elif encoding_type == -239: # cursor + cursor_pixels = await self.reader.readexactly(width * height * 4) + mask_row_bytes = (width + 7) // 8 + cursor_mask = await self.reader.readexactly(mask_row_bytes * height) + pixels = np.frombuffer(cursor_pixels, dtype=np.uint8).reshape( + (height, width, 4)).copy() + if width and height: + mask = np.unpackbits( + np.frombuffer(cursor_mask, dtype=np.uint8).reshape( + (height, mask_row_bytes)), + axis=1, bitorder='big')[:, :width] + pixels[:, :, 3] = mask * 0xff + self.cursor = Image.fromarray(pixels, 'RGBA') + self.cursor_hotspot = (x, y) else: - raise Exception(f'Unsupported encoding type: {encoding_type}') + raise Exception(f'Unsupported encoding type: {encoding_type}, x: {x}, y: {y}, width: {width}, height: {height}') async def _read_tight_length(self): length = 0