Compare commits

...

46 commits
v0.2.0 ... main

Author SHA1 Message Date
ea317efaa4 chore(deps): update dependencies 2026-01-10 17:10:38 +01:00
git
5c00c164dd Merge pull request 'chore(deps): update module golang.org/x/sys to v0.34.0' (#8) from renovate/golang.org-x-sys-0.x into main
Reviewed-on: #8
2025-09-07 12:38:38 +02:00
Renovate Bot
0a7a1c43d9 chore(deps): update module golang.org/x/sys to v0.34.0 2025-07-25 07:31:51 +00:00
e5ef2f0b88 deps: update dependencies and supported go version 2025-06-25 11:05:01 +02:00
9597007953 refactor: replace for loop over slice 2025-06-20 13:24:09 +02:00
git
73881bf9b5 Merge pull request 'chore(deps): update module golang.org/x/sys to v0.33.0' (#7) from renovate/golang.org-x-sys-0.x into main
Reviewed-on: #7
2025-06-05 08:14:44 +02:00
Renovate Bot
3cd2187932 chore(deps): update module golang.org/x/sys to v0.33.0 2025-06-04 20:06:30 +00:00
git
956aad51f5 Merge pull request 'chore(deps): update module github.com/spf13/cobra to v1.9.1' (#5) from renovate/github.com-spf13-cobra-1.x into main
Reviewed-on: #5
2025-06-04 20:12:07 +02:00
git
2081b002e5 Merge pull request 'chore(deps): update module github.com/charmbracelet/huh to v0.7.0' (#4) from renovate/github.com-charmbracelet-huh-0.x into main
Reviewed-on: #4
2025-06-04 20:11:55 +02:00
Renovate Bot
fc2372e0b1 chore(deps): update module github.com/spf13/cobra to v1.9.1 2025-06-04 18:03:54 +00:00
Renovate Bot
9c8b19405a chore(deps): update module github.com/charmbracelet/huh to v0.7.0 2025-06-04 18:03:41 +00:00
git
db4892c08a Merge pull request 'chore: Configure Renovate' (#3) from renovate/configure into main
Reviewed-on: #3
2025-06-04 19:20:59 +02:00
Renovate Bot
2f5c05bea2 Add renovate.json 2025-06-04 10:48:25 +00:00
594d7e48f5 chore: update modul name and imports. 2025-02-04 16:31:55 +01:00
5a92382063 chore(go.mod): update module name 2025-02-04 16:23:38 +01:00
179e2a6ec3 chore(docs): prepare release 2025-02-04 16:16:18 +01:00
0cc50e15c9 docs(cli): update version 2025-02-04 16:11:55 +01:00
e49138fdd2 refactor(packagemanager,cli,tui): move packages in specific subfolders 2025-02-04 16:11:03 +01:00
11b8541630 feat(cli): add check command 2025-01-31 17:56:02 +01:00
3519efeccc update Changelog.md 2025-01-24 10:28:04 +01:00
b18b7761fc refactor(cli): perform code clean up 2025-01-24 10:24:48 +01:00
7d4a54b34c feat(cli): add max history length 2025-01-24 10:11:32 +01:00
8ed3b0c013 feat(cli): add history command and logging for actions 2025-01-24 09:24:59 +01:00
f8abf29594 feat(config): add creation of default config if no config exists 2025-01-24 08:10:09 +01:00
70dbc9e062 feat(cli): add aliases to commands 2025-01-23 07:59:26 +01:00
53a50f2be4 fix(packagemanager): reimplement getPackagemanager to enable installing buildtools on linux 2025-01-22 13:21:47 +01:00
afaf7707aa feat(cli): add concurrent execution of search and update to improve performance 2025-01-20 22:10:19 +01:00
0306ad36ef docs: update changelog.md 2025-01-20 21:37:35 +01:00
05e774764f fix(packagemanager): added missing build tag for macos 2025-01-20 21:37:13 +01:00
fc966df8f5 docs: update changelog.md 2025-01-20 21:33:07 +01:00
8281883e4d feat(cli,packagemanager): implemented first working windows and macos versions
in order to implement sst for windows and macos the structure of
osmanager has been changed.
Now osmanager has a new field pm, which contains the specific
packagemanager for the os.
Linux packagemanagers have been implemented as packagemanagers on their
own.
2025-01-20 21:28:44 +01:00
8dabc7357b feat(packagemanager): added packagemanager definitions for winget and chocolatey 2025-01-19 22:54:49 +01:00
43eb3ae4cb Merge branch 'dev/add-update-command' into development
* dev/add-update-command:
  feat(cli): add update and update-all command
2025-01-19 22:23:09 +01:00
f39051f93d feat(cli): add update and update-all command 2025-01-19 22:21:28 +01:00
3b1799397e fix: fix nil pointer execution if no proper packagemanager selected 2025-01-19 13:26:48 +01:00
cc3cc5c916 Merge branch 'development'
* development:
  docs: update changelog.md
  refactor: move main.go back to project root
  refactor: seperate files, function and structs into seperate packages
  fix(cli): add missing yes flag in flatpak remove cmd
  feat(cli): cleanup install command and add remove and search command
  feat(cli): add first implementation for install command
  feat(cli): implement search argument
2025-01-18 16:56:33 +01:00
7f9d72bddd docs: update changelog.md 2025-01-18 16:55:56 +01:00
a90c3d52f9 Merge branch 'dev/clean-up-repo-structure' into development
* dev/clean-up-repo-structure:
  refactor: move main.go back to project root
  refactor: seperate files, function and structs into seperate packages
2025-01-18 16:53:51 +01:00
f76829cb92 refactor: move main.go back to project root 2025-01-18 16:40:08 +01:00
5b7775f33e refactor: seperate files, function and structs into seperate packages 2025-01-18 14:34:16 +01:00
Pata1704
7f951585c8 Merge pull request 'dev/add-install-command' (#2) from dev/add-install-command into development
Reviewed-on: https://codeberg.org/Pata1704/system_setup/pulls/2
2025-01-17 18:50:11 +00:00
24662bc91c fix(cli): add missing yes flag in flatpak remove cmd 2025-01-17 19:45:54 +01:00
4006852790 feat(cli): cleanup install command and add remove and search command 2025-01-17 19:45:54 +01:00
c0be9a6b43 feat(cli): add first implementation for install command 2025-01-17 19:45:54 +01:00
Pata1704
c1e913641e Merge pull request 'feat(cli): implement search argument' (#1) from dev/add-search-command-to-package-command into development
Reviewed-on: https://codeberg.org/Pata1704/system_setup/pulls/1
2025-01-17 18:44:33 +00:00
8a14b72c5b feat(cli): implement search argument 2025-01-17 10:50:48 +01:00
57 changed files with 2332 additions and 1184 deletions

View file

@ -1,20 +1,16 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
env:
- GITHUB_TOKEN={{ .Env.CODEBERG_TOKEN }}
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
- go test ./...
- go test ./... -v
builds:
- env:
@ -26,7 +22,6 @@ builds:
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
@ -34,7 +29,6 @@ archives:
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip

View file

@ -1,36 +0,0 @@
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()
}

View file

@ -2,6 +2,82 @@
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
@ -18,6 +94,7 @@ All notable changes to this project will be documented in this file.
### 📚 Documentation
- Update changelog.md
- Update changelog.md
### ⚙️ Miscellaneous Tasks

51
cmd.go
View file

@ -1,51 +0,0 @@
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")
packageCmd.AddCommand(addCmd, deleteCmd, showCmd, enableCmd)
rootCmd.AddCommand(packageCmd)
}
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)
}
}

50
cmd/check_cmd.go Normal file
View file

@ -0,0 +1,50 @@
package cmd
import (
"fmt"
"log"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
pm "codeberg.org/Pata1704/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
switch managerName {
case "os":
managerName = "OS Package Manager"
case "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)
}
},
}

141
cmd/cmd.go Normal file
View file

@ -0,0 +1,141 @@
package cmd
import (
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"codeberg.org/Pata1704/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")
err := viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config"))
if err != nil {
slog.Error("could not bind config flag")
}
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 {
slog.Error(fmt.Sprintf("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, 0o755); 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
}

29
cmd/history_cmd.go Normal file
View file

@ -0,0 +1,29 @@
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))
},
}

