mirror of
https://github.com/acepanel/helper.git
synced 2026-02-04 06:43:15 +08:00
178 lines
5.5 KiB
Go
178 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/acepanel/helper/internal/system"
|
|
"github.com/acepanel/helper/pkg/i18n"
|
|
"github.com/acepanel/helper/pkg/types"
|
|
)
|
|
|
|
// Mounter 磁盘挂载器接口
|
|
type Mounter interface {
|
|
ListDisks(ctx context.Context) ([]types.DiskInfo, error)
|
|
IsPartitioned(disk string) bool
|
|
Mount(ctx context.Context, cfg *types.MountConfig, progress ProgressCallback) error
|
|
SetVerboseCallback(cb system.VerboseCallback)
|
|
}
|
|
|
|
type mounter struct {
|
|
detector system.Detector
|
|
executor system.Executor
|
|
}
|
|
|
|
// NewMounter 创建挂载器
|
|
func NewMounter(detector system.Detector, executor system.Executor) Mounter {
|
|
return &mounter{
|
|
detector: detector,
|
|
executor: executor,
|
|
}
|
|
}
|
|
|
|
func (m *mounter) SetVerboseCallback(cb system.VerboseCallback) {
|
|
m.executor.SetVerboseCallback(cb)
|
|
}
|
|
|
|
func (m *mounter) ListDisks(ctx context.Context) ([]types.DiskInfo, error) {
|
|
return m.detector.ListDisks(ctx)
|
|
}
|
|
|
|
func (m *mounter) IsPartitioned(disk string) bool {
|
|
_, err := os.Stat("/dev/" + disk + "1")
|
|
return err == nil
|
|
}
|
|
|
|
func (m *mounter) Mount(ctx context.Context, cfg *types.MountConfig, progress ProgressCallback) error {
|
|
// 检查root权限
|
|
if err := m.detector.CheckRoot(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 检查磁盘是否存在
|
|
if !m.detector.CheckDiskExists(cfg.Disk) {
|
|
return errors.New(i18n.T.Get("Disk not found"))
|
|
}
|
|
|
|
// 检查是否为系统盘
|
|
if m.detector.IsSystemDisk(cfg.Disk) {
|
|
return errors.New(i18n.T.Get("Cannot operate on system disk"))
|
|
}
|
|
|
|
// 安装分区工具
|
|
progress(i18n.T.Get("Installing partition tools"), i18n.T.Get("Installing partition tools..."))
|
|
info, _ := m.detector.Detect(ctx)
|
|
pkgMgr := system.NewPackageManager(info.OS, m.executor)
|
|
if pkgMgr != nil {
|
|
if info.OS == types.OSRHEL {
|
|
if err := pkgMgr.Install(ctx, "xfsprogs", "e2fsprogs", "util-linux"); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := pkgMgr.Install(ctx, "xfsprogs", "e2fsprogs", "fdisk", "util-linux"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// 创建挂载点
|
|
progress(i18n.T.Get("Creating mount point"), i18n.T.Get("Creating %s...", cfg.MountPoint))
|
|
if err := os.MkdirAll(cfg.MountPoint, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 检查挂载点是否为空
|
|
entries, err := os.ReadDir(cfg.MountPoint)
|
|
if err == nil && len(entries) > 0 {
|
|
return errors.New(i18n.T.Get("Mount point is not empty"))
|
|
}
|
|
|
|
// 卸载已有分区
|
|
_, _ = m.executor.Run(ctx, "umount", "/dev/"+cfg.Disk+"1")
|
|
|
|
// 删除所有分区
|
|
progress(i18n.T.Get("Deleting existing partitions"), i18n.T.Get("Deleting existing partitions..."))
|
|
fdiskInput := ""
|
|
// 获取现有分区数
|
|
result, _ := m.executor.Run(ctx, "lsblk", "-no", "NAME", "/dev/"+cfg.Disk)
|
|
if result != nil {
|
|
lines := strings.Split(strings.TrimSpace(result.Stdout), "\n")
|
|
partCount := len(lines) - 1 // 减去磁盘本身
|
|
for i := 0; i < partCount; i++ {
|
|
fdiskInput += "d\n"
|
|
}
|
|
}
|
|
fdiskInput += "w\n"
|
|
_, _ = m.executor.RunWithInput(ctx, fdiskInput, "fdisk", "/dev/"+cfg.Disk)
|
|
|
|
// 创建新分区
|
|
progress(i18n.T.Get("Creating partition"), i18n.T.Get("Creating partition on /dev/%s...", cfg.Disk))
|
|
partitionInput := "g\nn\n1\n\n\nw\n"
|
|
_, err = m.executor.RunWithInput(ctx, partitionInput, "fdisk", "/dev/"+cfg.Disk)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", i18n.T.Get("Failed to create partition"), err)
|
|
}
|
|
|
|
// 格式化
|
|
progress(i18n.T.Get("Formatting partition"), i18n.T.Get("Formatting /dev/%s1 as %s...", cfg.Disk, cfg.FSType))
|
|
switch cfg.FSType {
|
|
case types.FSTypeExt4:
|
|
result, err = m.executor.Run(ctx, "mkfs.ext4", "-F", "/dev/"+cfg.Disk+"1")
|
|
case types.FSTypeXFS:
|
|
result, err = m.executor.Run(ctx, "mkfs.xfs", "-f", "/dev/"+cfg.Disk+"1")
|
|
default:
|
|
return errors.New(i18n.T.Get("Unsupported filesystem type: %s", cfg.FSType))
|
|
}
|
|
if err != nil || (result != nil && result.ExitCode != 0) {
|
|
return errors.New(i18n.T.Get("Format failed"))
|
|
}
|
|
|
|
// 重载systemd
|
|
_, _ = m.executor.Run(ctx, "systemctl", "daemon-reload")
|
|
|
|
// 挂载
|
|
progress(i18n.T.Get("Mounting partition"), i18n.T.Get("Mounting /dev/%s1 to %s...", cfg.Disk, cfg.MountPoint))
|
|
result, err = m.executor.Run(ctx, "mount", "/dev/"+cfg.Disk+"1", cfg.MountPoint)
|
|
if err != nil || (result != nil && result.ExitCode != 0) {
|
|
return errors.New(i18n.T.Get("Mount failed"))
|
|
}
|
|
|
|
// 获取UUID
|
|
progress(i18n.T.Get("Updating fstab"), i18n.T.Get("Updating /etc/fstab for auto-mount..."))
|
|
result, err = m.executor.Run(ctx, "blkid", "-s", "UUID", "-o", "value", "/dev/"+cfg.Disk+"1")
|
|
if err != nil || result == nil || result.ExitCode != 0 {
|
|
return errors.New(i18n.T.Get("Failed to get UUID"))
|
|
}
|
|
uuid := strings.TrimSpace(result.Stdout)
|
|
|
|
// 更新fstab
|
|
// 先删除旧条目
|
|
_, _ = m.executor.Run(ctx, "sed", "-i", fmt.Sprintf("\\|/dev/%s1|d", cfg.Disk), "/etc/fstab")
|
|
_, _ = m.executor.Run(ctx, "sed", "-i", fmt.Sprintf("\\|%s|d", cfg.MountPoint), "/etc/fstab")
|
|
|
|
// 添加新条目
|
|
fstabEntry := fmt.Sprintf("UUID=%s %s %s defaults 0 0\n", uuid, cfg.MountPoint, cfg.FSType)
|
|
f, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func(f *os.File) { _ = f.Close() }(f)
|
|
_, err = f.WriteString(fstabEntry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 重载并挂载
|
|
_, _ = m.executor.Run(ctx, "systemctl", "daemon-reload")
|
|
result, err = m.executor.Run(ctx, "mount", "-a")
|
|
if err != nil || (result != nil && result.ExitCode != 0) {
|
|
return errors.New(i18n.T.Get("fstab configuration error"))
|
|
}
|
|
|
|
progress(i18n.T.Get("Mount complete"), i18n.T.Get("Disk partition and mount successful"))
|
|
return nil
|
|
}
|