All checks were successful
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m36s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
//go:build linux
|
|
|
|
package qemu
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
firstNICSlot = 0x03
|
|
lastNICSlot = 0x1d
|
|
maxNICs = lastNICSlot - firstNICSlot + 1
|
|
)
|
|
|
|
func Start(cfg Config) error {
|
|
memory := cfg.Memory
|
|
if memory == 0 {
|
|
memory = 512
|
|
}
|
|
|
|
cpus := cfg.CPUs
|
|
if cpus == 0 {
|
|
cpus = 1
|
|
}
|
|
|
|
for _, dir := range []string{cfg.SerialDir, cfg.MonitorDir, cfg.QMPDir} {
|
|
if dir != "" {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", dir, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
serialSock := filepath.Join(cfg.SerialDir, cfg.Name+".sock")
|
|
monitorSock := filepath.Join(cfg.MonitorDir, cfg.Name+".sock")
|
|
qmpSock := filepath.Join(cfg.QMPDir, cfg.Name+".sock")
|
|
|
|
args := []string{
|
|
"-enable-kvm",
|
|
"-cpu", "host",
|
|
"-m", fmt.Sprintf("%d", memory),
|
|
"-smp", fmt.Sprintf("%d", cpus),
|
|
"-serial", fmt.Sprintf("unix:%s,server,nowait", serialSock),
|
|
"-monitor", fmt.Sprintf("unix:%s,server,nowait", monitorSock),
|
|
"-qmp", fmt.Sprintf("unix:%s,server,nowait", qmpSock),
|
|
"-display", "none",
|
|
}
|
|
|
|
if cfg.UEFICodePath != "" && cfg.UEFIVarsPath != "" {
|
|
args = append(args,
|
|
"-drive", fmt.Sprintf("if=pflash,format=raw,readonly=on,file=%s", cfg.UEFICodePath),
|
|
"-drive", fmt.Sprintf("if=pflash,format=raw,file=%s", cfg.UEFIVarsPath),
|
|
)
|
|
}
|
|
|
|
hasScsi := false
|
|
virtioBlk := 0
|
|
for _, d := range cfg.Disks {
|
|
if strings.HasPrefix(d.Dev, "sd") {
|
|
hasScsi = true
|
|
} else {
|
|
virtioBlk++
|
|
}
|
|
}
|
|
if virtioBlk > 1 {
|
|
return fmt.Errorf("qemu: %d disques virtio-blk (vd*) déclarés, un seul est supporté — utiliser des disques sd* pour les disques additionnels", virtioBlk)
|
|
}
|
|
if hasScsi {
|
|
args = append(args, "-device", "virtio-scsi-pci,id=scsi0,bus=pci.0,addr=0x1e")
|
|
}
|
|
|
|
sorted := make([]DiskConfig, len(cfg.Disks))
|
|
copy(sorted, cfg.Disks)
|
|
// vd* avant sd* : les disques virtio-blk bootent en premier.
|
|
// À lettre égale de type, ordre alphabétique.
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
iVirtio := strings.HasPrefix(sorted[i].Dev, "vd")
|
|
jVirtio := strings.HasPrefix(sorted[j].Dev, "vd")
|
|
if iVirtio != jVirtio {
|
|
return iVirtio
|
|
}
|
|
return sorted[i].Dev < sorted[j].Dev
|
|
})
|
|
|
|
for idx, d := range sorted {
|
|
bootindex := idx + 1
|
|
if strings.HasPrefix(d.Dev, "sd") {
|
|
scsiID := int(d.Dev[2] - 'a')
|
|
args = append(args,
|
|
"-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev),
|
|
"-device", fmt.Sprintf("scsi-hd,drive=%s,bus=scsi0.0,scsi-id=%d,bootindex=%d", d.Dev, scsiID, bootindex),
|
|
)
|
|
} else {
|
|
args = append(args,
|
|
"-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev),
|
|
"-device", fmt.Sprintf("virtio-blk-pci,drive=%s,bus=pci.0,addr=0x1f,bootindex=%d", d.Dev, bootindex),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Slots 0x03 à 0x1d réservés au réseau par la carte PCI (#36). Le slot est
|
|
// dérivé de l'index et non laissé à QEMU : c'est lui qui fixe le nom de
|
|
// l'interface dans le guest, et un slot flottant la renomme d'un démarrage
|
|
// à l'autre.
|
|
if len(cfg.NICs) == 0 {
|
|
return fmt.Errorf("vm %s has no network interface", cfg.Name)
|
|
}
|
|
if len(cfg.NICs) > maxNICs {
|
|
return fmt.Errorf("vm %s has %d interfaces, the pci map holds %d", cfg.Name, len(cfg.NICs), maxNICs)
|
|
}
|
|
for i, n := range cfg.NICs {
|
|
args = append(args,
|
|
"-netdev", fmt.Sprintf("tap,id=net%d,ifname=tap%d,script=no,downscript=no", i, n.TapID),
|
|
"-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s,bus=pci.0,addr=0x%02x", i, n.Mac, firstNICSlot+i),
|
|
)
|
|
}
|
|
|
|
args = append(args, "-daemonize")
|
|
|
|
scopeArgs := append([]string{
|
|
"--scope",
|
|
"--unit=" + ScopeName(cfg.Name),
|
|
"--collect",
|
|
"qemu-system-x86_64",
|
|
}, args...)
|
|
|
|
cmd := exec.Command("systemd-run", scopeArgs...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("systemd-run qemu-system-x86_64: %w: %s", err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
}
|