Merge branch '2.2' into 3.1

# Conflicts:
#	CHANGELOG
#	conf/gns3_server.conf
#	gns3server/compute/docker/__init__.py
#	gns3server/compute/docker/docker_vm.py
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/import_project.py
#	gns3server/crash_report.py
#	gns3server/version.py
This commit is contained in:
grossmj 2026-07-21 17:03:00 +02:00
commit 7487ec14e0
No known key found for this signature in database
GPG Key ID: 1E7DD6DBB53FF3D7
4 changed files with 49 additions and 13 deletions

View File

@ -1,5 +1,19 @@
# Change Log
## 2.2.60 15/07/2026
* Sync appliances
* Only set IOU images to be executable when importing images
* fix(import): project import does not move images symlinks into place
* Search for system UEFI that is compatible with Python < 3.12
* Add OVMF firmware directory configuration
* Automatically add a Random Number Generator (RNG) device when using uefi option is enabled
* fix(docker): handle container name conflict automatically
* fix: gns3-server crashes on startup if "Open this project in the background" is active but there is a problem with that project
* Handle HTTPNotFound exception when retrieving compute status
* Fix: Check compute connectivity before open() during project deletion
* API endpoints to manage base configuration files for templates
## 3.1.0a4 09/07/2026
* Bundle web-ui v3.1.0a4

View File

@ -14,7 +14,7 @@
"symbol": "linux_guest.svg",
"docker": {
"adapters": 1,
"image": "gns3/ubuntu:noble",
"image": "gns3/ubuntu:resolute",
"console_type": "telnet"
}
}

View File

@ -34,7 +34,8 @@ import json
import shlex
import psutil
from gns3server.utils import parse_version
from pathlib import Path
from gns3server.utils import parse_version, shlex_quote
from gns3server.utils.asyncio import subprocess_check_output, cancellable_wait_run_in_executor
from .qemu_error import QemuError
from .utils.qcow2 import Qcow2, Qcow2Error
@ -2292,15 +2293,23 @@ class QemuVM(BaseNode):
options.extend(["-bios", self._bios_image.replace(",", ",,")])
elif self._uefi:
ovmf_firmware_dir = self._manager.config.get_section_config("Qemu").get("ovmf_firmware_dir", "/usr/share/OVMF")
system_ovmf_firmware_dir = Path(ovmf_firmware_dir)
log.info("Using OVMF firmware directory: {}".format(system_ovmf_firmware_dir))
old_ovmf_vars_path = os.path.join(self.working_dir, "OVMF_VARS.fd")
if os.path.exists(old_ovmf_vars_path):
# the node has its own UEFI variables store already, we must also use the old UEFI firmware
ovmf_firmware_path = self.manager.get_abs_image_path("OVMF_CODE.fd")
else:
system_ovmf_firmware_path = "/usr/share/OVMF/OVMF_CODE_4M.fd"
if os.path.exists(system_ovmf_firmware_path):
ovmf_firmware_path = system_ovmf_firmware_path
# Use a manual case-insensitive search instead
try:
system_ovmf_firmware_path = next((f for f in system_ovmf_firmware_dir.glob("*.fd")
if f.name.lower() == "ovmf_code_4m.fd"), None)
except (FileNotFoundError, StopIteration):
system_ovmf_firmware_path = None
if system_ovmf_firmware_path:
ovmf_firmware_path = str(system_ovmf_firmware_path)
else:
# otherwise, get the UEFI firmware from the images directory
ovmf_firmware_path = self.manager.get_abs_image_path("OVMF_CODE_4M.fd")
@ -2309,9 +2318,13 @@ class QemuVM(BaseNode):
options.extend(["-drive", "if=pflash,format=raw,readonly,file={}".format(ovmf_firmware_path)])
# try to use the UEFI variables store from the system first
system_ovmf_vars_path = "/usr/share/OVMF/OVMF_VARS_4M.fd"
if os.path.exists(system_ovmf_vars_path):
ovmf_vars_path = system_ovmf_vars_path
try:
system_ovmf_vars_path = next((f for f in system_ovmf_firmware_dir.glob("*.fd")
if f.name.lower() == "ovmf_vars_4m.fd"), None)
except (FileNotFoundError, StopIteration):
system_ovmf_vars_path = None
if system_ovmf_vars_path:
ovmf_vars_path = str(system_ovmf_vars_path)
else:
# otherwise, get the UEFI variables store from the images directory
ovmf_vars_path = self.manager.get_abs_image_path("OVMF_VARS_4M.fd")
@ -2327,6 +2340,10 @@ class QemuVM(BaseNode):
except OSError as e:
raise QemuError("Cannot copy OVMF_VARS_4M.fd file to the node working directory: {}".format(e))
options.extend(["-drive", "if=pflash,format=raw,file={}".format(ovmf_vars_node_path)])
# edk2 firmware requires a Random Number Generator (RNG) device in order to turn network adapters on
options.extend(["-object", "rng-random,filename=/dev/urandom,id=rng0 "])
options.extend(["-device", "virtio-rng-pci,rng=rng0"])
return options
def _linux_boot_options(self):

View File

@ -295,14 +295,19 @@ async def _import_images(controller, images_path):
for (dirpath, dirnames, filenames) in os.walk(root, followlinks=False):
for filename in filenames:
path = os.path.join(dirpath, filename)
if os.path.islink(path):
continue
dst = os.path.join(image_dir, os.path.relpath(path, root))
os.makedirs(os.path.dirname(dst), exist_ok=True)
if not os.path.exists(dst):
await wait_run_in_executor(shutil.move, path, dst)
os.chmod(dst, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
try:
with open(dst, "rb") as f:
# read the first 7 bytes of the file.
elf_header_start = f.read(7)
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian and have an ELF version of 1
if elf_header_start == b'\x7fELF\x01\x01\x01' or elf_header_start == b'\x7fELF\x02\x01\x01':
os.chmod(dst, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
except OSError as e:
continue
async def update_snapshots(snapshots_dir, project_path, project_name, project_id, reset_mac_addresses=True):
"""