Compare commits

..

55 commits
v0.1.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
4c1e85018f docs: update changelog.md 2025-01-16 22:31:56 +01:00
c9bf9c9a6f feature(cli): add packages command and various subcommands
add commands to handle package management
add -> adds packages
delete -> deletes packages
show -> shows config sorted by package_managers
2025-01-16 22:27:29 +01:00
ffd3faadcd docs: update changelog.md 2025-01-15 22:24:47 +01:00
5a579c9fa7 chore: update gitignore and goreleaser.yaml 2025-01-15 22:23:10 +01:00
f8ef2ef2d5 refactor: implement mocking functions and tests 2025-01-15 21:39:17 +01:00
339dba4e13 refactor: implement a simple progressbar instead of bubbletea 2025-01-15 14:28:36 +01:00
0504d88775 refactor: add progressbar and implement package interface 2025-01-15 11:54:00 +01:00
8dabf244bb feat: update config.toml 2025-01-13 10:13:15 +01:00
c4fbac6b8d refactor: change package management for different software sources 2025-01-12 23:38:14 +01:00
55 changed files with 2876 additions and 1156 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
dist/
github_test/

View file

@ -1,19 +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 ./... -v
builds:
- env:
@ -25,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 }}_
@ -33,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

@ -2,7 +2,106 @@
All notable changes to this project will be documented in this file.
## [unreleased]
## [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
- Update config.toml
- *(cli)* Add packages command and various subcommands
### 🚜 Refactor
- Change package management for different software sources
- Add progressbar and implement package interface
- Implement a simple progressbar instead of bubbletea
- Implement mocking functions and tests
### 📚 Documentation
- Update changelog.md
- Update changelog.md
### ⚙️ Miscellaneous Tasks
- Update gitignore and goreleaser.yaml
## [0.1.0] - 2024-12-28
### 🚀 Features
@ -13,6 +112,7 @@ All notable changes to this project will be documented in this file.
- *(cli)* Add checks for pacman
- *(cli,config)* Add flatpak and dotfiles support
- *(cli)* Add a box display
- Prepare initial release
### 🐛 Bug Fixes

35
cmd.go
View file

@ -1,35 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var rootCmd = &cobra.Command{
Use: "package-installer",
Short: "Installiert Pakete basierend auf TOML-Konfiguration",
Run: run,
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringP("config", "c", "", "Pfad zur Konfigurationsdatei")
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
}
func initConfig() {
if cfgFile := viper.GetString("config"); cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName("config")
viper.AddConfigPath(".")
}
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Fehler beim Lesen der Konfigurationsdatei:", 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)
}
},
}

200
cmd/package_commands.go Normal file
View file

