mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-08 14:34:49 +08:00
支持部署KVM XFCE桌面系统
This commit is contained in:
@@ -28,6 +28,7 @@ type ImageInfo struct {
|
|||||||
Downloading bool `json:"downloading"`
|
Downloading bool `json:"downloading"`
|
||||||
SizeBytes int64 `json:"size_bytes"`
|
SizeBytes int64 `json:"size_bytes"`
|
||||||
ManualPath string `json:"manual_path,omitempty"`
|
ManualPath string `json:"manual_path,omitempty"`
|
||||||
|
Desktop string `json:"desktop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageDownloadsMu sync.Mutex
|
var imageDownloadsMu sync.Mutex
|
||||||
@@ -140,6 +141,7 @@ func HandleImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
Downloading: downloading,
|
Downloading: downloading,
|
||||||
SizeBytes: size,
|
SizeBytes: size,
|
||||||
ManualPath: manualPath,
|
ManualPath: manualPath,
|
||||||
|
Desktop: t.Desktop,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +338,7 @@ func HandleEnabledImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
if downloaded, _ := kvm.ImageDownloadedInfo(t.ID); enabledSet[t.ID] && downloaded {
|
if downloaded, _ := kvm.ImageDownloadedInfo(t.ID); enabledSet[t.ID] && downloaded {
|
||||||
result = append(result, map[string]string{
|
result = append(result, map[string]string{
|
||||||
"id": t.ID, "name": t.Name, "distro": t.Distro, "release": t.Release, "arch": t.Arch,
|
"id": t.ID, "name": t.Name, "distro": t.Distro, "release": t.Release, "arch": t.Arch,
|
||||||
"description": t.Description, "type": config.VirtualizationKVM,
|
"description": t.Description, "type": config.VirtualizationKVM, "desktop": t.Desktop,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,13 +371,21 @@ func (m *Manager) defineContainer(id int, vmName string, cfg lxc.ContainerConfig
|
|||||||
}
|
}
|
||||||
xml = windowsDomainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, ImagePath(image.ID), unattendPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps)
|
xml = windowsDomainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, ImagePath(image.ID), unattendPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps)
|
||||||
} else {
|
} else {
|
||||||
|
if image.Desktop != "" {
|
||||||
|
if cfg.RAMMB < 2048 {
|
||||||
|
cfg.RAMMB = 2048
|
||||||
|
}
|
||||||
|
if cfg.DiskGB < 20 {
|
||||||
|
cfg.DiskGB = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := createOverlayDisk(ImagePath(image.ID), diskPath, cfg.DiskGB); err != nil {
|
if err := createOverlayDisk(ImagePath(image.ID), diskPath, cfg.DiskGB); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := createSeedISO(seedPath, vmName, cfg.Name, sshPassword, mac, ipv6); err != nil {
|
if err := createSeedISO(seedPath, vmName, cfg.Name, sshPassword, mac, ipv6, *image); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
xml = domainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, seedPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps)
|
xml = domainXML(vmName, int(cfg.VCPU), cfg.RAMMB, diskPath, seedPath, mac, cfg.IOSpeedMBps, cfg.NetworkBWMbps, image.Desktop != "")
|
||||||
}
|
}
|
||||||
xmlPath := filepath.Join(m.instanceDir(vmName), "domain.xml")
|
xmlPath := filepath.Join(m.instanceDir(vmName), "domain.xml")
|
||||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||||
@@ -706,7 +714,7 @@ func (m *Manager) ApplyContainerLimits(c *config.Container) error {
|
|||||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
||||||
} else {
|
} else {
|
||||||
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
||||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps, isKVMDesktopTemplate(c.Template))
|
||||||
}
|
}
|
||||||
xmlPath := filepath.Join(m.instanceDir(c.VirshName()), "domain.xml")
|
xmlPath := filepath.Join(m.instanceDir(c.VirshName()), "domain.xml")
|
||||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||||
@@ -731,7 +739,7 @@ func (m *Manager) ensureDomainDefinition(c *config.Container) error {
|
|||||||
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
xml = windowsDomainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, winISO, unattendISO, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
||||||
} else {
|
} else {
|
||||||
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
seedPath := filepath.Join(m.instanceDir(c.VirshName()), "seed.iso")
|
||||||
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps)
|
xml = domainXML(c.VirshName(), int(c.VCPU), c.RAMMB, c.DiskImage, seedPath, c.MACAddress, c.IOSpeedMBps, c.NetworkBWMbps, isKVMDesktopTemplate(c.Template))
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
if err := os.WriteFile(xmlPath, []byte(xml), 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1668,8 +1676,11 @@ func shellQuoteWindows(value string) string {
|
|||||||
return `"` + strings.ReplaceAll(value, `"`, `\"`) + `"`
|
return `"` + strings.ReplaceAll(value, `"`, `\"`) + `"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSeedISO(seedPath, instanceID, hostname, password, mac, ipv6 string) error {
|
func createSeedISO(seedPath, instanceID, hostname, password, mac, ipv6 string, image Image) error {
|
||||||
guestSetup := kvmSSHSetupScript(password)
|
guestSetup := kvmSSHSetupScript(password)
|
||||||
|
if desktopSetup := kvmDesktopSetupScript(image); desktopSetup != "" {
|
||||||
|
guestSetup += "\n" + desktopSetup
|
||||||
|
}
|
||||||
if strings.TrimSpace(ipv6) != "" {
|
if strings.TrimSpace(ipv6) != "" {
|
||||||
guestSetup += "\n" + kvmIPv6SetupScript(ipv6)
|
guestSetup += "\n" + kvmIPv6SetupScript(ipv6)
|
||||||
}
|
}
|
||||||
@@ -1743,7 +1754,12 @@ func indentScript(script string, spaces int) string {
|
|||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string, ioSpeedMBps int, networkBWMbps int) string {
|
func isKVMDesktopTemplate(templateID string) bool {
|
||||||
|
image := FindImage(templateID)
|
||||||
|
return image != nil && image.Desktop != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string, ioSpeedMBps int, networkBWMbps int, desktop bool) string {
|
||||||
if vcpu < 1 {
|
if vcpu < 1 {
|
||||||
vcpu = 1
|
vcpu = 1
|
||||||
}
|
}
|
||||||
@@ -1767,6 +1783,12 @@ func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string,
|
|||||||
<outbound average='%d'/>
|
<outbound average='%d'/>
|
||||||
</bandwidth>`, averageKiB, averageKiB)
|
</bandwidth>`, averageKiB, averageKiB)
|
||||||
}
|
}
|
||||||
|
video := "<video><model type='virtio'/></video>"
|
||||||
|
input := ""
|
||||||
|
if desktop {
|
||||||
|
video = "<video><model type='qxl' ram='65536' vram='65536' heads='1' primary='yes'/></video>"
|
||||||
|
input = "\n\t <input type='tablet' bus='usb'/>"
|
||||||
|
}
|
||||||
return fmt.Sprintf(`<domain type='kvm'>
|
return fmt.Sprintf(`<domain type='kvm'>
|
||||||
<name>%s</name>
|
<name>%s</name>
|
||||||
%s
|
%s
|
||||||
@@ -1810,10 +1832,10 @@ func domainXML(name string, vcpu int, ramMB int, diskPath, seedPath, mac string,
|
|||||||
<memballoon model='virtio'>
|
<memballoon model='virtio'>
|
||||||
<stats period='10'/>
|
<stats period='10'/>
|
||||||
</memballoon>
|
</memballoon>
|
||||||
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'/>
|
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'/>%s
|
||||||
<video><model type='virtio'/></video>
|
%s
|
||||||
</devices>
|
</devices>
|
||||||
</domain>`, xmlEscape(name), domainUUIDXML(name), ramMB, ramMB, vcpu, vcpu, xmlEscape(diskPath), iotune, xmlEscape(seedPath), xmlEscape(mac), bandwidth)
|
</domain>`, xmlEscape(name), domainUUIDXML(name), ramMB, ramMB, vcpu, vcpu, xmlEscape(diskPath), iotune, xmlEscape(seedPath), xmlEscape(mac), bandwidth, input, video)
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowsDomainXML(name string, vcpu int, ramMB int, diskPath, winISOPath, unattendISOPath, mac string, ioSpeedMBps int, networkBWMbps int) string {
|
func windowsDomainXML(name string, vcpu int, ramMB int, diskPath, winISOPath, unattendISOPath, mac string, ioSpeedMBps int, networkBWMbps int) string {
|
||||||
@@ -2263,6 +2285,64 @@ fi
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kvmDesktopSetupScript(image Image) string {
|
||||||
|
if strings.ToLower(strings.TrimSpace(image.Desktop)) != "xfce" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
packages := ""
|
||||||
|
switch image.Distro {
|
||||||
|
case "ubuntu":
|
||||||
|
packages = "xubuntu-desktop"
|
||||||
|
case "debian":
|
||||||
|
packages = "task-xfce-desktop"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return `if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
{
|
||||||
|
exec >>/var/log/clicd-desktop-setup.log 2>&1
|
||||||
|
echo "CLICD XFCE setup started at $(date -Is)"
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
export APT_LISTCHANGES_FRONTEND=none
|
||||||
|
apt-get update || true
|
||||||
|
apt-get install -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold ` + packages + ` || apt-get install -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold xfce4 lightdm lightdm-gtk-greeter dbus-x11 xorg || true
|
||||||
|
if command -v useradd >/dev/null 2>&1 && ! id clicd >/dev/null 2>&1; then
|
||||||
|
useradd -m -s /bin/bash clicd || true
|
||||||
|
fi
|
||||||
|
if command -v chpasswd >/dev/null 2>&1 && id clicd >/dev/null 2>&1; then
|
||||||
|
printf 'clicd:%s\n' "$ROOT_PASSWORD" | chpasswd || true
|
||||||
|
fi
|
||||||
|
usermod -aG sudo clicd >/dev/null 2>&1 || true
|
||||||
|
usermod -aG autologin clicd >/dev/null 2>&1 || true
|
||||||
|
if id clicd >/dev/null 2>&1; then
|
||||||
|
printf 'startxfce4\n' >/home/clicd/.xsession || true
|
||||||
|
chown clicd:clicd /home/clicd/.xsession >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
mkdir -p /etc/lightdm/lightdm.conf.d
|
||||||
|
cat >/etc/lightdm/lightdm.conf.d/50-clicd-autologin.conf <<'EOF'
|
||||||
|
[Seat:*]
|
||||||
|
autologin-user=clicd
|
||||||
|
autologin-user-timeout=0
|
||||||
|
user-session=xfce
|
||||||
|
greeter-session=lightdm-gtk-greeter
|
||||||
|
EOF
|
||||||
|
if [ -x /usr/sbin/lightdm ]; then
|
||||||
|
printf '/usr/sbin/lightdm\n' >/etc/X11/default-display-manager || true
|
||||||
|
fi
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
systemctl set-default graphical.target >/dev/null 2>&1 || true
|
||||||
|
systemctl enable display-manager.service >/dev/null 2>&1 || true
|
||||||
|
systemctl enable lightdm.service >/dev/null 2>&1 || true
|
||||||
|
systemctl restart lightdm.service >/dev/null 2>&1 || systemctl start lightdm.service >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
apt-get clean || true
|
||||||
|
echo "CLICD XFCE setup finished at $(date -Is)"
|
||||||
|
} || true
|
||||||
|
fi
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
func qemuGuestPing(name string) error {
|
func qemuGuestPing(name string) error {
|
||||||
out, err := exec.Command("virsh", "qemu-agent-command", name, `{"execute":"guest-ping"}`).CombinedOutput()
|
out, err := exec.Command("virsh", "qemu-agent-command", name, `{"execute":"guest-ping"}`).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Image struct {
|
|||||||
Arch string `json:"arch"`
|
Arch string `json:"arch"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
Desktop string `json:"desktop,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetImages() []Image {
|
func GetImages() []Image {
|
||||||
@@ -22,6 +23,13 @@ func GetImages() []Image {
|
|||||||
Description: "Ubuntu 24.04 LTS cloud image for KVM",
|
Description: "Ubuntu 24.04 LTS cloud image for KVM",
|
||||||
URL: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img",
|
URL: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "kvm-ubuntu-noble-xfce", Name: "Ubuntu 24.04 XFCE KVM",
|
||||||
|
Distro: "ubuntu", Release: "noble", Arch: "amd64",
|
||||||
|
Description: "Ubuntu 24.04 LTS cloud image with XFCE desktop provisioned via cloud-init",
|
||||||
|
URL: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img",
|
||||||
|
Desktop: "xfce",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "kvm-ubuntu-jammy", Name: "Ubuntu 22.04 KVM",
|
ID: "kvm-ubuntu-jammy", Name: "Ubuntu 22.04 KVM",
|
||||||
Distro: "ubuntu", Release: "jammy", Arch: "amd64",
|
Distro: "ubuntu", Release: "jammy", Arch: "amd64",
|
||||||
@@ -34,6 +42,13 @@ func GetImages() []Image {
|
|||||||
Description: "Debian 12 generic cloud image for KVM",
|
Description: "Debian 12 generic cloud image for KVM",
|
||||||
URL: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
|
URL: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "kvm-debian-bookworm-xfce", Name: "Debian 12 XFCE KVM",
|
||||||
|
Distro: "debian", Release: "bookworm", Arch: "amd64",
|
||||||
|
Description: "Debian 12 generic cloud image with XFCE desktop provisioned via cloud-init",
|
||||||
|
URL: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2",
|
||||||
|
Desktop: "xfce",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "kvm-debian-bullseye", Name: "Debian 11 KVM",
|
ID: "kvm-debian-bullseye", Name: "Debian 11 KVM",
|
||||||
Distro: "debian", Release: "bullseye", Arch: "amd64",
|
Distro: "debian", Release: "bullseye", Arch: "amd64",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export interface Template {
|
|||||||
release: string
|
release: string
|
||||||
arch: string
|
arch: string
|
||||||
variant?: string
|
variant?: string
|
||||||
|
desktop?: string
|
||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,6 +360,7 @@ export interface ImageInfo {
|
|||||||
downloading: boolean
|
downloading: boolean
|
||||||
size_bytes: number
|
size_bytes: number
|
||||||
manual_path?: string
|
manual_path?: string
|
||||||
|
desktop?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getImages = () =>
|
export const getImages = () =>
|
||||||
|
|||||||
Reference in New Issue
Block a user