From 49a6981e28b3f23c657fb04306634f13a4842969 Mon Sep 17 00:00:00 2001 From: MengMengCode <227010654+MengMengCode@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:01:24 +0800 Subject: [PATCH] Import existing LXC containers into CLICD --- backend/internal/cli/cli.go | 8 ++++---- backend/internal/config/config.go | 4 ++++ backend/internal/lxc/lxc.go | 34 +++++++++++++++++++++---------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index 1a1233f..f74c7db 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -113,7 +113,7 @@ func printMenu() { fmt.Println(" 7. Reinstall container") fmt.Println(" 8. Reset web admin password") fmt.Printf(" 9. %s web panel\n", webStatus) - fmt.Println(" 10. Import existing ct-* containers") + fmt.Println(" 10. Import existing LXC containers") fmt.Println(" 11. Uninstall CLICD") fmt.Println(" 0. System info") fmt.Println(" q. Quit") @@ -330,9 +330,9 @@ func isWebPanelRunning() bool { } func cliImportExistingContainers() { - fmt.Println("\n--- Import existing CLICD containers ---") - fmt.Println("This imports LXC containers named ct-{id} from /var/lib/lxc into CLICD config.") - fmt.Println("Containers with names like ubuntu or alpine are skipped because CLICD requires ct-{id}.") + fmt.Println("\n--- Import existing LXC containers ---") + fmt.Println("This imports containers from /var/lib/lxc into CLICD config.") + fmt.Println("Imported containers keep their real LXC name so Web and CLI manage the same container.") imported, err := manager.ImportExistingClicdContainers() if err != nil { diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 25c09e5..941a434 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -68,6 +68,7 @@ type Container struct { ID int `json:"id"` UUID string `json:"uuid"` Name string `json:"name"` + LXCName string `json:"lxc_name,omitempty"` Template string `json:"template"` VCPU float64 `json:"vcpu"` RAMMB int `json:"ram_mb"` @@ -97,6 +98,9 @@ type Container struct { // LxcName returns the internal LXC container name (ct-{id}) func (c *Container) LxcName() string { + if c.LXCName != "" { + return c.LXCName + } return fmt.Sprintf("ct-%d", c.ID) } diff --git a/backend/internal/lxc/lxc.go b/backend/internal/lxc/lxc.go index 2eddf06..89e4eba 100644 --- a/backend/internal/lxc/lxc.go +++ b/backend/internal/lxc/lxc.go @@ -1915,9 +1915,9 @@ func (m *Manager) ListContainers() ([]config.Container, error) { return containers, nil } -// ImportExistingClicdContainers imports LXC containers named ct-{id} into the -// CLICD config. Containers with arbitrary LXC names cannot be imported because -// CLICD derives the runtime LXC name from the numeric container ID. +// ImportExistingClicdContainers imports existing LXC containers into the CLICD +// config. Native CLICD containers keep ct-{id}; arbitrary LXC names are stored +// in Container.LXCName so Web and CLI can manage the same imported container. func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { entries, err := os.ReadDir(m.LxcPath) if err != nil { @@ -1926,10 +1926,12 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { existingIDs := make(map[int]bool) existingNames := make(map[string]bool) + existingLXCNames := make(map[string]bool) maxID := config.AppConfig.NextContainerID - 1 for _, c := range config.AppConfig.Containers { existingIDs[c.ID] = true existingNames[c.Name] = true + existingLXCNames[c.LxcName()] = true if c.ID > maxID { maxID = c.ID } @@ -1941,22 +1943,30 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { if !entry.IsDir() { continue } - matches := re.FindStringSubmatch(entry.Name()) - if len(matches) != 2 { + lxcName := entry.Name() + if existingLXCNames[lxcName] { continue } - id, err := strconv.Atoi(matches[1]) - if err != nil || id <= 0 || existingIDs[id] { - continue + id := 0 + if matches := re.FindStringSubmatch(lxcName); len(matches) == 2 { + if parsed, err := strconv.Atoi(matches[1]); err == nil && parsed > 0 && !existingIDs[parsed] { + id = parsed + } + } + if id == 0 { + id = maxID + 1 + for existingIDs[id] { + id++ + } } - name := entry.Name() + name := lxcName if existingNames[name] { name = fmt.Sprintf("imported-%d", id) } - status, err := m.GetContainerStatus(entry.Name()) + status, err := m.GetContainerStatus(lxcName) if err != nil || status == "" { status = "unknown" } @@ -1965,6 +1975,7 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { ID: id, UUID: config.NewContainerUUID(), Name: name, + LXCName: lxcName, Template: "imported", VCPU: 1, RAMMB: 512, @@ -1978,7 +1989,7 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { } if status == "running" { - if ip, err := m.GetContainerIP(entry.Name()); err == nil { + if ip, err := m.GetContainerIP(lxcName); err == nil { c.IP = ip } } @@ -1987,6 +1998,7 @@ func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { imported = append(imported, c) existingIDs[id] = true existingNames[name] = true + existingLXCNames[lxcName] = true if id > maxID { maxID = id }