@ -0,0 +1,200 @@
package cmd
import (
"codeberg.org/Pata1704/system_setup_tool/internal/utils"
"fmt"
"os"
"slices"
"sort"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var packageCmd = &cobra.Command{
Use: "package",
Short: "Manage packages in the configuration",
}
var addCmd = &cobra.Command{
Use: "add",
Short: "Adds a package to the config.toml",
Run: func(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
manager, _ := cmd.Flags().GetString("manager")
isSystem, _ := cmd.Flags().GetBool("system")
isHeadless, _ := cmd.Flags().GetBool("headless")
if isSystem {
var packages []string
if isHeadless {
packages = viper.GetStringSlice("packages.headless")
} else {
packages = viper.GetStringSlice("packages.non_headless")
}
packages = append(packages, name)
if isHeadless {
viper.Set("packages.headless", packages)
} else {
viper.Set("packages.non_headless", packages)
}
} else {
packages := viper.GetStringSlice(fmt.Sprintf("package_managers.%s.packages", manager))
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)
viper.Set(fmt.Sprintf("package_managers.%s.packages", manager), packages)
}
if err := viper.WriteConfig(); err != nil {
fmt.Printf("Error writing configuration: %v\n", err)
os.Exit(1)
}
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)
}
},
}
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "Deletes a package from the config.toml",
Run: func(cmd *cobra.Command, args []string) {
name, _ := cmd.Flags().GetString("name")
manager, _ := cmd.Flags().GetString("manager")
isSystem, _ := cmd.Flags().GetBool("system")
isHeadless, _ := cmd.Flags().GetBool("headless")
if isSystem {
var packages []string
var packageType string
if isHeadless {
packages = viper.GetStringSlice("packages.headless")
packageType = "headless"
} else {
packages = viper.GetStringSlice("packages.non_headless")
packageType = "non-headless"
}
for i, pkg := range packages {
if pkg == name {
packages = append(packages[:i], packages[i+1:]...)
if isHeadless {
viper.Set("packages.headless", packages)
} else {
viper.Set("packages.non_headless", packages)
}
if err := viper.WriteConfig(); err != nil {
fmt.Printf("Error writing configuration: %v\n", err)
os.Exit(1)
}
fmt.Printf("Package %s has been deleted from the %s system packages\n", name, packageType)
return
}
}
fmt.Printf("Package %s was not found in the %s system packages\n", name, packageType)
} else {
packages := viper.GetStringSlice(fmt.Sprintf("package_managers.%s.packages", manager))
for i, pkg := range packages {
if pkg == name {
packages = append(packages[:i], packages[i+1:]...)
viper.Set(fmt.Sprintf("package_managers.%s.packages", manager), packages)
if err := viper.WriteConfig(); err != nil {
fmt.Printf("Error writing configuration: %v\n", err)
os.Exit(1)
}
fmt.Printf("Package %s has been deleted from the configuration for %s\n", name, manager)
return
}
}
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)
}
}
},
}
var showCmd = &cobra.Command{
Use: "show",
Short: "Shows the current package configuration",
Run: func(cmd *cobra.Command, args []string) {
packageManagers := viper.GetStringMap("package_managers")
if len(packageManagers) == 0 {
fmt.Println("No package managers configured.")
return
}
var managers []string
for manager := range packageManagers {
managers = append(managers, manager)
}
sort.Strings(managers)
for _, manager := range managers {
fmt.Printf("Package Manager: %s\n", manager)
packages := viper.GetStringSlice(fmt.Sprintf("package_managers.%s.packages", manager))
sort.Strings(packages)
for _, pkg := range packages {
fmt.Printf(" - %s\n", pkg)
}
fmt.Println()
}
fmt.Println("System Packages:")
headless := viper.GetStringSlice("packages.headless")
nonHeadless := viper.GetStringSlice("packages.non_headless")
fmt.Println(" Headless:")
for _, pkg := range headless {
fmt.Printf(" - %s\n", pkg)
}
fmt.Println(" Non-Headless:")
for _, pkg := range nonHeadless {
fmt.Printf(" - %s\n", pkg)
}
},
}
var enableCmd = &cobra.Command{
Use: "enable [package_manager]",
Short: "Enable or disable a package manager (Options: homebrew|go|cargo|pipx|flatpak)",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
manager := args[0]
enable, _ := cmd.Flags().GetBool("value")
if !viper.IsSet(fmt.Sprintf("package_managers.%s", manager)) {
fmt.Printf("Package manager %s not found in configuration\n", manager)
return
}
viper.Set(fmt.Sprintf("package_managers.%s.enable", manager), enable)
if err := viper.WriteConfig(); err != nil {
fmt.Printf("Error writing configuration: %v\n", err)
os.Exit(1)
}
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,9 +0,0 @@
package main
type Config struct {
Headless bool `mapstructure:"headless"`
Packages Packages `mapstructure:"packages"`
SpecialPackages SpecialPackages `mapstructure:"special_packages"`
Flatpak FlatpakConfig `mapstructure:"flatpak"`
Dotfiles DotfilesConfig `mapstructure:"dotfiles"`
}

View file

@ -1,103 +0,0 @@
headless = false
[packages]
headless = [
"git",
"curl",
"wget",
"ripgrep",
"bottom",
"bat",
"biber",
"bear",
"docker",
"docker-compose",
"fd",
"bat",
"lsd",
"zsh",
"jq",
"neofetch",
"neovim",
"helix",
"npm",
"stow",
"task",
"timew",
"typst-lsp",
"tree-sitter-cli",
"tmux",
"zoxide",
"python3-pip",
"pipx",
"golang",
"rustup",
"latexmk",
"luarocks",
]
non_headless = [
"discord",
"flameshot",
"fuzzel",
"google-chrome",
"hyprcursor",
"hypridle",
"hyprland",
"hyprland-qtutils",
"hyprlock",
"hyprpaper",
"hyprutils",
"kitty",
"mako",
"mpv",
"neovide",
"pidgin",
"remmina",
"spotify",
"thunderbird",
"thunderbird-i18n-de",
"virt-manager",
"vlc",
"waybar",
"xdg-desktop-portal-hyprland",
"zathura",
"zathura-pdf-mupdf",
"zoom",
"zotero",
"android-studio",
]
[special_packages]
go = [
"github.com/jesseduffield/lazygit",
"github.com/stefanlogue/meteor",
"golang.org/x/tools/gopls",
"github.com/go-delve/delve/cmd/dlv",
"github.com/air-verse/air",
"go.senan.xyz/cliphist",
"github.com/goreleaser/goreleaser/v2",
]
cargo = ["git-cliff", "bottom", "typst-cli"]
pipx = ["harlequin", "posting", "euporie"]
[flatpak]
enable = true
remotes = [
{ name = "flathub", url = "https://flathub.org/repo/flathub.flatpakrepo" },
]
packages = [
"com.spotify.Client",
"us.zoom.Zoom",
"org.zotero.Zotero",
"com.google.AndroidStudio",
"io.freetubeapp.FreeTube",
"com.discordapp.Discord",
"com.nextcloud.desktopclient.nextcloud",
]
[dotfiles]
enable = true
git_repo = "https://codeberg.org/Pata1704/dotfiles.git"

View file

@ -1,44 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
)
type DotfilesConfig struct {
Enable bool `mapstructure:"enable"`
GitRepo string `mapstructure:"git_repo"`
}
func setupDotfiles(config DotfilesConfig) error {
if _, err := exec.LookPath("git"); err != nil {
return fmt.Errorf("git ist nicht installiert")
}
if _, err := exec.LookPath("stow"); err != nil {
return fmt.Errorf("gnu stow ist nicht installiert")
}
dotfilesDir := filepath.Join(os.Getenv("HOME"), "dotfiles")
// cmd := exec.Command("git", "clone", config.GitRepo)
cmd := exec.Command("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 = exec.Command("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,73 +0,0 @@
package main
import (
"fmt"
"log"
"os/exec"
"github.com/charmbracelet/bubbles/progress"
)
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 := exec.Command("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 installFlatpakPackages(packages []string) error {
if len(packages) == 0 {
return nil
}
fmt.Println("\nInstalliere Flatpak-Pakete...")
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
)
for i, pkg := range packages {
p.SetPercent(float64(i) / float64(len(packages)))
fmt.Printf("Installiere Flatpak: %s\n", pkg)
cmd := exec.Command("flatpak", "install", "-y", pkg)
if err := cmd.Run(); err != nil {
log.Printf("Fehler bei der Installation von %s: %v", pkg, err)
continue
}
}
return nil
}

79
go.mod
View file

@ -1,60 +1,61 @@
module system_setup
module codeberg.org/Pata1704/system_setup_tool
go 1.23.4
go 1.25.5
require (
github.com/Delta456/box-cli-maker/v2 v2.3.0
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/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.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.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/gookit/color v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // 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/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // 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/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
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // 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.27.0 // indirect
golang.org/x/term v0.0.0-20221017184919-83659145692c // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/ini.v1 v1.67.0 // 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/yaml.v3 v3.0.1 // indirect
)

248
go.sum
View file

@ -1,52 +1,84 @@
github.com/Delta456/box-cli-maker/v2 v2.3.0 h1:rGdoK/Qt3shdT1uqRMGgPqrhtisGD7PamTW8vY5MyCA=
github.com/Delta456/box-cli-maker/v2 v2.3.0/go.mod h1:Uv/kSX95LuNQn3C8wWazEIETE6MunPuYN+/knckbPQc=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
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/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
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=
@ -58,16 +90,20 @@ 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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/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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@ -76,100 +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/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
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=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20221017184919-83659145692c h1:dveknrit5futqEmXAvd2I1BbZIDhxRijsyWHM86NlcA=
golang.org/x/term v0.0.0-20221017184919-83659145692c/go.mod h1:VTIZ7TEbF0BS9Sv9lPTvGbtW8i4z6GGbJBCM37uMCzY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=

18
internal/config/config.go Normal file
View file

@ -0,0 +1,18 @@
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"`
Packages []string `mapstructure:"packages"`
}
type Config struct {
Headless bool `mapstructure:"headless"`
Packages pm.Packages `mapstructure:"packages"`
PackageManagers map[string]PackageManagerConfig `mapstructure:"package_managers"`
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)
}
}

