This commit is contained in:
2026-02-14 05:36:00 +08:00
commit d7cd899983
37 changed files with 4169 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
package system
import (
"fmt"
"os"
"os/exec"
"strings"
)
// SetHostname 设置系统主机名
// 参数: hostname - 目标主机名
// 返回: error - 如果设置失败返回错误信息
func SetHostname(hostname string) error {
if hostname == "" {
return nil // 空值跳过,不报错
}
// 检查是否已有相同主机名
current, err := os.Hostname()
if err == nil && current == hostname {
return nil // 已经设置正确,无需修改
}
// 使用 hostnamectl 设置主机名(适用于 systemd 系统)
if _, err := exec.LookPath("hostnamectl"); err == nil {
cmd := exec.Command("hostnamectl", "set-hostname", hostname)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("hostnamectl 设置主机名失败: %v", err)
}
} else {
// 传统方法:直接修改 /etc/hostname
if err := os.WriteFile("/etc/hostname", []byte(hostname+"\n"), 0644); err != nil {
return fmt.Errorf("写入 /etc/hostname 失败: %v", err)
}
// 立即生效(需要内核支持)
cmd := exec.Command("sysctl", "kernel.hostname="+hostname)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
// 不返回错误,因为重启后会生效
fmt.Printf("警告: 主机名将在重启后完全生效\n")
}
}
// 更新 /etc/hosts确保本机解析正确
if err := updateHostsFile(hostname); err != nil {
fmt.Printf("警告: 更新 /etc/hosts 失败: %v\n", err)
}
return nil
}
// updateHostsFile 更新 /etc/hosts 文件中的本机映射
func updateHostsFile(hostname string) error {
content, err := os.ReadFile("/etc/hosts")
if err != nil {
return err
}
lines := strings.Split(string(content), "\n")
newLines := make([]string, 0, len(lines))
hostnameSet := false
for _, line := range lines {
// 跳过空行和注释
if line == "" || strings.HasPrefix(line, "#") {
newLines = append(newLines, line)
continue
}
fields := strings.Fields(line)
if len(fields) >= 2 && fields[0] == "127.0.1.1" {
// 替换 Ubuntu/Debian 风格的本地主机名
newLines = append(newLines, "127.0.1.1\t"+hostname)
hostnameSet = true
} else if len(fields) >= 2 && fields[0] == "127.0.0.1" {
// 保留原行,但检查是否包含主机名
if !strings.Contains(line, hostname) {
line = line + " " + hostname
}
newLines = append(newLines, line)
hostnameSet = true
} else {
newLines = append(newLines, line)
}
}
// 如果没有找到合适的位置,添加一行
if !hostnameSet {
newLines = append(newLines, "127.0.1.1\t"+hostname)
}
return os.WriteFile("/etc/hosts", []byte(strings.Join(newLines, "\n")), 0644)
}

47
internal/system/motd.go Normal file
View File