60
cmd/install_cmd.go Normal file
View file

@ -0,0 +1,60 @@
package cmd
import (
"fmt"
"log"
"runtime"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
pm "codeberg.org/Pata1704/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
switch managerName {
case "os":
managerName = "OS Package Manager"
case "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)
}
},
}

View file

@ -1,8 +1,10 @@
package main
package cmd
import (
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
"fmt"
"os"
"slices"
"sort"
"github.com/spf13/cobra"
@ -31,7 +33,6 @@ var addCmd = &cobra.Command{
packages = viper.GetStringSlice("packages.non_headless")
}
// Add package to the appropriate list
packages = append(packages, name)
if isHeadless {
@ -42,11 +43,9 @@ var addCmd = &cobra.Command{
} else {
packages := viper.GetStringSlice(fmt.Sprintf("package_managers.%s.packages", manager))
for _, pkg := range packages {
if pkg == name {
fmt.Printf("Package %s is already present in the configuration for %s\n", name, manager)
return
}
if slices.Contains(packages, name) {
fmt.Printf("Package %s is already present in the configuration for %s\n", name, manager)
return
}
packages = append(packages, name)
@ -59,6 +58,9 @@ 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)
}
},
}
@ -121,6 +123,9 @@ 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)
}
}
},
}
@ -188,5 +193,8 @@ 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)
}
},
}