56
magefile.go Normal file
View file

@ -0,0 +1,56 @@
//go:build mage
package main
import (
"fmt"
"os"
"os/exec"
"github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps
)
// Default target to run when none is specified
// If not set, running mage will list available targets
// var Default = Build
// A build step that requires additional params, or platform specific steps for example
func Build() error {
fmt.Println("Building...")
cmd := exec.Command("go", "build", "-o", "sst", ".")
return cmd.Run()
}
// A custom install step if you need your bin someplace other than go/bin
func Install() error {
mg.Deps(Build)
fmt.Println("Installing...")
return os.Rename("./sst", "~/.local/bin/sst")
}
// Clean up after yourself
func Clean() {
fmt.Println("Cleaning...")
os.RemoveAll("system_setup_tool")
}
// Use gofmt to format your code
func Format() error {
fmt.Println("Checking Format")
cmd := exec.Command("gofmt", "./")
return cmd.Run()
}
// get linting information from golangci-lint
func Lint() error {
fmt.Println("Linting...")
cmd := exec.Command("golangci-lint", "./...")
return cmd.Run()
}
// perform all modul tests
func Test() error {
fmt.Println("Testing...")
cmd := exec.Command("go", "test", "./...")
return cmd.Run()
}

10
main.go
View file

@ -1,17 +1,15 @@
package main
import (
"fmt"
"log"
"os"
"github.com/Delta456/box-cli-maker/v2"
"codeberg.org/Pata1704/system_setup_tool/cmd"
)
func main() {
Box := box.New(box.Config{Px: 2, Py: 5, Type: "Single"})
Box.Print("System Setup", "Configure and install your predefined system")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
if err := cmd.RootCmd.Execute(); err != nil {
log.Println(err)
os.Exit(1)
}
}

