416 lines
12 KiB
Go
416 lines
12 KiB
Go
package dependency
|
|
|
|
import (
|
|
"fmt"
|
|
"jws/internal/logger"
|
|
osinfo "jws/internal/os"
|
|
"jws/pkg/download"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/widget"
|
|
"fyne.io/tools"
|
|
)
|
|
|
|
var log *slog.Logger
|
|
|
|
func init() {
|
|
log = logger.GetChildLogger("dependency")
|
|
}
|
|
|
|
type Dependency struct {
|
|
Name string
|
|
Installed bool
|
|
Icon fyne.Resource
|
|
}
|
|
|
|
func CheckDependencies(dependencies []Dependency) {
|
|
log.Info("Checking dependencies")
|
|
// Check VSCode
|
|
dependencies[0].Installed = checkVSCode()
|
|
|
|
// Check Docker
|
|
if runtime.GOOS == "windows" {
|
|
dependencies[1].Installed = checkDockerDesktop()
|
|
} else {
|
|
dependencies[1].Installed = checkDocker()
|
|
}
|
|
}
|
|
|
|
func checkVSCode() bool {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
_, err := os.Stat(filepath.Join(os.Getenv("LOCALAPPDATA"), "Programs", "Microsoft VS Code", "Code.exe"))
|
|
if err == nil {
|
|
return true
|
|
}
|
|
_, err = os.Stat(filepath.Join(os.Getenv("ProgramFiles"), "Microsoft VS Code", "Code.exe"))
|
|
return err == nil
|
|
case "darwin":
|
|
_, err := os.Stat("/Applications/Visual Studio Code.app")
|
|
if err != nil {
|
|
cmd := tools.CommandInShell("which", "code")
|
|
return cmd.Run() == nil
|
|
}
|
|
return err == nil
|
|
case "linux":
|
|
cmd := tools.CommandInShell("which", "code")
|
|
return cmd.Run() == nil
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func checkDocker() bool {
|
|
cmd := tools.CommandInShell("which", "docker")
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func checkDockerDesktop() bool {
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
_, err := os.Stat(filepath.Join(os.Getenv("ProgramFiles"), "Docker", "Docker", "Docker Desktop.exe"))
|
|
return err == nil
|
|
case "darwin":
|
|
_, err := os.Stat("/Applications/Docker.app")
|
|
return err == nil
|
|
case "linux":
|
|
cmd := tools.CommandInShell("systemctl", "is-active", "docker")
|
|
output, _ := cmd.Output()
|
|
return strings.TrimSpace(string(output)) == "active"
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func InstallDependency(index int, sudoPassword string, dependencies []Dependency, mainWindow fyne.Window) {
|
|
depName := dependencies[index].Name
|
|
|
|
var cmd *exec.Cmd
|
|
var err error
|
|
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
cmd, err = installWindowsDependencies(index, cmd, &mainWindow)
|
|
if err != nil {
|
|
dialog.ShowError(err, mainWindow)
|
|
log.Error(err.Error())
|
|
}
|
|
case "darwin":
|
|
cmd, err = installDarwinDependencies(index, cmd, &mainWindow)
|
|
dialog.ShowError(err, mainWindow)
|
|
log.Error(err.Error())
|
|
case "linux":
|
|
err = installLinuxDependencies(index, sudoPassword, cmd, dependencies, &mainWindow)
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
dialog.ShowError(err, mainWindow)
|
|
}
|
|
}
|
|
|
|
if cmd != nil {
|
|
showInstallProgressBar(mainWindow, fmt.Sprintf("installing %v", depName), cmd, depName, dependencies)
|
|
}
|
|
}
|
|
|
|
func installWindowsDependencies(index int, cmd *exec.Cmd, mainWindow *fyne.Window) (*exec.Cmd, error) {
|
|
switch index {
|
|
case 0: // VSCode
|
|
cmd = tools.CommandInShell("winget", "install", "-e", "--id",
|
|
"Microsoft.VisualStudioCode")
|
|
case 1: // Docker Desktop
|
|
wslEnabled, err := checkWslFeaturesEnabled()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error checking WSL features: %v", err)
|
|
}
|
|
|
|
if !wslEnabled {
|
|
dialog.ShowInformation("activate wsl", "WSL has to be activated. Please confirm to active necessary features.", *mainWindow)
|
|
err = enableWslFeatures()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error enabling WSL features: %v", err)
|
|
}
|
|
dialog.ShowInformation("reboot required", "Please reboot your PC to finish wsl activation and reopen 'jws'.", *mainWindow)
|
|
return nil, fmt.Errorf("reboot required")
|
|
}
|
|
|
|
wslCheckCmd := tools.CommandInShell("wsl", "--status")
|
|
err = wslCheckCmd.Run()
|
|
if err != nil {
|
|
wslInstallCmd := tools.CommandInShell("wsl", "--install", "ubuntu")
|
|
dialog.ShowInformation("installing wsl", "WSL will be installed. Please wait untill installation is finished and reopen 'jws'.", *mainWindow)
|
|
err = wslInstallCmd.Run()
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("error: installing WSL: %v", err)
|
|
return nil, errMsg
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
cmd = tools.CommandInShell("winget", "install", "-e", "--id",
|
|
"Docker.DockerDesktop")
|
|
}
|
|
return cmd, nil
|
|
}
|
|
|
|
func checkWslFeaturesEnabled() (bool, error) {
|
|
cmd := tools.CommandInShell("powershell", "-Command",
|
|
"[bool](Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform).State -and [bool](Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux).State")
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return false, fmt.Errorf("error executing PowerShell command: %v\nOutput: %s", err, string(output))
|
|
}
|
|
|
|
enabled := strings.TrimSpace(string(output)) == "True"
|
|
return enabled, nil
|
|
}
|
|
|
|
func enableWslFeatures() error {
|
|
cmd := tools.CommandInShell("powershell", "-Command",
|
|
"Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart; Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart")
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("error executing PowerShell command: %v\nOutput: %s", err, string(output))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func installDarwinDependencies(index int, cmd *exec.Cmd, mainWindow *fyne.Window) (*exec.Cmd, error) {
|
|
brewCheckCmd := tools.CommandInShell("which", "brew")
|
|
err := brewCheckCmd.Run()
|
|
if err != nil {
|
|
brewInstallCmd := tools.CommandInShell("bin/bash", "-c", "\"$(curl -fsSl https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
|
|
err := brewInstallCmd.Run()
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("error: installing homebrew falied: %v", err)
|
|
return nil, errMsg
|
|
}
|
|
}
|
|
switch index {
|
|
case 0: // VSCode
|
|
cmd = tools.CommandInShell("brew", "install", "--cask", "visual-studio-code")
|
|
case 1: // Docker
|
|
cmd = tools.CommandInShell("brew", "install", "docker")
|
|
}
|
|
return cmd, nil
|
|
}
|
|
|
|
func installLinuxDependencies(index int, sudoPassword string, cmd *exec.Cmd, dependencies []Dependency, mainWindow *fyne.Window) error {
|
|
osInfo, err := osinfo.GetLinuxDistribution()
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("error getting OS info: %v", err)
|
|
return errMsg
|
|
}
|
|
|
|
var downloadURL, fileName string
|
|
switch index {
|
|
case 0: // VSCode
|
|
switch osInfo.ID {
|
|
case "debian", "ubuntu", "linuxmint":
|
|
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64"
|
|
fileName = "vscode.deb"
|
|
case "fedora":
|
|
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-rpm-x64"
|
|
fileName = "vscode.rpm"
|
|
default:
|
|
dialog.ShowInformation("Nicht unterstützt", fmt.Sprintf("Automatische Installation für dieses OS %v nicht verfügbar", osInfo), *mainWindow)
|
|
return nil
|
|
}
|
|
go func() {
|
|
progressBar := widget.NewProgressBar()
|
|
progressDialog := dialog.NewCustomWithoutButtons("Download in progress", progressBar, *mainWindow)
|
|
|
|
progressDialog.Show()
|
|
|
|
homeDir, _ := os.UserHomeDir()
|
|
downloadDir := filepath.Join(homeDir, "Downloads")
|
|
filePath := filepath.Join(downloadDir, fileName)
|
|
|
|
err := download.WithProgressBar(downloadURL, filePath, progressBar)
|
|
progressDialog.Hide()
|
|
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
dialog.ShowError(err, *mainWindow)
|
|
return
|
|
}
|
|
|
|
var installCmd string
|
|
switch osInfo.ID {
|
|
case "debian", "ubuntu", "linuxmint":
|
|
installCmd = fmt.Sprintf("sudo -S dpkg -i %s", filePath)
|
|
case "fedora":
|
|
installCmd = fmt.Sprintf("sudo -S dnf install -y %s", filePath)
|
|
}
|
|
|
|
cmd := exec.Command("sh", "-c", installCmd)
|
|
cmd.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
|
|
dialog.ShowInformation("Installation started",
|
|
"Installation of VSCode started.", *mainWindow)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("installation failed:\n%s", output)
|
|
dialog.ShowError(errMsg, *mainWindow)
|
|
log.Error(errMsg.Error())
|
|
return
|
|
} else {
|
|
dialog.ShowInformation("Success", "VSCode succesfully installed!", *mainWindow)
|
|
CheckDependencies(dependencies)
|
|
}
|
|
}()
|
|
case 1: // Docker
|
|
go func() {
|
|
progressBar := widget.NewProgressBar()
|
|
progressDialog := dialog.NewCustomWithoutButtons("Docker installation in progress...", progressBar, *mainWindow)
|
|
progressDialog.Show()
|
|
|
|
osInfo, err := osinfo.GetLinuxDistribution()
|
|
if err != nil {
|
|
progressDialog.Hide()
|
|
errMsg := fmt.Errorf("error getting os infos: %v", err)
|
|
dialog.ShowError(errMsg, *mainWindow)
|
|
log.Error(errMsg.Error())
|
|
return
|
|
}
|
|
|
|
var commands []string
|
|
var cleanupCommands []string
|
|
var totalSteps int
|
|
|
|
switch osInfo.ID {
|
|
case "ubuntu", "linuxmint", "debian":
|
|
// Ubuntu/Debian Commands
|
|
distroPath := "ubuntu"
|
|
codeName := "$(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\")"
|
|
if osInfo.ID == "debian" {
|
|
distroPath = "debian"
|
|
codeName = "$(. /etc/os-release && echo \"$VERSION_CODENAME\")"
|
|
}
|
|
arch := "$(dpkg --print-architecture)"
|
|
|
|
commands = []string{
|
|
"apt-get update",
|
|
"apt-get install -y wget",
|
|
fmt.Sprintf("wget -qO- https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/ | grep -oP 'href=\"\\K[^\"]*(?=.*deb)' | xargs -I{} wget https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/{}",
|
|
distroPath, codeName, arch, distroPath, codeName, arch),
|
|
"dpkg -i ./containerd.io*.deb docker-ce*.deb docker-ce-cli*.deb docker-buildx-plugin*.deb docker-compose-plugin*.deb",
|
|
"apt-get install -f -y",
|
|
"service docker start",
|
|
}
|
|
|
|
cleanupCommands = []string{
|
|
"rm -rf ./containerd.io*.deb ./docker-ce*.deb ./docker-ce-cli*.deb ./docker-buildx-plugin*.deb ./docker-compose-plugin*.deb",
|
|
"apt-get autoremove -y",
|
|
"apt-get clean",
|
|
}
|
|
|
|
totalSteps = len(commands) + len(cleanupCommands)
|
|
|
|
case "fedora":
|
|
fedoraVer := "$(rpm -E %fedora)"
|
|
commands = []string{
|
|
"dnf install -y wget",
|
|
fmt.Sprintf("wget https://download.docker.com/linux/fedora/%s/x86_64/stable/Packages/containerd-*.rpm docker-*.rpm docker-ce-*.rpm", fedoraVer),
|
|
"dnf install -y ./*.rpm",
|
|
"systemctl enable --now docker",
|
|
}
|
|
|
|
cleanupCommands = []string{
|
|
"rm -rf ./containerd-*.rpm ./docker-*.rpm ./docker-ce-*.rpm",
|
|
"dnf autoremove -y",
|
|
"dnf clean all",
|
|
}
|
|
|
|
totalSteps = len(commands) + len(cleanupCommands)
|
|
|
|
default:
|
|
progressDialog.Hide()
|
|
dialog.ShowInformation("not supported", "Automatic Docker installation not supported for your OS.", *mainWindow)
|
|
return
|
|
}
|
|
|
|
progressStep := 1.0 / float64(totalSteps)
|
|
currentProgress := 0.0
|
|
|
|
for _, cmd := range commands {
|
|
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
|
|
command.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
|
|
if output, err := command.CombinedOutput(); err != nil {
|
|
progressDialog.Hide()
|
|
errMsg := fmt.Errorf("error at %s:\n%s", cmd, output)
|
|
dialog.ShowError(errMsg, *mainWindow)
|
|
log.Error(errMsg.Error())
|
|
return
|
|
}
|
|
|
|
currentProgress += progressStep
|
|
progressBar.SetValue(currentProgress)
|
|
}
|
|
|
|
for _, cmd := range cleanupCommands {
|
|
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
|
|
command.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
|
|
if output, err := command.CombinedOutput(); err != nil {
|
|
progressDialog.Hide()
|
|
errMsg := fmt.Errorf("cleanup error at %s:\n%s", cmd, output)
|
|
dialog.ShowError(errMsg, *mainWindow)
|
|
log.Error(errMsg.Error())
|
|
return
|
|
}
|
|
|
|
currentProgress += progressStep
|
|
progressBar.SetValue(currentProgress)
|
|
}
|
|
|
|
progressDialog.Hide()
|
|
|
|
dialog.ShowInformation(
|
|
"Installation finished",
|
|
"Docker was succesfully installed! Please re-start your system to let the changes take effect.",
|
|
*mainWindow,
|
|
)
|
|
|
|
CheckDependencies(dependencies)
|
|
}()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func showInstallProgressBar(window fyne.Window, message string, cmd *exec.Cmd, depName string, dependencies []Dependency) {
|
|
progress := widget.NewProgressBarInfinite()
|
|
content := container.NewVBox(
|
|
widget.NewLabel(message),
|
|
progress,
|
|
)
|
|
popup := widget.NewModalPopUp(content, window.Canvas())
|
|
popup.Show()
|
|
|
|
go func() {
|
|
err := cmd.Run()
|
|
|
|
popup.Hide()
|
|
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("error during installation of %s: %v", depName, err)
|
|
dialog.ShowError(errMsg, window)
|
|
log.Error(errMsg.Error())
|
|
} else {
|
|
dialog.ShowInformation("Installation finished", fmt.Sprintf("%s successfully installed!", depName), window)
|
|
CheckDependencies(dependencies)
|
|
}
|
|
}()
|
|
}
|