2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 06:47:20 +08:00
Files
panel/pkg/ntp/system.go
Copilot 1f55c2448d feat: add NTP server configuration and manual sync server option (#1232)
* Initial plan

* feat: add NTP server configuration support for time sync

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: add system NTP server configuration with chrony and timesyncd support

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* fix: improve NTP service restart error handling

Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>

* feat: 优化ntp

* fix: logo跳转

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devhaozi <115467771+devhaozi@users.noreply.github.com>
Co-authored-by: 耗子 <haozi@loli.email>
2026-01-13 21:24:58 +08:00

300 lines
7.2 KiB
Go

package ntp
import (
"bufio"
"fmt"
"regexp"
"strings"
"github.com/acepanel/panel/pkg/io"
"github.com/acepanel/panel/pkg/shell"
)
// NTPServiceType 表示系统使用的 NTP 服务类型
type NTPServiceType string
const (
NTPServiceTimesyncd NTPServiceType = "timesyncd" // systemd-timesyncd (Debian/Ubuntu)
NTPServiceChrony NTPServiceType = "chrony" // chrony (RHEL/CentOS/Rocky)
NTPServiceUnknown NTPServiceType = "unknown" // 未知或不支持
)
// timesyncd 配置文件路径
const timesyncdConfigPath = "/etc/systemd/timesyncd.conf"
// chrony 配置文件路径(按优先级排序)
var chronyConfigPaths = []string{
"/etc/chrony.conf",
"/etc/chrony/chrony.conf",
}
// SystemNTPConfig 系统 NTP 配置信息
type SystemNTPConfig struct {
ServiceType NTPServiceType `json:"service_type"` // 服务类型
Servers []string `json:"servers"` // NTP 服务器列表
}
// DetectNTPService 检测系统使用的 NTP 服务类型
func DetectNTPService() NTPServiceType {
// 优先检查 chrony
if _, err := shell.Execf("systemctl is-active chronyd 2>/dev/null"); err == nil {
return NTPServiceChrony
}
if _, err := shell.Execf("systemctl is-active chrony 2>/dev/null"); err == nil {
return NTPServiceChrony
}
// 检查 systemd-timesyncd
if _, err := shell.Execf("systemctl is-active systemd-timesyncd 2>/dev/null"); err == nil {
return NTPServiceTimesyncd
}
// 检查配置文件是否存在
for _, path := range chronyConfigPaths {
if io.Exists(path) {
return NTPServiceChrony
}
}
if io.Exists(timesyncdConfigPath) {
return NTPServiceTimesyncd
}
return NTPServiceUnknown
}
// GetSystemNTPConfig 获取系统 NTP 配置
func GetSystemNTPConfig() (*SystemNTPConfig, error) {
serviceType := DetectNTPService()
config := &SystemNTPConfig{
ServiceType: serviceType,
Servers: []string{},
}
switch serviceType {
case NTPServiceTimesyncd:
servers, err := getTimesyncdServers()
if err != nil {
return config, err
}
config.Servers = servers
case NTPServiceChrony:
servers, err := getChronyServers()
if err != nil {
return config, err
}
config.Servers = servers
}
return config, nil
}
// SetSystemNTPServers 设置系统 NTP 服务器
func SetSystemNTPServers(servers []string) error {
serviceType := DetectNTPService()
switch serviceType {
case NTPServiceTimesyncd:
return setTimesyncdServers(servers)
case NTPServiceChrony:
return setChronyServers(servers)
default:
return fmt.Errorf("unsupported NTP service type")
}
}
// getTimesyncdServers 获取 systemd-timesyncd 的 NTP 服务器配置
func getTimesyncdServers() ([]string, error) {
if !io.Exists(timesyncdConfigPath) {
return []string{}, nil
}
content, err := io.Read(timesyncdConfigPath)
if err != nil {
return nil, err
}
// 解析配置文件,查找 NTP= 行
var servers []string
scanner := bufio.NewScanner(strings.NewReader(content))
ntpRegex := regexp.MustCompile(`^\s*NTP\s*=\s*(.+)$`)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
continue
}
if matches := ntpRegex.FindStringSubmatch(line); len(matches) > 1 {
// NTP 服务器以空格分隔
for _, server := range strings.Fields(matches[1]) {
if server != "" {
servers = append(servers, server)
}
}
}
}
return servers, nil
}
// setTimesyncdServers 设置 systemd-timesyncd 的 NTP 服务器配置
func setTimesyncdServers(servers []string) error {
var content string
if io.Exists(timesyncdConfigPath) {
var err error
content, err = io.Read(timesyncdConfigPath)
if err != nil {
return err
}
}
// 构建新的 NTP 配置行
ntpLine := "NTP=" + strings.Join(servers, " ")
// 检查是否已有 [Time] 段和 NTP= 行
hasTimeSection := strings.Contains(content, "[Time]")
ntpRegex := regexp.MustCompile(`(?m)^\s*#?\s*NTP\s*=.*$`)
if ntpRegex.MatchString(content) {
// 替换现有的 NTP= 行
content = ntpRegex.ReplaceAllString(content, ntpLine)
} else if hasTimeSection {
// 在 [Time] 段后添加 NTP= 行
content = strings.Replace(content, "[Time]", "[Time]\n"+ntpLine, 1)
} else {
// 添加 [Time] 段和 NTP= 行
if content != "" && !strings.HasSuffix(content, "\n") {
content += "\n"
}
content += "[Time]\n" + ntpLine + "\n"
}
// 写入配置文件
if err := io.Write(timesyncdConfigPath, content, 0644); err != nil {
return err
}
// 重启 systemd-timesyncd 服务
_, _ = shell.Execf("systemctl restart systemd-timesyncd 2>/dev/null")
return nil
}
// getChronyServers 获取 chrony 的 NTP 服务器配置
func getChronyServers() ([]string, error) {
var configPath string
for _, path := range chronyConfigPaths {
if io.Exists(path) {
configPath = path
break
}
}
if configPath == "" {
return []string{}, nil
}
content, err := io.Read(configPath)
if err != nil {
return nil, err
}
// 解析配置文件,查找 server 或 pool 行
var servers []string
scanner := bufio.NewScanner(strings.NewReader(content))
serverRegex := regexp.MustCompile(`^\s*(server|pool)\s+(\S+)`)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
continue
}
if matches := serverRegex.FindStringSubmatch(line); len(matches) > 2 {
servers = append(servers, matches[2])
}
}
return servers, nil
}
// setChronyServers 设置 chrony 的 NTP 服务器配置
func setChronyServers(servers []string) error {
var configPath string
for _, path := range chronyConfigPaths {
if io.Exists(path) {
configPath = path
break
}
}
if configPath == "" {
// 如果配置文件不存在,使用默认路径
configPath = chronyConfigPaths[0]
}
var content string
if io.Exists(configPath) {
var err error
content, err = io.Read(configPath)
if err != nil {
return err
}
}
// 移除现有的 server 和 pool 行
var newLines []string
scanner := bufio.NewScanner(strings.NewReader(content))
serverRegex := regexp.MustCompile(`^\s*(server|pool)\s+`)
for scanner.Scan() {
line := scanner.Text()
if !serverRegex.MatchString(line) {
newLines = append(newLines, line)
}
}
// 在文件开头添加新的 server 行
var serverLines []string
for _, server := range servers {
serverLines = append(serverLines, fmt.Sprintf("server %s iburst", server))
}
// 组合新内容
newContent := strings.Join(serverLines, "\n")
if len(newLines) > 0 {
newContent += "\n" + strings.Join(newLines, "\n")
}
if !strings.HasSuffix(newContent, "\n") {
newContent += "\n"
}
// 写入配置文件
if err := io.Write(configPath, newContent, 0644); err != nil {
return err
}
// 重启 chrony 服务
_, _ = shell.Execf("systemctl restart chronyd 2>/dev/null")
_, _ = shell.Execf("systemctl restart chrony 2>/dev/null")
return nil
}
// RestartNTPService 重启 NTP 服务
func RestartNTPService() error {
serviceType := DetectNTPService()
switch serviceType {
case NTPServiceTimesyncd:
_, err := shell.Execf("systemctl restart systemd-timesyncd")
return err
case NTPServiceChrony:
if _, err := shell.Execf("systemctl restart chronyd 2>/dev/null"); err != nil {
_, err = shell.Execf("systemctl restart chrony")
return err
}
return nil
default:
return fmt.Errorf("unsupported NTP service type")
}
}