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

199 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"
)
// DarwinPlatform macOS 平台实现
type DarwinPlatform struct {
homeDir string
}
// NewDarwinPlatform 创建 macOS 平台实例
func NewDarwinPlatform() *DarwinPlatform {
home, _ := os.UserHomeDir()
return &DarwinPlatform{homeDir: home}
}
func (p *DarwinPlatform) OS() string { return "darwin" }
func (p *DarwinPlatform) Arch() string { return "amd64" } // TODO: 正确检测架构
func (p *DarwinPlatform) HomeDir() string { return p.homeDir }
func (p *DarwinPlatform) ConfigDir() string {
return p.homeDir + "/Library/Application Support"
}
func (p *DarwinPlatform) DataDir() string {
return p.homeDir + "/Library/Application Support"
}
func (p *DarwinPlatform) GetEnvVar(key string) string {
return os.Getenv(key)
}
func (p *DarwinPlatform) SetEnvVar(ctx context.Context, key, value string) error {
// 在 macOS 上通过 launchctl 和 Shell 配置文件设置
cmd := exec.CommandContext(ctx, "launchctl", "setenv", key, value)
return cmd.Run()
}
func (p *DarwinPlatform) AddToPath(ctx context.Context, dir string) error {
// TODO: 添加到 Shell 配置文件
return nil
}
func (p *DarwinPlatform) IsAdmin() bool {
return os.Geteuid() == 0
}
func (p *DarwinPlatform) PackageManagers() []string {
var pms []string
if _, err := exec.LookPath("brew"); err == nil {
pms = append(pms, "homebrew")
}
return pms
}
func (p *DarwinPlatform) DefaultShell() string {
shell := os.Getenv("SHELL")
if shell != "" {
return shell
}
return "/bin/zsh"
}
// InstallFont 安装字体到用户级字体目录
// macOS 会自动检测 ~/Library/Fonts/ 中的新字体,无需手动刷新缓存
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
func (p *DarwinPlatform) 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)
}
// 用户级字体目录: ~/Library/Fonts/
// macOS 自动监视此目录,放入即可使用
destDir := filepath.Join(p.homeDir, "Library", "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 := darwinCopyFile(fontPath, destPath); err != nil {
return fmt.Errorf("复制字体文件失败: %w", err)
}
// 可选:通过 atsutil 强制刷新字体服务缓存以立即生效
// 某些应用可能需要重启才能看到新字体
if _, err := exec.LookPath("atsutil"); err == nil {
// 移除用户字体数据库缓存
cmd := exec.CommandContext(ctx, "atsutil", "databases", "-removeUser")
if out, err := cmd.CombinedOutput(); err != nil {
log.Debugf("清理字体缓存数据库: %s, %v", string(out), err)
}
// 重启字体服务
cmd = exec.CommandContext(ctx, "atsutil", "server", "-shutdown")
if out, err := cmd.CombinedOutput(); err != nil {
log.Debugf("重启字体服务: %s, %v", string(out), err)
}
}
log.Infof("字体安装成功: %s", fontName)
return nil
}
// darwinCopyFile 复制文件,失败时自动清理目标文件
func darwinCopyFile(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 权限运行命令
// 在 macOS 上通过 sudo 请求 root 权限
// 如果当前已具有 root 权限,则直接执行命令
func (p *DarwinPlatform) 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
}