diff options
| author | Nikolay Govorov <me@govorov.online> | 2026-07-12 22:52:18 +0100 |
|---|---|---|
| committer | Nikolay Govorov <me@govorov.online> | 2026-07-12 22:55:50 +0100 |
| commit | a0167823ba254acdda4c29c64fbbad1ca2f8a1af (patch) | |
| tree | cb338e5c73b2458df50be0a2099509bf5d6b8d69 | |
| parent | 9dede6532c0130dab8dc7d8d924655811e10ea60 (diff) | |
| download | tar tar.gz tar.bz2 tar.lz tar.xz tar.zst zip | |
Add qemu-guest-agent to golden images
Diffstat
| -rw-r--r-- | crates/hule-image/src/lib.rs | 41 | +37 −4 |
| -rw-r--r-- | crates/hule/src/main.rs | 2 | +1 −1 |
| -rwxr-xr-x | images/alpine/genimg | 20 | +17 −3 |
| -rwxr-xr-x | images/debian/genimg | 6 | +4 −2 |
| -rwxr-xr-x | images/fedora/genimg | 7 | +5 −2 |
| -rwxr-xr-x | images/freebsd/genimg | 6 | +4 −2 |
| -rwxr-xr-x | images/ubuntu/genimg | 7 | +5 −2 |
7 files changed, 73 insertions, 16 deletions
diff --git a/crates/hule-image/src/lib.rs b/crates/hule-image/src/lib.rs index 6974882..6165294 100644 --- a/crates/hule-image/src/lib.rs +++ b/crates/hule-image/src/lib.rs @@ -326,6 +326,12 @@ pub enum Access { user: String, auth: String, }, + /// qemu-guest-agent. The hypervisor backend chooses and configures a + /// compatible transport; the image only promises that the agent runs. + Qga { + #[serde(rename = "type")] + kind: String, + }, /// Unrecognized access surface, preserved verbatim. Unknown(serde_json::Value), } @@ -353,6 +359,7 @@ impl<'de> Deserialize<'de> for Access { auth: raw.auth, }) } + "qga" => Ok(Self::Qga { kind: "qga".into() }), _ => Ok(Self::Unknown(value)), } } @@ -375,7 +382,7 @@ impl Access { validation.required(user, &format!("{field}.user")); validation.required(auth, &format!("{field}.auth")); } - Self::Unknown(_) => {} + Self::Qga { .. } | Self::Unknown(_) => {} } } } @@ -413,6 +420,7 @@ pub struct Machine { pub cpu: ResourceRange, pub ram: ResourceRange, pub boot: Vec<Boot>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub access: Vec<Access>, pub network: Network, } @@ -429,10 +437,9 @@ impl Machine { for (index, boot) in self.boot.iter().enumerate() { boot.validate_into(&format!("{field}.boot[{index}]"), validation); } - if !self.access.iter().any(Access::is_recognized) { + if !self.access.is_empty() && !self.access.iter().any(Access::is_recognized) { validation.error(format!( - "{field}.access must contain at least one supported access method \ - (a running machine must be reachable by something, if only to shut it down)" + "{field}.access must contain at least one supported access method" )); } for (index, access) in self.access.iter().enumerate() { @@ -761,6 +768,32 @@ mod tests { } #[test] + fn parses_qga_access() { + let json = String::from_utf8(valid_json()).unwrap().replace( + r#"[{"type":"ssh","port":22,"user":"build","auth":"empty-password"}]"#, + r#"[{"type":"ssh","port":22,"user":"build","auth":"empty-password"},{"type":"qga"}]"#, + ); + let image = MachineImage::from_json(json.as_bytes()).unwrap(); + assert_eq!(image.machine.access.len(), 2); + assert!(matches!(image.machine.access[1], Access::Qga { .. })); + } + + #[test] + fn access_may_be_omitted_for_a_black_box_image() { + let json = String::from_utf8(valid_json()).unwrap().replace( + r#","access":[{"type":"ssh","port":22,"user":"build","auth":"empty-password"}]"#, + "", + ); + let image = MachineImage::from_json(json.as_bytes()).unwrap(); + assert!(image.machine.access.is_empty()); + assert!( + serde_json::to_value(image).unwrap()["machine"] + .get("access") + .is_none() + ); + } + + #[test] fn parses_dhcp_network_without_static_parameters() { let json = String::from_utf8(valid_json()).unwrap().replace( r#"{"mode":"static","address":"10.0.2.15/24","gateway":"10.0.2.2"}"#, diff --git a/crates/hule/src/main.rs b/crates/hule/src/main.rs index 58ffd34..82ff039 100644 --- a/crates/hule/src/main.rs +++ b/crates/hule/src/main.rs @@ -506,7 +506,7 @@ async fn cmd_run(reference_str: &str, port: u16) -> R<()> { .iter() .find_map(|a| match a { Access::Ssh { port, .. } => Some(*port), - Access::Unknown(_) => None, + Access::Qga { .. } | Access::Unknown(_) => None, }) .unwrap_or(22); let settings = Settings { diff --git a/images/alpine/genimg b/images/alpine/genimg index 3bf3e2d..76405a4 100755 --- a/images/alpine/genimg +++ b/images/alpine/genimg @@ -90,7 +90,7 @@ apk add -U \ --initdb \ acct alpine-base alpine-conf alpine-sdk linux-firmware-none $linux \ git mercurial openssh sudo syslinux tzdata gnupg haveged bash curl \ - doas + doas qemu-guest-agent mount --bind /dev /mnt/dev mount --bind /dev/pts /mnt/dev/pts @@ -135,7 +135,20 @@ fi extlinux -i /mnt/boot -for i in ntpd sshd crond haveged; do +# Alpine uses mdev rather than udev, so create the stable, named virtio-port +# symlink expected by qemu-ga without depending on controller/port numbers. +mkdir -p /mnt/etc/mdev +cat >/mnt/etc/mdev/virtio-port <<'EOF' +#!/bin/sh +name=$(cat "/sys/class/virtio-ports/$MDEV/name") || exit +[ "$name" = org.qemu.guest_agent.0 ] || exit +mkdir -p /dev/virtio-ports +ln -sf "/dev/$MDEV" "/dev/virtio-ports/$name" +EOF +chmod +x /mnt/etc/mdev/virtio-port +echo 'vport.* root:root 0600 @/etc/mdev/virtio-port' >>/mnt/etc/mdev.conf + +for i in ntpd sshd crond haveged qemu-guest-agent; do run_root rc-update add $i default done for i in hwclock modules sysctl hostname bootmisc loadkmap networking seedrng syslog swap; do @@ -228,7 +241,8 @@ cat >"$out/config.json" <<EOF } ], "access": [ - { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" } + { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }, + { "type": "qga" } ], "network": { "mode": "static", "address": "10.0.2.15/24", "gateway": "10.0.2.2" } }, diff --git a/images/debian/genimg b/images/debian/genimg index 116fab2..85e363a 100755 --- a/images/debian/genimg +++ b/images/debian/genimg @@ -135,7 +135,7 @@ run_root apt-get -y install locales run_root apt-get -y install $kpkg run_root apt-get -y install build-essential git mercurial ssh sudo \ gnupg dirmngr ca-certificates apt-transport-https curl dbus \ - systemd-timesyncd ncurses-term + systemd-timesyncd ncurses-term qemu-guest-agent run_root ln -sf /usr/share/zoneinfo/UTC /etc/localtime run_root systemctl enable systemd-timesyncd.service @@ -147,6 +147,7 @@ echo '%sudo ALL=(ALL) NOPASSWD: ALL' >>/mnt/etc/sudoers echo "PermitEmptyPasswords yes" >>/mnt/etc/ssh/sshd_config echo ssh >>/mnt/etc/securetty run_root systemctl enable ssh +run_root systemctl enable qemu-guest-agent # Prevent docker from mucking up networking mkdir -p /mnt/etc/docker @@ -275,7 +276,8 @@ cat >"$out/config.json" <<EOF $protocols ], "access": [ - { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" } + { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }, + { "type": "qga" } ], "network": { "mode": "static", "address": "10.0.2.15/24", "gateway": "10.0.2.2" } }, diff --git a/images/fedora/genimg b/images/fedora/genimg index 35c2695..56e2a5d 100755 --- a/images/fedora/genimg +++ b/images/fedora/genimg @@ -141,7 +141,8 @@ run_root systemctl enable systemd-timesyncd.service run_root dnf -y \ --releasever="$release" \ install \ - @development-tools git mercurial openssh-server sudo kernel grub2 + @development-tools git mercurial openssh-server sudo kernel grub2 \ + qemu-guest-agent run_root dnf clean all @@ -166,6 +167,7 @@ echo '%sudo ALL=(ALL) NOPASSWD: ALL' >>/mnt/etc/sudoers echo "PermitEmptyPasswords yes" >>/mnt/etc/ssh/sshd_config echo ssh >>/mnt/etc/securetty run_root systemctl enable sshd +run_root systemctl enable qemu-guest-agent boot_uuid=$(blkid --match-tag UUID --output value ${NBD_DEVICE}p1) swap_uuid=$(blkid --match-tag UUID --output value ${NBD_DEVICE}p2) @@ -213,7 +215,8 @@ cat >"$out/config.json" <<EOF } ], "access": [ - { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" } + { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }, + { "type": "qga" } ], "network": { "mode": "static", "address": "10.0.2.15/24", "gateway": "10.0.2.2" } }, diff --git a/images/freebsd/genimg b/images/freebsd/genimg index e360436..4d40076 100755 --- a/images/freebsd/genimg +++ b/images/freebsd/genimg @@ -141,7 +141,7 @@ esac # resolver; the guest resolv.conf written below is purely for runtime. pkg -C /dev/null -R "$reposdir" -r "$rootfs" update pkg -C /dev/null -R "$reposdir" -r "$rootfs" install -y \ - pkg git-lite mercurial sudo curl + pkg git-lite mercurial sudo curl qemu-guest-agent pkg -C /dev/null -R "$reposdir" -r "$rootfs" clean -ay if [ "$with_ports" = true ]; then @@ -157,6 +157,7 @@ cat >>"$rootfs"/etc/rc.conf <<EOF ntpd_enable=YES sshd_enable=YES growfs_enable=YES +qemu_guest_agent_enable=YES hostname="build" ifconfig_DEFAULT="inet 10.0.2.15 netmask 255.255.255.0" defaultrouter="10.0.2.2" @@ -229,7 +230,8 @@ cat >"$out/config.json" <<CONFIG } ], "access": [ - { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" } + { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }, + { "type": "qga" } ], "network": { "mode": "static", "address": "10.0.2.15/24", "gateway": "10.0.2.2" } }, diff --git a/images/ubuntu/genimg b/images/ubuntu/genimg index 114438c..28d6e08 100755 --- a/images/ubuntu/genimg +++ b/images/ubuntu/genimg @@ -142,7 +142,8 @@ run_root apt-get -y install locales run_root apt-get -y install linux-base run_root apt-get -y install linux-image-generic run_root apt-get -y install \ - build-essential git mercurial ssh sudo dirmngr curl ca-certificates + build-essential git mercurial ssh sudo dirmngr curl ca-certificates \ + qemu-guest-agent if [ "$arch" = "amd64" ]; then extlinux -i /mnt/boot @@ -159,6 +160,7 @@ echo '%sudo ALL=(ALL) NOPASSWD: ALL' >>/mnt/etc/sudoers echo "PermitEmptyPasswords yes" >>/mnt/etc/ssh/sshd_config echo ssh >>/mnt/etc/securetty run_root systemctl enable ssh +run_root systemctl enable qemu-guest-agent # Prevent docker from mucking up networking mkdir -p /mnt/etc/docker @@ -272,7 +274,8 @@ cat >"$out/config.json" <<EOF $protocols ], "access": [ - { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" } + { "type": "ssh", "port": 22, "user": "build", "auth": "empty-password" }, + { "type": "qga" } ], "network": { "mode": "$net_mode", |
