From 7613d9135bfa6b7d6b3e1c8c127cb0ff40877945 Mon Sep 17 00:00:00 2001 From: MengMengCode <227010654+MengMengCode@users.noreply.github.com> Date: Fri, 5 Jun 2026 19:58:42 +0800 Subject: [PATCH] Sync CLI and Web container state --- .gitignore | 1 + backend/internal/cli/cli.go | 59 +++++++++++++++++++++++-- backend/internal/lxc/lxc.go | 87 +++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f631566..baf5dff 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ backend/tmp/ # OS .DS_Store Thumbs.db +linux.txt diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index a13d2f0..1a1233f 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -19,9 +19,13 @@ func Run() { reader := bufio.NewReader(os.Stdin) for { + if _, err := config.InitConfig(); err != nil { + fmt.Printf("Failed to reload config: %v\n", err) + waitEnter(reader) + } clearScreen() printMenu() - fmt.Print("\nSelect action [1-10,0/q]: ") + fmt.Print("\nSelect action [1-11,0/q]: ") input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) @@ -63,6 +67,10 @@ func Run() { cliToggleWebPanel() waitEnter(reader) case "10": + clearScreen() + cliImportExistingContainers() + waitEnter(reader) + case "11": clearScreen() cliUninstall(reader) return @@ -105,7 +113,8 @@ 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. Uninstall CLICD") + fmt.Println(" 10. Import existing ct-* containers") + fmt.Println(" 11. Uninstall CLICD") fmt.Println(" 0. System info") fmt.Println(" q. Quit") } @@ -179,6 +188,7 @@ func cliCreateContainer(reader *bufio.Reader) { if container != nil { fmt.Printf("SSH: root / %s, port %d -> 22\n", container.SSHPassword, container.SSHPort) } + restartWebPanelForConfigChange() } func cliStartContainer(reader *bufio.Reader) { @@ -232,6 +242,7 @@ func cliDeleteContainer(reader *bufio.Reader) { return } fmt.Printf("Container %s deleted\n", name) + restartWebPanelForConfigChange() } func cliReinstallContainer(reader *bufio.Reader) { @@ -263,6 +274,7 @@ func cliReinstallContainer(reader *bufio.Reader) { return } fmt.Printf("Container %s reinstalled\n", name) + restartWebPanelForConfigChange() } func cliResetPassword(reader *bufio.Reader) { @@ -281,7 +293,8 @@ func cliResetPassword(reader *bufio.Reader) { fmt.Printf("Reset failed: %v\n", err) return } - fmt.Println("Admin password reset. Restart the web service for it to take effect.") + fmt.Println("Admin password reset.") + restartWebPanelForConfigChange() } func cliToggleWebPanel() { @@ -316,6 +329,28 @@ func isWebPanelRunning() bool { return false } +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}.") + + imported, err := manager.ImportExistingClicdContainers() + if err != nil { + fmt.Printf("Import failed: %v\n", err) + return + } + if len(imported) == 0 { + fmt.Println("No new ct-* containers found to import.") + return + } + + fmt.Printf("Imported %d container(s):\n", len(imported)) + for _, c := range imported { + fmt.Printf(" [%d] %s [%s]\n", c.ID, c.Name, c.Status) + } + restartWebPanelForConfigChange() +} + func cliUninstall(reader *bufio.Reader) { fmt.Println("\n--- Uninstall CLICD ---") fmt.Println("This removes the CLICD service and /usr/local/bin/clicd.") @@ -421,6 +456,14 @@ func runQuiet(name string, args ...string) { _ = exec.Command(name, args...).Run() } +func restartWebPanelForConfigChange() { + if err := restartService("clicd"); err != nil { + fmt.Printf("Web panel reload skipped: %v\n", err) + return + } + fmt.Println("Web panel reloaded to pick up config changes.") +} + func stopService(name string) error { if commandExists("systemctl") { return exec.Command("systemctl", "stop", name).Run() @@ -441,6 +484,16 @@ func startService(name string) error { return fmt.Errorf("no supported service manager found") } +func restartService(name string) error { + if commandExists("systemctl") { + return exec.Command("systemctl", "restart", name).Run() + } + if commandExists("rc-service") { + return exec.Command("rc-service", name, "restart").Run() + } + return fmt.Errorf("no supported service manager found") +} + func cliShowInfo() { containers, err := manager.ListContainers() if err != nil { diff --git a/backend/internal/lxc/lxc.go b/backend/internal/lxc/lxc.go index 53c62c8..2eddf06 100644 --- a/backend/internal/lxc/lxc.go +++ b/backend/internal/lxc/lxc.go @@ -1915,6 +1915,93 @@ 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. +func (m *Manager) ImportExistingClicdContainers() ([]config.Container, error) { + entries, err := os.ReadDir(m.LxcPath) + if err != nil { + return nil, err + } + + existingIDs := make(map[int]bool) + existingNames := make(map[string]bool) + maxID := config.AppConfig.NextContainerID - 1 + for _, c := range config.AppConfig.Containers { + existingIDs[c.ID] = true + existingNames[c.Name] = true + if c.ID > maxID { + maxID = c.ID + } + } + + re := regexp.MustCompile(`^ct-([0-9]+)$`) + imported := make([]config.Container, 0) + for _, entry := range entries { + if !entry.IsDir() { + continue + } + matches := re.FindStringSubmatch(entry.Name()) + if len(matches) != 2 { + continue + } + + id, err := strconv.Atoi(matches[1]) + if err != nil || id <= 0 || existingIDs[id] { + continue + } + + name := entry.Name() + if existingNames[name] { + name = fmt.Sprintf("imported-%d", id) + } + + status, err := m.GetContainerStatus(entry.Name()) + if err != nil || status == "" { + status = "unknown" + } + + c := config.Container{ + ID: id, + UUID: config.NewContainerUUID(), + Name: name, + Template: "imported", + VCPU: 1, + RAMMB: 512, + DiskGB: 10, + NetworkBWMbps: 100, + MonthlyTrafficGB: 1000, + TrafficMode: "total", + Status: status, + CreatedAt: time.Now().Format(time.RFC3339), + PortMappingLimit: 2, + } + + if status == "running" { + if ip, err := m.GetContainerIP(entry.Name()); err == nil { + c.IP = ip + } + } + + config.AppConfig.Containers = append(config.AppConfig.Containers, c) + imported = append(imported, c) + existingIDs[id] = true + existingNames[name] = true + if id > maxID { + maxID = id + } + } + + if len(imported) > 0 { + config.AppConfig.NextContainerID = maxID + 1 + if err := config.SaveConfig(); err != nil { + return nil, err + } + } + + return imported, nil +} + // ReinstallContainer reinstalls the container OS func (m *Manager) ReinstallContainer(id int, templateID string) error { c := config.FindContainer(id)