yoohoo
This commit is contained in:
358
internal/tlp/manager.go
Normal file
358
internal/tlp/manager.go
Normal file
@@ -0,0 +1,358 @@
|
||||
package tlp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/internal/system"
|
||||
"git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/types"
|
||||
"git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/utils"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
logger *utils.Logger
|
||||
}
|
||||
|
||||
func NewManager(logger *utils.Logger) *Manager {
|
||||
return &Manager{
|
||||
logger: logger.WithComponent("tlp"),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) GetStatus(ctx context.Context) (*types.TLPStatus, error) {
|
||||
status := &types.TLPStatus{
|
||||
CurrentConfig: make(map[string]string),
|
||||
}
|
||||
|
||||
if utils.CommandExists("tlp") {
|
||||
status.Installed = true
|
||||
|
||||
if version, err := utils.RunCommand("tlp", "--version"); err == nil {
|
||||
parts := strings.Fields(version)
|
||||
if len(parts) >= 2 {
|
||||
status.Version = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
if output, err := utils.RunCommand("systemctl", "is-active", "tlp"); err == nil {
|
||||
status.Active = strings.TrimSpace(output) == "active"
|
||||
}
|
||||
}
|
||||
|
||||
configPaths := []string{
|
||||
"/etc/tlp.conf",
|
||||
"/etc/default/tlp",
|
||||
}
|
||||
|
||||
for _, path := range configPaths {
|
||||
if utils.FileExists(path) {
|
||||
status.ConfigPath = path
|
||||
status.ConfigExists = true
|
||||
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
modTime := info.ModTime()
|
||||
status.LastModified = &modTime
|
||||
}
|
||||
|
||||
if config, err := m.readConfig(path); err == nil {
|
||||
status.CurrentConfig = config
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Install(ctx context.Context, sysInfo *system.Info) error {
|
||||
m.logger.Info("Installing TLP", "distro", sysInfo.Distribution, "package_manager", sysInfo.PackageManager)
|
||||
|
||||
if utils.CommandExists("tlp") {
|
||||
return fmt.Errorf("TLP is already installed")
|
||||
}
|
||||
|
||||
if !utils.IsRoot() {
|
||||
return fmt.Errorf("root privileges required for TLP installation")
|
||||
}
|
||||
|
||||
var installCmd []string
|
||||
var updateCmd []string
|
||||
|
||||
switch sysInfo.PackageManager {
|
||||
case "apt":
|
||||
updateCmd = []string{"apt", "update"}
|
||||
installCmd = []string{"apt", "install", "-y", "tlp", "tlp-rdw"}
|
||||
case "dnf":
|
||||
installCmd = []string{"dnf", "install", "-y", "tlp", "tlp-rdw"}
|
||||
case "yum":
|
||||
installCmd = []string{"yum", "install", "-y", "tlp", "tlp-rdw"}
|
||||
case "zypper":
|
||||
installCmd = []string{"zypper", "install", "-y", "tlp", "tlp-rdw"}
|
||||
case "pacman":
|
||||
updateCmd = []string{"pacman", "-Sy"}
|
||||
installCmd = []string{"pacman", "-S", "--noconfirm", "tlp", "tlp-rdw"}
|
||||
case "apk":
|
||||
installCmd = []string{"apk", "add", "tlp"}
|
||||
default:
|
||||
return fmt.Errorf("unsupported package manager: %s", sysInfo.PackageManager)
|
||||
}
|
||||
|
||||
if len(updateCmd) > 0 {
|
||||
m.logger.Info("Updating package lists")
|
||||
if _, err := utils.RunCommand(updateCmd[0], updateCmd[1:]...); err != nil {
|
||||
m.logger.Warn("Failed to update package lists", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Info("Installing TLP packages", "command", strings.Join(installCmd, " "))
|
||||
if _, err := utils.RunCommand(installCmd[0], installCmd[1:]...); err != nil {
|
||||
return fmt.Errorf("failed to install TLP: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Info("Enabling TLP service")
|
||||
if _, err := utils.RunCommand("systemctl", "enable", "tlp"); err != nil {
|
||||
m.logger.Warn("Failed to enable TLP service", "error", err)
|
||||
}
|
||||
|
||||
if _, err := utils.RunCommand("systemctl", "start", "tlp"); err != nil {
|
||||
m.logger.Warn("Failed to start TLP service", "error", err)
|
||||
}
|
||||
|
||||
if utils.CommandExists("systemctl") {
|
||||
if _, err := utils.RunCommand("systemctl", "mask", "power-profiles-daemon"); err != nil {
|
||||
m.logger.Debug("power-profiles-daemon not found or already masked")
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Info("TLP installation completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) ApplyConfig(ctx context.Context, config *types.TLPConfiguration) error {
|
||||
m.logger.Info("Applying TLP configuration")
|
||||
|
||||
if err := m.ValidateConfig(config); err != nil {
|
||||
return fmt.Errorf("configuration validation failed: %w", err)
|
||||
}
|
||||
|
||||
configPath := "/etc/tlp.conf"
|
||||
if !utils.FileExists(configPath) {
|
||||
configPath = "/etc/default/tlp"
|
||||
}
|
||||
|
||||
if utils.FileExists(configPath) {
|
||||
backupPath := fmt.Sprintf("%s.backup.%d", configPath, time.Now().Unix())
|
||||
if err := m.backupConfig(configPath, backupPath); err != nil {
|
||||
m.logger.Warn("Failed to backup existing configuration", "error", err)
|
||||
} else {
|
||||
m.logger.Info("Backed up existing configuration", "backup", backupPath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.writeConfig(configPath, config); err != nil {
|
||||
return fmt.Errorf("failed to write configuration: %w", err)
|
||||
}
|
||||
|
||||
m.logger.Info("Reloading TLP configuration")
|
||||
if _, err := utils.RunCommand("tlp", "start"); err != nil {
|
||||
m.logger.Warn("Failed to reload TLP configuration", "error", err)
|
||||
}
|
||||
|
||||
m.logger.Info("TLP configuration applied successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) ValidateConfig(config *types.TLPConfiguration) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("configuration is nil")
|
||||
}
|
||||
|
||||
if len(config.Settings) == 0 {
|
||||
return fmt.Errorf("configuration has no settings")
|
||||
}
|
||||
|
||||
for key, value := range config.Settings {
|
||||
if key == "" {
|
||||
return fmt.Errorf("empty setting key found")
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "TLP_ENABLE":
|
||||
if value != "1" && value != "0" {
|
||||
return fmt.Errorf("TLP_ENABLE must be 0 or 1, got: %s", value)
|
||||
}
|
||||
case "CPU_SCALING_GOVERNOR_ON_AC", "CPU_SCALING_GOVERNOR_ON_BAT":
|
||||
validGovernors := []string{"performance", "powersave", "ondemand", "conservative", "schedutil"}
|
||||
if !utils.Contains(validGovernors, value) {
|
||||
return fmt.Errorf("invalid CPU governor: %s", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) readConfig(configPath string) (map[string]string, error) {
|
||||
config := make(map[string]string)
|
||||
|
||||
lines, err := utils.ReadFileLines(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
if key, value, ok := utils.ParseKeyValue(line); ok {
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (m *Manager) writeConfig(configPath string, config *types.TLPConfiguration) error {
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(configPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open config file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fmt.Fprintf(file, "# TLP Configuration File\n")
|
||||
fmt.Fprintf(file, "# Generated by WiseTLP on %s\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(file, "# %s\n\n", config.Description)
|
||||
|
||||
for key, value := range config.Settings {
|
||||
if rationale, exists := config.Rationale[key]; exists {
|
||||
fmt.Fprintf(file, "# %s\n", rationale)
|
||||
}
|
||||
fmt.Fprintf(file, "%s=%s\n\n", key, value)
|
||||
}
|
||||
|
||||
if len(config.Warnings) > 0 {
|
||||
fmt.Fprintf(file, "# WARNINGS:\n")
|
||||
for _, warning := range config.Warnings {
|
||||
fmt.Fprintf(file, "# - %s\n", warning)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) backupConfig(configPath, backupPath string) error {
|
||||
input, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(backupPath, input, 0644)
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
*types.TLPConfiguration
|
||||
}
|
||||
|
||||
func (c *Configuration) Present() error {
|
||||
fmt.Println("\n" + strings.Repeat("=", 60))
|
||||
fmt.Println("GENERATED TLP CONFIGURATION")
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
|
||||
fmt.Printf("\nDescription: %s\n", c.Description)
|
||||
|
||||
if len(c.Warnings) > 0 {
|
||||
fmt.Println("\n⚠️ WARNINGS:")
|
||||
for _, warning := range c.Warnings {
|
||||
fmt.Printf(" - %s\n", warning)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\nConfiguration Settings:")
|
||||
fmt.Println(strings.Repeat("-", 40))
|
||||
|
||||
// Group settings by category for better presentation
|
||||
categories := map[string][]string{
|
||||
"General": {"TLP_ENABLE", "TLP_WARN_LEVEL", "TLP_DEBUG"},
|
||||
"CPU": {"CPU_SCALING_GOVERNOR_ON_AC", "CPU_SCALING_GOVERNOR_ON_BAT",
|
||||
"CPU_SCALING_MIN_FREQ_ON_AC", "CPU_SCALING_MAX_FREQ_ON_AC",
|
||||
"CPU_SCALING_MIN_FREQ_ON_BAT", "CPU_SCALING_MAX_FREQ_ON_BAT"},
|
||||
"Platform": {"PLATFORM_PROFILE_ON_AC", "PLATFORM_PROFILE_ON_BAT"},
|
||||
"Disk": {"DISK_APM_LEVEL_ON_AC", "DISK_APM_LEVEL_ON_BAT",
|
||||
"DISK_SPINDOWN_TIMEOUT_ON_AC", "DISK_SPINDOWN_TIMEOUT_ON_BAT"},
|
||||
"Graphics": {"RADEON_DPM_STATE_ON_AC", "RADEON_DPM_STATE_ON_BAT"},
|
||||
"Network": {"WIFI_PWR_ON_AC", "WIFI_PWR_ON_BAT"},
|
||||
"USB": {"USB_AUTOSUSPEND", "USB_BLACKLIST"},
|
||||
}
|
||||
|
||||
for category, keys := range categories {
|
||||
hasSettings := false
|
||||
var categorySettings []string
|
||||
|
||||
for _, key := range keys {
|
||||
if value, exists := c.Settings[key]; exists {
|
||||
if !hasSettings {
|
||||
categorySettings = append(categorySettings, fmt.Sprintf("\n%s:", category))
|
||||
hasSettings = true
|
||||
}
|
||||
|
||||
rationale := ""
|
||||
if r, exists := c.Rationale[key]; exists {
|
||||
rationale = fmt.Sprintf(" (%s)", r)
|
||||
}
|
||||
|
||||
categorySettings = append(categorySettings, fmt.Sprintf(" %s = %s%s", key, value, rationale))
|
||||
}
|
||||
}
|
||||
|
||||
if hasSettings {
|
||||
for _, setting := range categorySettings {
|
||||
fmt.Println(setting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show any remaining settings not in categories
|
||||
fmt.Println("\nOther Settings:")
|
||||
for key, value := range c.Settings {
|
||||
found := false
|
||||
for _, keys := range categories {
|
||||
if utils.Contains(keys, key) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
rationale := ""
|
||||
if r, exists := c.Rationale[key]; exists {
|
||||
rationale = fmt.Sprintf(" (%s)", r)
|
||||
}
|
||||
fmt.Printf(" %s = %s%s\n", key, value, rationale)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("=", 60))
|
||||
|
||||
// Get user approval
|
||||
fmt.Print("\nDo you want to apply this configuration? (y/N): ")
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
if response != "y" && response != "yes" {
|
||||
return fmt.Errorf("configuration rejected by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user