From e94f0e47d742f3c8f228bf21907954008ade7ba0 Mon Sep 17 00:00:00 2001 From: Christian Breunig Date: Tue, 1 Sep 2026 09:39:03 +0200 Subject: image: T9269: skip GRUB updates on container and diskless systems Kernel cmdline and serial console updates rewrite the GRUB configuration on the persistence partition. A container has no GRUB at all, and on a system where the persistence partition is not mounted disk.find_persistence() returns an empty string, so the code reads '//boot/grub/grub.cfg' and dies with FileNotFoundError. Extend is_running_as_container() to also honor /run/.containerenv (Podman) and the container= variable systemd sets for PID 1, and add if_not_container() plus if_persistence() decorators guarding all grub_util entry points. --- python/vyos/system/grub_util.py | 8 ++++++++ python/vyos/system/image.py | 45 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/system/grub_util.py b/python/vyos/system/grub_util.py index edaea8ad3..98d3c1ddc 100644 --- a/python/vyos/system/grub_util.py +++ b/python/vyos/system/grub_util.py @@ -18,6 +18,8 @@ from vyos.system import grub from vyos.system import image from vyos.system import compat +@image.if_not_container +@image.if_persistence @compat.grub_cfg_update def set_serial_console(console_type: str, console_num: str, console_speed: str, root_dir: str = '') -> None: @@ -34,6 +36,8 @@ def set_serial_console(console_type: str, console_num: str, grub.set_serial_console(console_type, console_num, console_speed, root_dir) @image.if_not_live_boot +@image.if_not_container +@image.if_persistence def update_serial_console(console_type: str, console_num: str, console_speed: str, root_dir: str = '') -> None: """Update console_speed if different from current value""" @@ -53,6 +57,8 @@ def update_serial_console(console_type: str, console_num: str, console_speed != console_speed_current: set_serial_console(console_type, console_num, console_speed, root_dir) +@image.if_not_container +@image.if_persistence @compat.grub_cfg_update def set_kernel_cmdline_options(cmdline_options: str, version: str = '', root_dir: str = '') -> None: @@ -66,6 +72,8 @@ def set_kernel_cmdline_options(cmdline_options: str, version: str = '', grub.set_kernel_cmdline_options(cmdline_options, version, root_dir) @image.if_not_live_boot +@image.if_not_container +@image.if_persistence def update_kernel_cmdline_options(cmdline_options: str, root_dir: str = '', version = image.get_running_image()) -> None: diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py index 02f2303b2..0b2eccf6d 100644 --- a/python/vyos/system/image.py +++ b/python/vyos/system/image.py @@ -283,6 +283,49 @@ def if_not_live_boot(func): return wrapper def is_running_as_container() -> bool: - if Path('/.dockerenv').exists(): + """Detect if the system is running inside a container + + Returns: + bool: True if running as container (Docker, Podman, LXC, ...) + """ + # Docker and Podman drop a marker file into the container root + if Path('/.dockerenv').exists() or Path('/run/.containerenv').exists(): return True + # systemd sets container= in the environment of PID 1 for all container + # types it knows about - this is only readable by root + try: + environ = Path('/proc/1/environ').read_bytes().decode(errors='ignore') + if 'container=' in environ: + return True + except (OSError, PermissionError): + pass return False + +def has_persistence() -> bool: + """Detect if a persistence storage partition is mounted + + Returns: + bool: True if the persistence partition is available + """ + return bool(disk.find_persistence()) + +def if_persistence(func): + """Decorator to call function only if persistence storage is available. + Without it there is no writeable GRUB configuration to operate on""" + @wraps(func) + def wrapper(*args, **kwargs): + if has_persistence(): + ret = func(*args, **kwargs) + return ret + return None + return wrapper + +def if_not_container(func): + """Decorator to call function only if not running inside a container""" + @wraps(func) + def wrapper(*args, **kwargs): + if not is_running_as_container(): + ret = func(*args, **kwargs) + return ret + return None + return wrapper -- cgit v1.2.3