59
cmd/remove_cmd.go Normal file
View file

@ -0,0 +1,59 @@
package cmd
import (
"fmt"
"log"
"runtime"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
pm "codeberg.org/Pata1704/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
switch managerName {
case "os":
managerName = "OS Package Manager"
case "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)
}
},
}

117
cmd/search_cmd.go Normal file
View file

@ -0,0 +1,117 @@
package cmd
import (
"fmt"
"log"
"runtime"
"strings"
"sync"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
pm "codeberg.org/Pata1704/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")
switch managerName {
case "os":
managerName = "OS Package Manager"
case "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)
}
}
}

129
cmd/update_command.go Normal file
View file

@ -0,0 +1,129 @@
package cmd
import (
"fmt"
"log"
"runtime"
"sync"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
pm "codeberg.org/Pata1704/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
switch managerName {
case "os":
managerName = "OS Package Manager"
case "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()
}

View file

@ -1,31 +0,0 @@
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']

View file

@ -1,42 +0,0 @@
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
}

View file

@ -1,89 +0,0 @@
package main
import (
"fmt"
"os/exec"
)
type FlatpakManager struct {
OS *OS
SudoPassword string
Config FlatpakConfig
}
func (f *FlatpakManager) Install(packages []string) error {
if len(packages) == 0 {
return nil
}
err := installWithProgress(f, packages)
if err != nil {
return err
}
return nil
}
func (f *FlatpakManager) Name() string {
return "Flatpak"
}
func (f *FlatpakManager) InstallManager() error {
if _, err := exec.LookPath("flatpak"); err == nil {
return nil
}
err := installFlatpak(f.OS, f.SudoPassword)
if err != nil {
return err
}
err = addFlatpakRemotes(f.Config.Remotes)
if err != nil {
return err
}
return nil
}
type FlatpakConfig struct {
Enable bool `mapstructure:"enable"`
Remotes []Remote `mapstructure:"remotes"`
Packages []string `mapstructure:"packages"`
}
type Remote struct {
Name string `mapstructure:"name"`
URL string `mapstructure:"url"`
}
func installFlatpak(os *OS, sudoPassword string) error {
var command string
switch os.PackageManager {
case "pacman":
command = "pacman -S --noconfirm --needed flatpak"
case "apt":
command = "apt install -y flatpak"
case "dnf":
command = "dnf install -y flatpak"
default:
return fmt.Errorf("keine Flatpak-Installation für OS %s definiert", os.ID)
}
if err := installPackage(command, "", sudoPassword); err != nil {
return fmt.Errorf("fehler bei der Flatpak-Installation: %v", err)
}
return nil
}
func addFlatpakRemotes(remotes []Remote) error {
for _, remote := range remotes {
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)
}
}
return nil
}
func (f *FlatpakManager) InstallPackage(pkg string) error {
cmd := execCommand("flatpak", "install", "-y", pkg)
return cmd.Run()
}

