refactor: seperate files, function and structs into seperate packages

This commit is contained in:
Patryk Hegenberg 2025-01-18 14:34:16 +01:00
parent 7f951585c8
commit 5b7775f33e
37 changed files with 547 additions and 677 deletions

View file

@ -3,6 +3,9 @@
version: 2 version: 2
env:
- GITHUB_TOKEN={{ .Env.CODEBERG_TOKEN }}
before: before:
hooks: hooks:
- go mod tidy - go mod tidy

View file

@ -1,23 +1,24 @@
package main package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"system_setup_tool/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var rootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
Use: "system_setup_tool", Use: "system_setup_tool",
Short: "Installs packages based on TOML configuration", Short: "Installs packages based on TOML configuration",
Run: run, Run: tui.Run,
} }
func init() { func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringP("config", "c", "", "Path to the configuration file") RootCmd.PersistentFlags().StringP("config", "c", "", "Path to the configuration file")
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config"))
addCmd.Flags().StringP("name", "n", "", "The name of the package you want to add") 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().StringP("manager", "m", "", "The package manager you want to add the package to (homebrew|cargo|flatpak|pipx|go)")
@ -38,7 +39,7 @@ func init() {
searchCmd.Flags().StringP("manager", "m", "", "The package manager you want to search a package with. (Options: os|homebrew|flatpak)") searchCmd.Flags().StringP("manager", "m", "", "The package manager you want to search a package with. (Options: os|homebrew|flatpak)")
packageCmd.AddCommand(addCmd, deleteCmd, showCmd, enableCmd) packageCmd.AddCommand(addCmd, deleteCmd, showCmd, enableCmd)
rootCmd.AddCommand(packageCmd, searchCmd, installCmd, removeCmd) RootCmd.AddCommand(packageCmd, searchCmd, installCmd, removeCmd)
} }
func initConfig() { func initConfig() {

View file

@ -1,8 +1,11 @@
package main package cmd
import ( import (
"fmt" "fmt"
"log" "log"
"system_setup_tool/utils"
pm "system_setup_tool/packagemanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -14,29 +17,25 @@ var installCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
packageName := args[0] packageName := args[0]
managerName, _ := cmd.Flags().GetString("manager") managerName, _ := cmd.Flags().GetString("manager")
var manager PackageManager var manager pm.PackageManager
switch managerName { switch managerName {
case "os": case "os":
opSys, err := getLinuxDistribution() sudoPassword, err := utils.GetSudoPassword()
if err != nil {
log.Printf("error getting OS information: %v", err)
}
sudoPassword, err := getSudoPassword()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
osManager := NewOSManager(opSys, sudoPassword, []string{packageName}) osManager := pm.NewOSManager(sudoPassword)
if err := osManager.Install([]string{packageName}); err != nil { if err := osManager.Install([]string{packageName}); err != nil {
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
} }
case "homebrew": case "homebrew":
manager = &HomebrewManager{} manager = &pm.HomebrewManager{}
case "cargo": case "cargo":
manager = &CargoManager{} manager = &pm.CargoManager{}
case "pipx": case "pipx":
manager = &PipxManager{} manager = &pm.PipxManager{}
case "flatpak": case "flatpak":
manager = &FlatpakManager{} manager = &pm.FlatpakManager{}
default: default:
fmt.Println("No PackageManager found") fmt.Println("No PackageManager found")
} }

View file

@ -1,4 +1,4 @@
package main package cmd
import ( import (
"fmt" "fmt"

View file

@ -1,7 +1,10 @@
package main package cmd
import ( import (
"log" "log"
"system_setup_tool/utils"
pm "system_setup_tool/packagemanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -13,29 +16,25 @@ var removeCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
packageName := args[0] packageName := args[0]
managerName, _ := cmd.Flags().GetString("manager") managerName, _ := cmd.Flags().GetString("manager")
var manager PackageManager var manager pm.PackageManager
switch managerName { switch managerName {
case "os": case "os":
opSys, err := getLinuxDistribution() sudoPassword, err := utils.GetSudoPassword()
if err != nil {
log.Printf("error getting OS information: %v", err)
}
sudoPassword, err := getSudoPassword()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
osManager := NewOSManager(opSys, sudoPassword, []string{packageName}) osManager := pm.NewOSManager(sudoPassword)
if err := osManager.Install([]string{packageName}); err != nil { if err := osManager.Install([]string{packageName}); err != nil {
log.Printf("error: %v\n", err) log.Printf("error: %v\n", err)
} }
case "homebrew": case "homebrew":
manager = &HomebrewManager{} manager = &pm.HomebrewManager{}
case "cargo": case "cargo":
manager = &CargoManager{} manager = &pm.CargoManager{}
case "pipx": case "pipx":
manager = &PipxManager{} manager = &pm.PipxManager{}
case "flatpak": case "flatpak":
manager = &FlatpakManager{} manager = &pm.FlatpakManager{}
default: default:
} }
if err := manager.RemovePackage(packageName); err != nil { if err := manager.RemovePackage(packageName); err != nil {

View file

@ -1,10 +1,11 @@
package main package cmd
import ( import (
"fmt" "fmt"
"log"
"strings" "strings"
pm "system_setup_tool/packagemanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -18,16 +19,11 @@ var searchCmd = &cobra.Command{
if managerName == "os" { if managerName == "os" {
managerName = "OS Package Manager" managerName = "OS Package Manager"
} }
opSys, err := getLinuxDistribution()
if err != nil {
log.Printf("error getting OS information: %v", err)
return
}
managers := []PackageManager{ managers := []pm.PackageManager{
NewOSManager(opSys, "", nil), pm.NewOSManager(""),
&HomebrewManager{}, &pm.HomebrewManager{},
&FlatpakManager{}, &pm.FlatpakManager{},
} }
if managerName != "" { if managerName != "" {
@ -57,7 +53,7 @@ func displayResults(results []string, manager string) {
} }
} }
func searchAndDisplayResults(manager PackageManager, packageName string) { func searchAndDisplayResults(manager pm.PackageManager, packageName string) {
fmt.Printf("Searching in %s:\n", manager.Name()) fmt.Printf("Searching in %s:\n", manager.Name())
results := manager.SearchPackage(packageName) results := manager.SearchPackage(packageName)
displayResults(results, manager.Name()) displayResults(results, manager.Name())

View file

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

View file

@ -12,20 +12,107 @@ packages = ['typst-cli']
[package_managers.flatpak] [package_managers.flatpak]
enable = true enable = true
packages = ['com.spotify.Client', 'us.zoom.Zoom', 'org.zotero.Zotero', 'com.google.AndroidStudio', 'io.freetubeapp.FreeTube', 'com.discordapp.Discord', 'com.nextcloud.desktopclient.nextcloud', 'com.github.tchx84.Flatseal', 'io.github.flattool.Warehouse', 'org.onlyoffice.desktopeditors'] packages = [
'com.spotify.Client',
'us.zoom.Zoom',
'org.zotero.Zotero',
'com.google.AndroidStudio',
'io.freetubeapp.FreeTube',
'com.discordapp.Discord',
'com.nextcloud.desktopclient.nextcloud',
'com.github.tchx84.Flatseal',
'io.github.flattool.Warehouse',
'org.onlyoffice.desktopeditors',
]
[package_managers.go] [package_managers.go]
enable = true enable = true
packages = ['github.com/stefanlogue/meteor', 'golang.org/x/tools/gopls', 'github.com/go-delve/delve/cmd/dlv', 'github.com/air-verse/air'] packages = [
'github.com/stefanlogue/meteor',
'golang.org/x/tools/gopls',
'github.com/go-delve/delve/cmd/dlv',
'github.com/air-verse/air',
]
[package_managers.homebrew] [package_managers.homebrew]
enable = true enable = true
packages = ['fd', 'fzf', 'ripgrep', 'neovim', 'helix', 'node', 'yazi', 'zk', 'bat', 'bottom', 'btop', 'git-cliff', 'glow', 'lazygit', 'goreleaser', 'harlequin', 'mage', 'posting', 'typst', 'wails', 'zoxide', 'lsd', 'jq', 'yq', 'timewarrior', 'tmux', 'fastfetch', 'stow', 'distrobox'] packages = [
'oh-my-posh',
'fd',
'fzf',
'ripgrep',
'neovim',
'helix',
'node',
'yazi',
'zk',
'bat',
'bottom',
'btop',
'git-cliff',
'glow',
'lazygit',
'goreleaser',
'harlequin',
'mage',
'posting',
'typst',
'wails',
'zoxide',
'lsd',
'jq',
'yq',
'timewarrior',
'tmux',
'fastfetch',
'stow',
'distrobox',
]
[package_managers.pipx] [package_managers.pipx]
enable = true enable = true
packages = ['euporie'] packages = ['euporie']
[packages] [package_managers.os]
headless = ['git', 'curl', 'wget', 'biber', 'bear', 'docker', 'docker-compose', 'zsh', 'npm', 'task', 'tree-sitter-cli', 'python3-pip', 'latexmk', 'luarocks'] enable = true
non_headless = ['flameshot', 'fuzzel', 'hyprcursor', 'hypridle', 'hyprland', 'hyprland-qtutils', 'hyprlock', 'hyprpaper', 'hyprutils', 'kitty', 'mako', 'SwayNotificationCenter', 'SwayNotificationCenter-zsh-completion', 'mpv', 'pidgin', 'remmina', 'thunderbird-i18n-de', 'virt-manager', 'vlc', 'waybar', 'nwg-panel', 'nwg-look', 'xdg-desktop-portal-hyprland', 'zathura', 'zathura-pdf-mupdf'] packages = [
'git',
'curl',
'wget',
'biber',
'bear',
'docker',
'docker-compose',
'zsh',
'npm',
'task',
'tree-sitter-cli',
'python3-pip',
'latexmk',
'luarocks',
'flameshot',
'fuzzel',
'hyprcursor',
'hypridle',
'hyprland',
'hyprland-qtutils',
'hyprlock',
'hyprpaper',
'hyprutils',
'kitty',
'mako',
'SwayNotificationCenter',
'SwayNotificationCenter-zsh-completion',
'mpv',
'pidgin',
'remmina',
'thunderbird-i18n-de',
'virt-manager',
'vlc',
'waybar',
'nwg-panel',
'nwg-look',
'xdg-desktop-portal-hyprland',
'zathura',
'zathura-pdf-mupdf',
]

View file

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

View file

@ -1,42 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
type DotfilesConfig struct {
Enable bool `mapstructure:"enable"`
GitRepo string `mapstructure:"git_repo"`
}
func setupDotfiles(config DotfilesConfig) error {
if _, err := execLookPath("git"); err != nil {
return fmt.Errorf("git ist nicht installiert")
}
if _, err := execLookPath("stow"); err != nil {
return fmt.Errorf("gnu stow ist nicht installiert")
}
dotfilesDir := filepath.Join(os.Getenv("HOME"), "dotfiles")
cmd := execCommand("git", "clone", config.GitRepo, dotfilesDir)
if err := cmd.Run(); err != nil {
return fmt.Errorf("fehler beim Klonen der Dotfiles: %v", err)
}
if err := os.Chdir(dotfilesDir); err != nil {
return fmt.Errorf("fehler beim Wechseln in das Dotfiles-Verzeichnis: %v", err)
}
cmd = execCommand("stow", ".", "--override='*'")
if err := cmd.Run(); err != nil {
log.Printf("Fehler beim Linken: %v", err)
}
fmt.Printf("Alles erfolgreich verlinkt\n")
return nil
}

43
dotfiles/dotfiles.go Normal file
View file

@ -0,0 +1,43 @@
package dotfiles
import (
"fmt"
"log"
"os"
"path/filepath"
"system_setup_tool/internal/shell"
)
type DotfilesConfig struct {
Enable bool `mapstructure:"enable"`
GitRepo string `mapstructure:"git_repo"`
}
func SetupDotfiles(config DotfilesConfig) error {
if _, err := shell.ExecLookPath("git"); err != nil {
return fmt.Errorf("git ist nicht installiert")
}
if _, err := shell.ExecLookPath("stow"); err != nil {
return fmt.Errorf("gnu stow not installed")
}
dotfilesDir := filepath.Join(os.Getenv("HOME"), "dotfiles")
cmd := shell.ExecCommand("git", "clone", config.GitRepo, dotfilesDir)
if err := cmd.Run(); err != nil {
return fmt.Errorf("error cloning dotfiles: %v", err)
}
if err := os.Chdir(dotfilesDir); err != nil {
return fmt.Errorf("error changing into dotfiles directory: %v", err)
}
cmd = shell.ExecCommand("stow", ".", "--override='*'")
if err := cmd.Run(); err != nil {
log.Printf("error creating links: %v", err)
}
fmt.Printf("all linked properly\n")
return nil
}

7
go.mod
View file

@ -3,10 +3,7 @@ module system_setup_tool
go 1.23.4 go 1.23.4
require ( require (
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/huh v0.6.0
github.com/charmbracelet/lipgloss v1.0.0
github.com/magefile/mage v1.15.0 github.com/magefile/mage v1.15.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/schollz/progressbar/v3 v3.18.0 github.com/schollz/progressbar/v3 v3.18.0
@ -18,7 +15,9 @@ require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/bubbletea v1.2.4 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // 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/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect

2
go.sum
View file

@ -10,8 +10,6 @@ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQW
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/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 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= 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 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= 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 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=

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
}

View file

@ -1,5 +1,4 @@
//go:build mage //go:build mage
// +build mage
package main package main

164
model.go
View file

@ -1,164 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type model struct {
packages []string
index int
width int
height int
spinner spinner.Model
progress progress.Model
done bool
sudoPassword string
os OS
}
func (m model) installSpecialSoftware() error {
if _, err := execLookPath("oh-my-posh"); err == nil {
fmt.Println("Oh-my-posh ist bereits installiert")
} else {
poshCommand := "curl -s https://ohmyposh.dev/install.sh | bash -s"
if err := installPackage(poshCommand, "", ""); err != nil {
return err
}
}
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("Fehler beim Ermitteln des Home-Verzeichnisses: %v", err)
}
ohMyZshDir := filepath.Join(homeDir, ".oh-my-zsh")
if _, err := os.Stat(ohMyZshDir); !os.IsNotExist(err) {
fmt.Println("Oh My Zsh ist bereits installiert.")
return nil
}
fmt.Println("Installiere Oh My Zsh...")
err = executeShellCommand(`sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"`, "")
if err != nil {
log.Printf("Fehler bei der Installation von Oh My Zsh: %v\n", err)
}
plugins := []string{
"git clone https://github.com/zsh-users/zsh-autosuggestions.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions",
"git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting",
"git clone https://github.com/zdharma-continuum/fast-syntax-highlighting.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/fast-syntax-highlighting",
"git clone --depth 1 -- https://github.com/marlonrichert/zsh-autocomplete.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autocomplete",
}
for _, plugin := range plugins {
err := executeShellCommand(plugin, "")
if err != nil {
log.Printf("Fehler bei der Installation des Plugins: %v\n", err)
}
}
fmt.Println("Oh My Zsh wurde erfolgreich installiert.")
return nil
}
func newModel(packages []string, sudoPassword string, os *OS) model {
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
progress.WithoutPercentage(),
)
s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
return model{
packages: packages,
spinner: s,
progress: p,
sudoPassword: sudoPassword,
os: *os,
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(m.installPackageCmd(m.packages[m.index]), m.spinner.Tick)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width, m.height = msg.Width, msg.Height
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc", "q":
return m, tea.Quit
}
case installedPkgMsg:
pkg := m.packages[m.index]
if m.index >= len(m.packages)-1 {
m.done = true
return m, tea.Sequence(
tea.Printf("%s %s", checkMark, pkg),
tea.Quit,
)
}
m.index++
progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages)))
return m, tea.Batch(
progressCmd,
tea.Printf("%s %s", checkMark, pkg),
m.installPackageCmd(m.packages[m.index]),
)
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
case progress.FrameMsg:
newModel, cmd := m.progress.Update(msg)
if newModel, ok := newModel.(progress.Model); ok {
m.progress = newModel
}
return m, cmd
}
return m, nil
}
func (m model) View() string {
n := len(m.packages)
w := lipgloss.Width(fmt.Sprintf("%d", n))
if m.done {
return doneStyle.Render(fmt.Sprintf("Done! Installed %d packages.\n", n))
}
pkgCount := fmt.Sprintf(" %*d/%*d", w, m.index, w, n)
spin := m.spinner.View() + " "
prog := m.progress.View()
cellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount))
pkgName := currentPkgNameStyle.Render(m.packages[m.index])
info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render("Installing " + pkgName)
cellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount))
gap := strings.Repeat(" ", cellsRemaining)
return spin + info + gap + prog + pkgCount
}
func (m model) installPackageCmd(pkg string) tea.Cmd {
return func() tea.Msg {
if err := installPackage(m.os.InstallCommand, pkg, m.sudoPassword); err != nil {
log.Printf("Fehler beim Installieren von %s: %v", pkg, err)
}
return installedPkgMsg(pkg)
}
}

View file

@ -1,4 +1,6 @@
package main package packagemanager
import "system_setup_tool/internal/shell"
type CargoManager struct{} type CargoManager struct{}
@ -6,7 +8,7 @@ func (c *CargoManager) Install(packages []string) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
err := installWithProgress(c, packages) err := InstallWithProgress(c, packages)
if err != nil { if err != nil {
return err return err
} }
@ -18,25 +20,25 @@ func (c *CargoManager) Name() string {
} }
func (c *CargoManager) InstallManager() error { func (c *CargoManager) InstallManager() error {
if _, err := execLookPath("brew"); err != nil { if _, err := shell.ExecLookPath("brew"); err != nil {
installHomebrew() installHomebrew()
} }
if _, err := execLookPath("cargo"); err == nil { if _, err := shell.ExecLookPath("cargo"); err == nil {
return nil return nil
} }
cmd := execCommand("brew", "install", "rust") cmd := shell.ExecCommand("brew", "install", "rust")
return cmd.Run() return cmd.Run()
} }
func (c *CargoManager) InstallPackage(pkg string) error { func (c *CargoManager) InstallPackage(pkg string) error {
cmd := execCommand("cargo", "install", pkg) cmd := shell.ExecCommand("cargo", "install", pkg)
return cmd.Run() return cmd.Run()
} }
func (c *CargoManager) RemovePackage(pkg string) error { func (c *CargoManager) RemovePackage(pkg string) error {
cmd := execCommand("cargo", "uninstall", pkg) cmd := shell.ExecCommand("cargo", "uninstall", pkg)
return cmd.Run() return cmd.Run()
} }

View file

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

View file

@ -1,10 +1,10 @@
package main package packagemanager
import ( import (
"fmt" "fmt"
"log" "log"
"os/exec"
"strings" "strings"
"system_setup_tool/internal/shell"
) )
type FlatpakManager struct { type FlatpakManager struct {
@ -13,11 +13,23 @@ type FlatpakManager struct {
Config FlatpakConfig 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 { func (f *FlatpakManager) Install(packages []string) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
err := installWithProgress(f, packages) err := InstallWithProgress(f, packages)
if err != nil { if err != nil {
return err return err
} }
@ -29,7 +41,7 @@ func (f *FlatpakManager) Name() string {
} }
func (f *FlatpakManager) InstallManager() error { func (f *FlatpakManager) InstallManager() error {
if _, err := exec.LookPath("flatpak"); err == nil { if _, err := shell.ExecLookPath("flatpak"); err == nil {
return nil return nil
} }
@ -69,7 +81,7 @@ func installFlatpak(os *OS, sudoPassword string) error {
return fmt.Errorf("keine Flatpak-Installation für OS %s definiert", os.ID) return fmt.Errorf("keine Flatpak-Installation für OS %s definiert", os.ID)
} }
if err := installPackage(command, "", sudoPassword); err != nil { if err := InstallPackage(command, "", sudoPassword); err != nil {
return fmt.Errorf("fehler bei der Flatpak-Installation: %v", err) return fmt.Errorf("fehler bei der Flatpak-Installation: %v", err)
} }
return nil return nil
@ -77,7 +89,7 @@ func installFlatpak(os *OS, sudoPassword string) error {
func addFlatpakRemotes(remotes []Remote) error { func addFlatpakRemotes(remotes []Remote) error {
for _, remote := range remotes { for _, remote := range remotes {
cmd := execCommand("flatpak", "remote-add", "--if-not-exists", remote.Name, remote.URL) cmd := shell.ExecCommand("flatpak", "remote-add", "--if-not-exists", remote.Name, remote.URL)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("fehler beim Hinzufügen des Remotes %s: %v", remote.Name, err) return fmt.Errorf("fehler beim Hinzufügen des Remotes %s: %v", remote.Name, err)
} }
@ -86,17 +98,17 @@ func addFlatpakRemotes(remotes []Remote) error {
} }
func (f *FlatpakManager) InstallPackage(pkg string) error { func (f *FlatpakManager) InstallPackage(pkg string) error {
cmd := execCommand("flatpak", "install", "-y", pkg) cmd := shell.ExecCommand("flatpak", "install", "-y", pkg)
return cmd.Run() return cmd.Run()
} }
func (f *FlatpakManager) RemovePackage(pkg string) error { func (f *FlatpakManager) RemovePackage(pkg string) error {
cmd := execCommand("flatpak", "uninstall", "-y", pkg) cmd := shell.ExecCommand("flatpak", "uninstall", "-y", pkg)
return cmd.Run() return cmd.Run()
} }
func (f *FlatpakManager) SearchPackage(pkg string) []string { func (f *FlatpakManager) SearchPackage(pkg string) []string {
cmd := execCommand("flatpak", "search", pkg) cmd := shell.ExecCommand("flatpak", "search", pkg)
packages, err := cmd.Output() packages, err := cmd.Output()
if err != nil { if err != nil {
log.Printf("error fetching %s packages: %v", f.Name(), err) log.Printf("error fetching %s packages: %v", f.Name(), err)

View file

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

View file

@ -1,4 +1,6 @@
package main package packagemanager
import "system_setup_tool/internal/shell"
type GolangManager struct{} type GolangManager struct{}
@ -6,7 +8,7 @@ func (g *GolangManager) Install(packages []string) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
err := installWithProgress(g, packages) err := InstallWithProgress(g, packages)
if err != nil { if err != nil {
return err return err
} }
@ -18,25 +20,25 @@ func (g *GolangManager) Name() string {
} }
func (g *GolangManager) InstallManager() error { func (g *GolangManager) InstallManager() error {
if _, err := execLookPath("brew"); err != nil { if _, err := shell.ExecLookPath("brew"); err != nil {
installHomebrew() installHomebrew()
} }
if _, err := execLookPath("go"); err == nil { if _, err := shell.ExecLookPath("go"); err == nil {
return nil return nil
} }
cmd := execCommand("brew", "install", "golang") cmd := shell.ExecCommand("brew", "install", "golang")
return cmd.Run() return cmd.Run()
} }
func (g *GolangManager) InstallPackage(pkg string) error { func (g *GolangManager) InstallPackage(pkg string) error {
cmd := execCommand("go", "install", pkg+"@latest") cmd := shell.ExecCommand("go", "install", pkg+"@latest")
return cmd.Run() return cmd.Run()
} }
func (g *GolangManager) RemovePackage(pkg string) error { func (g *GolangManager) RemovePackage(pkg string) error {
cmd := execCommand("go", "uninstall", pkg) cmd := shell.ExecCommand("go", "uninstall", pkg)
return cmd.Run() return cmd.Run()
} }

View file

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

View file

@ -1,9 +1,10 @@
package main package packagemanager
import ( import (
"fmt" "fmt"
"log" "log"
"strings" "strings"
"system_setup_tool/internal/shell"
) )
type HomebrewManager struct{} type HomebrewManager struct{}
@ -12,7 +13,7 @@ func (h *HomebrewManager) Install(packages []string) error {
if len(packages) == 0 { if len(packages) == 0 {
return nil return nil
} }
err := installWithProgress(h, packages) err := InstallWithProgress(h, packages)
if err != nil { if err != nil {
return err return err
} }
@ -24,7 +25,7 @@ func (h *HomebrewManager) Name() string {
} }
func (h *HomebrewManager) InstallManager() error { func (h *HomebrewManager) InstallManager() error {
if _, err := execLookPath("brew"); err == nil { if _, err := shell.ExecLookPath("brew"); err == nil {
return nil return nil
} }
@ -32,7 +33,7 @@ func (h *HomebrewManager) InstallManager() error {
} }
func installHomebrew() error { func installHomebrew() error {
err := executeShellCommand("sh -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", "NONINTERACTE=1") err := shell.ExecuteShellCommand("sh -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", "NONINTERACTE=1")
if err != nil { if err != nil {
return fmt.Errorf("fehler bei der Installation von Homebrew: %v", err) return fmt.Errorf("fehler bei der Installation von Homebrew: %v", err)
} }
@ -40,17 +41,17 @@ func installHomebrew() error {
} }
func (h *HomebrewManager) InstallPackage(pkg string) error { func (h *HomebrewManager) InstallPackage(pkg string) error {
cmd := execCommand("brew", "install", pkg) cmd := shell.ExecCommand("brew", "install", pkg)
return cmd.Run() return cmd.Run()
} }
func (h *HomebrewManager) RemovePackage(pkg string) error { func (h *HomebrewManager) RemovePackage(pkg string) error {
cmd := execCommand("brew", "uninstall", pkg) cmd := shell.ExecCommand("brew", "uninstall", pkg)
return cmd.Run() return cmd.Run()
} }
func (h *HomebrewManager) SearchPackage(pkg string) []string { func (h *HomebrewManager) SearchPackage(pkg string) []string {
cmd := execCommand("brew", "search", pkg) cmd := shell.ExecCommand("brew", "search", pkg)
packages, err := cmd.Output() packages, err := cmd.Output()
if err != nil { if err != nil {
log.Printf("error fetching %s packages: %v", h.Name(), err) log.Printf("error fetching %s packages: %v", h.Name(), err)

View file

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

View file

@ -1,27 +1,26 @@
package main package packagemanager
import ( import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings" "strings"
"system_setup_tool/internal/shell"
tea "github.com/charmbracelet/bubbletea"
) )
type OSManager struct { type OSManager struct {
OS *OS OS *OS
SudoPassword string SudoPassword string
Packages []string
Model model
} }
func NewOSManager(os *OS, sudoPassword string, packages []string) *OSManager { func NewOSManager(sudoPassword string) *OSManager {
os, err := GetLinuxDistribution()
if err != nil {
log.Fatalf("error geting os information: %v", err)
}
return &OSManager{ return &OSManager{
OS: os, OS: os,
SudoPassword: sudoPassword, SudoPassword: sudoPassword,
Packages: packages,
Model: newModel(packages, sudoPassword, os),
} }
} }
@ -32,42 +31,39 @@ func (o *OSManager) Name() string {
func (o *OSManager) InstallManager() error { return nil } func (o *OSManager) InstallManager() error { return nil }
func (o *OSManager) Install(packages []string) error { func (o *OSManager) Install(packages []string) error {
o.Packages = packages if len(packages) == 0 {
o.Model = newModel(packages, o.SudoPassword, o.OS) return nil
}
p := tea.NewProgram(o.Model) err := InstallWithProgress(o, packages)
_, err := p.Run() if err != nil {
return err return err
}
return nil
} }
func (o *OSManager) InstallPackage(pkg string) error { func (o *OSManager) InstallPackage(pkg string) error {
return installPackage(o.OS.InstallCommand, pkg, o.SudoPassword) return InstallPackage(o.OS.InstallCommand, pkg, o.SudoPassword)
} }
func (o *OSManager) InstallBuildEssentials() error { func (o *OSManager) InstallBuildEssentials() error {
return installBuildEssentials(o.OS, o.SudoPassword) return InstallBuildEssentials(o.OS, o.SudoPassword)
}
func (o *OSManager) InstallSpecialSoftware() error {
return o.Model.installSpecialSoftware()
} }
func (o *OSManager) RemovePackage(pkg string) error { func (o *OSManager) RemovePackage(pkg string) error {
fullCmd := fmt.Sprintf("%s %s", o.OS.RemoveCommand, pkg) fullCmd := fmt.Sprintf("%s %s", o.OS.RemoveCommand, pkg)
command := execCommand("sudo", "-S", "sh", "-c", fullCmd) command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd)
command.Stdin = strings.NewReader(o.SudoPassword + "\n") command.Stdin = strings.NewReader(o.SudoPassword + "\n")
return command.Run() return command.Run()
} }
func (o *OSManager) SearchPackage(pkg string) []string { func (o *OSManager) SearchPackage(pkg string) []string {
cmdParts := strings.Fields(o.OS.SearchCommand) cmdParts := strings.Fields(o.OS.SearchCommand)
if len(cmdParts) == 0 { if len(cmdParts) == 0 {
log.Printf("Invalid search command for OS package manager") log.Printf("Invalid search command for OS package manager")
return []string{} return []string{}
} }
cmd := execCommand(cmdParts[0], append(cmdParts[1:], pkg)...) cmd := shell.ExecCommand(cmdParts[0], append(cmdParts[1:], pkg)...)
packages, err := cmd.Output() packages, err := cmd.Output()
if err != nil { if err != nil {
log.Printf("Error fetching %s packages: %v", o.OS.PackageManager, err) log.Printf("Error fetching %s packages: %v", o.OS.PackageManager, err)
@ -127,7 +123,7 @@ func parseOsRelease(osRelease string) *OS {
return &result return &result
} }
func getLinuxDistribution() (*OS, error) { func GetLinuxDistribution() (*OS, error) {
_, err := os.Stat("/etc/os-release") _, err := os.Stat("/etc/os-release")
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, fmt.Errorf("unable to read system information") return nil, fmt.Errorf("unable to read system information")
@ -155,7 +151,7 @@ func (os *OS) getPackageManager() error {
"pacman", "pacman",
} }
for _, pmname := range pmcommands { for _, pmname := range pmcommands {
_, err := execLookPath(pmname) _, err := shell.ExecLookPath(pmname)
if err == nil { if err == nil {
os.PackageManager = pmname os.PackageManager = pmname
return nil return nil
@ -213,7 +209,7 @@ func (os *OS) getDeleteCommand() error {
} }
} }
func installBuildEssentials(os *OS, sudoPassword string) error { func InstallBuildEssentials(os *OS, sudoPassword string) error {
var command string var command string
switch os.PackageManager { switch os.PackageManager {
case "pacman": case "pacman":
@ -227,8 +223,8 @@ func installBuildEssentials(os *OS, sudoPassword string) error {
} }
fmt.Printf("Installiere Build Essentials für %s...\n", os.Name) fmt.Printf("Installiere Build Essentials für %s...\n", os.Name)
if err := installPackage(command, "", sudoPassword); err != nil { if err := InstallPackage(command, "", sudoPassword); err != nil {
return fmt.Errorf("fehler bei der Installation der Build Essentials: %v", err) return fmt.Errorf("error installing Build Essentials: %v", err)
} }
return nil return nil
} }

View file

@ -1,26 +1,20 @@
// osmanager_test.go package packagemanager
package main
import ( import (
"testing" "testing"
) )
func TestNewOSManager(t *testing.T) { func TestNewOSManager(t *testing.T) {
os := &OS{ID: "ubuntu", PackageManager: "apt"}
sudoPassword := "testpassword" sudoPassword := "testpassword"
packages := []string{"git", "curl"}
manager := NewOSManager(os, sudoPassword, packages) manager := NewOSManager(sudoPassword)
if manager.OS != os { if manager.OS == nil {
t.Errorf("Expected OS to be %v, got %v", os, manager.OS) t.Error("Expected OS to be non-nil")
} }
if manager.SudoPassword != sudoPassword { if manager.SudoPassword != sudoPassword {
t.Errorf("Expected SudoPassword to be %s, got %s", sudoPassword, manager.SudoPassword) t.Errorf("Expected SudoPassword to be %s, got %s", sudoPassword, manager.SudoPassword)
} }
if len(manager.Packages) != len(packages) {
t.Errorf("Expected Packages length to be %d, got %d", len(packages), len(manager.Packages))
}
} }
func TestOSManagerName(t *testing.T) { func TestOSManagerName(t *testing.T) {
@ -47,4 +41,13 @@ VERSION_ID="20.04"
if os.Version != "20.04" { if os.Version != "20.04" {
t.Errorf("Expected Version to be '20.04', got %s", os.Version) t.Errorf("Expected Version to be '20.04', got %s", os.Version)
} }
if os.PackageManager != "apt" {
t.Errorf("Expected PackageManager to be 'apt', got %s", os.PackageManager)
}
if os.InstallCommand != "apt install -y" {
t.Errorf("Expected InstallCommand to be 'apt install -y', got %s", os.InstallCommand)
}
if os.SearchCommand != "apt search" {
t.Errorf("Expected SearchCommand to be 'apt search', got %s", os.SearchCommand)
}
} }

View file

@ -1,8 +1,10 @@
package main package packagemanager
import ( import (
"fmt" "fmt"
"log"
"strings" "strings"
"system_setup_tool/internal/shell"
) )
type Packages struct { type Packages struct {
@ -10,26 +12,17 @@ type Packages struct {
NonHeadless []string `mapstructure:"non_headless"` NonHeadless []string `mapstructure:"non_headless"`
} }
func installPackage(cmd, pkg, sudoPassword string) error { func InstallPackage(cmd, pkg, sudoPassword string) error {
fullCmd := fmt.Sprintf("%s %s", cmd, pkg) fullCmd := fmt.Sprintf("%s %s", cmd, pkg)
command := execCommand("sudo", "-S", "sh", "-c", fullCmd) command := shell.ExecCommand("sudo", "-S", "sh", "-c", fullCmd)
command.Stdin = strings.NewReader(sudoPassword + "\n") command.Stdin = strings.NewReader(sudoPassword + "\n")
output, err := command.CombinedOutput() output, err := command.CombinedOutput()
if err != nil { 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") { if strings.Contains(string(output), "not found") || strings.Contains(string(output), "no matching package") || strings.Contains(string(output), "Keine Übereinstimmung") || strings.Contains(string(output), "Ziel nicht gefunden") {
unavailablePackages = append(unavailablePackages, pkg) log.Printf("Package %s not available\n", pkg)
return nil return nil
} }
return fmt.Errorf("failed to install %s: %v\n%s", pkg, err, string(output)) return fmt.Errorf("failed to install %s: %v\n%s", pkg, err, string(output))
} }
return nil return nil
} }
func printUnavailablePackages() {
if len(unavailablePackages) > 0 {
fmt.Println("\nFolgende Pakete waren nicht verfügbar:")
for _, pkg := range unavailablePackages {
fmt.Printf("- %s\n", pkg)
}
}
}

View file

@ -1,4 +1,4 @@
package main package packagemanager
type PackageManager interface { type PackageManager interface {
Install(packages []string) error Install(packages []string) error

View file

@ -1,4 +1,6 @@
package main package packagemanager
import "system_setup_tool/internal/shell"
type PipxManager struct{} type PipxManager struct{}
@ -7,7 +9,7 @@ func (p *PipxManager) Install(packages []string) error {
return nil return nil
} }
err := installWithProgress(p, packages) err := InstallWithProgress(p, packages)
if err != nil { if err != nil {
return err return err
} }
@ -19,25 +21,25 @@ func (p *PipxManager) Name() string {
} }
func (p *PipxManager) InstallManager() error { func (p *PipxManager) InstallManager() error {
if _, err := execLookPath("brew"); err != nil { if _, err := shell.ExecLookPath("brew"); err != nil {
installHomebrew() installHomebrew()
} }
if _, err := execLookPath("pipx"); err == nil { if _, err := shell.ExecLookPath("pipx"); err == nil {
return nil return nil
} }
cmd := execCommand("brew", "install", "pipx") cmd := shell.ExecCommand("brew", "install", "pipx")
return cmd.Run() return cmd.Run()
} }
func (p *PipxManager) InstallPackage(pkg string) error { func (p *PipxManager) InstallPackage(pkg string) error {
cmd := execCommand("pipx", "install", pkg) cmd := shell.ExecCommand("pipx", "install", pkg)
return cmd.Run() return cmd.Run()
} }
func (p *PipxManager) RemovePackage(pkg string) error { func (p *PipxManager) RemovePackage(pkg string) error {
cmd := execCommand("pipx", "uninstall", pkg) cmd := shell.ExecCommand("pipx", "uninstall", pkg)
return cmd.Run() return cmd.Run()
} }

View file

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

19
packagemanager/utils.go Normal file
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
}

146
tui.go
View file

@ -1,146 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
doneStyle = lipgloss.NewStyle().Margin(1, 2)
checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓")
unavailablePackages []string
)
type installedPkgMsg string
func run(cmd *cobra.Command, args []string) {
os, err := getLinuxDistribution()
if err != nil {
log.Fatal(err)
}
sudoPassword, err := getSudoPassword()
if err != nil {
log.Fatal(err)
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
log.Fatalf("Fehler beim Lesen der Konfiguration: %v", err)
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Möchten Sie eine headless Installation durchführen?").
Value(&cfg.Headless),
),
).WithTheme(huh.ThemeCatppuccin())
if err := form.Run(); err != nil {
log.Fatalf("Fehler bei der Benutzerabfrage: %v", err)
}
unavailablePackages = []string{}
osManager := NewOSManager(os, sudoPassword, cfg.Packages.Headless)
if err := osManager.Install(cfg.Packages.Headless); err != nil {
log.Printf("Warnung bei der Installation der Headless-Pakete: %v", err)
}
if !cfg.Headless {
if err := osManager.Install(cfg.Packages.NonHeadless); err != nil {
log.Printf("Warnung bei der Installation der Non-Headless-Pakete: %v", err)
}
}
printUnavailablePackages()
var installBuild bool
form = huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Möchten Sie die Build Essentials installieren?").
Value(&installBuild),
),
).WithTheme(huh.ThemeCatppuccin())
err = form.Run()
if err != nil {
log.Fatal(err)
}
if installBuild {
if err := osManager.InstallBuildEssentials(); err != nil {
log.Printf("Warnung: %v", err)
}
}
var installShellExtensions bool
form = huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Möchten Sie die Shell-Extensions installieren?").
Value(&installShellExtensions),
),
).WithTheme(huh.ThemeCatppuccin())
err = form.Run()
if err != nil {
log.Fatal(err)
}
if installShellExtensions {
if err := osManager.InstallSpecialSoftware(); err != nil {
log.Printf("Warnung bei der Installation spezieller Software: %v", err)
}
}
for name, config := range cfg.PackageManagers {
if config.Enable {
var manager PackageManager
switch name {
case "homebrew":
manager = &HomebrewManager{}
case "go":
manager = &GolangManager{}
case "cargo":
manager = &CargoManager{}
case "pipx":
manager = &PipxManager{}
case "flatpak":
flatpakConfig := FlatpakConfig{}
if err := mapstructure.Decode(config, &flatpakConfig); err != nil {
log.Printf("Fehler beim Dekodieren der Flatpak-Konfiguration: %v", err)
continue
}
manager = &FlatpakManager{
OS: os,
SudoPassword: sudoPassword,
Config: flatpakConfig,
}
default:
log.Printf("Unbekannter Paketmanager: %s", name)
continue
}
if err := manager.InstallManager(); err != nil {
log.Printf("Warnung Packagemanager %s nicht vorhanden und konnte nicht installiert werden: %v", manager.Name(), err)
}
if err := manager.Install(config.Packages); err != nil {
log.Printf("Warnung bei %s-Paketen: %v", manager.Name(), err)
}
}
}
if cfg.Dotfiles.Enable {
fmt.Println("\nKonfiguriere Dotfiles...")
if err := setupDotfiles(cfg.Dotfiles); err != nil {
log.Printf("Warnung bei Dotfiles-Setup: %v", err)
}
}
}

115
tui/tui.go Normal file
View file

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

View file

@ -1,64 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"github.com/charmbracelet/huh"
"github.com/schollz/progressbar/v3"
)
var (
execCommand = exec.Command
execLookPath = exec.LookPath
)
func getSudoPassword() (string, error) {
var password string
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Bitte geben Sie Ihr sudo-Passwort ein").
EchoMode(huh.EchoModePassword).
Value(&password),
),
).WithTheme(huh.ThemeCatppuccin())
err := form.Run()
if err != nil {
return "", fmt.Errorf("fehler bei der Passwortabfrage: %v", err)
}
return password, nil
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func executeShellCommand(command string, env string) error {
cmd := execCommand("bash", "-c", command)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, env)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("befehl fehlgeschlagen: %v\nAusgabe: %s", err, output)
}
return nil
}
func installWithProgress(manager PackageManager, packages []string) error {
bar := progressbar.Default(int64(len(packages)), "Installiere "+manager.Name()+"-Pakete...")
for _, pkg := range packages {
err := manager.InstallPackage(pkg)
if err != nil {
log.Printf("\nError installing %s: %v\n", pkg, err)
}
bar.Add(1)
}
return nil
}

32
utils/utils.go Normal file
View file

@ -0,0 +1,32 @@
package utils
import (
"fmt"
"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 max(a, b int) int {
if a > b {
return a
}
return b
}

40
utils/utils_test.go Normal file
View file

@ -0,0 +1,40 @@
package utils
import (
"os/exec"
"system_setup_tool/internal/shell"
"testing"
)
func TestMax(t *testing.T) {
tests := []struct {
a, b, want int
}{
{1, 2, 2},
{5, 3, 5},
{0, 0, 0},
{-1, -5, -1},
}
for _, tt := range tests {
got := max(tt.a, tt.b)
if got != tt.want {
t.Errorf("max(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
}
}
func TestExecuteShellCommand(t *testing.T) {
// Mock execCommand
oldExecCommand := shell.ExecCommand
defer func() { shell.ExecCommand = oldExecCommand }()
shell.ExecCommand = func(command string, args ...string) *exec.Cmd {
return exec.Command("echo", "mocked command")
}
err := shell.ExecuteShellCommand("test command", "TEST_ENV=value")
if err != nil {
t.Errorf("executeShellCommand() error = %v; want nil", err)
}
}

View file

@ -1,85 +0,0 @@
package main
import (
"os/exec"
"testing"
)
func TestMax(t *testing.T) {
tests := []struct {
a, b, want int
}{
{1, 2, 2},
{5, 3, 5},
{0, 0, 0},
{-1, -5, -1},
}
for _, tt := range tests {
got := max(tt.a, tt.b)
if got != tt.want {
t.Errorf("max(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
}
}
func TestExecuteShellCommand(t *testing.T) {
// Mock execCommand
oldExecCommand := execCommand
defer func() { execCommand = oldExecCommand }()
execCommand = func(command string, args ...string) *exec.Cmd {
return exec.Command("echo", "mocked command")
}
err := executeShellCommand("test command", "TEST_ENV=value")
if err != nil {
t.Errorf("executeShellCommand() error = %v; want nil", err)
}
}
func TestInstallWithProgress(t *testing.T) {
mockManager := &MockPackageManager{
packages: []string{"pkg1", "pkg2"},
}
err := installWithProgress(mockManager, mockManager.packages)
if err != nil {
t.Errorf("installWithProgress() error = %v; want nil", err)
}
if len(mockManager.installedPackages) != len(mockManager.packages) {
t.Errorf("installWithProgress() installed %d packages; want %d", len(mockManager.installedPackages), len(mockManager.packages))
}
}
type MockPackageManager struct {
packages []string
installedPackages []string
}
func (m *MockPackageManager) Install(packages []string) error {
m.installedPackages = append(m.installedPackages, packages...)
return nil
}
func (m *MockPackageManager) InstallPackage(pkg string) error {
m.installedPackages = append(m.installedPackages, pkg)
return nil
}
func (m *MockPackageManager) InstallManager() error {
return nil
}
func (m *MockPackageManager) Name() string {
return "MockManager"
}
func (m *MockPackageManager) RemovePackage(pkg string) error {
return nil
}
func (m *MockPackageManager) SearchPackage(pkg string) []string {
return nil
}