@@ -0,0 +1,47 @@
package system
import (
"os"
"time"
)
// SetMOTD 设置 /etc/motd 文件内容
// 参数: content - MOTD 文本内容
// 返回: error - 写入文件错误
func SetMOTD(content string) error {
if content == "" {
// 如果内容为空,不清除现有 MOTD避免误操作
return nil
}
// 添加时间和系统信息
finalContent := "========================================\n"
finalContent += "SunHPC 集群管理系统\n"
finalContent += "时间: " + time.Now().Format("2006-01-02 15:04:05") + "\n"
finalContent += "========================================\n\n"
finalContent += content
// 确保行尾有换行
if content[len(content)-1] != '\n' {
finalContent += "\n"
}
return os.WriteFile("/etc/motd", []byte(finalContent), 0644)
}
// ClearMOTD 清空 MOTD
func ClearMOTD() error {
return os.WriteFile("/etc/motd", []byte{}, 0644)
}
// AppendToMOTD 追加内容到 MOTD
func AppendToMOTD(additional string) error {
f, err := os.OpenFile("/etc/motd", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(additional + "\n")
return err
}

View File

@@ -0,0 +1,97 @@
package system
import (
"fmt"
"os"
"os/exec"
"strings"
)
// ConfigureSELinux 设置 SELinux 模式
// 参数: mode - enforcing, permissive, disabled
// 返回: error - 配置错误
func ConfigureSELinux(mode string) error {
if mode == "" {
return nil
}
// 验证输入
mode = strings.ToLower(strings.TrimSpace(mode))
validModes := map[string]bool{
"enforcing": true,
"permissive": true,
"disabled": true,
}
if !validModes[mode] {
return fmt.Errorf("无效的 SELinux 模式: %s (可选: enforcing, permissive, disabled)", mode)
}
// 检查 SELinux 是否可用
if _, err := os.Stat("/selinux/enforce"); os.IsNotExist(err) {
if _, err := os.Stat("/sys/fs/selinux/enforce"); os.IsNotExist(err) {
return fmt.Errorf("系统不支持 SELinux 或未启用")
}
}
// 临时生效
if mode != "disabled" { // disabled 需要重启才能完全生效
cmd := exec.Command("setenforce", mode)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("setenforce %s 失败: %v", mode, err)
}
}
// 持久化配置
return persistSELinuxMode(mode)
}
// persistSELinuxMode 修改 /etc/selinux/config 实现持久化
func persistSELinuxMode(mode string) error {
const selinuxConfig = "/etc/selinux/config"
// 读取配置文件
content, err := os.ReadFile(selinuxConfig)
if err != nil {
// 如果文件不存在,创建默认配置
if os.IsNotExist(err) {
defaultConfig := fmt.Sprintf(`# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=%s
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
`, mode)
return os.WriteFile(selinuxConfig, []byte(defaultConfig), 0644)
}
return err
}
// 替换 SELINUX= 行
lines := strings.Split(string(content), "\n")
for i, line := range lines {
if strings.HasPrefix(line, "SELINUX=") {
lines[i] = fmt.Sprintf("SELINUX=%s", mode)
break
}
}
return os.WriteFile(selinuxConfig, []byte(strings.Join(lines, "\n")), 0644)
}
// GetSELinuxMode 获取当前 SELinux 模式
func GetSELinuxMode() (string, error) {
cmd := exec.Command("getenforce")
output, err := cmd.Output()
if err != nil {
return "", err
}
return strings.ToLower(strings.TrimSpace(string(output))), nil
}

187
internal/system/ssh.go Normal file
View File

