diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index f37fa30..8cd65f8 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -21,8 +21,9 @@ import ( var manager = lxc.NewManager() const ( - clicdBackupDir = "/root/clicd-backups" - clicdNewBinaryPath = "/usr/local/bin/clicd.new" + clicdBackupDir = "/root/clicd-backups" + clicdNewBinaryPath = "/usr/local/bin/clicd.new" + libvirtDefaultNetworkMarker = "/var/lib/clicd/kvm/default-network.created" ) // Run starts the CLI interface. @@ -754,6 +755,7 @@ func cliUninstall(reader *bufio.Reader) { destroyAllLXCContainers() destroyAllKVMDomains() + removeCLICDLibvirtDefaultNetwork() cleanupCLICDNetworking() removeCLICDHostHooks() removeCLICDQuotaRecords() @@ -840,6 +842,56 @@ func removeKVMDomain(name string) { runQuiet("virsh", "undefine", name) } +func removeCLICDLibvirtDefaultNetwork() { + if !commandExists("virsh") || !fileExists(libvirtDefaultNetworkMarker) { + return + } + if libvirtDefaultUsedByNonCLICDDomain() { + fmt.Println("检测到非 CLICD 虚拟机仍在使用 libvirt default 网络,已保留 default/virbr0。") + return + } + fmt.Println("Removing CLICD-created libvirt default network...") + runQuiet("virsh", "net-destroy", "default") + runQuiet("virsh", "net-undefine", "default") + removePath(libvirtDefaultNetworkMarker) +} + +func libvirtDefaultUsedByNonCLICDDomain() bool { + if !commandExists("virsh") { + return false + } + out, err := exec.Command("virsh", "list", "--all", "--name").Output() + if err != nil { + return false + } + for _, line := range strings.Split(string(out), "\n") { + name := strings.TrimSpace(line) + if name == "" || isCLICDKVMDomain(name) { + continue + } + if usesLibvirtDefaultNetwork(name) { + return true + } + } + return false +} + +func usesLibvirtDefaultNetwork(domain string) bool { + out, err := exec.Command("virsh", "domiflist", domain).Output() + if err != nil { + return false + } + for _, line := range strings.Split(string(out), "\n") { + fields := strings.Fields(line) + for _, field := range fields { + if field == "default" || field == "virbr0" { + return true + } + } + } + return false +} + func cleanupCLICDNetworking() { removeCLICDNATRules() cleanupCLICDIPv6Runtime() diff --git a/backend/internal/kvm/kvm.go b/backend/internal/kvm/kvm.go index 1e04146..250ba56 100644 --- a/backend/internal/kvm/kvm.go +++ b/backend/internal/kvm/kvm.go @@ -37,6 +37,7 @@ type Manager struct { } const ipv6GatewayLinkLocal = "fe80::1" +const libvirtDefaultNetworkMarker = "/var/lib/clicd/kvm/default-network.created" type usageSample struct { CPUUsec uint64 @@ -1476,6 +1477,9 @@ func ensureDefaultNetwork() error { if out, err := exec.Command("virsh", "net-define", tmpFile).CombinedOutput(); err != nil { return fmt.Errorf("failed to define libvirt default network: %v, output: %s", err, string(out)) } + if err := os.MkdirAll(filepath.Dir(libvirtDefaultNetworkMarker), 0755); err == nil { + _ = os.WriteFile(libvirtDefaultNetworkMarker, []byte("created-by-clicd\n"), 0644) + } } // Start and autostart the default network if out, err := exec.Command("virsh", "net-info", "default").Output(); err == nil { diff --git a/install.sh b/install.sh index a42cb6e..be66e63 100644 --- a/install.sh +++ b/install.sh @@ -9,6 +9,7 @@ ACTION_CONFIRM="${2:-}" ISSUE_URL="https://github.com/${REPO}/issues" LOG_FILE="${CLICD_LOG_FILE:-/var/log/clicd-install.log}" INSTALL_DOWNLOAD_MARKER="${CLICD_INSTALL_DOWNLOAD_MARKER:-/tmp/clicd-install-dir.$$}" +LIBVIRT_DEFAULT_MARKER="/var/lib/clicd/kvm/default-network.created" echo "=====================================" echo " CLICD 中文安装/卸载脚本" @@ -293,6 +294,48 @@ destroy_clicd_kvm_domains() { done } +domain_is_clicd_kvm() { + domain="$1" + case "$domain" in + vm-[0-9]*) + return 0 + ;; + esac + virsh dumpxml "$domain" 2>/dev/null | grep -q '/var/lib/clicd/kvm/' +} + +libvirt_default_used_by_non_clicd_domain() { + if ! has_cmd virsh; then + return 1 + fi + + for domain in $(virsh list --all --name 2>/dev/null); do + [ -n "$domain" ] || continue + if domain_is_clicd_kvm "$domain"; then + continue + fi + if virsh domiflist "$domain" 2>/dev/null | awk '$3 == "default" || $3 == "virbr0" {found = 1} END {exit found ? 0 : 1}'; then + return 0 + fi + done + return 1 +} + +remove_clicd_libvirt_default_network() { + if ! has_cmd virsh || [ ! -f "$LIBVIRT_DEFAULT_MARKER" ]; then + return + fi + if libvirt_default_used_by_non_clicd_domain; then + warn "检测到非 CLICD 虚拟机仍在使用 libvirt default 网络,已保留 default/virbr0。" + return + fi + + log "正在删除 CLICD 创建的 libvirt default NAT 网络..." + virsh net-destroy default >/dev/null 2>&1 || true + virsh net-undefine default >/dev/null 2>&1 || true + rm -f "$LIBVIRT_DEFAULT_MARKER" +} + delete_iptables_lines() { table="$1" chain="$2" @@ -593,6 +636,7 @@ uninstall_clicd() { done remove_clicd_lxc_image_cache destroy_clicd_kvm_domains + remove_clicd_libvirt_default_network cleanup_clicd_networking remove_clicd_host_hooks remove_clicd_quota_records @@ -943,6 +987,8 @@ setup_default_libvirt_network() { EOF virsh net-define "$net_xml" rm -f "$net_xml" + mkdir -p "$(dirname "$LIBVIRT_DEFAULT_MARKER")" + touch "$LIBVIRT_DEFAULT_MARKER" fi if ! libvirt_network_active; then virsh net-start default