Skip to content

Commit 4bc8d79

Browse files
committed
libvirt: Add opt-in --graphical-console flag for SPICE display
Add a --graphical-console flag to 'bcvk libvirt run' that enables a SPICE graphics console for virt-manager access. When enabled, the domain XML includes a SPICE display with autoport, a virtio GPU, and a spicevmc channel for clipboard and display resize support. The flag also adds console=tty1 as a kernel argument so the guest kernel outputs to the graphical console. This also removes unused VNC support code. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent bd4b055 commit 4bc8d79

2 files changed

Lines changed: 49 additions & 20 deletions

File tree

crates/kit/src/libvirt/domain.rs

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub struct DomainBuilder {
4343
disk_path: Option<String>,
4444
transient_disk: bool, // Use transient disk with temporary overlay
4545
network: Option<String>,
46-
vnc_port: Option<u16>,
46+
graphical_console: bool,
4747
kernel_args: Option<String>,
4848
metadata: HashMap<String, String>,
4949
qemu_args: Vec<String>,
@@ -76,7 +76,7 @@ impl DomainBuilder {
7676
disk_path: None,
7777
transient_disk: false,
7878
network: None,
79-
vnc_port: None,
79+
graphical_console: false,
8080
kernel_args: None,
8181
metadata: HashMap::new(),
8282
qemu_args: Vec::new(),
@@ -129,10 +129,9 @@ impl DomainBuilder {
129129
self
130130
}
131131

132-
/// Enable VNC on specified port
133-
#[allow(dead_code)]
134-
pub fn with_vnc(mut self, port: u16) -> Self {
135-
self.vnc_port = Some(port);
132+
/// Enable graphical console (SPICE) for virt-manager access
133+
pub fn with_graphical_console(mut self) -> Self {
134+
self.graphical_console = true;
136135
self
137136
}
138137

@@ -474,19 +473,23 @@ impl DomainBuilder {
474473
}
475474
}
476475

477-
// VNC graphics if enabled
478-
if let Some(vnc_port) = self.vnc_port {
476+
// Graphical console (SPICE) for virt-manager access
477+
if self.graphical_console {
478+
writer.start_element("graphics", &[("type", "spice"), ("autoport", "yes")])?;
479+
writer.write_empty_element("listen", &[("type", "address")])?;
480+
writer.end_element("graphics")?;
481+
writer.start_element("video", &[])?;
479482
writer.write_empty_element(
480-
"graphics",
481-
&[
482-
("type", "vnc"),
483-
("port", &vnc_port.to_string()),
484-
("listen", "127.0.0.1"),
485-
],
483+
"model",
484+
&[("type", "virtio"), ("heads", "1"), ("primary", "yes")],
486485
)?;
487-
writer.start_element("video", &[])?;
488-
writer.write_empty_element("model", &[("type", "vga")])?;
489486
writer.end_element("video")?;
487+
writer.start_element("channel", &[("type", "spicevmc")])?;
488+
writer.write_empty_element(
489+
"target",
490+
&[("type", "virtio"), ("name", "com.redhat.spice.0")],
491+
)?;
492+
writer.end_element("channel")?;
490493
}
491494

492495
// Virtiofs filesystems
@@ -633,15 +636,28 @@ mod tests {
633636
}
634637

635638
#[test]
636-
fn test_vnc_configuration() {
639+
fn test_graphical_console_configuration() {
640+
// Test with graphical console enabled
637641
let xml = DomainBuilder::new()
638642
.with_name("test")
639-
.with_vnc(5901)
643+
.with_graphical_console()
644+
.build_xml()
645+
.unwrap();
646+
647+
assert!(xml.contains("graphics type=\"spice\" autoport=\"yes\""));
648+
assert!(xml.contains("model type=\"virtio\" heads=\"1\" primary=\"yes\""));
649+
assert!(xml.contains("channel type=\"spicevmc\""));
650+
assert!(xml.contains("target type=\"virtio\" name=\"com.redhat.spice.0\""));
651+
652+
// Test without graphical console (default)
653+
let xml_no_graphics = DomainBuilder::new()
654+
.with_name("test-no-graphics")
640655
.build_xml()
641656
.unwrap();
642657

643-
assert!(xml.contains("graphics type=\"vnc\" port=\"5901\""));
644-
assert!(xml.contains("model type=\"vga\""));
658+
assert!(!xml_no_graphics.contains("<graphics"));
659+
assert!(!xml_no_graphics.contains("<video"));
660+
assert!(!xml_no_graphics.contains("spicevmc"));
645661
}
646662

647663
#[test]

crates/kit/src/libvirt/run.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ pub struct LibvirtRunOpts {
297297
#[clap(long)]
298298
pub label: Vec<String>,
299299

300+
/// Enable graphical console (SPICE) for virt-manager access
301+
#[clap(long)]
302+
pub graphical_console: bool,
303+
300304
/// Create a transient VM that disappears on shutdown/reboot
301305
#[clap(long)]
302306
pub transient: bool,
@@ -473,6 +477,12 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, mut opts: LibvirtRunOpt
473477
opts.install.target_transport = Some(UPDATE_FROM_HOST_TRANSPORT.to_owned());
474478
}
475479

480+
// Add console=tty1 kernel argument for graphical console support
481+
if opts.graphical_console {
482+
opts.install.karg.push("console=tty1".to_string());
483+
debug!("Added console=tty1 kernel argument for graphical console");
484+
}
485+
476486
// Add Ignition kernel argument to install options if Ignition config is specified
477487
// This ensures the kernel arg is baked into the installed system's GRUB configuration
478488
if opts.ignition_config.is_some() {
@@ -1193,6 +1203,9 @@ fn create_libvirt_domain_from_disk(
11931203
domain_builder =
11941204
domain_builder.with_firmware_log(crate::libvirt::domain::FirmwareLogOutput::Console);
11951205
}
1206+
if opts.graphical_console {
1207+
domain_builder = domain_builder.with_graphical_console();
1208+
}
11961209
domain_builder = domain_builder
11971210
.with_metadata("bootc:source-image", &opts.image)
11981211
.with_metadata("bootc:memory-mb", &memory.to_string())

0 commit comments

Comments
 (0)