diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2f22d5a..3a2d8da 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -3,9 +3,6 @@ version: 2 -env: - - GITHUB_TOKEN={{ .Env.CODEBERG_TOKEN }} - before: hooks: - go mod tidy diff --git a/cargo.go b/cargo.go new file mode 100644 index 0000000..a5a80d8 --- /dev/null +++ b/cargo.go @@ -0,0 +1,45 @@ +package main + +type CargoManager struct{} + +func (c *CargoManager) Install(packages []string) error { + if len(packages) == 0 { + return nil + } + err := installWithProgress(c, packages) + if err != nil { + return err + } + return nil +} + +func (c *CargoManager) Name() string { + return "Cargo" +} + +func (c *CargoManager) InstallManager() error { + if _, err := execLookPath("brew"); err != nil { + installHomebrew() + } + + if _, err := execLookPath("cargo"); err == nil { + return nil + } + + cmd := execCommand("brew", "install", "rust") + return cmd.Run() +} + +func (c *CargoManager) InstallPackage(pkg string) error { + cmd := execCommand("cargo", "install", pkg) + return cmd.Run() +} + +func (c *CargoManager) RemovePackage(pkg string) error { + cmd := execCommand("cargo", "uninstall", pkg) + return cmd.Run() +} + +func (c *CargoManager) SearchPackage(pkg string) []string { + return []string{} +} diff --git a/pkg/packagemanager/cargo_test.go b/cargo_test.go similarity index 72% rename from pkg/packagemanager/cargo_test.go rename to cargo_test.go index ef491de..5129f56 100644 --- a/pkg/packagemanager/cargo_test.go +++ b/cargo_test.go @@ -1,9 +1,8 @@ // cargo_test.go -package packagemanager +package main import ( "os/exec" - "system_setup_tool/internal/shell" "testing" ) @@ -18,13 +17,13 @@ func TestCargoManager_InstallManager(t *testing.T) { cm := &CargoManager{} // Mock exec.LookPath - shell.ExecLookPath = func(file string) (string, error) { + execLookPath = func(file string) (string, error) { if file == "cargo" { return "/usr/bin/cargo", nil } return "", exec.ErrNotFound } - defer func() { shell.ExecLookPath = exec.LookPath }() + defer func() { execLookPath = exec.LookPath }() if err := cm.InstallManager(); err != nil { t.Errorf("Expected no error, got %v", err) @@ -35,10 +34,10 @@ func TestCargoManager_InstallPackage(t *testing.T) { cm := &CargoManager{} // Mock exec.Command - shell.ExecCommand = func(name string, arg ...string) *exec.Cmd { + execCommand = func(name string, arg ...string) *exec.Cmd { return exec.Command("echo", "mocked cargo install") } - defer func() { shell.ExecCommand = exec.Command }() + defer func() { execCommand = exec.Command }() if err := cm.InstallPackage("test-package"); err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/changelog.md b/changelog.md index ac3aeeb..10db077 100644 --- a/changelog.md +++ b/changelog.md @@ -2,82 +2,6 @@ All notable changes to this project will be documented in this file. -## [0.6.0] - 2025-02-04 - -### 🚀 Features - -- *(cli)* Add check command - -### 🚜 Refactor - -- *(packagemanager,cli,tui)* Move packages in specific subfolders - -### 📚 Documentation - -- *(cli)* Update version - -## [0.5.0] - 2025-01-24 - -### 🚀 Features - -- *(cli)* Add concurrent execution of search and update to improve performance -- *(cli)* Add aliases to commands -- *(config)* Add creation of default config if no config exists -- *(cli)* Add history command and logging for actions -- *(cli)* Add max history length - -### 🐛 Bug Fixes - -- *(packagemanager)* Reimplement getPackagemanager to enable installing buildtools on linux - -### 🚜 Refactor - -- *(cli)* Perform code clean up - -## [0.4.0] - 2025-01-20 - -### 🚀 Features - -- *(cli)* Add update and update-all command -- *(packagemanager)* Added packagemanager definitions for winget and chocolatey -- *(cli,packagemanager)* Implemented first working windows and macos versions - -### 🐛 Bug Fixes - -- *(packagemanager)* Added missing build tag for macos - -### 📚 Documentation - -- Update changelog.md -- Update changelog.md - -## [0.3.1] - 2025-01-19 - -### 🐛 Bug Fixes - -- Fix nil pointer execution if no proper packagemanager selected - -## [0.3.0] - 2025-01-18 - -### 🚀 Features - -- *(cli)* Implement search argument -- *(cli)* Add first implementation for install command -- *(cli)* Cleanup install command and add remove and search command - -### 🐛 Bug Fixes - -- *(cli)* Add missing yes flag in flatpak remove cmd - -### 🚜 Refactor - -- Seperate files, function and structs into seperate packages -- Move main.go back to project root - -### 📚 Documentation - -- Update changelog.md - ## [0.2.0] - 2025-01-16 ### 🚀 Features @@ -94,7 +18,6 @@ All notable changes to this project will be documented in this file. ### 📚 Documentation -- Update changelog.md - Update changelog.md ### ⚙️ Miscellaneous Tasks diff --git a/cmd.go b/cmd.go new file mode 100644 index 0000000..598ccdb --- /dev/null +++ b/cmd.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var rootCmd = &cobra.Command{ + Use: "system_setup_tool", + Short: "Installs packages based on TOML configuration", + Run: run, +} + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringP("config", "c", "", "Path to the configuration file") + viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) + + addCmd.Flags().StringP("name", "n", "", "The name of the package you want to add") + addCmd.Flags().StringP("manager", "m", "", "The package manager you want to add the package to (homebrew|cargo|flatpak|pipx|go)") + addCmd.Flags().Bool("system", false, "Add as a system package") + addCmd.Flags().Bool("headless", false, "Add as a headless system package (only used with --system)") + + deleteCmd.Flags().StringP("name", "n", "", "The name of the package you want to delete") + deleteCmd.Flags().StringP("manager", "m", "", "The package manager you want to delete the package from (homebrew|cargo|flatpak|pipx|go)") + deleteCmd.Flags().Bool("system", false, "Delete from system packages") + deleteCmd.Flags().Bool("headless", false, "Delete from headless system packages (only used with --system)") + + enableCmd.Flags().Bool("value", true, "Set to true to enable, false to disable") + + installCmd.Flags().StringP("manager", "m", "os", "The package manager you want to install a package with. (Options: os|homebrew|pipx|flatpak|cargo)") + + removeCmd.Flags().StringP("manager", "m", "os", "The package manager you want to remove a package with. (Options: os|homebrew|pipx|flatpak|cargo)") + + searchCmd.Flags().StringP("manager", "m", "", "The package manager you want to search a package with. (Options: os|homebrew|flatpak)") + + packageCmd.AddCommand(addCmd, deleteCmd, showCmd, enableCmd) + rootCmd.AddCommand(packageCmd, searchCmd, installCmd, removeCmd) +} + +func initConfig() { + if cfgFile := viper.GetString("config"); cfgFile != "" { + viper.SetConfigFile(cfgFile) + } else { + viper.SetConfigName("config") + viper.SetConfigType("toml") + viper.AddConfigPath(".") + } + + if err := viper.ReadInConfig(); err != nil { + fmt.Println("Error reading configuration file:", err) + os.Exit(1) + } +} diff --git a/cmd/check_cmd.go b/cmd/check_cmd.go deleted file mode 100644 index d31efcd..0000000 --- a/cmd/check_cmd.go +++ /dev/null @@ -1,49 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - - "system_setup_tool/internal/utils" - pm "system_setup_tool/pkg/packagemanager" - - "github.com/spf13/cobra" -) - -var checkCmd = &cobra.Command{ - Use: "check [package-name]", - Short: "Check if the specified package is installed", - Args: cobra.ExactArgs(1), - Aliases: []string{"c"}, - Run: func(cmd *cobra.Command, args []string) { - packageName := args[0] - managerName, _ := cmd.Flags().GetString("manager") - var manager pm.PackageManager - if managerName == "os" { - managerName = "OS Package Manager" - } else if managerName == "brew" { - managerName = "homebrew" - } - switch managerName { - case "OS Package Manager": - manager = pm.NewOSManager("") - case "homebrew": - manager = &pm.HomebrewManager{} - case "cargo": - manager = &pm.CargoManager{} - case "pipx": - manager = &pm.PipxManager{} - case "flatpak": - manager = &pm.FlatpakManager{} - default: - fmt.Println("No PackageManager found") - } - if err := manager.Check(packageName); err != nil { - log.Printf("error: %v\n", err) - } - fmt.Printf("Package: %s installed\n", packageName) - if err := utils.LogToHistory("checked", packageName, manager.Name()); err != nil { - fmt.Println("error logging action:", err) - } - }, -} diff --git a/cmd/cmd.go b/cmd/cmd.go deleted file mode 100644 index 92de0c4..0000000 --- a/cmd/cmd.go +++ /dev/null @@ -1,136 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "os" - "path/filepath" - "system_setup_tool/internal/tui" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var RootCmd = &cobra.Command{ - Use: "system_setup_tool", - Short: "Installs packages based on TOML configuration", - Version: "0.6.0", - Run: tui.Run, -} - -func init() { - cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringP("config", "c", "", "Path to the configuration file") - viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config")) - - addCmd.Flags().StringP("name", "n", "", "The name of the package you want to add") - addCmd.Flags().StringP("manager", "m", "", "The package manager you want to add the package to (homebrew|cargo|flatpak|pipx|go)") - addCmd.Flags().Bool("system", false, "Add as a system package") - addCmd.Flags().Bool("headless", false, "Add as a headless system package (only used with --system)") - - deleteCmd.Flags().StringP("name", "n", "", "The name of the package you want to delete") - deleteCmd.Flags().StringP("manager", "m", "", "The package manager you want to delete the package from (homebrew|cargo|flatpak|pipx|go)") - deleteCmd.Flags().Bool("system", false, "Delete from system packages") - deleteCmd.Flags().Bool("headless", false, "Delete from headless system packages (only used with --system)") - - enableCmd.Flags().Bool("value", true, "Set to true to enable, false to disable") - - installCmd.Flags().StringP("manager", "m", "os", "The package manager you want to install a package with. (Options: os|homebrew|pipx|flatpak|cargo)") - - removeCmd.Flags().StringP("manager", "m", "os", "The package manager you want to remove a package with. (Options: os|homebrew|pipx|flatpak|cargo)") - - searchCmd.Flags().StringP("manager", "m", "", "The package manager you want to search a package with. (Options: os|homebrew|flatpak)") - - updateCmd.Flags().StringP("manager", "m", "os", "The package manager you want to update packages with. (Options: os|homebrew|pipx|flatpak)") - - checkCmd.Flags().StringP("manager", "m", "os", "The package manager you want to check for a packages with. (Options: os|homebrew|pipx|flatpak)") - - packageCmd.AddCommand( - addCmd, - deleteCmd, - showCmd, - enableCmd, - ) - RootCmd.AddCommand( - packageCmd, - searchCmd, - installCmd, - removeCmd, - updateCmd, - updateAllCmd, - historyCmd, - checkCmd, - ) -} - -func initConfig() { - if cfgFile := viper.GetString("config"); cfgFile != "" { - viper.SetConfigFile(cfgFile) - } else { - viper.SetConfigName("config") - viper.SetConfigType("toml") - dirname, err := os.UserConfigDir() - if err != nil { - log.Println("cant obtain config dir") - } else { - viper.AddConfigPath(filepath.Join(dirname, "sst")) - } - } - - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - fmt.Println("no config file found, creating a new one...") - if err := createDefaultConfig(); err != nil { - log.Printf("error creating default config: %v\n", err) - os.Exit(1) - } - } else { - log.Printf("error reading config file: %v\n", err) - os.Exit(1) - } - } -} - -func createDefaultConfig() error { - v := viper.New() - - v.SetDefault("config", "") - v.SetDefault("headless", true) - - v.SetDefault("dotfiles.enable", false) - v.SetDefault("dotfiles.git_repo", "") - - v.SetDefault("package_managers.cargo.enable", false) - v.SetDefault("package_managers.cargo.packages", []string{}) - - v.SetDefault("package_managers.flatpak.enable", false) - v.SetDefault("package_managers.flatpak.packages", []string{}) - - v.SetDefault("package_managers.go.enable", false) - v.SetDefault("package_managers.go.packages", []string{}) - - v.SetDefault("package_managers.homebrew.enable", false) - v.SetDefault("package_managers.homebrew.packages", []string{}) - - v.SetDefault("package_managers.pipx.enable", false) - v.SetDefault("package_managers.pipx.packages", []string{}) - - v.SetDefault("package_managers.os.enable", true) - v.SetDefault("package_managers.os.packages", []string{}) - - configDir := filepath.Join(os.Getenv("HOME"), ".config", "sst") - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("error creating config dir: %w", err) - } - - configPath := filepath.Join(configDir, "config.toml") - v.SetConfigFile(configPath) - v.SetConfigType("toml") - - if err := v.WriteConfig(); err != nil { - return fmt.Errorf("error writing default config: %w", err) - } - - fmt.Println("default config created:", configPath) - return nil -} diff --git a/cmd/history_cmd.go b/cmd/history_cmd.go deleted file mode 100644 index 2b1fedb..0000000 --- a/cmd/history_cmd.go +++ /dev/null @@ -1,29 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "os" - "path/filepath" - - "github.com/spf13/cobra" -) - -var historyCmd = &cobra.Command{ - Use: "history", - Short: "show command history", - Long: "Shows history of the last 2000 commands", - Run: func(cmd *cobra.Command, args []string) { - dirname, err := os.UserConfigDir() - if err != nil { - log.Printf("error getting user config dir: %v\n", err) - } - historyFile := filepath.Join(dirname, "sst", "sst_history") - content, err := os.ReadFile(historyFile) - if err != nil { - fmt.Println("error reading history: ", err) - return - } - fmt.Println(string(content)) - }, -} diff --git a/cmd/install_cmd.go b/cmd/install_cmd.go deleted file mode 100644 index 73d179c..0000000 --- a/cmd/install_cmd.go +++ /dev/null @@ -1,58 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "runtime" - "system_setup_tool/internal/utils" - - pm "system_setup_tool/pkg/packagemanager" - - "github.com/spf13/cobra" -) - -var installCmd = &cobra.Command{ - Use: "install [package_name]", - Short: "Install a package with the specified package manager", - Args: cobra.ExactArgs(1), - Aliases: []string{"i"}, - Run: func(cmd *cobra.Command, args []string) { - packageName := args[0] - managerName, _ := cmd.Flags().GetString("manager") - var manager pm.PackageManager - if managerName == "os" { - managerName = "OS Package Manager" - } else if managerName == "brew" { - managerName = "homebrew" - } - switch managerName { - case "OS Package Manager": - if runtime.GOOS != "windows" { - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - manager = pm.NewOSManager(sudoPassword) - } else { - manager = pm.NewOSManager("") - } - case "homebrew": - manager = &pm.HomebrewManager{} - case "cargo": - manager = &pm.CargoManager{} - case "pipx": - manager = &pm.PipxManager{} - case "flatpak": - manager = &pm.FlatpakManager{} - default: - fmt.Println("No PackageManager found") - return - } - if err := manager.Install([]string{packageName}); err != nil { - log.Printf("error: %v\n", err) - } - if err := utils.LogToHistory("installed", packageName, manager.Name()); err != nil { - fmt.Println("error logging action:", err) - } - }, -} diff --git a/cmd/remove_cmd.go b/cmd/remove_cmd.go deleted file mode 100644 index 00af703..0000000 --- a/cmd/remove_cmd.go +++ /dev/null @@ -1,57 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "runtime" - "system_setup_tool/internal/utils" - - pm "system_setup_tool/pkg/packagemanager" - - "github.com/spf13/cobra" -) - -var removeCmd = &cobra.Command{ - Use: "remove [package_name]", - Short: "Remove a package with the specified package manager", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - packageName := args[0] - managerName, _ := cmd.Flags().GetString("manager") - var manager pm.PackageManager - if managerName == "os" { - managerName = "OS Package Manager" - } else if managerName == "brew" { - managerName = "homebrew" - } - switch managerName { - case "OS Package Manager": - if runtime.GOOS != "windows" { - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - manager = pm.NewOSManager(sudoPassword) - } else { - manager = pm.NewOSManager("") - } - case "homebrew": - manager = &pm.HomebrewManager{} - case "cargo": - manager = &pm.CargoManager{} - case "pipx": - manager = &pm.PipxManager{} - case "flatpak": - manager = &pm.FlatpakManager{} - default: - fmt.Println("No PackageManager found") - return - } - if err := manager.RemovePackage(packageName); err != nil { - log.Printf("error: %v\n", err) - } - if err := utils.LogToHistory("removed", packageName, manager.Name()); err != nil { - fmt.Println("error logging action:", err) - } - }, -} diff --git a/cmd/search_cmd.go b/cmd/search_cmd.go deleted file mode 100644 index d076c94..0000000 --- a/cmd/search_cmd.go +++ /dev/null @@ -1,116 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "runtime" - "strings" - "sync" - - "system_setup_tool/internal/utils" - pm "system_setup_tool/pkg/packagemanager" - - "github.com/spf13/cobra" -) - -var searchCmd = &cobra.Command{ - Use: "search [package_name]", - Short: "Search for a package across package managers (will search in os|flatpak|homebrew)", - Args: cobra.ExactArgs(1), - Aliases: []string{"s"}, - Run: func(cmd *cobra.Command, args []string) { - packageName := args[0] - managerName, _ := cmd.Flags().GetString("manager") - if managerName == "os" { - managerName = "OS Package Manager" - } else if managerName == "brew" { - managerName = "homebrew" - } - - var managers []pm.PackageManager - switch runtime.GOOS { - case "linux": - managers = []pm.PackageManager{ - pm.NewOSManager(""), - &pm.HomebrewManager{}, - &pm.FlatpakManager{}, - } - case "windows": - managers = []pm.PackageManager{ - pm.NewOSManager(""), - &pm.WingetManager{}, - &pm.ChocoManager{}, - } - case "darwin": - managers = []pm.PackageManager{ - pm.NewOSManager(""), - } - default: - log.Println("No Package Managers found") - } - - if managerName != "" { - for _, m := range managers { - if strings.EqualFold(m.Name(), managerName) { - searchAndDisplayResults(m, packageName) - return - } - } - fmt.Printf("Not supported for '%s'\n", managerName) - return - } - - searchConcurrently(managers, packageName) - }, -} - -func displayResults(results []string, manager string) { - if len(results) == 0 { - fmt.Printf("No results found in %s\n", manager) - return - } - for _, result := range results { - fmt.Printf("- %s\n", result) - } -} - -func searchAndDisplayResults(manager pm.PackageManager, packageName string) { - fmt.Printf("Searching in %s:\n", manager.Name()) - results := manager.SearchPackage(packageName) - displayResults(results, manager.Name()) - fmt.Println() -} - -func searchConcurrently(managers []pm.PackageManager, packageName string) { - var wg sync.WaitGroup - results := make(chan struct { - manager pm.PackageManager - results []string - }, len(managers)) - - for _, manager := range managers { - wg.Add(1) - go func(m pm.PackageManager) { - defer wg.Done() - searchResults := m.SearchPackage(packageName) - results <- struct { - manager pm.PackageManager - results []string - }{m, searchResults} - }(manager) - } - - go func() { - wg.Wait() - close(results) - }() - - for result := range results { - fmt.Printf("Results from %s:\n", result.manager.Name()) - displayResults(result.results, result.manager.Name()) - fmt.Println() - if err := utils.LogToHistory("searched", packageName, result.manager.Name()); err != nil { - fmt.Println("error logging action:", err) - } - } -} diff --git a/cmd/update_command.go b/cmd/update_command.go deleted file mode 100644 index 1731c8c..0000000 --- a/cmd/update_command.go +++ /dev/null @@ -1,128 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "runtime" - "sync" - "system_setup_tool/internal/utils" - pm "system_setup_tool/pkg/packagemanager" - - "github.com/spf13/cobra" -) - -var updateCmd = &cobra.Command{ - Use: "update [package_name]", - Short: "update a package with the specified package manager.", - Long: "update a package with the specified package manager.\nIf no package specified, all packages for the package manager will be updated", - Args: cobra.ExactArgs(1), - Aliases: []string{"u"}, - Run: func(cmd *cobra.Command, args []string) { - packageName := args[0] - managerName, _ := cmd.Flags().GetString("manager") - var manager pm.PackageManager - if managerName == "os" { - managerName = "OS Package Manager" - } else if managerName == "brew" { - managerName = "homebrew" - } - - switch managerName { - case "OS Package Manager": - if runtime.GOOS != "windows" { - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - manager = pm.NewOSManager(sudoPassword) - } else { - manager = pm.NewOSManager("") - } - case "homebrew": - manager = &pm.HomebrewManager{} - case "cargo": - manager = &pm.CargoManager{} - case "pipx": - manager = &pm.PipxManager{} - case "flatpak": - manager = &pm.FlatpakManager{} - default: - fmt.Println("No PackageManager found") - return - } - if packageName != "" { - if err := manager.UpdatePackage(packageName); err != nil { - log.Printf("error: %v\n", err) - } else { - if err := utils.LogToHistory("updated", packageName, manager.Name()); err != nil { - fmt.Println("error logging action:", err) - } - } - } else { - if err := manager.UpdateAllPackages(); err != nil { - log.Printf("error: %v\n", err) - } else { - if err := utils.LogToHistory("updated", "all", "all"); err != nil { - fmt.Println("error logging action:", err) - } - } - } - }, -} - -var updateAllCmd = &cobra.Command{ - Use: "update-all", - Short: "update all packages with all package managers.", - Run: func(cmd *cobra.Command, args []string) { - - var managers []pm.PackageManager - switch runtime.GOOS { - case "linux": - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - managers = []pm.PackageManager{ - pm.NewOSManager(sudoPassword), - &pm.HomebrewManager{}, - &pm.FlatpakManager{}, - &pm.PipxManager{}, - } - case "windows": - managers = []pm.PackageManager{ - pm.NewOSManager(""), - &pm.WingetManager{}, - &pm.ChocoManager{}, - &pm.PipxManager{}, - } - case "darwin": - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - managers = []pm.PackageManager{ - pm.NewOSManager(sudoPassword), - &pm.PipxManager{}, - } - default: - log.Println("No Package Managers found") - } - updateAllConcurrently(managers) - }, -} - -func updateAllConcurrently(managers []pm.PackageManager) { - var wg sync.WaitGroup - for _, m := range managers { - wg.Add(1) - go func(manager pm.PackageManager) { - defer wg.Done() - fmt.Printf("Updating %s-Packages\n", manager.Name()) - err := manager.UpdateAllPackages() - if err != nil { - log.Printf("Error updating %s: %v\n", manager.Name(), err) - } - }(m) - } - wg.Wait() -} diff --git a/internal/config/config.go b/config.go similarity index 58% rename from internal/config/config.go rename to config.go index 9a8eb7e..287a3fa 100644 --- a/internal/config/config.go +++ b/config.go @@ -1,9 +1,4 @@ -package config - -import ( - "system_setup_tool/internal/dotfiles" - pm "system_setup_tool/pkg/packagemanager" -) +package main type PackageManagerConfig struct { Enable bool `mapstructure:"enable"` @@ -12,7 +7,7 @@ type PackageManagerConfig struct { type Config struct { Headless bool `mapstructure:"headless"` - Packages pm.Packages `mapstructure:"packages"` + Packages Packages `mapstructure:"packages"` PackageManagers map[string]PackageManagerConfig `mapstructure:"package_managers"` - Dotfiles dotfiles.DotfilesConfig `mapstructure:"dotfiles"` + Dotfiles DotfilesConfig `mapstructure:"dotfiles"` } diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..e3f63a0 --- /dev/null +++ b/config.toml @@ -0,0 +1,31 @@ +config = '' +headless = true + +[dotfiles] +enable = false +git_repo = 'https://codeberg.org/Pata1704/dotfiles.git' + +[package_managers] +[package_managers.cargo] +enable = true +packages = ['typst-cli'] + +[package_managers.flatpak] +enable = true +packages = ['com.spotify.Client', 'us.zoom.Zoom', 'org.zotero.Zotero', 'com.google.AndroidStudio', 'io.freetubeapp.FreeTube', 'com.discordapp.Discord', 'com.nextcloud.desktopclient.nextcloud', 'com.github.tchx84.Flatseal', 'io.github.flattool.Warehouse', 'org.onlyoffice.desktopeditors'] + +[package_managers.go] +enable = true +packages = ['github.com/stefanlogue/meteor', 'golang.org/x/tools/gopls', 'github.com/go-delve/delve/cmd/dlv', 'github.com/air-verse/air'] + +[package_managers.homebrew] +enable = true +packages = ['fd', 'fzf', 'ripgrep', 'neovim', 'helix', 'node', 'yazi', 'zk', 'bat', 'bottom', 'btop', 'git-cliff', 'glow', 'lazygit', 'goreleaser', 'harlequin', 'mage', 'posting', 'typst', 'wails', 'zoxide', 'lsd', 'jq', 'yq', 'timewarrior', 'tmux', 'fastfetch', 'stow', 'distrobox'] + +[package_managers.pipx] +enable = true +packages = ['euporie'] + +[packages] +headless = ['git', 'curl', 'wget', 'biber', 'bear', 'docker', 'docker-compose', 'zsh', 'npm', 'task', 'tree-sitter-cli', 'python3-pip', 'latexmk', 'luarocks'] +non_headless = ['flameshot', 'fuzzel', 'hyprcursor', 'hypridle', 'hyprland', 'hyprland-qtutils', 'hyprlock', 'hyprpaper', 'hyprutils', 'kitty', 'mako', 'SwayNotificationCenter', 'SwayNotificationCenter-zsh-completion', 'mpv', 'pidgin', 'remmina', 'thunderbird-i18n-de', 'virt-manager', 'vlc', 'waybar', 'nwg-panel', 'nwg-look', 'xdg-desktop-portal-hyprland', 'zathura', 'zathura-pdf-mupdf'] diff --git a/dotfiles.go b/dotfiles.go new file mode 100644 index 0000000..849da76 --- /dev/null +++ b/dotfiles.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" +) + +type DotfilesConfig struct { + Enable bool `mapstructure:"enable"` + GitRepo string `mapstructure:"git_repo"` +} + +func setupDotfiles(config DotfilesConfig) error { + if _, err := execLookPath("git"); err != nil { + return fmt.Errorf("git ist nicht installiert") + } + + if _, err := execLookPath("stow"); err != nil { + return fmt.Errorf("gnu stow ist nicht installiert") + } + + dotfilesDir := filepath.Join(os.Getenv("HOME"), "dotfiles") + + cmd := execCommand("git", "clone", config.GitRepo, dotfilesDir) + if err := cmd.Run(); err != nil { + return fmt.Errorf("fehler beim Klonen der Dotfiles: %v", err) + } + + if err := os.Chdir(dotfilesDir); err != nil { + return fmt.Errorf("fehler beim Wechseln in das Dotfiles-Verzeichnis: %v", err) + } + + cmd = execCommand("stow", ".", "--override='*'") + if err := cmd.Run(); err != nil { + log.Printf("Fehler beim Linken: %v", err) + } + fmt.Printf("Alles erfolgreich verlinkt\n") + + return nil +} diff --git a/pkg/packagemanager/flatpak.go b/flatpak.go similarity index 61% rename from pkg/packagemanager/flatpak.go rename to flatpak.go index bfca427..0c926bc 100644 --- a/pkg/packagemanager/flatpak.go +++ b/flatpak.go @@ -1,10 +1,10 @@ -package packagemanager +package main import ( "fmt" "log" + "os/exec" "strings" - "system_setup_tool/internal/shell" ) type FlatpakManager struct { @@ -13,23 +13,11 @@ type FlatpakManager struct { Config FlatpakConfig } -func NewFlatpakManager(sudoPassword string, config FlatpakConfig) *FlatpakManager { - os, err := GetLinuxDistribution() - if err != nil { - log.Fatalf("error geting os information: %v", err) - } - return &FlatpakManager{ - OS: os, - SudoPassword: sudoPassword, - Config: config, - } -} - func (f *FlatpakManager) Install(packages []string) error { if len(packages) == 0 { return nil } - err := InstallWithProgress(f, packages) + err := installWithProgress(f, packages) if err != nil { return err } @@ -41,7 +29,7 @@ func (f *FlatpakManager) Name() string { } func (f *FlatpakManager) InstallManager() error { - if _, err := shell.ExecLookPath("flatpak"); err == nil { + if _, err := exec.LookPath("flatpak"); err == nil { return nil } @@ -81,7 +69,7 @@ func installFlatpak(os *OS, sudoPassword string) error { return fmt.Errorf("keine Flatpak-Installation für OS %s definiert", os.ID) } - if err := InstallPackage(command, "", sudoPassword); err != nil { + if err := installPackage(command, "", sudoPassword); err != nil { return fmt.Errorf("fehler bei der Flatpak-Installation: %v", err) } return nil @@ -89,7 +77,7 @@ func installFlatpak(os *OS, sudoPassword string) error { func addFlatpakRemotes(remotes []Remote) error { for _, remote := range remotes { - cmd := shell.ExecCommand("flatpak", "remote-add", "--if-not-exists", remote.Name, remote.URL) + cmd := execCommand("flatpak", "remote-add", "--if-not-exists", remote.Name, remote.URL) if err := cmd.Run(); err != nil { return fmt.Errorf("fehler beim Hinzufügen des Remotes %s: %v", remote.Name, err) } @@ -98,17 +86,17 @@ func addFlatpakRemotes(remotes []Remote) error { } func (f *FlatpakManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("flatpak", "install", "-y", pkg) + cmd := execCommand("flatpak", "install", "-y", pkg) return cmd.Run() } func (f *FlatpakManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("flatpak", "uninstall", "-y", pkg) + cmd := execCommand("flatpak", "uninstall", "-y", pkg) return cmd.Run() } func (f *FlatpakManager) SearchPackage(pkg string) []string { - cmd := shell.ExecCommand("flatpak", "search", pkg) + cmd := execCommand("flatpak", "search", pkg) packages, err := cmd.Output() if err != nil { log.Printf("error fetching %s packages: %v", f.Name(), err) @@ -116,18 +104,3 @@ func (f *FlatpakManager) SearchPackage(pkg string) []string { packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") return packageList } - -func (f *FlatpakManager) UpdatePackage(pkg string) error { - cmd := shell.ExecCommand("flatpak", "update", "-y", pkg) - return cmd.Run() -} - -func (f *FlatpakManager) UpdateAllPackages() error { - cmd := shell.ExecCommand("flatpak", "update", "-y") - return cmd.Run() -} - -func (f *FlatpakManager) Check(pkg string) error { - cmd := shell.ExecCommand("/bin/sh", "-c", fmt.Sprintf("flatpak list | grep %s", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/flatpak_test.go b/flatpak_test.go similarity index 83% rename from pkg/packagemanager/flatpak_test.go rename to flatpak_test.go index 96fad8f..c99e2d5 100644 --- a/pkg/packagemanager/flatpak_test.go +++ b/flatpak_test.go @@ -1,8 +1,8 @@ -package packagemanager +// flatpak_test.go +package main import ( "os/exec" - "system_setup_tool/internal/shell" "testing" ) @@ -33,10 +33,10 @@ func TestFlatpakManager_InstallPackage(t *testing.T) { fm := &FlatpakManager{} // Mock exec.Command - shell.ExecCommand = func(name string, arg ...string) *exec.Cmd { + execCommand = func(name string, arg ...string) *exec.Cmd { return exec.Command("echo", "mocked flatpak install") } - defer func() { shell.ExecCommand = exec.Command }() + defer func() { execCommand = exec.Command }() if err := fm.InstallPackage("test-package"); err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/go.mod b/go.mod index 396303f..f283e32 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module system_setup_tool go 1.23.4 require ( + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.2.4 github.com/charmbracelet/huh v0.6.0 + github.com/charmbracelet/lipgloss v1.0.0 github.com/magefile/mage v1.15.0 github.com/mitchellh/mapstructure v1.5.0 github.com/schollz/progressbar/v3 v3.18.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - golang.org/x/sys v0.29.0 ) require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.2.0 // indirect - github.com/charmbracelet/bubbles v0.20.0 // indirect - github.com/charmbracelet/bubbletea v1.2.4 // indirect - github.com/charmbracelet/lipgloss v1.0.0 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect @@ -51,6 +51,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.18.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 1146891..f3365b5 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQW github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= diff --git a/golang.go b/golang.go new file mode 100644 index 0000000..690c2d4 --- /dev/null +++ b/golang.go @@ -0,0 +1,45 @@ +package main + +type GolangManager struct{} + +func (g *GolangManager) Install(packages []string) error { + if len(packages) == 0 { + return nil + } + err := installWithProgress(g, packages) + if err != nil { + return err + } + return nil +} + +func (g *GolangManager) Name() string { + return "Golang" +} + +func (g *GolangManager) InstallManager() error { + if _, err := execLookPath("brew"); err != nil { + installHomebrew() + } + + if _, err := execLookPath("go"); err == nil { + return nil + } + + cmd := execCommand("brew", "install", "golang") + return cmd.Run() +} + +func (g *GolangManager) InstallPackage(pkg string) error { + cmd := execCommand("go", "install", pkg+"@latest") + return cmd.Run() +} + +func (g *GolangManager) RemovePackage(pkg string) error { + cmd := execCommand("go", "uninstall", pkg) + return cmd.Run() +} + +func (g *GolangManager) SearchPackage(pkg string) []string { + return []string{} +} diff --git a/pkg/packagemanager/golang_test.go b/golang_test.go similarity index 72% rename from pkg/packagemanager/golang_test.go rename to golang_test.go index 930fe0b..5e67b0a 100644 --- a/pkg/packagemanager/golang_test.go +++ b/golang_test.go @@ -1,8 +1,8 @@ -package packagemanager +// golang_test.go +package main import ( "os/exec" - "system_setup_tool/internal/shell" "testing" ) @@ -17,13 +17,13 @@ func TestGolangManager_InstallManager(t *testing.T) { gm := &GolangManager{} // Mock exec.LookPath - shell.ExecLookPath = func(file string) (string, error) { + execLookPath = func(file string) (string, error) { if file == "go" { return "/usr/local/go/bin/go", nil } return "", exec.ErrNotFound } - defer func() { shell.ExecLookPath = exec.LookPath }() + defer func() { execLookPath = exec.LookPath }() if err := gm.InstallManager(); err != nil { t.Errorf("Expected no error, got %v", err) @@ -34,10 +34,10 @@ func TestGolangManager_InstallPackage(t *testing.T) { gm := &GolangManager{} // Mock exec.Command - shell.ExecCommand = func(name string, arg ...string) *exec.Cmd { + execCommand = func(name string, arg ...string) *exec.Cmd { return exec.Command("echo", "mocked go install") } - defer func() { shell.ExecCommand = exec.Command }() + defer func() { execCommand = exec.Command }() if err := gm.InstallPackage("github.com/test/package"); err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/pkg/packagemanager/homebrew.go b/homebrew.go similarity index 53% rename from pkg/packagemanager/homebrew.go rename to homebrew.go index 2cf3258..9cd259d 100644 --- a/pkg/packagemanager/homebrew.go +++ b/homebrew.go @@ -1,10 +1,9 @@ -package packagemanager +package main import ( "fmt" "log" "strings" - "system_setup_tool/internal/shell" ) type HomebrewManager struct{} @@ -13,7 +12,7 @@ func (h *HomebrewManager) Install(packages []string) error { if len(packages) == 0 { return nil } - err := InstallWithProgress(h, packages) + err := installWithProgress(h, packages) if err != nil { return err } @@ -25,7 +24,7 @@ func (h *HomebrewManager) Name() string { } func (h *HomebrewManager) InstallManager() error { - if _, err := shell.ExecLookPath("brew"); err == nil { + if _, err := execLookPath("brew"); err == nil { return nil } @@ -33,7 +32,7 @@ func (h *HomebrewManager) InstallManager() error { } func installHomebrew() error { - err := shell.ExecuteShellCommand("sh -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", "NONINTERACTE=1") + err := executeShellCommand("sh -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", "NONINTERACTE=1") if err != nil { return fmt.Errorf("fehler bei der Installation von Homebrew: %v", err) } @@ -41,17 +40,17 @@ func installHomebrew() error { } func (h *HomebrewManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("brew", "install", pkg) + cmd := execCommand("brew", "install", pkg) return cmd.Run() } func (h *HomebrewManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("brew", "uninstall", pkg) + cmd := execCommand("brew", "uninstall", pkg) return cmd.Run() } func (h *HomebrewManager) SearchPackage(pkg string) []string { - cmd := shell.ExecCommand("brew", "search", pkg) + cmd := execCommand("brew", "search", pkg) packages, err := cmd.Output() if err != nil { log.Printf("error fetching %s packages: %v", h.Name(), err) @@ -59,18 +58,3 @@ func (h *HomebrewManager) SearchPackage(pkg string) []string { packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") return packageList } - -func (h *HomebrewManager) UpdatePackage(pkg string) error { - cmd := shell.ExecCommand("brew", "upgrade", pkg) - return cmd.Run() -} - -func (h *HomebrewManager) UpdateAllPackages() error { - cmd := shell.ExecCommand("brew", "upgrade") - return cmd.Run() -} - -func (h *HomebrewManager) Check(pkg string) error { - cmd := shell.ExecCommand("brew", "list", pkg) - return cmd.Run() -} diff --git a/pkg/packagemanager/homebrew_test.go b/homebrew_test.go similarity index 73% rename from pkg/packagemanager/homebrew_test.go rename to homebrew_test.go index 14128f0..72da6ee 100644 --- a/pkg/packagemanager/homebrew_test.go +++ b/homebrew_test.go @@ -1,8 +1,8 @@ -package packagemanager +// homebrew_test.go +package main import ( "os/exec" - "system_setup_tool/internal/shell" "testing" ) @@ -17,13 +17,13 @@ func TestHomebrewManager_InstallManager(t *testing.T) { hm := &HomebrewManager{} // Mock exec.LookPath and executeShellCommand - shell.ExecLookPath = func(file string) (string, error) { + execLookPath = func(file string) (string, error) { if file == "brew" { return "/usr/local/bin/brew", nil } return "", exec.ErrNotFound } - defer func() { shell.ExecLookPath = exec.LookPath }() + defer func() { execLookPath = exec.LookPath }() if err := hm.InstallManager(); err != nil { t.Errorf("Expected no error, got %v", err) @@ -34,10 +34,10 @@ func TestHomebrewManager_InstallPackage(t *testing.T) { hm := &HomebrewManager{} // Mock exec.Command - shell.ExecCommand = func(name string, arg ...string) *exec.Cmd { + execCommand = func(name string, arg ...string) *exec.Cmd { return exec.Command("echo", "mocked brew install") } - defer func() { shell.ExecCommand = exec.Command }() + defer func() { execCommand = exec.Command }() if err := hm.InstallPackage("test-package"); err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/install_cmd.go b/install_cmd.go new file mode 100644 index 0000000..468ab9f --- /dev/null +++ b/install_cmd.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "log" + + "github.com/spf13/cobra" +) + +var installCmd = &cobra.Command{ + Use: "install [package_name]", + Short: "Install a package with the specified package manager", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + packageName := args[0] + managerName, _ := cmd.Flags().GetString("manager") + var manager PackageManager + switch managerName { + case "os": + opSys, err := getLinuxDistribution() + if err != nil { + log.Printf("error getting OS information: %v", err) + } + sudoPassword, err := getSudoPassword() + if err != nil { + log.Fatal(err) + } + osManager := NewOSManager(opSys, sudoPassword, []string{packageName}) + if err := osManager.Install([]string{packageName}); err != nil { + log.Printf("error: %v\n", err) + } + case "homebrew": + manager = &HomebrewManager{} + case "cargo": + manager = &CargoManager{} + case "pipx": + manager = &PipxManager{} + case "flatpak": + manager = &FlatpakManager{} + default: + fmt.Println("No PackageManager found") + } + if err := manager.InstallPackage(packageName); err != nil { + log.Printf("error: %v\n", err) + } + }, +} diff --git a/internal/dotfiles/dotfiles.go b/internal/dotfiles/dotfiles.go deleted file mode 100644 index f6eea51..0000000 --- a/internal/dotfiles/dotfiles.go +++ /dev/null @@ -1,43 +0,0 @@ -package dotfiles - -import ( - "fmt" - "log" - "os" - "path/filepath" - "system_setup_tool/internal/shell" -) - -type DotfilesConfig struct { - Enable bool `mapstructure:"enable"` - GitRepo string `mapstructure:"git_repo"` -} - -func SetupDotfiles(config DotfilesConfig) error { - if _, err := shell.ExecLookPath("git"); err != nil { - return fmt.Errorf("git ist nicht installiert") - } - - if _, err := shell.ExecLookPath("stow"); err != nil { - return fmt.Errorf("gnu stow not installed") - } - - dotfilesDir := filepath.Join(os.Getenv("HOME"), "dotfiles") - - cmd := shell.ExecCommand("git", "clone", config.GitRepo, dotfilesDir) - if err := cmd.Run(); err != nil { - return fmt.Errorf("error cloning dotfiles: %v", err) - } - - if err := os.Chdir(dotfilesDir); err != nil { - return fmt.Errorf("error changing into dotfiles directory: %v", err) - } - - cmd = shell.ExecCommand("stow", ".", "--override='*'") - if err := cmd.Run(); err != nil { - log.Printf("error creating links: %v", err) - } - fmt.Printf("all linked properly\n") - - return nil -} diff --git a/internal/shell/shell.go b/internal/shell/shell.go deleted file mode 100644 index 5b7a8b7..0000000 --- a/internal/shell/shell.go +++ /dev/null @@ -1,23 +0,0 @@ -package shell - -import ( - "fmt" - "os" - "os/exec" -) - -var ( - ExecCommand = exec.Command - ExecLookPath = exec.LookPath -) - -func ExecuteShellCommand(command string, env string) error { - cmd := ExecCommand("bash", "-c", command) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, env) - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("befehl fehlgeschlagen: %v\nAusgabe: %s", err, output) - } - return nil -} diff --git a/internal/tui/tui.go b/internal/tui/tui.go deleted file mode 100644 index a387915..0000000 --- a/internal/tui/tui.go +++ /dev/null @@ -1,115 +0,0 @@ -package tui - -import ( - "fmt" - "log" - "system_setup_tool/internal/config" - "system_setup_tool/internal/dotfiles" - "system_setup_tool/internal/utils" - - pm "system_setup_tool/pkg/packagemanager" - - "github.com/charmbracelet/huh" - "github.com/mitchellh/mapstructure" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -type installedPkgMsg string - -func Run(cmd *cobra.Command, args []string) { - sudoPassword, err := utils.GetSudoPassword() - if err != nil { - log.Fatal(err) - } - - var cfg config.Config - if err := viper.Unmarshal(&cfg); err != nil { - log.Fatalf("Fehler beim Lesen der Konfiguration: %v", err) - } - - // form := huh.NewForm( - // huh.NewGroup( - // huh.NewConfirm(). - // Title("Möchten Sie eine headless Installation durchführen?"). - // Value(&cfg.Headless), - // ), - // ).WithTheme(huh.ThemeCatppuccin()) - // - // if err := form.Run(); err != nil { - // log.Fatalf("Fehler bei der Benutzerabfrage: %v", err) - // } - // - // osManager := pm.NewOSManager(sudoPassword) - // - // if err := osManager.Install(cfg.Packages.Headless); err != nil { - // log.Printf("Warnung bei der Installation der Headless-Pakete: %v", err) - // } - // if !cfg.Headless { - // if err := osManager.Install(cfg.Packages.NonHeadless); err != nil { - // log.Printf("Warnung bei der Installation der Non-Headless-Pakete: %v", err) - // } - // } - // - - for name, config := range cfg.PackageManagers { - if config.Enable { - var manager pm.PackageManager - switch name { - case "os": - manager = pm.NewOSManager(sudoPassword) - case "homebrew": - manager = &pm.HomebrewManager{} - case "go": - manager = &pm.GolangManager{} - case "cargo": - manager = &pm.CargoManager{} - case "pipx": - manager = &pm.PipxManager{} - case "flatpak": - flatpakConfig := pm.FlatpakConfig{} - if err := mapstructure.Decode(config, &flatpakConfig); err != nil { - log.Printf("error decoding flatpak config: %v", err) - continue - } - manager = pm.NewFlatpakManager(sudoPassword, flatpakConfig) - default: - log.Printf("unknown packagemanager: %s", name) - continue - } - if err := manager.InstallManager(); err != nil { - log.Printf("warning packagemanager %s not installed and could not be installed: %v", manager.Name(), err) - } - - if err := manager.Install(config.Packages); err != nil { - log.Printf("warning at %s-packages: %v", manager.Name(), err) - } - } - } - osManager := pm.NewOSManager(sudoPassword) - var installBuild bool - form := huh.NewForm( - huh.NewGroup( - huh.NewConfirm(). - Title("Möchten Sie die Build Essentials installieren?"). - Value(&installBuild), - ), - ).WithTheme(huh.ThemeCatppuccin()) - - err = form.Run() - if err != nil { - log.Fatal(err) - } - - if installBuild { - if err := osManager.InstallBuildEssentials(); err != nil { - log.Printf("Warnung: %v", err) - } - } - if cfg.Dotfiles.Enable { - fmt.Println("\nconfiguring dotfiles...") - if err := dotfiles.SetupDotfiles(cfg.Dotfiles); err != nil { - log.Printf("Warnung bei Dotfiles-Setup: %v", err) - } - } -} diff --git a/internal/utils/utils.go b/internal/utils/utils.go deleted file mode 100644 index 33d87e5..0000000 --- a/internal/utils/utils.go +++ /dev/null @@ -1,80 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/charmbracelet/huh" -) - -func GetSudoPassword() (string, error) { - var password string - form := huh.NewForm( - huh.NewGroup( - huh.NewInput(). - Title("Enter your password"). - EchoMode(huh.EchoModePassword). - Value(&password), - ), - ).WithTheme(huh.ThemeCatppuccin()) - - err := form.Run() - if err != nil { - return "", fmt.Errorf("error at password-dialog: %v", err) - } - return password, nil -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -const ( - maxHistoryLines = 2000 - historyFileName = "sst_history" -) - -func LogToHistory(action, pkg, manager string) error { - dirname, err := os.UserConfigDir() - if err != nil { - return fmt.Errorf("cant obtain config dir: %v", err) - } - historyFile := filepath.Join(dirname, "sst", historyFileName) - - lines, err := readHistoryLines(historyFile) - if err != nil { - return err - } - - timestamp := time.Now().Format("2006-01-02 15:04:05") - logEntry := fmt.Sprintf("%s: action: [%s] - package: [%s] - packagemanager: [%s]", timestamp, action, pkg, manager) - lines = append(lines, logEntry) - - if len(lines) > maxHistoryLines { - lines = lines[len(lines)-maxHistoryLines:] - } - - return writeHistoryLines(historyFile, lines) -} - -func readHistoryLines(filePath string) ([]string, error) { - content, err := os.ReadFile(filePath) - if err != nil { - if os.IsNotExist(err) { - return []string{}, nil - } - return nil, err - } - return strings.Split(string(content), "\n"), nil -} - -func writeHistoryLines(filePath string, lines []string) error { - content := strings.Join(lines, "\n") - return os.WriteFile(filePath, []byte(content), 0644) -} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go deleted file mode 100644 index 243432e..0000000 --- a/internal/utils/utils_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package utils - -import ( - "os/exec" - "system_setup_tool/internal/shell" - "testing" -) - -func TestMax(t *testing.T) { - tests := []struct { - a, b, want int - }{ - {1, 2, 2}, - {5, 3, 5}, - {0, 0, 0}, - {-1, -5, -1}, - } - - for _, tt := range tests { - got := max(tt.a, tt.b) - if got != tt.want { - t.Errorf("max(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) - } - } -} - -func TestExecuteShellCommand(t *testing.T) { - // Mock execCommand - oldExecCommand := shell.ExecCommand - defer func() { shell.ExecCommand = oldExecCommand }() - - shell.ExecCommand = func(command string, args ...string) *exec.Cmd { - return exec.Command("echo", "mocked command") - } - - err := shell.ExecuteShellCommand("test command", "TEST_ENV=value") - if err != nil { - t.Errorf("executeShellCommand() error = %v; want nil", err) - } -} diff --git a/magefile.go b/magefile.go index b0be26b..3603577 100644 --- a/magefile.go +++ b/magefile.go @@ -1,4 +1,5 @@ //go:build mage +// +build mage package main @@ -16,8 +17,9 @@ import ( // A build step that requires additional params, or platform specific steps for example func Build() error { + mg.Deps(InstallDeps) fmt.Println("Building...") - cmd := exec.Command("go", "build", "-o", "sst", ".") + cmd := exec.Command("go", "build", "-o", "system_setup_tool", ".") return cmd.Run() } @@ -25,13 +27,20 @@ func Build() error { func Install() error { mg.Deps(Build) fmt.Println("Installing...") - return os.Rename("./sst", "~/.local/bin/sst") + return os.Rename("./system_setup_tool", "/usr/bin/system_setup_tool") +} + +// Manage your deps, or running package managers. +func InstallDeps() error { + fmt.Println("Installing Deps...") + cmd := exec.Command("go", "get", "github.com/stretchr/piglatin") + return cmd.Run() } // Clean up after yourself func Clean() { fmt.Println("Cleaning...") - os.RemoveAll("system_setup_tool") + os.RemoveAll("MyApp") } // Use gofmt to format your code diff --git a/main.go b/main.go index 959f384..376027d 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,10 @@ package main import ( "log" "os" - "system_setup_tool/cmd" ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { + if err := rootCmd.Execute(); err != nil { log.Println(err) os.Exit(1) } diff --git a/model.go b/model.go new file mode 100644 index 0000000..41cb2e6 --- /dev/null +++ b/model.go @@ -0,0 +1,164 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/charmbracelet/bubbles/progress" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type model struct { + packages []string + index int + width int + height int + spinner spinner.Model + progress progress.Model + done bool + sudoPassword string + os OS +} + +func (m model) installSpecialSoftware() error { + if _, err := execLookPath("oh-my-posh"); err == nil { + fmt.Println("Oh-my-posh ist bereits installiert") + } else { + poshCommand := "curl -s https://ohmyposh.dev/install.sh | bash -s" + if err := installPackage(poshCommand, "", ""); err != nil { + return err + } + } + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("Fehler beim Ermitteln des Home-Verzeichnisses: %v", err) + } + + ohMyZshDir := filepath.Join(homeDir, ".oh-my-zsh") + if _, err := os.Stat(ohMyZshDir); !os.IsNotExist(err) { + fmt.Println("Oh My Zsh ist bereits installiert.") + return nil + } + + fmt.Println("Installiere Oh My Zsh...") + err = executeShellCommand(`sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"`, "") + if err != nil { + log.Printf("Fehler bei der Installation von Oh My Zsh: %v\n", err) + } + + plugins := []string{ + "git clone https://github.com/zsh-users/zsh-autosuggestions.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions", + "git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting", + "git clone https://github.com/zdharma-continuum/fast-syntax-highlighting.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/fast-syntax-highlighting", + "git clone --depth 1 -- https://github.com/marlonrichert/zsh-autocomplete.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autocomplete", + } + + for _, plugin := range plugins { + err := executeShellCommand(plugin, "") + if err != nil { + log.Printf("Fehler bei der Installation des Plugins: %v\n", err) + } + } + + fmt.Println("Oh My Zsh wurde erfolgreich installiert.") + return nil +} + +func newModel(packages []string, sudoPassword string, os *OS) model { + p := progress.New( + progress.WithDefaultGradient(), + progress.WithWidth(40), + progress.WithoutPercentage(), + ) + s := spinner.New() + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63")) + return model{ + packages: packages, + spinner: s, + progress: p, + sudoPassword: sudoPassword, + os: *os, + } +} + +func (m model) Init() tea.Cmd { + return tea.Batch(m.installPackageCmd(m.packages[m.index]), m.spinner.Tick) +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width, m.height = msg.Width, msg.Height + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "esc", "q": + return m, tea.Quit + } + case installedPkgMsg: + pkg := m.packages[m.index] + if m.index >= len(m.packages)-1 { + m.done = true + return m, tea.Sequence( + tea.Printf("%s %s", checkMark, pkg), + tea.Quit, + ) + } + + m.index++ + progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages))) + + return m, tea.Batch( + progressCmd, + tea.Printf("%s %s", checkMark, pkg), + m.installPackageCmd(m.packages[m.index]), + ) + case spinner.TickMsg: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + case progress.FrameMsg: + newModel, cmd := m.progress.Update(msg) + if newModel, ok := newModel.(progress.Model); ok { + m.progress = newModel + } + return m, cmd + } + return m, nil +} + +func (m model) View() string { + n := len(m.packages) + w := lipgloss.Width(fmt.Sprintf("%d", n)) + + if m.done { + return doneStyle.Render(fmt.Sprintf("Done! Installed %d packages.\n", n)) + } + + pkgCount := fmt.Sprintf(" %*d/%*d", w, m.index, w, n) + + spin := m.spinner.View() + " " + prog := m.progress.View() + cellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount)) + + pkgName := currentPkgNameStyle.Render(m.packages[m.index]) + info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render("Installing " + pkgName) + + cellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount)) + gap := strings.Repeat(" ", cellsRemaining) + + return spin + info + gap + prog + pkgCount +} + +func (m model) installPackageCmd(pkg string) tea.Cmd { + return func() tea.Msg { + if err := installPackage(m.os.InstallCommand, pkg, m.sudoPassword); err != nil { + log.Printf("Fehler beim Installieren von %s: %v", pkg, err) + } + return installedPkgMsg(pkg) + } +} diff --git a/osinfo.go b/osinfo.go new file mode 100644 index 0000000..bba7397 --- /dev/null +++ b/osinfo.go @@ -0,0 +1,234 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + tea "github.com/charmbracelet/bubbletea" +) + +type OSManager struct { + OS *OS + SudoPassword string + Packages []string + Model model +} + +func NewOSManager(os *OS, sudoPassword string, packages []string) *OSManager { + return &OSManager{ + OS: os, + SudoPassword: sudoPassword, + Packages: packages, + Model: newModel(packages, sudoPassword, os), + } +} + +func (o *OSManager) Name() string { + return "OS Package Manager" +} + +func (o *OSManager) InstallManager() error { return nil } + +func (o *OSManager) Install(packages []string) error { + o.Packages = packages + o.Model = newModel(packages, o.SudoPassword, o.OS) + + p := tea.NewProgram(o.Model) + _, err := p.Run() + return err +} + +func (o *OSManager) InstallPackage(pkg string) error { + return installPackage(o.OS.InstallCommand, pkg, o.SudoPassword) +} + +func (o *OSManager) InstallBuildEssentials() error { + return installBuildEssentials(o.OS, o.SudoPassword) +} + +func (o *OSManager) InstallSpecialSoftware() error { + return o.Model.installSpecialSoftware() +} + +func (o *OSManager) RemovePackage(pkg string) error { + fullCmd := fmt.Sprintf("%s %s", o.OS.RemoveCommand, pkg) + command := execCommand("sudo", "-S", "sh", "-c", fullCmd) + command.Stdin = strings.NewReader(o.SudoPassword + "\n") + return command.Run() +} + +func (o *OSManager) SearchPackage(pkg string) []string { + + cmdParts := strings.Fields(o.OS.SearchCommand) + if len(cmdParts) == 0 { + log.Printf("Invalid search command for OS package manager") + return []string{} + } + + cmd := execCommand(cmdParts[0], append(cmdParts[1:], pkg)...) + packages, err := cmd.Output() + if err != nil { + log.Printf("Error fetching %s packages: %v", o.OS.PackageManager, err) + return []string{} + } + + packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") + return packageList +} + +type OS struct { + ID string + Name string + Version string + PackageManager string + InstallCommand string + SearchCommand string + RemoveCommand string +} + +func parseOsRelease(osRelease string) *OS { + var result OS + result.ID = "Unknown" + result.Name = "Unknown" + result.Version = "Unknown" + result.PackageManager = "Unkown" + result.InstallCommand = "Unkown" + + lines := strings.Split(osRelease, "\n") + + for _, line := range lines { + splitLine := strings.SplitN(line, "=", 2) + if len(splitLine) != 2 { + continue + } + switch splitLine[0] { + case "ID": + result.ID = strings.ToLower(strings.Trim(splitLine[1], "\"")) + case "NAME": + result.Name = strings.Trim(splitLine[1], "\"") + case "VERSION_ID": + result.Version = strings.Trim(splitLine[1], "\"") + } + } + err := result.getPackageManager() + if err != nil { + log.Fatal(err) + } + err = result.getInstallCommand() + if err != nil { + log.Fatal(err) + } + err = result.getSearchCommand() + if err != nil { + log.Fatal(err) + } + return &result +} + +func getLinuxDistribution() (*OS, error) { + _, err := os.Stat("/etc/os-release") + if os.IsNotExist(err) { + return nil, fmt.Errorf("unable to read system information") + } + + osRelease, _ := os.ReadFile("/etc/os-release") + return parseOsRelease(string(osRelease)), nil +} + +func (os *OS) getPackageManager() error { + switch os.ID { + case "debian", "ubuntu": + os.PackageManager = "apt" + return nil + case "arch": + os.PackageManager = "pacman" + return nil + case "fedora": + os.PackageManager = "dnf" + return nil + default: + pmcommands := []string{ + "apt", + "dnf", + "pacman", + } + for _, pmname := range pmcommands { + _, err := execLookPath(pmname) + if err == nil { + os.PackageManager = pmname + return nil + } + } + return fmt.Errorf("no packagemanager found for os: %s", os) + } +} + +func (os *OS) getInstallCommand() error { + switch os.PackageManager { + case "apt": + os.InstallCommand = "apt install -y" + return nil + case "pacman": + os.InstallCommand = "pacman -S --noconfirm --needed" + return nil + case "dnf": + os.InstallCommand = "dnf install -y --best" + return nil + default: + return fmt.Errorf("no install command found for package manager: %s", os.ID) + } +} + +func (os *OS) getSearchCommand() error { + switch os.PackageManager { + case "apt": + os.SearchCommand = "apt search" + return nil + case "pacman": + os.SearchCommand = "pacman -Ss" + return nil + case "dnf": + os.SearchCommand = "dnf search" + return nil + default: + return fmt.Errorf("no install command found for package manager: %s", os.ID) + } +} + +func (os *OS) getDeleteCommand() error { + switch os.PackageManager { + case "apt": + os.RemoveCommand = "apt remove" + return nil + case "pacman": + os.RemoveCommand = "pacman -R" + return nil + case "dnf": + os.RemoveCommand = "dnf remove" + return nil + default: + return fmt.Errorf("no install command found for package manager: %s", os.ID) + } +} + +func installBuildEssentials(os *OS, sudoPassword string) error { + var command string + switch os.PackageManager { + case "pacman": + command = "pacman -S --noconfirm --needed base-devel" + case "apt": + command = "apt install -y build-essential" + case "dnf": + command = "dnf install -y @development-tools" + default: + return fmt.Errorf("keine Build Essentials für OS %s definiert", os.ID) + } + + fmt.Printf("Installiere Build Essentials für %s...\n", os.Name) + if err := installPackage(command, "", sudoPassword); err != nil { + return fmt.Errorf("fehler bei der Installation der Build Essentials: %v", err) + } + return nil +} diff --git a/pkg/packagemanager/os_test.go b/osinfo_test.go similarity index 68% rename from pkg/packagemanager/os_test.go rename to osinfo_test.go index f4d76eb..ddca2ec 100644 --- a/pkg/packagemanager/os_test.go +++ b/osinfo_test.go @@ -1,20 +1,26 @@ -package packagemanager +// osmanager_test.go +package main import ( "testing" ) func TestNewOSManager(t *testing.T) { + os := &OS{ID: "ubuntu", PackageManager: "apt"} sudoPassword := "testpassword" + packages := []string{"git", "curl"} - manager := NewOSManager(sudoPassword) + manager := NewOSManager(os, sudoPassword, packages) - if manager.OS == nil { - t.Error("Expected OS to be non-nil") + if manager.OS != os { + t.Errorf("Expected OS to be %v, got %v", os, manager.OS) } if manager.SudoPassword != sudoPassword { t.Errorf("Expected SudoPassword to be %s, got %s", sudoPassword, manager.SudoPassword) } + if len(manager.Packages) != len(packages) { + t.Errorf("Expected Packages length to be %d, got %d", len(packages), len(manager.Packages)) + } } func TestOSManagerName(t *testing.T) { diff --git a/pkg/packagemanager/package.go b/package.go similarity index 62% rename from pkg/packagemanager/package.go rename to package.go index ca38da6..77b7a46 100644 --- a/pkg/packagemanager/package.go +++ b/package.go @@ -1,10 +1,8 @@ -package packagemanager +package main import ( "fmt" - "log" "strings" - "system_setup_tool/internal/shell" ) type Packages struct { @@ -12,17 +10,26 @@ type Packages struct { NonHeadless []string `mapstructure:"non_headless"` } -func InstallPackage(cmd, pkg, sudoPassword string) error { +func installPackage(cmd, pkg, sudoPassword string) error { fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) + command := execCommand("sudo", "-S", "sh", "-c", fullCmd) command.Stdin = strings.NewReader(sudoPassword + "\n") output, err := command.CombinedOutput() if err != nil { if strings.Contains(string(output), "not found") || strings.Contains(string(output), "no matching package") || strings.Contains(string(output), "Keine Übereinstimmung") || strings.Contains(string(output), "Ziel nicht gefunden") { - log.Printf("Package %s not available\n", pkg) + unavailablePackages = append(unavailablePackages, pkg) return nil } return fmt.Errorf("failed to install %s: %v\n%s", pkg, err, string(output)) } return nil } + +func printUnavailablePackages() { + if len(unavailablePackages) > 0 { + fmt.Println("\nFolgende Pakete waren nicht verfügbar:") + for _, pkg := range unavailablePackages { + fmt.Printf("- %s\n", pkg) + } + } +} diff --git a/cmd/package_commands.go b/package_commands.go similarity index 92% rename from cmd/package_commands.go rename to package_commands.go index d0f1da4..a73126d 100644 --- a/cmd/package_commands.go +++ b/package_commands.go @@ -1,10 +1,9 @@ -package cmd +package main import ( "fmt" "os" "sort" - "system_setup_tool/internal/utils" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -59,9 +58,6 @@ var addCmd = &cobra.Command{ } fmt.Printf("Package %s has been added to the configuration for %s\n", name, manager) - if err := utils.LogToHistory("added", name, manager); err != nil { - fmt.Println("error logging action:", err) - } }, } @@ -124,9 +120,6 @@ var deleteCmd = &cobra.Command{ } fmt.Printf("Package %s was not found in the configuration for %s\n", name, manager) - if err := utils.LogToHistory("removed", name, manager); err != nil { - fmt.Println("error logging action:", err) - } } }, } @@ -194,8 +187,5 @@ var enableCmd = &cobra.Command{ } fmt.Printf("Package manager %s has been %s\n", manager, map[bool]string{true: "enabled", false: "disabled"}[enable]) - if err := utils.LogToHistory("enabled", "", manager); err != nil { - fmt.Println("error logging action:", err) - } }, } diff --git a/pkg/packagemanager/packagemanager.go b/packagemanager.go similarity index 66% rename from pkg/packagemanager/packagemanager.go rename to packagemanager.go index 55e5236..a5f17c2 100644 --- a/pkg/packagemanager/packagemanager.go +++ b/packagemanager.go @@ -1,13 +1,10 @@ -package packagemanager +package main type PackageManager interface { Install(packages []string) error - Check(pkg string) error InstallPackage(pkg string) error InstallManager() error RemovePackage(pkg string) error SearchPackage(pkg string) []string - UpdatePackage(pkg string) error - UpdateAllPackages() error Name() string } diff --git a/pipx.go b/pipx.go new file mode 100644 index 0000000..8f6820a --- /dev/null +++ b/pipx.go @@ -0,0 +1,46 @@ +package main + +type PipxManager struct{} + +func (p *PipxManager) Install(packages []string) error { + if len(packages) == 0 { + return nil + } + + err := installWithProgress(p, packages) + if err != nil { + return err + } + return nil +} + +func (p *PipxManager) Name() string { + return "Pipx" +} + +func (p *PipxManager) InstallManager() error { + if _, err := execLookPath("brew"); err != nil { + installHomebrew() + } + + if _, err := execLookPath("pipx"); err == nil { + return nil + } + + cmd := execCommand("brew", "install", "pipx") + return cmd.Run() +} + +func (p *PipxManager) InstallPackage(pkg string) error { + cmd := execCommand("pipx", "install", pkg) + return cmd.Run() +} + +func (p *PipxManager) RemovePackage(pkg string) error { + cmd := execCommand("pipx", "uninstall", pkg) + return cmd.Run() +} + +func (p *PipxManager) SearchPackage(pkg string) []string { + return []string{} +} diff --git a/pkg/packagemanager/pipx_test.go b/pipx_test.go similarity index 71% rename from pkg/packagemanager/pipx_test.go rename to pipx_test.go index b87fb2c..a7f7389 100644 --- a/pkg/packagemanager/pipx_test.go +++ b/pipx_test.go @@ -1,8 +1,8 @@ -package packagemanager +// pipx_test.go +package main import ( "os/exec" - "system_setup_tool/internal/shell" "testing" ) @@ -17,13 +17,13 @@ func TestPipxManager_InstallManager(t *testing.T) { pm := &PipxManager{} // Mock exec.LookPath - shell.ExecLookPath = func(file string) (string, error) { + execLookPath = func(file string) (string, error) { if file == "pipx" { return "/usr/bin/pipx", nil } return "", exec.ErrNotFound } - defer func() { shell.ExecLookPath = exec.LookPath }() + defer func() { execLookPath = exec.LookPath }() if err := pm.InstallManager(); err != nil { t.Errorf("Expected no error, got %v", err) @@ -34,10 +34,10 @@ func TestPipxManager_InstallPackage(t *testing.T) { pm := &PipxManager{} // Mock exec.Command - shell.ExecCommand = func(name string, arg ...string) *exec.Cmd { + execCommand = func(name string, arg ...string) *exec.Cmd { return exec.Command("echo", "mocked pipx install") } - defer func() { shell.ExecCommand = exec.Command }() + defer func() { execCommand = exec.Command }() if err := pm.InstallPackage("test-package"); err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/pkg/packagemanager/apt.go b/pkg/packagemanager/apt.go deleted file mode 100644 index 67df45b..0000000 --- a/pkg/packagemanager/apt.go +++ /dev/null @@ -1,81 +0,0 @@ -package packagemanager - -import ( - "fmt" - "log" - "strings" - "system_setup_tool/internal/shell" -) - -type AptManager struct { - SudoPassword string -} - -func (a *AptManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(a, packages) - if err != nil { - return err - } - return nil -} - -func (a *AptManager) Name() string { - return "OS Home Manager" -} - -func (a *AptManager) InstallManager() error { - return nil -} - -func (a *AptManager) InstallPackage(pkg string) error { - cmd := "apt install -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(a.SudoPassword + "\n") - return command.Run() -} - -func (a *AptManager) RemovePackage(pkg string) error { - cmd := "apt remove -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(a.SudoPassword + "\n") - return command.Run() -} - -func (a *AptManager) SearchPackage(pkg string) []string { - cmd := "apt search -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(a.SudoPassword + "\n") - packages, err := command.Output() - if err != nil { - log.Printf("error fetching %s packages: %v", a.Name(), err) - } - packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") - return packageList -} - -func (a *AptManager) UpdatePackage(pkg string) error { - cmd := "apt update -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(a.SudoPassword + "\n") - return command.Run() -} - -func (a *AptManager) UpdateAllPackages() error { - cmd := "apt update -y" - fullCmd := fmt.Sprintf("%s", cmd) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(a.SudoPassword + "\n") - return command.Run() -} - -func (a *AptManager) Check(pkg string) error { - cmd := shell.ExecCommand("/bin/sh", "-c", fmt.Sprintf("dpkg -l | grep %s", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/cargo.go b/pkg/packagemanager/cargo.go deleted file mode 100644 index fe63d6e..0000000 --- a/pkg/packagemanager/cargo.go +++ /dev/null @@ -1,69 +0,0 @@ -package packagemanager - -import ( - "fmt" - "os" - "path/filepath" - "system_setup_tool/internal/shell" -) - -type CargoManager struct{} - -func (c *CargoManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(c, packages) - if err != nil { - return err - } - return nil -} - -func (c *CargoManager) Name() string { - return "Cargo" -} - -func (c *CargoManager) InstallManager() error { - if _, err := shell.ExecLookPath("brew"); err != nil { - installHomebrew() - } - - if _, err := shell.ExecLookPath("cargo"); err == nil { - return nil - } - - cmd := shell.ExecCommand("brew", "install", "rust") - return cmd.Run() -} - -func (c *CargoManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("cargo", "install", pkg) - return cmd.Run() -} - -func (c *CargoManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("cargo", "uninstall", pkg) - return cmd.Run() -} - -func (c *CargoManager) SearchPackage(pkg string) []string { - return []string{} -} - -func (c *CargoManager) UpdatePackage(pkg string) error { - return fmt.Errorf("update not supported") -} - -func (c *CargoManager) UpdateAllPackages() error { - return fmt.Errorf("update not supported") -} - -func (c *CargoManager) Check(pkg string) error { - home, err := os.UserHomeDir() - if err != nil { - return err - } - cmd := shell.ExecCommand("ls", filepath.Join(home, ".cargo", "bin", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/choco.go b/pkg/packagemanager/choco.go deleted file mode 100644 index dc38249..0000000 --- a/pkg/packagemanager/choco.go +++ /dev/null @@ -1,71 +0,0 @@ -package packagemanager - -import ( - "log" - "strings" - "system_setup_tool/internal/shell" -) - -type ChocoManager struct{} - -func (c *ChocoManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(c, packages) - if err != nil { - return err - } - return nil -} - -func (c *ChocoManager) Name() string { - return "Chocolatey" -} - -func (c *ChocoManager) InstallManager() error { - if _, err := shell.ExecLookPath("choco"); err == nil { - return nil - } - - return installHomebrew() -} - -func installChoco() error { - return nil -} - -func (c *ChocoManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("choco", "install", pkg) - return cmd.Run() -} - -func (c *ChocoManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("choco", "uninstall", pkg) - return cmd.Run() -} - -func (c *ChocoManager) SearchPackage(pkg string) []string { - cmd := shell.ExecCommand("choco", "search", pkg) - packages, err := cmd.Output() - if err != nil { - log.Printf("error fetching %s packages: %v", c.Name(), err) - } - packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") - return packageList -} - -func (c *ChocoManager) UpdatePackage(pkg string) error { - cmd := shell.ExecCommand("choco", "update", pkg) - return cmd.Run() -} - -func (c *ChocoManager) UpdateAllPackages() error { - cmd := shell.ExecCommand("choco", "update") - return cmd.Run() -} - -func (c *ChocoManager) Check(pkg string) error { - cmd := shell.ExecCommand("choco", "search", "--local-only", pkg) - return cmd.Run() -} diff --git a/pkg/packagemanager/dnf.go b/pkg/packagemanager/dnf.go deleted file mode 100644 index 02be368..0000000 --- a/pkg/packagemanager/dnf.go +++ /dev/null @@ -1,78 +0,0 @@ -package packagemanager - -import ( - "fmt" - "log" - "strings" - "system_setup_tool/internal/shell" -) - -type DnfManager struct { - SudoPassword string -} - -func (d *DnfManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(d, packages) - if err != nil { - return err - } - return nil -} - -func (d *DnfManager) Name() string { - return "OS Home Manager" -} - -func (d *DnfManager) InstallManager() error { - return nil -} - -func (d *DnfManager) InstallPackage(pkg string) error { - cmd := "dnf install -y --best" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(d.SudoPassword + "\n") - return command.Run() -} - -func (d *DnfManager) RemovePackage(pkg string) error { - cmd := "dnf remove -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(d.SudoPassword + "\n") - return command.Run() -} - -func (d *DnfManager) SearchPackage(pkg string) []string { - cmd := shell.ExecCommand("dnf", "search", pkg) - packages, err := cmd.Output() - if err != nil { - log.Printf("error fetching %s packages: %v", d.Name(), err) - } - packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") - return packageList -} - -func (d *DnfManager) UpdatePackage(pkg string) error { - cmd := "dnf update -y" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(d.SudoPassword + "\n") - return command.Run() -} - -func (d *DnfManager) UpdateAllPackages() error { - cmd := "dnf update -y" - fullCmd := fmt.Sprintf("%s", cmd) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(d.SudoPassword + "\n") - return command.Run() -} - -func (d *DnfManager) Check(pkg string) error { - cmd := shell.ExecCommand("/bin/sh", "-c", fmt.Sprintf("dnf list installed | grep %s", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/golang.go b/pkg/packagemanager/golang.go deleted file mode 100644 index cfd25ab..0000000 --- a/pkg/packagemanager/golang.go +++ /dev/null @@ -1,69 +0,0 @@ -package packagemanager - -import ( - "fmt" - "os" - "path/filepath" - "system_setup_tool/internal/shell" -) - -type GolangManager struct{} - -func (g *GolangManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(g, packages) - if err != nil { - return err - } - return nil -} - -func (g *GolangManager) Name() string { - return "Golang" -} - -func (g *GolangManager) InstallManager() error { - if _, err := shell.ExecLookPath("brew"); err != nil { - installHomebrew() - } - - if _, err := shell.ExecLookPath("go"); err == nil { - return nil - } - - cmd := shell.ExecCommand("brew", "install", "golang") - return cmd.Run() -} - -func (g *GolangManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("go", "install", pkg+"@latest") - return cmd.Run() -} - -func (g *GolangManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("go", "uninstall", pkg) - return cmd.Run() -} - -func (g *GolangManager) SearchPackage(pkg string) []string { - return []string{} -} - -func (g *GolangManager) UpdatePackage(pkg string) error { - return fmt.Errorf("update not supported") -} - -func (g *GolangManager) UpdateAllPackages() error { - return fmt.Errorf("update not supported") -} - -func (g *GolangManager) Check(pkg string) error { - home, err := os.UserHomeDir() - if err != nil { - fmt.Printf("error: %v\n", err) - } - cmd := shell.ExecCommand("ls", filepath.Join(home, "go", "bin", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/os.go b/pkg/packagemanager/os.go deleted file mode 100644 index a1d6dff..0000000 --- a/pkg/packagemanager/os.go +++ /dev/null @@ -1,268 +0,0 @@ -package packagemanager - -import ( - "fmt" - "log" - "os" - "runtime" - "strings" - "system_setup_tool/internal/shell" -) - -type OSManager struct { - OS *OS - SudoPassword string - pm PackageManager -} - -func NewOSManager(sudoPassword string) *OSManager { - switch runtime.GOOS { - case "linux": - os, err := GetLinuxDistribution() - if err != nil { - log.Fatalf("error getting os information: %v", err) - } - var pm PackageManager - switch os.ID { - case "debian", "ubuntu": - pm = &AptManager{SudoPassword: sudoPassword} - case "arch": - pm = &PacmanManager{SudoPassword: sudoPassword} - case "fedora": - pm = &DnfManager{SudoPassword: sudoPassword} - default: - log.Fatalf("Unsupported Linux distribution: %s", os.ID) - } - return &OSManager{ - OS: os, - SudoPassword: sudoPassword, - pm: pm, - } - case "windows": - os, err := getPlatformInfo() - if err != nil { - return nil - } - - if _, err := shell.ExecLookPath("winget"); err == nil { - return &OSManager{ - OS: os, - SudoPassword: "", - pm: &WingetManager{}, - } - } else if _, err := shell.ExecLookPath("choco"); err == nil { - return &OSManager{ - OS: os, - SudoPassword: "", - pm: &ChocoManager{}, - } - } else { - log.Println("Neither Winget nor Chocolatey found on Windows. Package management functionality will be limited.") - } - case "darwin": - os, err := platformInfoMac() - if err != nil { - return nil - } - _, err = shell.ExecLookPath("brew") - if err == nil { - return &OSManager{ - OS: os, - SudoPassword: sudoPassword, - pm: &HomebrewManager{}, - } - } else { - manager := &HomebrewManager{} - manager.InstallManager() - return &OSManager{ - OS: os, - SudoPassword: sudoPassword, - pm: manager, - } - } - default: - log.Fatal("Operating system not supported") - } - return nil -} - -func (o *OSManager) Name() string { - return "OS Package Manager" -} - -func (o *OSManager) InstallManager() error { return nil } - -func (o *OSManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(o.pm, packages) - if err != nil { - return err - } - return nil -} - -func (o *OSManager) InstallPackage(pkg string) error { - return o.pm.InstallPackage(pkg) //(o.OS.InstallCommand, pkg, o.SudoPassword) -} - -func (o *OSManager) InstallBuildEssentials() error { - return InstallBuildEssentials(o.OS, o.SudoPassword) -} - -func (o *OSManager) RemovePackage(pkg string) error { - return o.pm.RemovePackage(pkg) -} - -func (o *OSManager) SearchPackage(pkg string) []string { - return o.pm.SearchPackage(pkg) -} - -func (o *OSManager) UpdatePackage(pkg string) error { - return o.pm.UpdatePackage(pkg) -} - -func (o *OSManager) UpdateAllPackages() error { - return o.pm.UpdateAllPackages() -} - -func (o *OSManager) Check(pkg string) error { - return o.pm.Check(pkg) -} - -type OS struct { - ID string - Name string - Version string - PackageManager string -} - -func parseOsRelease(osRelease string) *OS { - var result OS - result.ID = "Unknown" - result.Name = "Unknown" - result.Version = "Unknown" - - lines := strings.Split(osRelease, "\n") - - for _, line := range lines { - splitLine := strings.SplitN(line, "=", 2) - if len(splitLine) != 2 { - continue - } - switch splitLine[0] { - case "ID": - result.ID = strings.ToLower(strings.Trim(splitLine[1], "\"")) - case "NAME": - result.Name = strings.Trim(splitLine[1], "\"") - case "VERSION_ID": - result.Version = strings.Trim(splitLine[1], "\"") - } - } - err := result.getPackageManager() - if err != nil { - log.Fatal(err) - } - return &result -} - -func GetLinuxDistribution() (*OS, error) { - _, err := os.Stat("/etc/os-release") - if os.IsNotExist(err) { - return nil, fmt.Errorf("unable to read system information") - } - - osRelease, _ := os.ReadFile("/etc/os-release") - return parseOsRelease(string(osRelease)), nil -} - -func InstallBuildEssentials(os *OS, sudoPassword string) error { - var command string - switch os.PackageManager { - case "pacman": - command = "pacman -S --noconfirm --needed base-devel" - case "apt": - command = "apt install -y build-essential" - case "dnf": - command = "dnf install -y @development-tools" - default: - return fmt.Errorf("keine Build Essentials für OS %s definiert", os.ID) - } - - fmt.Printf("Installiere Build Essentials für %s...\n", os.Name) - if err := InstallPackage(command, "", sudoPassword); err != nil { - return fmt.Errorf("error installing Build Essentials: %v", err) - } - return nil -} - -func getSysctlValue(key string) (string, error) { - stdout, err := shell.ExecCommand(".", "sysctl", key).Output() - if err != nil { - return "", err - } - version := strings.TrimPrefix(string(stdout), key+": ") - return strings.TrimSpace(version), nil -} - -func platformInfoMac() (*OS, error) { - var result OS - result.ID = "Unknown" - result.Name = "MacOS" - result.Version = "Unknown" - - version, err := getSysctlValue("kern.osproductversion") - if err != nil { - return nil, err - } - result.Version = version - ID, err := getSysctlValue("kern.osversion") - if err != nil { - return nil, err - } - result.ID = ID - - return &result, nil -} - -func getPlatformInfo() (*OS, error) { - switch runtime.GOOS { - case "windows": - return platformInfoWin() - case "darwin": - return platformInfoMac() - case "linux": - return GetLinuxDistribution() - default: - return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) - } -} - -func (os *OS) getPackageManager() error { - switch os.ID { - case "debian", "ubuntu": - os.PackageManager = "apt" - return nil - case "arch": - os.PackageManager = "pacman" - return nil - case "fedora": - os.PackageManager = "dnf" - return nil - default: - pmcommands := []string{ - "apt", - "dnf", - "pacman", - } - for _, pmname := range pmcommands { - _, err := shell.ExecLookPath(pmname) - if err == nil { - os.PackageManager = pmname - return nil - } - } - return fmt.Errorf("no packagemanager found fos os: %s", os.Name) - } -} diff --git a/pkg/packagemanager/pacman.go b/pkg/packagemanager/pacman.go deleted file mode 100644 index 05cad0f..0000000 --- a/pkg/packagemanager/pacman.go +++ /dev/null @@ -1,81 +0,0 @@ -package packagemanager - -import ( - "fmt" - "log" - "strings" - "system_setup_tool/internal/shell" -) - -type PacmanManager struct { - SudoPassword string -} - -func (m *PacmanManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(m, packages) - if err != nil { - return err - } - return nil -} - -func (m *PacmanManager) Name() string { - return "OS Home Manager" -} - -func (m *PacmanManager) InstallManager() error { - return nil -} - -func (m *PacmanManager) InstallPackage(pkg string) error { - cmd := "pacman -S --noconfirm --needed" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(m.SudoPassword + "\n") - return command.Run() -} - -func (m *PacmanManager) RemovePackage(pkg string) error { - cmd := "pacman -R" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(m.SudoPassword + "\n") - return command.Run() -} - -func (p *PacmanManager) SearchPackage(pkg string) []string { - cmd := "pacman -Ss" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(p.SudoPassword + "\n") - packages, err := command.Output() - if err != nil { - log.Printf("error fetching %s packages: %v", p.Name(), err) - } - packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") - return packageList -} - -func (p *PacmanManager) UpdatePackage(pkg string) error { - cmd := "pacman -S" - fullCmd := fmt.Sprintf("%s %s", cmd, pkg) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(p.SudoPassword + "\n") - return command.Run() -} - -func (p *PacmanManager) UpdateAllPackages() error { - cmd := "apt -S" - fullCmd := fmt.Sprintf("%s", cmd) - command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd) - command.Stdin = strings.NewReader(p.SudoPassword + "\n") - return command.Run() -} - -func (p *PacmanManager) Check(pkg string) error { - cmd := shell.ExecCommand("/bin/sh", "-c", fmt.Sprintf("paxman -Q | grep %s", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/pipx.go b/pkg/packagemanager/pipx.go deleted file mode 100644 index 81d3c90..0000000 --- a/pkg/packagemanager/pipx.go +++ /dev/null @@ -1,66 +0,0 @@ -package packagemanager - -import ( - "fmt" - "system_setup_tool/internal/shell" -) - -type PipxManager struct{} - -func (p *PipxManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - - err := InstallWithProgress(p, packages) - if err != nil { - return err - } - return nil -} - -func (p *PipxManager) Name() string { - return "Pipx" -} - -func (p *PipxManager) InstallManager() error { - if _, err := shell.ExecLookPath("brew"); err != nil { - installHomebrew() - } - - if _, err := shell.ExecLookPath("pipx"); err == nil { - return nil - } - - cmd := shell.ExecCommand("brew", "install", "pipx") - return cmd.Run() -} - -func (p *PipxManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("pipx", "install", pkg) - return cmd.Run() -} - -func (p *PipxManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("pipx", "uninstall", pkg) - return cmd.Run() -} - -func (p *PipxManager) SearchPackage(pkg string) []string { - return []string{} -} - -func (p *PipxManager) UpdatePackage(pkg string) error { - cmd := shell.ExecCommand("pipx", "upgrade", pkg) - return cmd.Run() -} - -func (p *PipxManager) UpdateAllPackages() error { - cmd := shell.ExecCommand("pipx", "upgrade-all") - return cmd.Run() -} - -func (p *PipxManager) Check(pkg string) error { - cmd := shell.ExecCommand("/bin/sh", "-c", fmt.Sprintf("pipx list | grep %s", pkg)) - return cmd.Run() -} diff --git a/pkg/packagemanager/utils.go b/pkg/packagemanager/utils.go deleted file mode 100644 index 293679c..0000000 --- a/pkg/packagemanager/utils.go +++ /dev/null @@ -1,19 +0,0 @@ -package packagemanager - -import ( - "log" - - "github.com/schollz/progressbar/v3" -) - -func InstallWithProgress(manager PackageManager, packages []string) error { - bar := progressbar.Default(int64(len(packages)), "Installiere "+manager.Name()+"-Pakete...") - for _, pkg := range packages { - err := manager.InstallPackage(pkg) - if err != nil { - log.Printf("\nError installing %s: %v\n", pkg, err) - } - bar.Add(1) - } - return nil -} diff --git a/pkg/packagemanager/win.go b/pkg/packagemanager/win.go deleted file mode 100644 index 4488b57..0000000 --- a/pkg/packagemanager/win.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build windows -// +build windows - -package packagemanager - -import ( - "fmt" - - "golang.org/x/sys/windows/registry" -) - -func platformInfoWin() (*OS, error) { - var result OS - result.ID = "Unknown" - result.Name = "Windows" - result.Version = "Unknown" - - key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) - - productName, _, _ := key.GetStringValue("ProductName") - currentBuild, _, _ := key.GetStringValue("CurrentBuildNumber") - displayVersion, _, _ := key.GetStringValue("DisplayVersion") - releaseId, _, _ := key.GetStringValue("ReleaseId") - - result.Name = productName - result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild) - result.ID = displayVersion - - return &result, key.Close() -} diff --git a/pkg/packagemanager/win_dummy.go b/pkg/packagemanager/win_dummy.go deleted file mode 100644 index b98c8d2..0000000 --- a/pkg/packagemanager/win_dummy.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build linux || darwin -// +build linux darwin - -package packagemanager - -func platformInfoWin() (*OS, error) { - return nil, nil -} diff --git a/pkg/packagemanager/winget.go b/pkg/packagemanager/winget.go deleted file mode 100644 index bdfabad..0000000 --- a/pkg/packagemanager/winget.go +++ /dev/null @@ -1,71 +0,0 @@ -package packagemanager - -import ( - "log" - "strings" - "system_setup_tool/internal/shell" -) - -type WingetManager struct{} - -func (w *WingetManager) Install(packages []string) error { - if len(packages) == 0 { - return nil - } - err := InstallWithProgress(w, packages) - if err != nil { - return err - } - return nil -} - -func (w *WingetManager) Name() string { - return "Winget" -} - -func (w *WingetManager) InstallManager() error { - if _, err := shell.ExecLookPath("winget"); err == nil { - return nil - } - - return installHomebrew() -} - -func installWinget() error { - return nil -} - -func (w *WingetManager) InstallPackage(pkg string) error { - cmd := shell.ExecCommand("winget", "install", pkg) - return cmd.Run() -} - -func (w *WingetManager) RemovePackage(pkg string) error { - cmd := shell.ExecCommand("winget", "uninstall", pkg) - return cmd.Run() -} - -func (w *WingetManager) SearchPackage(pkg string) []string { - cmd := shell.ExecCommand("winget", "search", pkg) - packages, err := cmd.Output() - if err != nil { - log.Printf("error fetching %s packages: %v", w.Name(), err) - } - packageList := strings.Split(strings.TrimSpace(string(packages)), "\n") - return packageList -} - -func (w *WingetManager) UpdatePackage(pkg string) error { - cmd := shell.ExecCommand("winget", "update", pkg) - return cmd.Run() -} - -func (w *WingetManager) UpdateAllPackages() error { - cmd := shell.ExecCommand("winget", "update") - return cmd.Run() -} - -func (w *WingetManager) Check(pkg string) error { - cmd := shell.ExecCommand("wiget", "show", pkg) - return cmd.Run() -} diff --git a/remove_cmd.go b/remove_cmd.go new file mode 100644 index 0000000..6609578 --- /dev/null +++ b/remove_cmd.go @@ -0,0 +1,45 @@ +package main + +import ( + "log" + + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove [package_name]", + Short: "Remove a package with the specified package manager", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + packageName := args[0] + managerName, _ := cmd.Flags().GetString("manager") + var manager PackageManager + switch managerName { + case "os": + opSys, err := getLinuxDistribution() + if err != nil { + log.Printf("error getting OS information: %v", err) + } + sudoPassword, err := getSudoPassword() + if err != nil { + log.Fatal(err) + } + osManager := NewOSManager(opSys, sudoPassword, []string{packageName}) + if err := osManager.Install([]string{packageName}); err != nil { + log.Printf("error: %v\n", err) + } + case "homebrew": + manager = &HomebrewManager{} + case "cargo": + manager = &CargoManager{} + case "pipx": + manager = &PipxManager{} + case "flatpak": + manager = &FlatpakManager{} + default: + } + if err := manager.RemovePackage(packageName); err != nil { + log.Printf("error: %v\n", err) + } + }, +} diff --git a/search_cmd.go b/search_cmd.go new file mode 100644 index 0000000..8514355 --- /dev/null +++ b/search_cmd.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "log" + "strings" + + "github.com/spf13/cobra" +) + +var searchCmd = &cobra.Command{ + Use: "search [package_name]", + Short: "Search for a package across package managers (will search in os|flatpak|homebrew)", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + packageName := args[0] + managerName, _ := cmd.Flags().GetString("manager") + if managerName == "os" { + managerName = "OS Package Manager" + } + opSys, err := getLinuxDistribution() + if err != nil { + log.Printf("error getting OS information: %v", err) + return + } + + managers := []PackageManager{ + NewOSManager(opSys, "", nil), + &HomebrewManager{}, + &FlatpakManager{}, + } + + if managerName != "" { + for _, m := range managers { + if strings.EqualFold(m.Name(), managerName) { + searchAndDisplayResults(m, packageName) + return + } + } + fmt.Printf("Not supported for '%s'\n", managerName) + return + } + + for _, manager := range managers { + searchAndDisplayResults(manager, packageName) + } + }, +} + +func displayResults(results []string, manager string) { + if len(results) == 0 { + fmt.Printf("No results found in %s\n", manager) + return + } + for _, result := range results { + fmt.Printf("- %s\n", result) + } +} + +func searchAndDisplayResults(manager PackageManager, packageName string) { + fmt.Printf("Searching in %s:\n", manager.Name()) + results := manager.SearchPackage(packageName) + displayResults(results, manager.Name()) + fmt.Println() +} diff --git a/tui.go b/tui.go new file mode 100644 index 0000000..ab1be10 --- /dev/null +++ b/tui.go @@ -0,0 +1,146 @@ +package main + +import ( + "fmt" + "log" + + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" + "github.com/mitchellh/mapstructure" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211")) + doneStyle = lipgloss.NewStyle().Margin(1, 2) + checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓") + unavailablePackages []string +) + +type installedPkgMsg string + +func run(cmd *cobra.Command, args []string) { + os, err := getLinuxDistribution() + if err != nil { + log.Fatal(err) + } + + sudoPassword, err := getSudoPassword() + if err != nil { + log.Fatal(err) + } + + var cfg Config + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatalf("Fehler beim Lesen der Konfiguration: %v", err) + } + + form := huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Title("Möchten Sie eine headless Installation durchführen?"). + Value(&cfg.Headless), + ), + ).WithTheme(huh.ThemeCatppuccin()) + + if err := form.Run(); err != nil { + log.Fatalf("Fehler bei der Benutzerabfrage: %v", err) + } + + unavailablePackages = []string{} + osManager := NewOSManager(os, sudoPassword, cfg.Packages.Headless) + + if err := osManager.Install(cfg.Packages.Headless); err != nil { + log.Printf("Warnung bei der Installation der Headless-Pakete: %v", err) + } + if !cfg.Headless { + if err := osManager.Install(cfg.Packages.NonHeadless); err != nil { + log.Printf("Warnung bei der Installation der Non-Headless-Pakete: %v", err) + } + } + + printUnavailablePackages() + + var installBuild bool + form = huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Title("Möchten Sie die Build Essentials installieren?"). + Value(&installBuild), + ), + ).WithTheme(huh.ThemeCatppuccin()) + + err = form.Run() + if err != nil { + log.Fatal(err) + } + + if installBuild { + if err := osManager.InstallBuildEssentials(); err != nil { + log.Printf("Warnung: %v", err) + } + } + + var installShellExtensions bool + form = huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Title("Möchten Sie die Shell-Extensions installieren?"). + Value(&installShellExtensions), + ), + ).WithTheme(huh.ThemeCatppuccin()) + + err = form.Run() + if err != nil { + log.Fatal(err) + } + if installShellExtensions { + if err := osManager.InstallSpecialSoftware(); err != nil { + log.Printf("Warnung bei der Installation spezieller Software: %v", err) + } + } + + for name, config := range cfg.PackageManagers { + if config.Enable { + var manager PackageManager + switch name { + case "homebrew": + manager = &HomebrewManager{} + case "go": + manager = &GolangManager{} + case "cargo": + manager = &CargoManager{} + case "pipx": + manager = &PipxManager{} + case "flatpak": + flatpakConfig := FlatpakConfig{} + if err := mapstructure.Decode(config, &flatpakConfig); err != nil { + log.Printf("Fehler beim Dekodieren der Flatpak-Konfiguration: %v", err) + continue + } + manager = &FlatpakManager{ + OS: os, + SudoPassword: sudoPassword, + Config: flatpakConfig, + } + default: + log.Printf("Unbekannter Paketmanager: %s", name) + continue + } + if err := manager.InstallManager(); err != nil { + log.Printf("Warnung Packagemanager %s nicht vorhanden und konnte nicht installiert werden: %v", manager.Name(), err) + } + + if err := manager.Install(config.Packages); err != nil { + log.Printf("Warnung bei %s-Paketen: %v", manager.Name(), err) + } + } + } + if cfg.Dotfiles.Enable { + fmt.Println("\nKonfiguriere Dotfiles...") + if err := setupDotfiles(cfg.Dotfiles); err != nil { + log.Printf("Warnung bei Dotfiles-Setup: %v", err) + } + } +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..58f6896 --- /dev/null +++ b/utils.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/charmbracelet/huh" + "github.com/schollz/progressbar/v3" +) + +var ( + execCommand = exec.Command + execLookPath = exec.LookPath +) + +func getSudoPassword() (string, error) { + var password string + form := huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("Bitte geben Sie Ihr sudo-Passwort ein"). + EchoMode(huh.EchoModePassword). + Value(&password), + ), + ).WithTheme(huh.ThemeCatppuccin()) + + err := form.Run() + if err != nil { + return "", fmt.Errorf("fehler bei der Passwortabfrage: %v", err) + } + return password, nil +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func executeShellCommand(command string, env string) error { + cmd := execCommand("bash", "-c", command) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, env) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("befehl fehlgeschlagen: %v\nAusgabe: %s", err, output) + } + return nil +} + +func installWithProgress(manager PackageManager, packages []string) error { + bar := progressbar.Default(int64(len(packages)), "Installiere "+manager.Name()+"-Pakete...") + for _, pkg := range packages { + err := manager.InstallPackage(pkg) + if err != nil { + log.Printf("\nError installing %s: %v\n", pkg, err) + } + bar.Add(1) + } + return nil +} diff --git a/utils_test.go b/utils_test.go new file mode 100644 index 0000000..ffd2eaa --- /dev/null +++ b/utils_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "os/exec" + "testing" +) + +func TestMax(t *testing.T) { + tests := []struct { + a, b, want int + }{ + {1, 2, 2}, + {5, 3, 5}, + {0, 0, 0}, + {-1, -5, -1}, + } + + for _, tt := range tests { + got := max(tt.a, tt.b) + if got != tt.want { + t.Errorf("max(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) + } + } +} + +func TestExecuteShellCommand(t *testing.T) { + // Mock execCommand + oldExecCommand := execCommand + defer func() { execCommand = oldExecCommand }() + + execCommand = func(command string, args ...string) *exec.Cmd { + return exec.Command("echo", "mocked command") + } + + err := executeShellCommand("test command", "TEST_ENV=value") + if err != nil { + t.Errorf("executeShellCommand() error = %v; want nil", err) + } +} + +func TestInstallWithProgress(t *testing.T) { + mockManager := &MockPackageManager{ + packages: []string{"pkg1", "pkg2"}, + } + + err := installWithProgress(mockManager, mockManager.packages) + if err != nil { + t.Errorf("installWithProgress() error = %v; want nil", err) + } + + if len(mockManager.installedPackages) != len(mockManager.packages) { + t.Errorf("installWithProgress() installed %d packages; want %d", len(mockManager.installedPackages), len(mockManager.packages)) + } +} + +type MockPackageManager struct { + packages []string + installedPackages []string +} + +func (m *MockPackageManager) Install(packages []string) error { + m.installedPackages = append(m.installedPackages, packages...) + return nil +} + +func (m *MockPackageManager) InstallPackage(pkg string) error { + m.installedPackages = append(m.installedPackages, pkg) + return nil +} + +func (m *MockPackageManager) InstallManager() error { + return nil +} + +func (m *MockPackageManager) Name() string { + return "MockManager" +} + +func (m *MockPackageManager) RemovePackage(pkg string) error { + return nil +} + +func (m *MockPackageManager) SearchPackage(pkg string) []string { + return nil +}