@@ -0,0 +1,187 @@
package system
import (
"fmt"
"os"
"os/exec"
"strings"
"sunhpc/internal/config"
)
// ConfigureSSH 配置 SSH 服务
// 参数: cfg - config.SSHConfig 结构体
// 返回: error - 配置错误
func ConfigureSSH(cfg config.SSHConfig) error {
const sshdConfig = "/etc/ssh/sshd_config"
// 读取现有配置
content, err := os.ReadFile(sshdConfig)
if err != nil {
return fmt.Errorf("读取 sshd_config 失败: %v", err)
}
// 备份原始配置
backupPath := sshdConfig + ".sunhpc.bak"
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
if err := os.WriteFile(backupPath, content, 0644); err != nil {
fmt.Printf("警告: 无法创建备份文件 %s: %v\n", backupPath, err)
}
}
// 解析和修改配置
lines := strings.Split(string(content), "\n")
newLines := make([]string, 0, len(lines))
configMap := make(map[string]bool)
// 处理每一行
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
newLines = append(newLines, line)
continue
}
parts := strings.Fields(trimmed)
if len(parts) < 2 {
newLines = append(newLines, line)
continue
}
key := parts[0]
configMap[key] = true
// 根据配置更新
switch key {
case "PermitRootLogin":
if cfg.PermitRootLogin != "" {
newLines = append(newLines, fmt.Sprintf("PermitRootLogin %s", cfg.PermitRootLogin))
} else {
newLines = append(newLines, line)
}
case "PasswordAuthentication":
if cfg.PasswordAuth != "" {
newLines = append(newLines, fmt.Sprintf("PasswordAuthentication %s", cfg.PasswordAuth))
} else {
newLines = append(newLines, line)
}
default:
newLines = append(newLines, line)
}
}
// 添加缺失的配置项
if cfg.PermitRootLogin != "" && !configMap["PermitRootLogin"] {
newLines = append(newLines, fmt.Sprintf("PermitRootLogin %s", cfg.PermitRootLogin))
}
if cfg.PasswordAuth != "" && !configMap["PasswordAuthentication"] {
newLines = append(newLines, fmt.Sprintf("PasswordAuthentication %s", cfg.PasswordAuth))
}
// 写入新配置
newContent := strings.Join(newLines, "\n")
if err := os.WriteFile(sshdConfig, []byte(newContent), 0644); err != nil {
return fmt.Errorf("写入 sshd_config 失败: %v", err)
}
// 测试配置语法
if err := testSSHDConfig(); err != nil {
// 恢复备份
if backup, err := os.ReadFile(backupPath); err == nil {
os.WriteFile(sshdConfig, backup, 0644)
}
return fmt.Errorf("SSH 配置语法错误: %v", err)
}
// 重启 SSH 服务
return restartSSHD()
}
// testSSHDConfig 测试 sshd 配置语法
func testSSHDConfig() error {
cmd := exec.Command("sshd", "-t")
cmd.Stderr = os.Stderr
return cmd.Run()
}
// restartSSHD 重启 SSH 服务
func restartSSHD() error {
// 尝试不同的服务管理器
serviceMgrs := []struct {
name string
args []string
}{
{"systemctl", []string{"restart", "sshd"}},
{"systemctl", []string{"restart", "ssh"}},
{"service", []string{"sshd", "restart"}},
{"service", []string{"ssh", "restart"}},
}
for _, mgr := range serviceMgrs {
if _, err := exec.LookPath(mgr.name); err == nil {
cmd := exec.Command(mgr.name, mgr.args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err == nil {
return nil
}
}
}
return fmt.Errorf("无法重启 SSH 服务,请手动重启")
}
// AddSSHKey 添加 SSH 公钥到指定用户
func AddSSHKey(username, pubkey string) error {
// 获取用户主目录
homeDir, err := getUserHomeDir(username)
if err != nil {
return err
}
sshDir := homeDir + "/.ssh"
authKeys := sshDir + "/authorized_keys"
// 创建 .ssh 目录
if err := os.MkdirAll(sshDir, 0700); err != nil {
return err
}
// 追加公钥
f, err := os.OpenFile(authKeys, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(pubkey + "\n")
if err != nil {
return err
}
// 修改所有权
return chownRecursive(sshDir, username)
}
// getUserHomeDir 获取用户主目录
func getUserHomeDir(username string) (string, error) {
cmd := exec.Command("getent", "passwd", username)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("用户 %s 不存在", username)
}
parts := strings.Split(strings.TrimSpace(string(output)), ":")
if len(parts) >= 6 {
return parts[5], nil
}
return "", fmt.Errorf("无法获取用户 %s 的主目录", username)
}
// chownRecursive 递归修改文件所有者
func chownRecursive(path, username string) error {
cmd := exec.Command("chown", "-R", username+":"+username, path)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

87
internal/system/sysctl.go Normal file
View File

@@ -0,0 +1,87 @@
package system
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
// ConfigureSysctl 设置内核参数
// 参数: params - 键值对映射,如 {"net.ipv4.ip_forward": "1"}
// 返回: error - 第一个失败的错误
func ConfigureSysctl(params map[string]string) error {
if len(params) == 0 {
return nil
}
// 首先应用临时配置
for k, v := range params {
cmd := exec.Command("sysctl", "-w", fmt.Sprintf("%s=%s", k, v))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("设置 sysctl %s=%s 失败: %v", k, v, err)
}
}
// 持久化配置到 /etc/sysctl.conf 或 /etc/sysctl.d/
return appendToSysctlConf(params)
}
// appendToSysctlConf 将参数写入持久化配置文件
func appendToSysctlConf(params map[string]string) error {
const sysctlFile = "/etc/sysctl.d/99-sunhpc.conf"
// 读取现有配置
existing := make(map[string]bool)
if data, err := os.ReadFile(sysctlFile); err == nil {
scanner := bufio.NewScanner(strings.NewReader(string(data)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
existing[strings.TrimSpace(parts[0])] = true
}
}
}
// 构建新内容
var content strings.Builder
content.WriteString("# SunHPC 系统优化配置\n")
content.WriteString("# 生成时间: " + time.Now().Format(time.RFC3339) + "\n\n")
for k, v := range params {
// 跳过已存在的配置(避免重复)
if existing[k] {
continue
}
content.WriteString(fmt.Sprintf("%s = %s\n", k, v))
}
// 如果有新配置才写入
if content.Len() > 100 {
if err := os.WriteFile(sysctlFile, []byte(content.String()), 0644); err != nil {
return err
}
// 应用持久化配置
return exec.Command("sysctl", "--system").Run()
}
return nil
}
// GetSysctl 获取当前内核参数值
func GetSysctl(key string) (string, error) {
cmd := exec.Command("sysctl", "-n", key)
output, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(output)), nil
}

178
internal/system/system.go Normal file
View File