73
go.mod
View file

@ -1,60 +1,61 @@
module system_setup_tool
module codeberg.org/Pata1704/system_setup_tool
go 1.23.4
go 1.25.5
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/charmbracelet/huh v0.8.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
github.com/schollz/progressbar/v3 v3.19.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
golang.org/x/sys v0.40.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/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
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.11.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20260109001716-2fbdffcb221f // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.6.2 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
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
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

199
go.sum
View file

@ -4,43 +4,81 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
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=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/strings v0.0.0-20250623112707-45752038d08d h1:fEsdno3tFo//l8pS4hlzH5hj6zGSaMzQcbyDLfToeJg=
github.com/charmbracelet/x/exp/strings v0.0.0-20250623112707-45752038d08d/go.mod h1:Rgw3/F+xlcUc5XygUtimVSxAqCOsqyvJjqF5UHRvc5k=
github.com/charmbracelet/x/exp/strings v0.0.0-20260109001716-2fbdffcb221f h1:c0cKImYFPrOEEzMsYss56Q7Q69HD7H4ss3Yu9Mw9vqQ=
github.com/charmbracelet/x/exp/strings v0.0.0-20260109001716-2fbdffcb221f/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -52,16 +90,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
@ -72,71 +112,82 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,36 +0,0 @@
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()
}

View file

@ -1,43 +0,0 @@
package main
import (
"fmt"
)
type HomebrewManager struct{}
func (h *HomebrewManager) Install(packages []string) error {
if len(packages) == 0 {
return nil
}
err := installWithProgress(h, packages)
if err != nil {
return err
}
return nil
}
func (h *HomebrewManager) Name() string {
return "Homebrew"
}
func (h *HomebrewManager) InstallManager() error {
if _, err := execLookPath("brew"); err == nil {
return nil
}
return installHomebrew()
}
func installHomebrew() error {
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)
}
return nil
}
func (h *HomebrewManager) InstallPackage(pkg string) error {
cmd := execCommand("brew", "install", pkg)
return cmd.Run()
}

View file

@ -1,4 +1,9 @@
package main
package config
import (
"codeberg.org/Pata1704/system_setup_tool/internal/dotfiles"
pm "codeberg.org/Pata1704/system_setup_tool/pkg/packagemanager"
)
type PackageManagerConfig struct {
Enable bool `mapstructure:"enable"`
@ -7,7 +12,7 @@ type PackageManagerConfig struct {
type Config struct {
Headless bool `mapstructure:"headless"`
Packages Packages `mapstructure:"packages"`
Packages pm.Packages `mapstructure:"packages"`
PackageManagers map[string]PackageManagerConfig `mapstructure:"package_managers"`
Dotfiles DotfilesConfig `mapstructure:"dotfiles"`
Dotfiles dotfiles.DotfilesConfig `mapstructure:"dotfiles"`
}

View file

@ -0,0 +1,43 @@
package dotfiles
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"os"
"path/filepath"
)
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
}

23
internal/shell/shell.go Normal file
View file

@ -0,0 +1,23 @@
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
}

115
internal/tui/tui.go Normal file
View file

@ -0,0 +1,115 @@
package tui
import (
"codeberg.org/Pata1704/system_setup_tool/internal/config"
"codeberg.org/Pata1704/system_setup_tool/internal/dotfiles"
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
"fmt"
"log"
pm "codeberg.org/Pata1704/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)
}
}
}