163
model.go
View file

@ -1,163 +0,0 @@
package main
import (
"fmt"
"log"
"os/exec"
"runtime"
"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 := exec.LookPath("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
}
}
if _, err := exec.LookPath("go"); err == nil {
fmt.Println("Go ist bereits installiert.")
} else {
golangVersion := "1.23.4"
if err := downloadGolang(golangVersion); err != nil {
return fmt.Errorf("fehler beim Herunterladen von Go: %v", err)
}
golangCommand := fmt.Sprintf("sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go%s.linux-%s.tar.gz", golangVersion, runtime.GOARCH)
if err := installPackage(golangCommand, "", m.sudoPassword); err != nil {
return err
}
}
if _, err := exec.LookPath("rustc"); err == nil {
fmt.Println("Rust ist bereits installiert.")
} else {
rustupCommand := "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -q -y"
if err := installPackage(rustupCommand, "", ""); err != nil {
return err
}
}
// if _, err := exec.LookPath("pipx"); err == nil {
// fmt.Println("Pipx ist bereits installiert.")
// } else {
// pipXCommand := "python3 -m pip install --user pipx && python3 -m pipx ensurepath"
// if err := installPackage(pipXCommand, "", ""); err != nil {
// return err
// }
// }
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)
}
}

126
osinfo.go
View file

@ -1,126 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
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 := exec.LookPath(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,67 +0,0 @@
package main
import (
"fmt"
"os/exec"
"strings"
)
type Packages struct {
Headless []string `mapstructure:"headless"`
NonHeadless []string `mapstructure:"non_headless"`
}
type SpecialPackages struct {
Go []string `mapstructure:"go"`
Cargo []string `mapstructure:"cargo"`
Pipx []string `mapstructure:"pipx"`
}
func installPackage(cmd, pkg, sudoPassword string) error {
fullCmd := fmt.Sprintf("%s %s", cmd, pkg)
command := exec.Command("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)
return nil
}
return fmt.Errorf("failed to install %s: %v\n%s", pkg, err, string(output))
}
return nil
}
func installSpecialPackages(sp SpecialPackages) error {
for _, pkg := range sp.Go {
cmd := exec.Command("go", "install", pkg+"@latest")
if err := cmd.Run(); err != nil {
return fmt.Errorf("fehler bei der Installation von %s: %v", pkg, err)
}
}
for _, pkg := range sp.Cargo {
cmd := exec.Command("cargo", "install", pkg)
if err := cmd.Run(); err != nil {
return fmt.Errorf("fehler bei der Installation von %s: %v", pkg, err)
}
}
for _, pkg := range sp.Pipx {
cmd := exec.Command("pipx", "install", pkg)
if err := cmd.Run(); err != nil {
return fmt.Errorf("fehler bei der Installation von %s: %v", pkg, err)
}
}
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)
}
}
}

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