@@ -0,0 +1,178 @@
package system
import (
"os"
"path/filepath"
"sunhpc/internal/config"
"sunhpc/internal/log"
)
// Context 系统配置上下文,包含所有命令行参数
type Context struct {
Force bool // 强制模式
DryRun bool // 干运行模式
Verbose bool // 详细输出
Timeout int // 超时时间
Backup string // 备份路径
YesMode bool // 自动确认
}
// ApplyAll 应用所有系统配置
func ApplyAll(cfg *config.SunHPCConfig) error {
log.Info("开始应用系统配置...")
if err := SetHostnameWithContext(cfg.Hostname, nil); err != nil {
log.Warnf("设置主机名失败: %v", err)
}
if err := SetMOTDWithContext(cfg.MOTD, nil); err != nil {
log.Warnf("设置 MOTD 失败: %v", err)
}
if err := ConfigureSysctlWithContext(cfg.Sysctl, nil); err != nil {
log.Warnf("配置 sysctl 失败: %v", err)
}
if err := ConfigureSELinuxWithContext(cfg.SELinux, nil); err != nil {
log.Warnf("配置 SELinux 失败: %v", err)
}
if err := ConfigureSSHWithContext(cfg.SSH, nil); err != nil {
log.Warnf("配置 SSH 失败: %v", err)
}
log.Info("系统配置应用完成")
return nil
}
// SetHostnameWithContext 设置系统主机名,带上下文参数
func SetHostnameWithContext(hostname string, ctx *Context) error {
if ctx != nil && ctx.DryRun {
log.Infof("[干运行] 设置主机名为: %s", hostname)
return nil
}
if hostname == "" {
return nil
}
// 检查是否需要强制设置
current, _ := os.Hostname()
if current == hostname && (ctx == nil || !ctx.Force) {
log.Infof("主机名已是 '%s',跳过设置", hostname)
return nil
}
log.Infof("设置主机名为: %s", hostname)
return SetHostname(hostname)
}
// SetMOTDWithContext 设置 MOTD带上下文参数
func SetMOTDWithContext(content string, ctx *Context) error {
if ctx != nil && ctx.DryRun {
log.Info("[干运行] 设置 MOTD")
return nil
}
if content == "" {
return nil
}
// 备份现有文件
if ctx != nil && ctx.Backup != "" {
backupMOTD(ctx.Backup)
}
log.Info("更新 /etc/motd")
return SetMOTD(content)
}
// ConfigureSysctlWithContext 配置内核参数,带上下文参数
func ConfigureSysctlWithContext(params map[string]string, ctx *Context) error {
if ctx != nil && ctx.DryRun {
log.Info("[干运行] 配置 sysctl 参数")
return nil
}
if len(params) == 0 {
return nil
}
// 备份现有配置
if ctx != nil && ctx.Backup != "" {
backupSysctl(ctx.Backup)
}
return ConfigureSysctl(params)
}
// ConfigureSELinuxWithContext 配置 SELinux带上下文参数
func ConfigureSELinuxWithContext(mode string, ctx *Context) error {
if ctx != nil && ctx.DryRun {
log.Infof("[干运行] 设置 SELinux 模式为: %s", mode)
return nil
}
if mode == "" {
return nil
}
// 检查当前模式
current, _ := GetSELinuxMode()
if current == mode && (ctx == nil || !ctx.Force) {
log.Infof("SELinux 已是 '%s' 模式,跳过设置", mode)
return nil
}
log.Infof("设置 SELinux 模式为: %s", mode)
return ConfigureSELinux(mode)
}
// ConfigureSSHWithContext 配置 SSH带上下文参数
func ConfigureSSHWithContext(cfg config.SSHConfig, ctx *Context) error {
if ctx != nil && ctx.DryRun {
log.Info("[干运行] 配置 SSH 服务")
return nil
}
// 备份配置文件
if ctx != nil && ctx.Backup != "" {
backupSSHConfig(ctx.Backup)
}
log.Info("配置 SSH 服务")
return ConfigureSSH(cfg)
}
// 备份函数
func backupMOTD(backupDir string) error {
backupPath := filepath.Join(backupDir, "motd."+filepath.Base(os.Args[0])+".bak")
if err := os.MkdirAll(backupDir, 0755); err != nil {
return err
}
return copyFile("/etc/motd", backupPath)
}
func backupSysctl(backupDir string) error {
backupPath := filepath.Join(backupDir, "sysctl.conf.bak")
if err := os.MkdirAll(backupDir, 0755); err != nil {
return err
}
return copyFile("/etc/sysctl.conf", backupPath)
}
func backupSSHConfig(backupDir string) error {
backupPath := filepath.Join(backupDir, "sshd_config.bak")
if err := os.MkdirAll(backupDir, 0755); err != nil {
return err
}
return copyFile("/etc/ssh/sshd_config", backupPath)
}
func copyFile(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0644)
}