80
internal/utils/utils.go Normal file
View file

@ -0,0 +1,80 @@
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)
}

View file

@ -0,0 +1,40 @@
package utils
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"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 := 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)
}
}

View file

@ -1,5 +1,4 @@
//go:build mage
// +build mage
package main
@ -17,9 +16,8 @@ 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", "system_setup_tool", ".")
cmd := exec.Command("go", "build", "-o", "sst", ".")
return cmd.Run()
}
@ -27,20 +25,13 @@ func Build() error {
func Install() error {
mg.Deps(Build)
fmt.Println("Installing...")
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()
return os.Rename("./sst", "~/.local/bin/sst")
}
// Clean up after yourself
func Clean() {
fmt.Println("Cleaning...")
os.RemoveAll("MyApp")
os.RemoveAll("system_setup_tool")
}
// Use gofmt to format your code

View file

@ -3,10 +3,12 @@ package main
import (
"log"
"os"
"codeberg.org/Pata1704/system_setup_tool/cmd"
)
func main() {
if err := rootCmd.Execute(); err != nil {
if err := cmd.RootCmd.Execute(); err != nil {
log.Println(err)
os.Exit(1)
}

164
model.go
View file

@ -1,164 +0,0 @@
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)
}
}

166
osinfo.go
View file

@ -1,166 +0,0 @@
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) InstallBuildEssentials() error {
return installBuildEssentials(o.OS, o.SudoPassword)
}
func (o *OSManager) InstallSpecialSoftware() error {
return o.Model.installSpecialSoftware()
}
type OS struct {
ID string
Name string
Version string
PackageManager string
InstallCommand 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)
}
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 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
}

View file

@ -1,8 +0,0 @@
package main
type PackageManager interface {
Install(packages []string) error
InstallPackage(pkg string) error
InstallManager() error
Name() string
}

37
pipx.go
View file

@ -1,37 +0,0 @@
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()
}

81
pkg/packagemanager/apt.go Normal file
View file

@ -0,0 +1,81 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
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()
}

View file

@ -0,0 +1,69 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"os"
"path/filepath"
)
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()
}

View file

@ -1,7 +1,8 @@
// cargo_test.go
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
@ -17,13 +18,13 @@ func TestCargoManager_InstallManager(t *testing.T) {
cm := &CargoManager{}
// Mock exec.LookPath
execLookPath = func(file string) (string, error) {
shell.ExecLookPath = func(file string) (string, error) {
if file == "cargo" {
return "/usr/bin/cargo", nil
}
return "", exec.ErrNotFound
}
defer func() { execLookPath = exec.LookPath }()
defer func() { shell.ExecLookPath = exec.LookPath }()
if err := cm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
@ -34,10 +35,10 @@ func TestCargoManager_InstallPackage(t *testing.T) {
cm := &CargoManager{}
// Mock exec.Command
execCommand = func(name string, arg ...string) *exec.Cmd {
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked cargo install")
}
defer func() { execCommand = exec.Command }()
defer func() { shell.ExecCommand = exec.Command }()
if err := cm.InstallPackage("test-package"); err != nil {
t.Errorf("Expected no error, got %v", err)

View file

@ -0,0 +1,71 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"log"
"strings"
)
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()
}

78
pkg/packagemanager/dnf.go Normal file
View file

@ -0,0 +1,78 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
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()
}

View file

@ -0,0 +1,133 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
type FlatpakManager struct {
OS *OS
SudoPassword string
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)
if err != nil {
return err
}
return nil
}
func (f *FlatpakManager) Name() string {
return "Flatpak"
}
func (f *FlatpakManager) InstallManager() error {
if _, err := shell.ExecLookPath("flatpak"); err == nil {
return nil
}
err := installFlatpak(f.OS, f.SudoPassword)
if err != nil {
return err
}
err = addFlatpakRemotes(f.Config.Remotes)
if err != nil {
return err
}
return nil
}
type FlatpakConfig struct {
Enable bool `mapstructure:"enable"`
Remotes []Remote `mapstructure:"remotes"`
Packages []string `mapstructure:"packages"`
}
type Remote struct {
Name string `mapstructure:"name"`
URL string `mapstructure:"url"`
}
func installFlatpak(os *OS, sudoPassword string) error {
var command string
switch os.PackageManager {
case "pacman":
command = "pacman -S --noconfirm --needed flatpak"
case "apt":
command = "apt install -y flatpak"
case "dnf":
command = "dnf install -y flatpak"
default:
return fmt.Errorf("keine Flatpak-Installation für OS %s definiert", os.ID)
}
if err := InstallPackage(command, "", sudoPassword); err != nil {
return fmt.Errorf("fehler bei der Flatpak-Installation: %v", err)
}
return nil
}
func addFlatpakRemotes(remotes []Remote) error {
for _, remote := range remotes {
cmd := shell.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)
}
}
return nil
}
func (f *FlatpakManager) InstallPackage(pkg string) error {
cmd := shell.ExecCommand("flatpak", "install", "-y", pkg)
return cmd.Run()
}
func (f *FlatpakManager) RemovePackage(pkg string) error {
cmd := shell.ExecCommand("flatpak", "uninstall", "-y", pkg)
return cmd.Run()
}
func (f *FlatpakManager) SearchPackage(pkg string) []string {
cmd := shell.ExecCommand("flatpak", "search", pkg)
packages, err := cmd.Output()
if err != nil {
log.Printf("error fetching %s packages: %v", f.Name(), err)
}
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()
}

