]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
config / qemu: add Console=headless
authorGregory Price <gourry@gourry.net>
Fri, 23 Jan 2026 19:48:09 +0000 (14:48 -0500)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sat, 14 Feb 2026 17:30:34 +0000 (18:30 +0100)
Add a headless option for Console so automation can run the qemu
instance in a background task.  In the current modes, qemu just
exits on boot because the console has nothing to attach to.

vmspawn does not support headless for now, just die if this is set.

Signed-off-by: Gregory Price <gourry@gourry.net>
mkosi/config.py
mkosi/qemu.py
mkosi/resources/man/mkosi.1.md
mkosi/vmspawn.py

index 0916dac4eff158c476086a5886e0e85f4d9fccf0..37d3b49121fce824be97bdf3c494c9d4b20354e2 100644 (file)
@@ -376,6 +376,7 @@ class ConsoleMode(StrEnum):
     read_only = enum.auto()
     native = enum.auto()
     gui = enum.auto()
+    headless = enum.auto()
 
 
 class Network(StrEnum):
index 6617677bfd5eaa5f3fb2446553737e13d91e93a9..e7cbdfeb9851e84589637ee33e6bdc43c4ab1a36 100644 (file)
@@ -809,7 +809,7 @@ def finalize_kernel_command_line_extra(config: Config) -> list[str]:
     ):
         cmdline += [f"systemd.hostname={config.machine}"]
 
-    if config.console != ConsoleMode.gui:
+    if config.console not in (ConsoleMode.gui, ConsoleMode.headless):
         cmdline += [
             f"systemd.tty.term.console={term}",
             f"systemd.tty.columns.console={columns}",
@@ -817,7 +817,7 @@ def finalize_kernel_command_line_extra(config: Config) -> list[str]:
             "console=hvc0",
             f"TERM={term}",
         ]
-    elif config.architecture.is_arm_variant():
+    elif config.console == ConsoleMode.gui and config.architecture.is_arm_variant():
         cmdline += ["console=tty0"]
 
     for s in config.kernel_command_line_extra:
@@ -993,9 +993,11 @@ def run_qemu(args: Args, config: Config) -> None:
     if config.vsock == ConfigFeature.enabled and QemuDeviceNode.vhost_vsock not in qemu_device_fds:
         die("VSock requested but cannot access /dev/vhost-vsock")
 
-    if config.console not in (ConsoleMode.native, ConsoleMode.gui) and not config.find_binary(
-        "systemd-pty-forward"
-    ):
+    if config.console not in (
+        ConsoleMode.native,
+        ConsoleMode.gui,
+        ConsoleMode.headless,
+    ) and not config.find_binary("systemd-pty-forward"):
         die(f"Console mode {config.console} requested but systemd-pty-forward not found")
 
     if config.linux:
@@ -1141,12 +1143,16 @@ def run_qemu(args: Args, config: Config) -> None:
         cmdline += [
             "-nographic",
             "-nodefaults",
-            "-chardev", "stdio,mux=on,id=console,signal=off",
-            "-device", "virtio-serial-pci,id=mkosi-virtio-serial-pci",
-            "-device", "virtconsole,chardev=console",
-            "-mon", "console",
         ]  # fmt: skip
 
+        if config.console != ConsoleMode.headless:
+            cmdline += [
+                "-chardev", "stdio,mux=on,id=console,signal=off",
+                "-device", "virtio-serial-pci,id=mkosi-virtio-serial-pci",
+                "-device", "virtconsole,chardev=console",
+                "-mon", "console",
+            ]  # fmt: skip
+
     # QEMU has built-in logic to look for the BIOS firmware so we don't need to do anything special for that.
     if firmware.is_uefi():
         assert ovmf
index cb3fcd53b962fd667dbbaaafbc5a2810ba2e69cc..23adf9d18c63766c97c56bfc12e92b1a4a614205 100644 (file)
@@ -1818,11 +1818,13 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
     kernel command line arguments.
 
 `Console=`, `--console=`
-:   Configures how to set up the console of the VM. Takes one of `interactive`, `read-only`, `native`, or
-    `gui`. Defaults to `interactive`. `interactive` provides an interactive terminal interface to the VM.
-    `read-only` is similar, but is strictly read-only, i.e. does not accept any input from the user.
-    `native` also provides a TTY-based interface, but uses **qemu**'s native implementation (which means the **qemu**
-    monitor is available). `gui` shows the **qemu** graphical UI.
+:   Configures how to set up the console of the VM. Takes one of `interactive`, `read-only`, `native`,
+    `gui`, or `headless`. Defaults to `interactive`. `interactive` provides an interactive terminal interface
+    to the VM. `read-only` is similar, but is strictly read-only, i.e. does not accept any input from the
+    user. `native` also provides a TTY-based interface, but uses **qemu**'s native implementation (which means
+    the **qemu** monitor is available). `gui` shows the **qemu** graphical UI. `headless` runs the VM without
+    any console attached, useful for fully automated or scripted VM usage. `headless` is only supported by
+    the `qemu` verb.
 
 `CPUs=`, `--cpus=`
 :   Configures the number of CPU cores to assign to the guest when booting a virtual machine.
index c8e7e55d9fb92faaa0d7151b50a573e6b73aefd6..c93f9453fb3d5d9bc28acfeb4588667cdc722647 100644 (file)
@@ -9,6 +9,7 @@ from pathlib import Path
 from mkosi.config import (
     Args,
     Config,
+    ConsoleMode,
     Firmware,
     Network,
     OutputFormat,
@@ -36,6 +37,9 @@ def run_vmspawn(args: Args, config: Config) -> None:
     if config.firmware_variables and config.firmware_variables != Path("microsoft"):
         die("mkosi vmspawn does not support FirmwareVariables=")
 
+    if config.console == ConsoleMode.headless:
+        die("Console=headless is not supported by vmspawn")
+
     kernel = config.expand_linux_specifiers() if config.linux else None
     firmware = finalize_firmware(config, kernel)