package platform import ( "context" "fmt" "io" "os" "os/exec" "path/filepath" "github.com/user/devpack/internal/logging" ) // LinuxPlatform Linux 平台实现 type LinuxPlatform struct { homeDir string } // NewLinuxPlatform 创建 Linux 平台实例 func NewLinuxPlatform() *LinuxPlatform { home, _ := os.UserHomeDir() return &LinuxPlatform{homeDir: home} } func (p *LinuxPlatform) OS() string { return "linux" } func (p *LinuxPlatform) Arch() string { return "amd64" } // TODO: 正确检测架构 func (p *LinuxPlatform) HomeDir() string { return p.homeDir } func (p *LinuxPlatform) ConfigDir() string { if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" { return dir } return p.homeDir + "/.config" } func (p *LinuxPlatform) DataDir() string { if dir := os.Getenv("XDG_DATA_HOME"); dir != "" { return dir } return p.homeDir + "/.local/share" } func (p *LinuxPlatform) GetEnvVar(key string) string { return os.Getenv(key) } func (p *LinuxPlatform) SetEnvVar(ctx context.Context, key, value string) error { // TODO: 写入 Shell 配置文件以持久化 return os.Setenv(key, value) } func (p *LinuxPlatform) AddToPath(ctx context.Context, dir string) error { // TODO: 添加到 Shell 配置文件 return nil } func (p *LinuxPlatform) IsAdmin() bool { return os.Geteuid() == 0 } func (p *LinuxPlatform) PackageManagers() []string { var pms []string if _, err := exec.LookPath("apt"); err == nil { pms = append(pms, "apt") } if _, err := exec.LookPath("dnf"); err == nil { pms = append(pms, "dnf") } if _, err := exec.LookPath("yum"); err == nil { pms = append(pms, "yum") } if _, err := exec.LookPath("pacman"); err == nil { pms = append(pms, "pacman") } if _, err := exec.LookPath("snap"); err == nil { pms = append(pms, "snap") } return pms } func (p *LinuxPlatform) DefaultShell() string { shell := os.Getenv("SHELL") if shell != "" { return shell } return "/bin/bash" } // InstallFont 安装字体到用户级字体目录并刷新字体缓存 // 支持 .ttf (TrueType) 和 .otf (OpenType) 格式 func (p *LinuxPlatform) InstallFont(ctx context.Context, fontPath string) error { log := logging.G() // 验证源字体文件是否存在且可访问 info, err := os.Stat(fontPath) if err != nil { log.Errorf("无法访问字体文件: %v", err) return fmt.Errorf("无法访问字体文件: %w", err) } if info.IsDir() { return fmt.Errorf("字体路径是目录而非文件: %s", fontPath) } // 检查字体格式是否受支持 fontName := filepath.Base(fontPath) ext := filepath.Ext(fontName) if ext != ".ttf" && ext != ".otf" { log.Errorf("不支持的字体格式: %s", ext) return fmt.Errorf("不支持的字体格式: %s,仅支持 .ttf 和 .otf", ext) } // 用户级字体目录: ~/.local/share/fonts/ destDir := filepath.Join(p.homeDir, ".local", "share", "fonts") log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir) // 确保目标目录存在 if err := os.MkdirAll(destDir, 0755); err != nil { return fmt.Errorf("创建字体目录失败: %w", err) } // 检查字体是否已安装 destPath := filepath.Join(destDir, fontName) if _, err := os.Stat(destPath); err == nil { log.Infof("字体已存在,跳过安装: %s", destPath) return nil } // 复制字体文件到目标路径 if err := linuxCopyFile(fontPath, destPath); err != nil { return fmt.Errorf("复制字体文件失败: %w", err) } // 刷新字体缓存 if _, err := exec.LookPath("fc-cache"); err == nil { cmd := exec.CommandContext(ctx, "fc-cache", "-f", destDir) if out, err := cmd.CombinedOutput(); err != nil { log.Warnf("刷新字体缓存失败: %s, %v", string(out), err) // 字体文件已复制成功,缓存刷新失败不是致命错误 } } else { log.Warnf("未找到 fc-cache 命令,字体缓存未刷新,可能需要重新登录后生效") } log.Infof("字体安装成功: %s", fontName) return nil } // linuxCopyFile 复制文件,失败时自动清理目标文件 func linuxCopyFile(src, dst string) error { input, err := os.Open(src) if err != nil { return err } defer input.Close() output, err := os.Create(dst) if err != nil { return err } _, err = io.Copy(output, input) closeErr := output.Close() if err != nil { os.Remove(dst) return err } if closeErr != nil { os.Remove(dst) return closeErr } return nil } // RunAsAdmin 以 root 权限运行命令 // 在 Linux 上通过 sudo 请求 root 权限 // 如果当前已具有 root 权限,则直接执行命令 func (p *LinuxPlatform) RunAsAdmin(ctx context.Context, name string, args ...string) error { log := logging.G() // 如果已经是 root 身份,直接执行 if p.IsAdmin() { log.Debugf("当前已具有 root 权限,直接执行: %s", name) cmd := exec.CommandContext(ctx, name, args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } log.Debugf("请求 sudo 权限运行: %s %v", name, args) // 构建 sudo 命令参数: sudo sudoArgs := make([]string, 0, len(args)+1) sudoArgs = append(sudoArgs, name) sudoArgs = append(sudoArgs, args...) cmd := exec.CommandContext(ctx, "sudo", sudoArgs...) cmd.Stdin = os.Stdin // 转发标准输入以便用户输入密码 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("管理员命令执行失败: %w", err) } log.Debugf("管理员命令执行完成: %s", name) return nil }