View file

@ -1,7 +1,7 @@
// flatpak_test.go
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
@ -33,10 +33,10 @@ func TestFlatpakManager_InstallPackage(t *testing.T) {
fm := &FlatpakManager{}
// Mock exec.Command
execCommand = func(name string, arg ...string) *exec.Cmd {
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked flatpak install")
}
defer func() { execCommand = exec.Command }()
defer func() { shell.ExecCommand = exec.Command }()
if err := fm.InstallPackage("test-package"); err != nil {
t.Errorf("Expected no error, got %v", err)

View file

@ -0,0 +1,69 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"os"
"path/filepath"
)
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()
}

View file

@ -1,7 +1,7 @@
// golang_test.go
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
@ -17,13 +17,13 @@ func TestGolangManager_InstallManager(t *testing.T) {
gm := &GolangManager{}
// Mock exec.LookPath
execLookPath = func(file string) (string, error) {
shell.ExecLookPath = func(file string) (string, error) {
if file == "go" {
return "/usr/local/go/bin/go", nil
}
return "", exec.ErrNotFound
}
defer func() { execLookPath = exec.LookPath }()
defer func() { shell.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
execCommand = func(name string, arg ...string) *exec.Cmd {
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked go install")
}
defer func() { execCommand = exec.Command }()
defer func() { shell.ExecCommand = exec.Command }()
if err := gm.InstallPackage("github.com/test/package"); err != nil {
t.Errorf("Expected no error, got %v", err)

View file

@ -0,0 +1,76 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
type HomebrewManager struct{}
func (h *HomebrewManager) Install(packages []string) error {
if len(packages) == 0 {
return nil
}
err := InstallWithProgress(h, packages)
if err != nil {
return err
}
return nil
}
func (h *HomebrewManager) Name() string {
return "Homebrew"
}
func (h *HomebrewManager) InstallManager() error {
if _, err := shell.ExecLookPath("brew"); err == nil {
return nil
}
return installHomebrew()
}
func installHomebrew() error {
err := shell.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)
}
return nil
}
func (h *HomebrewManager) InstallPackage(pkg string) error {
cmd := shell.ExecCommand("brew", "install", pkg)
return cmd.Run()
}
func (h *HomebrewManager) RemovePackage(pkg string) error {
cmd := shell.ExecCommand("brew", "uninstall", pkg)
return cmd.Run()
}
func (h *HomebrewManager) SearchPackage(pkg string) []string {
cmd := shell.ExecCommand("brew", "search", pkg)
packages, err := cmd.Output()
if err != nil {
log.Printf("error fetching %s packages: %v", h.Name(), err)
}
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()
}

View file

@ -1,7 +1,7 @@
// homebrew_test.go
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
@ -17,13 +17,13 @@ func TestHomebrewManager_InstallManager(t *testing.T) {
hm := &HomebrewManager{}
// Mock exec.LookPath and executeShellCommand
execLookPath = func(file string) (string, error) {
shell.ExecLookPath = func(file string) (string, error) {
if file == "brew" {
return "/usr/local/bin/brew", nil
}
return "", exec.ErrNotFound
}
defer func() { execLookPath = exec.LookPath }()
defer func() { shell.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
execCommand = func(name string, arg ...string) *exec.Cmd {
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked brew install")
}
defer func() { execCommand = exec.Command }()
defer func() { shell.ExecCommand = exec.Command }()
if err := hm.InstallPackage("test-package"); err != nil {
t.Errorf("Expected no error, got %v", err)

268
pkg/packagemanager/os.go Normal file
View file

@ -0,0 +1,268 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"os"
"runtime"
"strings"
)
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)
}
}

View file

@ -1,26 +1,20 @@
// osmanager_test.go
package main
package packagemanager
import (
"testing"
)
func TestNewOSManager(t *testing.T) {
os := &OS{ID: "ubuntu", PackageManager: "apt"}
sudoPassword := "testpassword"
packages := []string{"git", "curl"}
manager := NewOSManager(os, sudoPassword, packages)
manager := NewOSManager(sudoPassword)
if manager.OS != os {
t.Errorf("Expected OS to be %v, got %v", os, manager.OS)
if manager.OS == nil {
t.Error("Expected OS to be non-nil")
}
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) {

View file

@ -1,7 +1,9 @@
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
@ -10,26 +12,17 @@ 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 := execCommand("sudo", "-S", "sh", "-c", fullCmd)
command := shell.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") {
unavailablePackages = append(unavailablePackages, pkg)
log.Printf("Package %s not available\n", 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)
}
}
}

View file

@ -0,0 +1,13 @@
package packagemanager
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
}

