mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-04 21:31:23 +08:00
Fix some problem.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"clicd/internal/config"
|
||||
"clicd/internal/kvm"
|
||||
"clicd/internal/lxc"
|
||||
)
|
||||
|
||||
// CaptureRuntimeRestoreState records which managed workloads are actually
|
||||
// running before the CLICD service exits. On the next host boot, only those
|
||||
// workloads are started again.
|
||||
func CaptureRuntimeRestoreState() {
|
||||
if config.AppConfig == nil {
|
||||
return
|
||||
}
|
||||
lxcManager := lxc.NewManager()
|
||||
kvmManager := kvm.NewManager()
|
||||
changed := false
|
||||
|
||||
for i := range config.AppConfig.Containers {
|
||||
c := &config.AppConfig.Containers[i]
|
||||
status, err := runtimeStatus(*c, lxcManager, kvmManager)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to capture runtime state for %s: %v\n", c.Name, err)
|
||||
continue
|
||||
}
|
||||
restore := status == "running"
|
||||
if c.RestoreOnHostBoot != restore {
|
||||
c.RestoreOnHostBoot = restore
|
||||
changed = true
|
||||
}
|
||||
if status != "" && c.Status != status {
|
||||
c.Status = status
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
if err := config.SaveConfig(); err != nil {
|
||||
fmt.Printf("Warning: failed to save host boot restore state: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartHostBootRestore() {
|
||||
go RestoreHostBootState()
|
||||
}
|
||||
|
||||
func RestoreHostBootState() {
|
||||
if config.AppConfig == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
lxcManager := lxc.NewManager()
|
||||
kvmManager := kvm.NewManager()
|
||||
containers := append([]config.Container(nil), config.AppConfig.Containers...)
|
||||
|
||||
for _, c := range containers {
|
||||
if !c.RestoreOnHostBoot {
|
||||
continue
|
||||
}
|
||||
if c.PolicyBlocked {
|
||||
fmt.Printf("Skipping host boot restore for %s: policy blocked\n", c.Name)
|
||||
continue
|
||||
}
|
||||
if lxc.IsExpired(c) {
|
||||
fmt.Printf("Skipping host boot restore for %s: expired at %s\n", c.Name, c.ExpiresAt)
|
||||
continue
|
||||
}
|
||||
|
||||
status, err := runtimeStatus(c, lxcManager, kvmManager)
|
||||
if err == nil && status == "running" {
|
||||
config.UpdateContainerStatusAndRestore(c.ID, "running", true)
|
||||
if !c.IsKVM() {
|
||||
_ = lxcManager.ApplyPortMappings(c.ID)
|
||||
} else {
|
||||
_ = lxc.NewManager().ApplyPortMappings(c.ID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Restoring workload after host boot: %s (ID=%d)\n", c.Name, c.ID)
|
||||
if c.IsKVM() {
|
||||
if err := kvmManager.StartContainer(c.ID); err != nil {
|
||||
fmt.Printf("Warning: failed to restore KVM %s: %v\n", c.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := lxcManager.StartContainer(c.ID); err != nil {
|
||||
fmt.Printf("Warning: failed to restore LXC %s: %v\n", c.Name, err)
|
||||
}
|
||||
}
|
||||
lxc.EnsureAllRunningPortMappings()
|
||||
}
|
||||
|
||||
func runtimeStatus(c config.Container, lxcManager *lxc.Manager, kvmManager *kvm.Manager) (string, error) {
|
||||
if c.IsKVM() {
|
||||
return kvmManager.GetContainerStatus(c.VirshName())
|
||||
}
|
||||
return lxcManager.GetContainerStatus(c.LxcName())
|
||||
}
|
||||
@@ -128,6 +128,7 @@ type Container struct {
|
||||
IOReadMBps int `json:"io_read_mbps"`
|
||||
IOWriteMBps int `json:"io_write_mbps"`
|
||||
Status string `json:"status"`
|
||||
RestoreOnHostBoot bool `json:"restore_on_host_boot,omitempty"`
|
||||
IP string `json:"ip"`
|
||||
LANIPv4Mode string `json:"lan_ipv4_mode,omitempty"`
|
||||
LANInterface string `json:"lan_interface,omitempty"`
|
||||
@@ -1218,6 +1219,23 @@ func UpdateContainerStatus(id int, status string) {
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateContainerStatusAndRestore(id int, status string, restoreOnHostBoot bool) {
|
||||
c := FindContainer(id)
|
||||
if c != nil {
|
||||
c.Status = status
|
||||
c.RestoreOnHostBoot = restoreOnHostBoot
|
||||
SaveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func SetContainerRestoreOnHostBoot(id int, restore bool) {
|
||||
c := FindContainer(id)
|
||||
if c != nil {
|
||||
c.RestoreOnHostBoot = restore
|
||||
SaveConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func SetContainerPolicyBlock(id int, blocked bool, reason string) {
|
||||
c := FindContainer(id)
|
||||
if c == nil {
|
||||
|
||||
@@ -209,6 +209,7 @@ func ensureSchema() error {
|
||||
io_read_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
io_write_mbps INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT,
|
||||
restore_on_host_boot INTEGER NOT NULL DEFAULT 0,
|
||||
ip TEXT,
|
||||
lan_ipv4_mode TEXT,
|
||||
lan_interface TEXT,
|
||||
@@ -459,6 +460,7 @@ func ensureSchemaMigrations() error {
|
||||
{"containers", "firewall_rules", "TEXT"},
|
||||
{"containers", "allowed_image_ids", "TEXT"},
|
||||
{"containers", "image_limit_configured", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "restore_on_host_boot", "INTEGER NOT NULL DEFAULT 0"},
|
||||
{"containers", "lan_ipv4_mode", "TEXT"},
|
||||
{"containers", "lan_interface", "TEXT"},
|
||||
{"containers", "lan_ipv4_address", "TEXT NOT NULL DEFAULT ''"},
|
||||
@@ -739,20 +741,20 @@ func saveContainers(tx *sql.Tx) error {
|
||||
monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date,
|
||||
io_speed_mbps, io_read_mbps, io_write_mbps,
|
||||
status, ip, lan_ipv4_mode, lan_interface, lan_ipv4_address, lan_ipv4_prefix_len, lan_ipv4_gateway,
|
||||
status, restore_on_host_boot, ip, lan_ipv4_mode, lan_interface, lan_ipv4_address, lan_ipv4_prefix_len, lan_ipv4_gateway,
|
||||
ipv6, ipv6_prefix_len, ipv6_interface, vnc_port, ssh_port, ssh_password,
|
||||
ssh_host_key, port_mapping_limit, snapshot_limit, created_at, expires_at,
|
||||
snapshot_schedule_enabled, snapshot_schedule_interval_hours, snapshot_schedule_time,
|
||||
snapshot_schedule_last_run, snapshot_schedule_next_run, snapshot_schedule_created_by,
|
||||
policy_blocked, policy_blocked_reason, policy_blocked_at,
|
||||
firewall_enabled, firewall_default_action, firewall_rules, allowed_image_ids, image_limit_configured
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
c.ID, c.UUID, c.Name, c.Virtualization, c.LXCName, c.KVMName, c.DiskImage, c.MACAddress, c.Template,
|
||||
c.VCPU, c.RAMMB, c.DiskGB, c.NetworkBWMbps, c.NetworkDownMbps, c.NetworkUpMbps,
|
||||
c.MonthlyTrafficGB, c.TrafficMode, c.TrafficInGB,
|
||||
c.TrafficOutGB, c.TrafficUsedRX, c.TrafficUsedTX, c.TrafficResetDate,
|
||||
c.IOSpeedMBps, c.IOReadMBps, c.IOWriteMBps,
|
||||
c.Status, c.IP, c.LANIPv4Mode, c.LANInterface, c.LANIPv4Address, c.LANIPv4PrefixLen, c.LANIPv4Gateway,
|
||||
c.Status, boolInt(c.RestoreOnHostBoot), c.IP, c.LANIPv4Mode, c.LANInterface, c.LANIPv4Address, c.LANIPv4PrefixLen, c.LANIPv4Gateway,
|
||||
c.IPv6, c.IPv6PrefixLen, c.IPv6Interface, c.VNCPort, c.SSHPort, c.SSHPassword,
|
||||
c.SSHHostKey, c.PortMappingLimit, c.SnapshotLimit, c.CreatedAt, c.ExpiresAt,
|
||||
boolInt(c.SnapshotScheduleEnabled), c.SnapshotScheduleIntervalHours, c.SnapshotScheduleTime,
|
||||
@@ -960,7 +962,7 @@ func loadContainers() ([]Container, error) {
|
||||
monthly_traffic_gb, traffic_mode, traffic_in_gb,
|
||||
traffic_out_gb, traffic_used_rx, traffic_used_tx, traffic_reset_date,
|
||||
io_speed_mbps, io_read_mbps, io_write_mbps,
|
||||
status, ip, lan_ipv4_mode, lan_interface, lan_ipv4_address, lan_ipv4_prefix_len, lan_ipv4_gateway,
|
||||
status, restore_on_host_boot, ip, lan_ipv4_mode, lan_interface, lan_ipv4_address, lan_ipv4_prefix_len, lan_ipv4_gateway,
|
||||
ipv6, ipv6_prefix_len, ipv6_interface, vnc_port, ssh_port, ssh_password,
|
||||
ssh_host_key, port_mapping_limit, snapshot_limit, created_at, expires_at,
|
||||
snapshot_schedule_enabled, snapshot_schedule_interval_hours, snapshot_schedule_time,
|
||||
@@ -976,7 +978,7 @@ func loadContainers() ([]Container, error) {
|
||||
result := []Container{}
|
||||
for rows.Next() {
|
||||
var c Container
|
||||
var scheduleEnabled, policyBlocked, firewallEnabled, imageLimitConfigured int
|
||||
var scheduleEnabled, policyBlocked, firewallEnabled, imageLimitConfigured, restoreOnHostBoot int
|
||||
var firewallDefaultAction string
|
||||
var firewallRulesJSON, allowedImageIDs sql.NullString
|
||||
var lanIPv4Mode, lanInterface sql.NullString
|
||||
@@ -988,7 +990,7 @@ func loadContainers() ([]Container, error) {
|
||||
&c.MonthlyTrafficGB, &c.TrafficMode, &c.TrafficInGB,
|
||||
&c.TrafficOutGB, &c.TrafficUsedRX, &c.TrafficUsedTX, &c.TrafficResetDate,
|
||||
&c.IOSpeedMBps, &c.IOReadMBps, &c.IOWriteMBps,
|
||||
&c.Status, &c.IP, &lanIPv4Mode, &lanInterface, &lanIPv4Address, &lanIPv4PrefixLen, &lanIPv4Gateway,
|
||||
&c.Status, &restoreOnHostBoot, &c.IP, &lanIPv4Mode, &lanInterface, &lanIPv4Address, &lanIPv4PrefixLen, &lanIPv4Gateway,
|
||||
&c.IPv6, &c.IPv6PrefixLen, &c.IPv6Interface, &c.VNCPort, &c.SSHPort, &c.SSHPassword,
|
||||
&c.SSHHostKey, &c.PortMappingLimit, &c.SnapshotLimit, &c.CreatedAt, &c.ExpiresAt,
|
||||
&scheduleEnabled, &c.SnapshotScheduleIntervalHours, &c.SnapshotScheduleTime,
|
||||
@@ -1006,6 +1008,7 @@ func loadContainers() ([]Container, error) {
|
||||
}
|
||||
c.LANIPv4Gateway = lanIPv4Gateway.String
|
||||
c.SnapshotScheduleEnabled = scheduleEnabled != 0
|
||||
c.RestoreOnHostBoot = restoreOnHostBoot != 0
|
||||
c.PolicyBlocked = policyBlocked != 0
|
||||
c.FirewallEnabled = firewallEnabled != 0
|
||||
c.FirewallDefaultAction = normalizeFirewallDefaultAction(firewallDefaultAction)
|
||||
|
||||
@@ -649,7 +649,7 @@ func (m *Manager) StartContainer(id int) error {
|
||||
if !isWindows && c.IP != "" {
|
||||
m.waitForCloudInitReady(name, c.IP, c.SSHPassword)
|
||||
}
|
||||
config.UpdateContainerStatus(id, "running")
|
||||
config.UpdateContainerStatusAndRestore(id, "running", true)
|
||||
if c.IPv6 != "" || len(c.IPv6Addresses) > 0 {
|
||||
if err := m.applyIPv6Runtime(c); err != nil {
|
||||
return err
|
||||
@@ -728,13 +728,13 @@ func (m *Manager) StopContainer(id int) error {
|
||||
name := c.VirshName()
|
||||
status, _ := m.GetContainerStatus(name)
|
||||
if status != "running" {
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
return nil
|
||||
}
|
||||
exec.Command("virsh", "shutdown", name).Run()
|
||||
for i := 0; i < 20; i++ {
|
||||
if status, _ := m.GetContainerStatus(name); status != "running" {
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
return nil
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
@@ -743,7 +743,7 @@ func (m *Manager) StopContainer(id int) error {
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("virsh destroy failed: %v, output: %s", err, string(output))
|
||||
}
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1730,7 +1730,7 @@ func (m *Manager) StartContainer(id int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
config.UpdateContainerStatus(id, "running")
|
||||
config.UpdateContainerStatusAndRestore(id, "running", true)
|
||||
|
||||
var ip string
|
||||
for retry := 0; retry < 10; retry++ {
|
||||
@@ -1951,7 +1951,7 @@ func (m *Manager) StopContainer(id int) error {
|
||||
|
||||
status, _ := m.GetContainerStatus(lxcName)
|
||||
if status != "running" {
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
m.CleanPortMappings(id)
|
||||
CleanFirewallRules(id)
|
||||
m.cleanupBandwidthLimit(lxcName)
|
||||
@@ -1966,13 +1966,13 @@ func (m *Manager) StopContainer(id int) error {
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if strings.Contains(string(output), "not running") {
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to stop container: %v, output: %s", err, string(output))
|
||||
}
|
||||
|
||||
config.UpdateContainerStatus(id, "stopped")
|
||||
config.UpdateContainerStatusAndRestore(id, "stopped", false)
|
||||
fmt.Printf("Container %d (%s) stopped\n", id, c.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"clicd/internal/api"
|
||||
"clicd/internal/cli"
|
||||
@@ -16,6 +19,8 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var shutdownCaptureOnce sync.Once
|
||||
|
||||
func main() {
|
||||
isTerminal := term.IsTerminal(int(os.Stdin.Fd()))
|
||||
|
||||
@@ -44,6 +49,8 @@ func main() {
|
||||
_ = cfg
|
||||
|
||||
if isServerMode || (!isTerminal && !isCliMode) {
|
||||
installShutdownStateCapture()
|
||||
|
||||
// Restore persisted state
|
||||
api.RestoreTasks()
|
||||
api.RestoreLoginLogs()
|
||||
@@ -75,6 +82,7 @@ func main() {
|
||||
|
||||
// Clean up stale container configs (LXC dir was deleted but config remains)
|
||||
config.CleanStaleContainers()
|
||||
api.StartHostBootRestore()
|
||||
lxc.EnsureAllRunningPortMappings()
|
||||
|
||||
// Pre-warm SSH for containers already running after host boot or service restart.
|
||||
@@ -97,6 +105,17 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func installShutdownStateCapture() {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-signals
|
||||
fmt.Fprintf(os.Stderr, "Received %s, capturing workload restore state...\n", sig)
|
||||
shutdownCaptureOnce.Do(api.CaptureRuntimeRestoreState)
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func isWebPanelSystemdRunning() bool {
|
||||
cmd := exec.Command("systemctl", "is-active", "clicd")
|
||||
output, err := cmd.Output()
|
||||
|
||||
Reference in New Issue
Block a user