2
0
mirror of https://github.com/acepanel/helper.git synced 2026-02-04 04:07:16 +08:00

feat: 添加verbose模式

This commit is contained in:
2026-01-24 16:41:32 +08:00
parent 3335578e47
commit 865f7abffb
8 changed files with 170 additions and 2 deletions

View File

@@ -1,10 +1,18 @@
package main
import (
"flag"
_ "time/tzdata"
"github.com/acepanel/helper/pkg/config"
)
func main() {
verbose := flag.Bool("v", false, "verbose mode")
flag.Parse()
config.Global.Verbose = *verbose
helper, err := initHelper()
if err != nil {
panic(err)

View File

@@ -21,6 +21,7 @@ import (
// Installer 安装器接口
type Installer interface {
Install(ctx context.Context, cfg *types.InstallConfig, progress chan<- types.Progress) error
SetVerboseCallback(cb system.VerboseCallback)
}
type installer struct {
@@ -48,6 +49,10 @@ func NewInstaller(
}
}
func (i *installer) SetVerboseCallback(cb system.VerboseCallback) {
i.executor.SetVerboseCallback(cb)
}
func (i *installer) Install(ctx context.Context, cfg *types.InstallConfig, progress chan<- types.Progress) error {
steps := []struct {
name string

View File

@@ -17,6 +17,7 @@ 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 {
@@ -32,6 +33,10 @@ func NewMounter(detector system.Detector, executor system.Executor) Mounter {
}
}
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)
}

View File

@@ -15,6 +15,7 @@ type ProgressCallback func(step, message string)
// Uninstaller 卸载器接口
type Uninstaller interface {
Uninstall(ctx context.Context, setupPath string, progress ProgressCallback) error
SetVerboseCallback(cb system.VerboseCallback)
}
type uninstaller struct {
@@ -36,6 +37,10 @@ func NewUninstaller(
}
}
func (u *uninstaller) SetVerboseCallback(cb system.VerboseCallback) {
u.executor.SetVerboseCallback(cb)
}
func (u *uninstaller) Uninstall(ctx context.Context, setupPath string, progress ProgressCallback) error {
// 检查root权限
if err := u.detector.CheckRoot(); err != nil {

View File

@@ -91,7 +91,7 @@ func (d *detector) detectOS() types.OSType {
return types.OSDebian
case "ubuntu":
return types.OSUbuntu
case "rhel", "centos", "rocky", "almalinux", "fedora":
case "rhel", "centos", "rocky", "almalinux", "fedora", "tencentos":
return types.OSRHEL
default:
// 检查ID_LIKE

View File

@@ -4,8 +4,12 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os/exec"
"strings"
"github.com/acepanel/helper/pkg/config"
)
// CommandResult 命令执行结果
@@ -15,6 +19,9 @@ type CommandResult struct {
Stderr string
}
// VerboseCallback verbose 模式回调
type VerboseCallback func(cmd string)
// Executor 命令执行器接口
type Executor interface {
// Run 执行命令并等待完成
@@ -23,16 +30,36 @@ type Executor interface {
RunWithInput(ctx context.Context, input string, name string, args ...string) (*CommandResult, error)
// RunStream 执行命令并流式输出
RunStream(ctx context.Context, stdout, stderr io.Writer, name string, args ...string) error
// SetVerboseCallback 设置 verbose 回调
SetVerboseCallback(cb VerboseCallback)
}
type executor struct{}
type executor struct {
verboseCallback VerboseCallback
}
// NewExecutor 创建执行器
func NewExecutor() Executor {
return &executor{}
}
func (e *executor) SetVerboseCallback(cb VerboseCallback) {
e.verboseCallback = cb
}
func (e *executor) logVerbose(name string, args ...string) {
if config.Global.Verbose && e.verboseCallback != nil {
cmdStr := name
if len(args) > 0 {
cmdStr += " " + strings.Join(args, " ")
}
e.verboseCallback(cmdStr)
}
}
func (e *executor) Run(ctx context.Context, name string, args ...string) (*CommandResult, error) {
e.logVerbose(name, args...)
cmd := exec.CommandContext(ctx, name, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
@@ -49,6 +76,11 @@ func (e *executor) Run(ctx context.Context, name string, args ...string) (*Comma
if errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
}
// 包含 stderr 信息到错误中
stderrStr := strings.TrimSpace(stderr.String())
if stderrStr != "" {
return result, fmt.Errorf("%w: %s", err, stderrStr)
}
return result, err
}
@@ -56,6 +88,8 @@ func (e *executor) Run(ctx context.Context, name string, args ...string) (*Comma
}
func (e *executor) RunWithInput(ctx context.Context, input string, name string, args ...string) (*CommandResult, error) {
e.logVerbose(name, args...)
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdin = bytes.NewBufferString(input)
var stdout, stderr bytes.Buffer
@@ -73,6 +107,11 @@ func (e *executor) RunWithInput(ctx context.Context, input string, name string,
if errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
}
// 包含 stderr 信息到错误中
stderrStr := strings.TrimSpace(stderr.String())
if stderrStr != "" {
return result, fmt.Errorf("%w: %s", err, stderrStr)
}
return result, err
}
@@ -80,6 +119,8 @@ func (e *executor) RunWithInput(ctx context.Context, input string, name string,
}
func (e *executor) RunStream(ctx context.Context, stdout, stderr io.Writer, name string, args ...string) error {
e.logVerbose(name, args...)
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr

View File

@@ -8,9 +8,11 @@ import (
"strings"
"time"
"github.com/acepanel/helper/pkg/config"
"github.com/acepanel/helper/pkg/embed"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
@@ -45,6 +47,7 @@ const (
// 消息类型
type (
progressMsg types.Progress
verboseMsg string // verbose 模式命令输出
installCompleteMsg struct {
err error
info string
@@ -90,6 +93,10 @@ type App struct {
installDone bool
installInfo string // 安装完成后的面板信息
// verbose 模式
verboseLogs []string
verboseViewport viewport.Model
// 卸载
uninstallCountdown int
uninstallSkipCount int // 连按enter跳过倒计时的计数
@@ -146,6 +153,13 @@ func NewApp(installer service.Installer, uninstaller service.Uninstaller, mounte
// 初始化进度条
app.installProgress = progress.New(progress.WithDefaultGradient())
// 初始化 verbose viewport
app.verboseViewport = viewport.New(80, 8)
app.verboseViewport.Style = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorMuted).
Padding(0, 1)
return app
}
@@ -342,6 +356,16 @@ func (a *App) updateInstall(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return a, nil
case verboseMsg:
a.verboseLogs = append(a.verboseLogs, string(msg))
// 保留最近 100 条
if len(a.verboseLogs) > 100 {
a.verboseLogs = a.verboseLogs[len(a.verboseLogs)-100:]
}
a.verboseViewport.SetContent(strings.Join(a.verboseLogs, "\n"))
a.verboseViewport.GotoBottom()
return a, nil
case installCompleteMsg:
a.installRunning = false
a.installDone = true
@@ -366,6 +390,7 @@ func (a *App) updateInstall(msg tea.Msg) (tea.Model, tea.Cmd) {
if a.installForm.State == huh.StateCompleted {
if a.installConfirmed {
a.installRunning = true
a.verboseLogs = nil // 清空 verbose 日志
return a, tea.Batch(a.installSpinner.Tick, a.startInstall())
}
a.state = ViewMainMenu
@@ -390,6 +415,15 @@ func (a *App) startInstall() tea.Cmd {
}
}()
// 设置 verbose 回调
if config.Global.Verbose {
a.installer.SetVerboseCallback(func(cmd string) {
if a.program != nil {
a.program.Send(verboseMsg("$ " + cmd))
}
})
}
cfg := &types.InstallConfig{
SetupPath: a.setupPath,
AutoSwap: true,
@@ -440,6 +474,13 @@ func (a *App) viewInstall() string {
sb.WriteString(LogStyle.Render(log))
sb.WriteString("\n")
}
// verbose 模式显示命令日志
if config.Global.Verbose && len(a.verboseLogs) > 0 {
sb.WriteString("\n")
sb.WriteString(MutedStyle.Render("Commands:"))
sb.WriteString("\n")
sb.WriteString(a.verboseViewport.View())
}
} else {
sb.WriteString(a.installForm.View())
}
@@ -498,6 +539,15 @@ func (a *App) updateUninstall(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return a, nil
case verboseMsg:
a.verboseLogs = append(a.verboseLogs, string(msg))
if len(a.verboseLogs) > 100 {
a.verboseLogs = a.verboseLogs[len(a.verboseLogs)-100:]
}
a.verboseViewport.SetContent(strings.Join(a.verboseLogs, "\n"))
a.verboseViewport.GotoBottom()
return a, nil
case uninstallCompleteMsg:
a.uninstallRunning = false
a.uninstallDone = true
@@ -523,6 +573,7 @@ func (a *App) updateUninstall(msg tea.Msg) (tea.Model, tea.Cmd) {
if a.uninstallConfirm {
a.uninstallState = 3
a.uninstallRunning = true
a.verboseLogs = nil // 清空 verbose 日志
return a, tea.Batch(a.uninstallSpinner.Tick, a.startUninstall())
}
a.state = ViewMainMenu
@@ -557,6 +608,16 @@ func (a *App) tickCountdown() tea.Cmd {
func (a *App) startUninstall() tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
// 设置 verbose 回调
if config.Global.Verbose {
a.uninstaller.SetVerboseCallback(func(cmd string) {
if a.program != nil {
a.program.Send(verboseMsg("$ " + cmd))
}
})
}
err := a.uninstaller.Uninstall(ctx, a.setupPath, func(step, message string) {
if a.program != nil {
a.program.Send(progressMsg{Step: step, Message: message})
@@ -608,6 +669,13 @@ func (a *App) viewUninstall() string {
sb.WriteString(LogStyle.Render(log))
sb.WriteString("\n")
}
// verbose 模式显示命令日志
if config.Global.Verbose && len(a.verboseLogs) > 0 {
sb.WriteString("\n")
sb.WriteString(MutedStyle.Render("Commands:"))
sb.WriteString("\n")
sb.WriteString(a.verboseViewport.View())
}
case 4: // done
if a.uninstallErr != nil {
@@ -678,6 +746,15 @@ func (a *App) updateMount(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return a, nil
case verboseMsg:
a.verboseLogs = append(a.verboseLogs, string(msg))
if len(a.verboseLogs) > 100 {
a.verboseLogs = a.verboseLogs[len(a.verboseLogs)-100:]
}
a.verboseViewport.SetContent(strings.Join(a.verboseLogs, "\n"))
a.verboseViewport.GotoBottom()
return a, nil
case mountCompleteMsg:
a.mountRunning = false
a.mountDone = true
@@ -738,6 +815,7 @@ func (a *App) updateMount(msg tea.Msg) (tea.Model, tea.Cmd) {
if a.mountConfirmed {
a.mountState = 5
a.mountRunning = true
a.verboseLogs = nil // 清空 verbose 日志
return a, tea.Batch(a.mountSpinner.Tick, a.startMount())
}
a.state = ViewMainMenu
@@ -819,6 +897,16 @@ func (a *App) initMountConfirmForm() {
func (a *App) startMount() tea.Cmd {
return func() tea.Msg {
ctx := context.Background()
// 设置 verbose 回调
if config.Global.Verbose {
a.mounter.SetVerboseCallback(func(cmd string) {
if a.program != nil {
a.program.Send(verboseMsg("$ " + cmd))
}
})
}
err := a.mounter.Mount(ctx, &a.mountConfig, func(step, message string) {
if a.program != nil {
a.program.Send(progressMsg{Step: step, Message: message})
@@ -885,6 +973,13 @@ func (a *App) viewMount() string {
sb.WriteString(LogStyle.Render(log))
sb.WriteString("\n")
}
// verbose 模式显示命令日志
if config.Global.Verbose && len(a.verboseLogs) > 0 {
sb.WriteString("\n")
sb.WriteString(MutedStyle.Render("Commands:"))
sb.WriteString("\n")
sb.WriteString(a.verboseViewport.View())
}
}
return sb.String()

9
pkg/config/config.go Normal file
View File

@@ -0,0 +1,9 @@
package config
// Config 全局配置
type Config struct {
Verbose bool
}
// Global 全局配置实例
var Global = &Config{}