View file

@ -0,0 +1,81 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
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()
}

View file

@ -0,0 +1,66 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
)
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()
}

View file

@ -1,7 +1,7 @@
// pipx_test.go
package main
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
@ -17,13 +17,13 @@ func TestPipxManager_InstallManager(t *testing.T) {
pm := &PipxManager{}
// Mock exec.LookPath
execLookPath = func(file string) (string, error) {
shell.ExecLookPath = func(file string) (string, error) {
if file == "pipx" {
return "/usr/bin/pipx", nil
}
return "", exec.ErrNotFound
}
defer func() { execLookPath = exec.LookPath }()
defer func() { shell.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
execCommand = func(name string, arg ...string) *exec.Cmd {
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked pipx install")
}
defer func() { execCommand = exec.Command }()
defer func() { shell.ExecCommand = exec.Command }()
if err := pm.InstallPackage("test-package"); err != nil {
t.Errorf("Expected no error, got %v", err)

View file

@ -0,0 +1,19 @@
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
}

30
pkg/packagemanager/win.go Normal file
View file

@ -0,0 +1,30 @@
//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()
}

View file

@ -0,0 +1,8 @@
//go:build linux || darwin
// +build linux darwin
package packagemanager
func platformInfoWin() (*OS, error) {
return nil, nil
}

View file

@ -0,0 +1,71 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"log"
"strings"
)
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()
}

3
renovate.json Normal file
View file

@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

146
tui.go
View file

@ -1,146 +0,0 @@
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)
}
}
}

View file

@ -1,64 +0,0 @@
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
}

View file

@ -1,78 +0,0 @@
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))
}
}
// MockPackageManager implementiert das PackageManager Interface für Tests
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"
}