@ -0,0 +1,46 @@
// cargo_test.go
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
func TestCargoManager_Name(t *testing.T) {
cm := &CargoManager{}
if name := cm.Name(); name != "Cargo" {
t.Errorf("Expected name to be 'Cargo', got %s", name)
}
}
func TestCargoManager_InstallManager(t *testing.T) {
cm := &CargoManager{}
// Mock exec.LookPath
shell.ExecLookPath = func(file string) (string, error) {
if file == "cargo" {
return "/usr/bin/cargo", nil
}
return "", exec.ErrNotFound
}
defer func() { shell.ExecLookPath = exec.LookPath }()
if err := cm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestCargoManager_InstallPackage(t *testing.T) {
cm := &CargoManager{}
// Mock exec.Command
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked cargo install")
}
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

@ -0,0 +1,44 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
func TestFlatpakManager_Name(t *testing.T) {
fm := &FlatpakManager{}
if name := fm.Name(); name != "Flatpak" {
t.Errorf("Expected name to be 'Flatpak', got %s", name)
}
}
func TestFlatpakManager_InstallManager(t *testing.T) {
fm := &FlatpakManager{
OS: &OS{PackageManager: "apt"},
SudoPassword: "testpass",
Config: FlatpakConfig{
Remotes: []Remote{{Name: "test", URL: "http://test.com"}},
},
}
// Mock exec.LookPath and other functions as needed
if err := fm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestFlatpakManager_InstallPackage(t *testing.T) {
fm := &FlatpakManager{}
// Mock exec.Command
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked flatpak install")
}
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

@ -0,0 +1,45 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
func TestGolangManager_Name(t *testing.T) {
gm := &GolangManager{}
if name := gm.Name(); name != "Golang" {
t.Errorf("Expected name to be 'Golang', got %s", name)
}
}
func TestGolangManager_InstallManager(t *testing.T) {
gm := &GolangManager{}
// Mock exec.LookPath
shell.ExecLookPath = func(file string) (string, error) {
if file == "go" {
return "/usr/local/go/bin/go", nil
}
return "", exec.ErrNotFound
}
defer func() { shell.ExecLookPath = exec.LookPath }()
if err := gm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestGolangManager_InstallPackage(t *testing.T) {
gm := &GolangManager{}
// Mock exec.Command
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked go install")
}
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

@ -0,0 +1,45 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
func TestHomebrewManager_Name(t *testing.T) {
hm := &HomebrewManager{}
if name := hm.Name(); name != "Homebrew" {
t.Errorf("Expected name to be 'Homebrew', got %s", name)
}
}
func TestHomebrewManager_InstallManager(t *testing.T) {
hm := &HomebrewManager{}
// Mock exec.LookPath and executeShellCommand
shell.ExecLookPath = func(file string) (string, error) {
if file == "brew" {
return "/usr/local/bin/brew", nil
}
return "", exec.ErrNotFound
}
defer func() { shell.ExecLookPath = exec.LookPath }()
if err := hm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestHomebrewManager_InstallPackage(t *testing.T) {
hm := &HomebrewManager{}
// Mock exec.Command
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked brew install")
}
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

@ -0,0 +1,44 @@
package packagemanager
import (
"testing"
)
func TestNewOSManager(t *testing.T) {
sudoPassword := "testpassword"
manager := NewOSManager(sudoPassword)
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)
}
}
func TestOSManagerName(t *testing.T) {
manager := &OSManager{}
if manager.Name() != "OS Package Manager" {
t.Errorf("Expected name to be 'OS Package Manager', got %s", manager.Name())
}
}
func TestParseOsRelease(t *testing.T) {
osReleaseContent := `
ID=ubuntu
NAME="Ubuntu"
VERSION_ID="20.04"
`
os := parseOsRelease(osReleaseContent)
if os.ID != "ubuntu" {
t.Errorf("Expected ID to be 'ubuntu', got %s", os.ID)
}
if os.Name != "Ubuntu" {
t.Errorf("Expected Name to be 'Ubuntu', got %s", os.Name)
}
if os.Version != "20.04" {
t.Errorf("Expected Version to be '20.04', got %s", os.Version)
}
}

View file

@ -0,0 +1,28 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"fmt"
"log"
"strings"
)
type Packages struct {
Headless []string `mapstructure:"headless"`
NonHeadless []string `mapstructure:"non_headless"`
}
func InstallPackage(cmd, pkg, sudoPassword string) error {
fullCmd := fmt.Sprintf("%s %s", cmd, pkg)
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") {
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
}

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

@ -0,0 +1,45 @@
package packagemanager
import (
"codeberg.org/Pata1704/system_setup_tool/internal/shell"
"os/exec"
"testing"
)
func TestPipxManager_Name(t *testing.T) {
pm := &PipxManager{}
if name := pm.Name(); name != "Pipx" {
t.Errorf("Expected name to be 'Pipx', got %s", name)
}
}
func TestPipxManager_InstallManager(t *testing.T) {
pm := &PipxManager{}
// Mock exec.LookPath
shell.ExecLookPath = func(file string) (string, error) {
if file == "pipx" {
return "/usr/bin/pipx", nil
}
return "", exec.ErrNotFound
}
defer func() { shell.ExecLookPath = exec.LookPath }()
if err := pm.InstallManager(); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestPipxManager_InstallPackage(t *testing.T) {
pm := &PipxManager{}
// Mock exec.Command
shell.ExecCommand = func(name string, arg ...string) *exec.Cmd {
return exec.Command("echo", "mocked pipx install")
}
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"
}

View file

@ -1,133 +0,0 @@
package main
import (
"fmt"
"log"
"os/exec"
"runtime"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type specialSoftwareModel struct {
items []string
index int
spinner spinner.Model
progress progress.Model
done bool
sudoPassword string
}
func newSpecialSoftwareModel(sudoPassword string) specialSoftwareModel {
items := []string{"oh-my-posh", "golang", "rust"}
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
progress.WithoutPercentage(),
)
s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
return specialSoftwareModel{
items: items,
spinner: s,
progress: p,
sudoPassword: sudoPassword,
}
}
func (m specialSoftwareModel) Init() tea.Cmd {
return tea.Batch(m.installItemCmd(m.items[m.index]), m.spinner.Tick)
}
func (m specialSoftwareModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc", "q":
return m, tea.Quit
}
case installedItemMsg:
if m.index >= len(m.items)-1 {
m.done = true
return m, tea.Quit
}
m.index++
progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.items)))
return m, tea.Batch(
progressCmd,
tea.Printf("%s %s", checkMark, m.items[m.index-1]),
m.installItemCmd(m.items[m.index]),
)
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
return m, nil
}
func (m specialSoftwareModel) View() string {
if m.done {
return doneStyle.Render("Spezielle Software Installation abgeschlossen!\n")
}
spin := m.spinner.View() + " "
prog := m.progress.View()
info := fmt.Sprintf("Installiere %s", m.items[m.index])
return spin + info + " " + prog
}
func (m specialSoftwareModel) installItemCmd(item string) tea.Cmd {
return func() tea.Msg {
switch item {
case "oh-my-posh":
if _, err := exec.LookPath("oh-my-posh"); err == nil {
return installedItemMsg(item)
}
err := executeShellCommand("curl -s https://ohmyposh.dev/install.sh | bash -s")
if err != nil {
log.Printf("Fehler bei der Installation von oh-my-posh: %v\n", err)
}
case "golang":
if _, err := exec.LookPath("go"); err == nil {
return installedItemMsg(item)
}
golangVersion := "1.23.4"
if err := downloadGolang(golangVersion); err != nil {
log.Printf("Fehler beim Herunterladen von Go: %v", err)
}
golangCommand := fmt.Sprintf("sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go%s.linux-%s.tar.gz", golangVersion, runtime.GOARCH)
if err := installPackage(golangCommand, "", m.sudoPassword); err != nil {
log.Printf("Fehler beim Installieren von Go: %v", err)
}
case "rust":
if _, err := exec.LookPath("rustc"); err == nil {
if _, err := exec.LookPath("cargo"); err == nil {
return installedItemMsg(item)
}
}
err := executeShellCommand("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -q -y")
if err != nil {
log.Printf("Fehler bei der Installation von rustup: %v\n", err)
}
}
return installedItemMsg(item)
}
}
func executeShellCommand(command string) error {
cmd := exec.Command("bash", "-c", command)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("befehl fehlgeschlagen: %v\nAusgabe: %s", err, output)
}
return nil
}

