From 3081e34e2bc124cb447fd6268f1afee94a394693 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 27 Dec 2024 13:13:32 +0100 Subject: [PATCH] refactor: seperate functionality into files --- cmd.go | 35 ++ config.go | 9 + dotfiles.go | 43 +++ flatpak.go | 73 +++++ main.go | 792 --------------------------------------------- model.go | 163 ++++++++++ osinfo.go | 126 ++++++++ package.go | 67 ++++ specialSoftware.go | 122 +++++++ tui.go | 166 ++++++++++ utils.go | 67 ++++ 11 files changed, 871 insertions(+), 792 deletions(-) create mode 100644 cmd.go create mode 100644 config.go create mode 100644 dotfiles.go create mode 100644 flatpak.go create mode 100644 model.go create mode 100644 osinfo.go create mode 100644 package.go create mode 100644 specialSoftware.go create mode 100644 tui.go create mode 100644 utils.go diff --git a/cmd.go b/cmd.go new file mode 100644 index 0000000..658b3b8 --- /dev/null +++ b/cmd.go @@ -0,0 +1,35 @@ +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) + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..35ec318 --- /dev/null +++ b/config.go @@ -0,0 +1,9 @@ +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"` +} diff --git a/dotfiles.go b/dotfiles.go new file mode 100644 index 0000000..e493afc --- /dev/null +++ b/dotfiles.go @@ -0,0 +1,43 @@ +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) + 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", ".") + if err := cmd.Run(); err != nil { + log.Printf("Fehler beim Linken: %v", err) + } + fmt.Printf("Alles erfolgreich verlinkt\n") + + return nil +} diff --git a/flatpak.go b/flatpak.go new file mode 100644 index 0000000..8db20ee --- /dev/null +++ b/flatpak.go @@ -0,0 +1,73 @@ +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 +} diff --git a/main.go b/main.go index 855bf53..e61393f 100644 --- a/main.go +++ b/main.go @@ -2,801 +2,9 @@ package main import ( "fmt" - "io" - "log" - "net/http" "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/charmbracelet/bubbles/progress" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/huh" - "github.com/charmbracelet/lipgloss" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -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: - var 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 getSudoPassword() (string, error) { - var password string - form := huh.NewForm( - huh.NewGroup( - huh.NewInput(). - Title("Bitte geben Sie Ihr sudo-Passwort ein"). - Password(true). - Value(&password), - ), - ).WithTheme(huh.ThemeCatppuccin()) - - err := form.Run() - if err != nil { - return "", fmt.Errorf("Fehler bei der Passwortabfrage: %v", err) - } - return password, 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("Fehler bei der Installation der Build Essentials: %v", err) - } - return nil -} - -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) - } -} - -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(fmt.Sprintf("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 -} - -type installedItemMsg string - -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) - } - poshCommand := "curl -s https://ohmyposh.dev/install.sh | bash -s" - if err := installPackage(poshCommand, "", ""); err != nil { - log.Printf("Fehler beim Installieren von oh-my-posh: %v", 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 { - return installedItemMsg(item) - } - rustupCommand := "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -q -y" - if err := installPackage(rustupCommand, "", ""); err != nil { - log.Printf("Fehler beim Installieren von Rust: %v", err) - } - } - return installedItemMsg(item) - } -} - -var unavailablePackages []string - -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 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 (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 -} - -type model struct { - packages []string - index int - width int - height int - spinner spinner.Model - progress progress.Model - done bool - sudoPassword string - os OS -} - -var ( - currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211")) - doneStyle = lipgloss.NewStyle().Margin(1, 2) - checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓") -) - -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 -} - -type installedPkgMsg string - -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) - } -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -var rootCmd = &cobra.Command{ - Use: "package-installer", - Short: "Installiert Pakete basierend auf TOML-Konfiguration", - Run: run, -} - -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"` -} - -type Config struct { - Headless bool `mapstructure:"headless"` - Packages Packages `mapstructure:"packages"` - SpecialPackages SpecialPackages `mapstructure:"special_packages"` - Flatpak FlatpakConfig `mapstructure:"flatpak"` - Dotfiles DotfilesConfig `mapstructure:"dotfiles"` -} - -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"` -} - -type DotfilesConfig struct { - Enable bool `mapstructure:"enable"` - GitRepo string `mapstructure:"git_repo"` -} - -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 -} - -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) - 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) - } - - // Module mit stow verlinken - cmd = exec.Command("stow", ".") - if err := cmd.Run(); err != nil { - log.Printf("Fehler beim Linken: %v", err) - } - fmt.Printf("Alles erfolgreich verlinkt\n") - - return nil -} - -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) - } -} - -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 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) - } - } -} - -func printUnavailablePackages() { - if len(unavailablePackages) > 0 { - fmt.Println("\nFolgende Pakete waren nicht verfügbar:") - for _, pkg := range unavailablePackages { - fmt.Printf("- %s\n", pkg) - } - } -} - func main() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/model.go b/model.go new file mode 100644 index 0000000..532537f --- /dev/null +++ b/model.go @@ -0,0 +1,163 @@ +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) + } +} diff --git a/osinfo.go b/osinfo.go new file mode 100644 index 0000000..ca2aee5 --- /dev/null +++ b/osinfo.go @@ -0,0 +1,126 @@ +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 +} diff --git a/package.go b/package.go new file mode 100644 index 0000000..7ddebc2 --- /dev/null +++ b/package.go @@ -0,0 +1,67 @@ +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) + } + } +} diff --git a/specialSoftware.go b/specialSoftware.go new file mode 100644 index 0000000..4c1c7f3 --- /dev/null +++ b/specialSoftware.go @@ -0,0 +1,122 @@ +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(fmt.Sprintf("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) + } + poshCommand := "curl -s https://ohmyposh.dev/install.sh | bash -s" + if err := installPackage(poshCommand, "", ""); err != nil { + log.Printf("Fehler beim Installieren von oh-my-posh: %v", 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 { + return installedItemMsg(item) + } + rustupCommand := "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -q -y" + if err := installPackage(rustupCommand, "", ""); err != nil { + log.Printf("Fehler beim Installieren von Rust: %v", err) + } + } + return installedItemMsg(item) + } +} diff --git a/tui.go b/tui.go new file mode 100644 index 0000000..f542b21 --- /dev/null +++ b/tui.go @@ -0,0 +1,166 @@ +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) + } + } +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..6bbbb99 --- /dev/null +++ b/utils.go @@ -0,0 +1,67 @@ +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 +}