Files
DevPack/internal/platform/linux.go
2026-03-06 14:29:43 +08:00

210 lines
5.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <name> <args...>
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
}