166
tui.go
View file

@ -1,166 +0,0 @@
package main
import (
"fmt"
"log"
"os/exec"
"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"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 installedItemMsg 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)
}
var packages []string
packages = append(packages, cfg.Packages.Headless...)
if !cfg.Headless {
packages = append(packages, cfg.Packages.NonHeadless...)
}
unavailablePackages = []string{}
m := newModel(packages, sudoPassword, os)
p := tea.NewProgram(m)
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
printUnavailablePackages()
var installSpecial bool
form = huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Möchten Sie die speziellen Pakete (Go, Rust, Pipx) installieren?").
Value(&installSpecial),
),
).WithTheme(huh.ThemeCatppuccin())
err = form.Run()
if err != nil {
log.Fatal(err)
}
if installSpecial {
if err := installBuildEssentials(os, sudoPassword); err != nil {
log.Printf("Warnung: %v", err)
}
sm := newSpecialSoftwareModel(sudoPassword)
p = tea.NewProgram(sm)
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
if len(cfg.SpecialPackages.Go) > 0 || len(cfg.SpecialPackages.Cargo) > 0 || len(cfg.SpecialPackages.Pipx) > 0 {
var allSpecialPkgs []struct {
typ string
name string
command string
}
for _, pkg := range cfg.SpecialPackages.Go {
allSpecialPkgs = append(allSpecialPkgs, struct {
typ string
name string
command string
}{"go", pkg, "go install " + pkg + "@latest"})
}
for _, pkg := range cfg.SpecialPackages.Cargo {
allSpecialPkgs = append(allSpecialPkgs, struct {
typ string
name string
command string
}{"cargo", pkg, "cargo install " + pkg})
}
for _, pkg := range cfg.SpecialPackages.Pipx {
allSpecialPkgs = append(allSpecialPkgs, struct {
typ string
name string
command string
}{"pipx", pkg, "pipx install " + pkg})
}
if len(allSpecialPkgs) > 0 {
fmt.Println("\nInstalliere spezielle Pakete...")
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
)
for i, pkg := range allSpecialPkgs {
p.SetPercent(float64(i) / float64(len(allSpecialPkgs)))
fmt.Printf("Installiere %s Paket: %s\n", pkg.typ, pkg.name)
cmd := exec.Command("sh", "-c", pkg.command)
if err := cmd.Run(); err != nil {
log.Printf("Fehler bei der Installation von %s: %v", pkg.name, err)
continue
}
}
}
}
if cfg.Flatpak.Enable {
fmt.Println("\nKonfiguriere Flatpak...")
if err := installFlatpak(os, sudoPassword); err != nil {
log.Printf("Warnung bei Flatpak-Installation: %v", err)
}
if err := addFlatpakRemotes(cfg.Flatpak.Remotes); err != nil {
log.Printf("Warnung bei Flatpak-Remotes: %v", err)
}
if err := installFlatpakPackages(cfg.Flatpak.Packages); err != nil {
log.Printf("Warnung bei Flatpak-Paketen: %v", 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,67 +0,0 @@
package main
import (
"fmt"
"io"
"net/http"
"os"
"runtime"
"github.com/charmbracelet/huh"
)
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 downloadGolang(golangVersion string) error {
var link string
if runtime.GOARCH == "arm64" {
link = "https://go.dev/dl/go" + golangVersion + ".linux-arm64.tar.gz"
} else {
link = "https://go.dev/dl/go" + golangVersion + ".linux-amd64.tar.gz"
}
resp, err := http.Get(link)
if err != nil {
return fmt.Errorf("fehler beim Herunterladen von Go: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unerwarteter HTTP-Status: %s", resp.Status)
}
outFile, err := os.Create("go" + golangVersion + ".linux-" + runtime.GOARCH + ".tar.gz")
if err != nil {
return fmt.Errorf("fehler beim Erstellen der Ausgabedatei: %v", err)
}
defer outFile.Close()
_, err = io.Copy(outFile, resp.Body)
if err != nil {
return fmt.Errorf("fehler beim Schreiben der Ausgabedatei: %v", err)
}
return nil
}
func max(a, b int) int {
if a > b {
return a
}
return b
}