From 09b1054588292702b0a93749e307450382222137 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Sat, 15 Mar 2025 00:13:20 +0100 Subject: [PATCH] feat: initial project commit commit to push first working snapshot to codeberg --- README.md | 104 ++++++ cmd/jws/FyneApp.toml | 2 + cmd/jws/Icon.png | Bin 0 -> 2375 bytes cmd/jws/main.go | 113 +++++++ cmd/jws/projects/jakarta-jsp-todo/pom.xml | 0 cmd/jws/projects/jakarta-rest-todo/pom.xml | 0 .../.devcontainer/Dockerfile | 18 + .../.devcontainer/devcontainer.json | 9 + .../.devcontainer/docker-compose.yml | 32 ++ cmd/jws/projects/jakarta-servlet-todo/pom.xml | 49 +++ .../main/java/com/example/dao/TodoDAO.java | 36 ++ .../src/main/java/com/example/model/Todo.java | 43 +++ .../java/com/example/servlet/TodoServlet.java | 40 +++ .../main/resources/META-INF/persistence.xml | 19 ++ .../src/main/webapp/WEB-INF/web.xml | 9 + cmd/jws/projects/spring-boot-todo/pom.xml | 0 go.mod | 40 +++ go.sum | 76 +++++ internal/dependency/dependency.go | 315 ++++++++++++++++++ internal/gui/dependency_screen.go | 92 +++++ internal/gui/gui.go | 47 +++ internal/gui/project_screen.go | 91 +++++ internal/os/osinfo.go | 83 +++++ internal/project/project.go | 127 +++++++ pkg/download/download.go | 60 ++++ 25 files changed, 1405 insertions(+) create mode 100644 README.md create mode 100644 cmd/jws/FyneApp.toml create mode 100644 cmd/jws/Icon.png create mode 100644 cmd/jws/main.go create mode 100644 cmd/jws/projects/jakarta-jsp-todo/pom.xml create mode 100644 cmd/jws/projects/jakarta-rest-todo/pom.xml create mode 100644 cmd/jws/projects/jakarta-servlet-todo/.devcontainer/Dockerfile create mode 100644 cmd/jws/projects/jakarta-servlet-todo/.devcontainer/devcontainer.json create mode 100644 cmd/jws/projects/jakarta-servlet-todo/.devcontainer/docker-compose.yml create mode 100644 cmd/jws/projects/jakarta-servlet-todo/pom.xml create mode 100644 cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/dao/TodoDAO.java create mode 100644 cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/model/Todo.java create mode 100644 cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/servlet/TodoServlet.java create mode 100644 cmd/jws/projects/jakarta-servlet-todo/src/main/resources/META-INF/persistence.xml create mode 100644 cmd/jws/projects/jakarta-servlet-todo/src/main/webapp/WEB-INF/web.xml create mode 100644 cmd/jws/projects/spring-boot-todo/pom.xml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/dependency/dependency.go create mode 100644 internal/gui/dependency_screen.go create mode 100644 internal/gui/gui.go create mode 100644 internal/gui/project_screen.go create mode 100644 internal/os/osinfo.go create mode 100644 internal/project/project.go create mode 100644 pkg/download/download.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d48e7c --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# JakartaEE & Spring Boot Starter + +This application is a GUI tool developed in Go using the Fyne framework (v2.5.4). It helps users set up a development environment for JakartaEE and Spring Boot projects by checking and installing necessary dependencies, and then allowing users to deploy starter projects. + +## Features +- *Dependency Check*: Verifies the installation of required tools (Visual Studio Code and Docker). + +- *Automatic Installation*: Offers to install missing dependencies on supported platforms. + +- *Project Selection*: Provides a list of starter projects for JakartaEE and Spring Boot. + +- *Project Deployment*: Allows users to deploy selected projects and open them in Visual Studio Code with dev containers. + +## Prerequisites + +- Go 1.23 or later + +- Fyne v2.5.4 + +## Installation + +### Option 1: Download Pre-built Binary + +Go to the Releases page of this repository. + +Download the appropriate binary for your operating system. + +### Option 2: Build from Source + +- Clone the repository: + +```bash +git clone https://github.com/yourusername/jakartaee-springboot-starter.git +``` + +Navigate to the project directory: + +```bash +cd jakartaee-springboot-starter +``` + +Install dependencies: + +The list of dependencies required for the development of Fyne Apps can be found at “https://docs.fyne.io/started/”. + +```bash +go mod tidy +``` + +Build the application: + +```bash +go build +``` +## Usage + +Run the application: + +If you downloaded a pre-built binary: + +```bash +./jakartaee-springboot-starter +``` + +or by double-clicking the excutable. + +If you built from source: + +```bash +./jakartaee-springboot-starter +``` + +The application will launch a GUI window where you can: + +1. Check and install dependencies (Visual Studio Code and Docker). +2. Select a starter project from the available options. +3. Deploy the selected project and open it in Visual Studio Code. + +Project Structure + +// TODO +- main.go: Contains the entire application logic, including GUI setup, dependency checking, and project deployment. +- projects/: Directory containing starter project templates. +- projects.json: Configuration file for additional plugin projects. + +## Customization + +To add new starter projects: + +1. Create a new project template in the projects/ directory. +2. Add the project details to the projects.json file. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +License +[Insert your chosen license here] + +## Acknowledgments + +- Fyne: https://fyne.io/ +- JakartaEE: https://jakarta.ee/ +- Spring Boot: https://spring.io/projects/spring-boot diff --git a/cmd/jws/FyneApp.toml b/cmd/jws/FyneApp.toml new file mode 100644 index 0000000..65ec495 --- /dev/null +++ b/cmd/jws/FyneApp.toml @@ -0,0 +1,2 @@ +[Details] + Build = 4 diff --git a/cmd/jws/Icon.png b/cmd/jws/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b9c666c8b562ce1ed8dc2a5dcaaded184093088a GIT binary patch literal 2375 zcmZuzdpwkB8@}I}mpNz{6XP^8z9msE6GHor*4 zNG(O0{W=iJOrkqOb9_`R{`NB?CEKvej8tPe@;{=086RBwwRAz|rO{Y~i!tr7qC#pL|dpn^8@ z=E4o9u_J8HOQu0RINzu1NVG2e*_bj#vGs<5Ji1V+bv%n$$2r6O?;IM7 zw*Q3*VRuK0V#m+Tx9{VG?}ga#kv!M9^RCx?4ick6W-60zS2RTP2wdBG#oeCqZM0JK z<{ujj3vT=w!HKue>)}O*-0#k}-R)fWxZJpQIo;ew*8a_#>o*QOi@HbM-13<1r1B}L z4$X?+H{v`ci{JQ!cf;1cN^BZ;ckdH}1@|5GmkS@XRGi5)CKNw&jAab@z8d=dU`NK( zZ-xJC7s;(MKBhEnbsLGESUTO|`_rGxqWfR8s2aLxo&CD{5<$bHQTG*(xnrnCrNXu8 z#oQ*96$>eN)4jh5-O0S9XsT5C^4dLo=B7Ot&)h|Ausf=%D{AtuYC6r-U@QJm9dx=3 zCik{B&N#_QO2!LqaF&zu6jwXP{JqHu?HQ^LuLRt#$qIh(IW9s+R~(2~$whiI7rt{2 zz#0V$E@w~DnEM^1_;u6*s1dcCJ4O@zl`2_!;>$*gv)lwGa-#G%6O`>iYNJ-GUZtM| z|EyMlf|I4Ij2hMrGxfq=ir<*si9{=(pAzq7qS9Up@((o|wj~G3N7mpjbRY%hXxMlN zA@2xPDLKUOp$W0`uGpR|9p*;9@~SQiTOtP84I=Eaz9JEGaOF<$>oxd`_x;NmF5OY+`C%)+mL;1LWK;8h7XY~k*RIy$#amz}m-bt1hQ`WLy2KFl zo`Iw@P%D89gFc|VQA$_3LKs5@ne#SCwbE83%uT~Yiv{2!A`E}^-f_FGspQecZ*`#8 z)#G_Lf;|sCU2VV+gf{ivzxeYa892u2_0!#cWr*+-Ke>Sn5t^l(`Ze{NMHumYEg!(@ zDA>kjqW3@AADwENJc)#9SteZXFVRBUZNN~=(qm}ByU$OF{u*KocW}RfF+W#wD2)P! zw}KG|eUYX_VU`0D3M>SWWSg-*7p8e89|U%*!5ukzPhg3g0a>8}KA>US3EB2a>_k~X z2fot64sE0@+a}WgpiygrTUJ%A(eBBop^$){4P_SkKXawIqhwo z@ro4>KfdUYq-SjXYqtfmMEBlG)l?CmhFOTjw)29Ypwn*6%n z4&Nw#d5#4zSQRmobM{=^hb*ue2HQy4#x_J2+3q}!L;(Cf3kM#Ya*x8NqM%)d_xZ-9 zReykzr@N0~ZX|mveM5Z;(??B@`Dg~2ezpWjU5PnSQ26k)yCr^D$4xt5OK=VavvPdV zn$Rc{S1eihh{Zt2Oo6Q{NX5rpz1om;Ng$$k)^Wh-1or5Q7^N!MbLrS5KA*N zU|7}@&7f$ln?OCv<>u~LI7`$cBqF~KEEI>2f~RtImG3NQA0E0z;$fQkoWrSry{wM0 zK^m*3T8qo^(?SG*M1Fh^uoxjl^c@KI^$xi$Y!GQ0`1$M4f$AG!9ak9 z1vVL)P!d9y6RE~4_Fa&mL{4RYoI5FxZpeE7Z~q|NEscE`9<>t0NOT${N|flqx@V)_ zCNjx^7Hot*Uzm3xEHDViUh)jNH-}nds{G0nf}AAv96=i0NC8-{b@zmQ@AQwlg3+RB z*Iy{8wd_;(bW@?P`plN@iT*d1Y@~f*jF#9#^n&yLCi>j)^+X}0HGi!d%An`#iM*c- z^oD!Y+n9`aFRaA}S}%7&R^9G+S3B`6xw$H)`bfd1?sVsb8I{P5wHLQaL7AP@-oDdi z^xCSP9b3{SE6&$@Zcgv#=3It2<%b)EwWG<|K4j_G)x^1(8C!KMi7QW>d${GP+Eg}j zyyx<9gM3HZrYUzj)_>F&ynnaKY4YjD;jY!=Ktjh d5l*yAg==Q5+oTg*Q}#I_`FaL;)Cfgc{{^bg+hqU% literal 0 HcmV?d00001 diff --git a/cmd/jws/main.go b/cmd/jws/main.go new file mode 100644 index 0000000..8588067 --- /dev/null +++ b/cmd/jws/main.go @@ -0,0 +1,113 @@ +// cmd/jws/main.go +package main + +import ( + "embed" + "encoding/json" + "fmt" + "jws/internal/dependency" + "jws/internal/gui" + "jws/internal/project" + "os" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" +) + +//go:embed all:projects +var projectsFS embed.FS + +var ( + dependencies []dependency.Dependency + projects []project.Project + mainWindow fyne.Window +) + +func main() { + a := app.New() + mainWindow = a.NewWindow("JakartaEE & Spring Boot Starter") + mainWindow.Resize(fyne.NewSize(600, 600)) + mainWindow.SetFixedSize(true) + + // Initialize dependencies + dependencies = []dependency.Dependency{ + {Name: "Visual Studio Code", Installed: false, Icon: theme.DocumentIcon()}, + {Name: "Docker", Installed: false, Icon: theme.MediaPlayIcon()}, + } + + // Standard projects + standartProjects := []project.Project{ + { + Name: "JakartaEE Todo-App mit Servlet", + Description: "Eine Todo-Anwendung mit Jakarta Servlet und\nPostgreSQL-Datenbank.", + FolderName: "jakarta-servlet-todo", + }, + { + Name: "JakartaEE Todo-App mit JSP", + Description: "Eine Todo-Anwendung mit Jakarta Server Pages und PostgreSQL-Datenbank.", + FolderName: "jakarta-jsp-todo", + }, + { + Name: "JakartaEE Todo-App als REST-API", + Description: "Eine Todo-Anwendung als REST-API mit JakartaEE und PostgreSQL-Datenbank.", + FolderName: "jakarta-rest-todo", + }, + { + Name: "Spring Boot Todo-App als Microservice", + Description: "Eine Todo-Anwendung als Microservice mit Spring Boot und PostgreSQL-Datenbank.", + FolderName: "spring-boot-todo", + }, + } + + // Load additional projects from projects.json + pluginProjects, err := loadProjects("projects.json") + if err != nil { + dialog.ShowError(fmt.Errorf("error loading projects: %v", err), mainWindow) + return + } + + projects = append(standartProjects, pluginProjects...) + + // Initialize GUI package + gui.Init(mainWindow, dependencies, projects, projectsFS) + + // Check dependencies + dependency.CheckDependencies(dependencies) + allInstalled := true + for _, dep := range dependencies { + if !dep.Installed { + allInstalled = false + break + } + } + + // Show appropriate screen + if allInstalled { + gui.ShowProjectScreen() + } else { + gui.ShowDependencyScreen() + } + + mainWindow.ShowAndRun() +} + +func loadProjects(filePath string) ([]project.Project, error) { + file, err := os.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return []project.Project{}, nil + } + return nil, fmt.Errorf("error opening JSON-file: %v", err) + } + defer file.Close() + + var projects []project.Project + decoder := json.NewDecoder(file) + if err := decoder.Decode(&projects); err != nil { + return nil, fmt.Errorf("error decoding JSON-file: %v", err) + } + + return projects, nil +} diff --git a/cmd/jws/projects/jakarta-jsp-todo/pom.xml b/cmd/jws/projects/jakarta-jsp-todo/pom.xml new file mode 100644 index 0000000..e69de29 diff --git a/cmd/jws/projects/jakarta-rest-todo/pom.xml b/cmd/jws/projects/jakarta-rest-todo/pom.xml new file mode 100644 index 0000000..e69de29 diff --git a/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/Dockerfile b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/Dockerfile new file mode 100644 index 0000000..88ce4c6 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/devcontainers/java:1-21-bullseye + +ARG INSTALL_MAVEN="false" +ARG MAVEN_VERSION="" + +ARG INSTALL_GRADLE="false" +ARG GRADLE_VERSION="" + +RUN apt-get update && apt-get upgrade -y && \ + apt-get install -y \ + git + +RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ + && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi + +RUN mkdir -p /workspaces/${localWorkspaceFolderBasename} && chown -R vscode:vscode /workspaces/${localWorkspaceFolderBasename} + +ENTRYPOINT ["/bin/bash"] diff --git a/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/devcontainer.json b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3fc9403 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "Jakarta EE Todo App & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces", + "extensions": ["vscjava.vscode-java-pack", "redhat.vscode-xml"], + "forwardPorts": [9080], + "remoteUser": "vscode" +} diff --git a/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/docker-compose.yml b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..96374b2 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/.devcontainer/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3.8" + +volumes: + postgres-data: + +services: + app: + container_name: javadev + build: + context: . + dockerfile: Dockerfile + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_HOSTNAME: postgresdb + volumes: + - ..:/workspace + command: sleep infinity + network_mode: service:db + stdin_open: true + + db: + container_name: postgresdb + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: postgres diff --git a/cmd/jws/projects/jakarta-servlet-todo/pom.xml b/cmd/jws/projects/jakarta-servlet-todo/pom.xml new file mode 100644 index 0000000..f4e6fc5 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + + com.example + jakarta-servlet-todo + 1.0-SNAPSHOT + war + + + 11 + 11 + 10.0.0 + 6.0.0.Final + 42.3.1 + + + + + jakarta.platform + jakarta.jakartaee-api + ${jakartaee.version} + provided + + + org.hibernate.orm + hibernate-core + ${hibernate.version} + + + org.postgresql + postgresql + ${postgresql.version} + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-war-plugin + 3.3.2 + + + + + diff --git a/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/dao/TodoDAO.java b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/dao/TodoDAO.java new file mode 100644 index 0000000..1006346 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/dao/TodoDAO.java @@ -0,0 +1,36 @@ +package com.example.dao; + +import com.example.model.Todo; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; + +import java.util.List; + +public class TodoDAO { + + @PersistenceContext + private EntityManager em; + + @Transactional + public void create(Todo todo) { + em.persist(todo); + } + + public List findAll() { + return em.createQuery("SELECT t FROM Todo t", Todo.class).getResultList(); + } + + @Transactional + public void update(Todo todo) { + em.merge(todo); + } + + @Transactional + public void delete(Long id) { + Todo todo = em.find(Todo.class, id); + if (todo != null) { + em.remove(todo); + } + } +} diff --git a/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/model/Todo.java b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/model/Todo.java new file mode 100644 index 0000000..49572c3 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/model/Todo.java @@ -0,0 +1,43 @@ +package com.example.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "todos") +public class Todo { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private boolean completed; + + // Getter und Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } +} diff --git a/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/servlet/TodoServlet.java b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/servlet/TodoServlet.java new file mode 100644 index 0000000..ae8b325 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/src/main/java/com/example/servlet/TodoServlet.java @@ -0,0 +1,40 @@ +package com.example.servlet; + +import com.example.dao.TodoDAO; +import com.example.model.Todo; +import jakarta.inject.Inject; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.List; + +@WebServlet("/todos") +public class TodoServlet extends HttpServlet { + + @Inject + private TodoDAO todoDAO; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + List todos = todoDAO.findAll(); + req.setAttribute("todos", todos); + req.getRequestDispatcher("/index.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String title = req.getParameter("title"); + boolean completed = "on".equals(req.getParameter("completed")); + + Todo todo = new Todo(); + todo.setTitle(title); + todo.setCompleted(completed); + + todoDAO.create(todo); + resp.sendRedirect(req.getContextPath() + "/todos"); + } +} diff --git a/cmd/jws/projects/jakarta-servlet-todo/src/main/resources/META-INF/persistence.xml b/cmd/jws/projects/jakarta-servlet-todo/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..3e553ee --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,19 @@ + + + + com.example.model.Todo + + + + + + + + + + + + diff --git a/cmd/jws/projects/jakarta-servlet-todo/src/main/webapp/WEB-INF/web.xml b/cmd/jws/projects/jakarta-servlet-todo/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..1a8fbb4 --- /dev/null +++ b/cmd/jws/projects/jakarta-servlet-todo/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,9 @@ + + + JakartaEE Todo App + + + diff --git a/cmd/jws/projects/spring-boot-todo/pom.xml b/cmd/jws/projects/spring-boot-todo/pom.xml new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2188dfe --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module jws + +go 1.23.5 + +require ( + fyne.io/fyne/v2 v2.6.0-alpha1 + fyne.io/tools v1.0.0-alpha1 +) + +require ( + fyne.io/systray v1.11.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fredbi/uri v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fyne-io/gl-js v0.1.0 // indirect + github.com/fyne-io/glfw-js v0.1.0 // indirect + github.com/fyne-io/image v0.1.0 // indirect + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect + github.com/go-text/render v0.2.0 // indirect + github.com/go-text/typesetting v0.2.1 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect + github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rymdport/portal v0.3.0 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect + github.com/stretchr/testify v1.10.0 // indirect + github.com/yuin/goldmark v1.7.8 // indirect + golang.org/x/image v0.24.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a92ba16 --- /dev/null +++ b/go.sum @@ -0,0 +1,76 @@ +fyne.io/fyne/v2 v2.6.0-alpha1 h1:ALx1JJDdCYQpm5RS7CIK9bam9H7hddSDuRy/fyj9pb8= +fyne.io/fyne/v2 v2.6.0-alpha1/go.mod h1:Bzv2yK+ncZ8LJbHKjyJJpEAFlbs6oulHgKm04ObOqA8= +fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= +fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +fyne.io/tools v1.0.0-alpha1 h1:350eF+LVPbIHNgYPCoP04yCsQSFQ3aupATx33ClZjZk= +fyne.io/tools v1.0.0-alpha1/go.mod h1:7gcHTl85tD/yLcGcU2bJGECAwSVAWrr99Ngmgrz/s54= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= +github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM= +github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= +github.com/fyne-io/glfw-js v0.1.0 h1:RGGMmVjcsG17Oifl3X2UJ5vH3PgS4B1UY3ASeN5BXbI= +github.com/fyne-io/glfw-js v0.1.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/image v0.1.0 h1:Vm2TQJ2PWGHCf3jYi1/XroaNNMu+GfI/O2QpSbZd4XQ= +github.com/fyne-io/image v0.1.0/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= +github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= +github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= +github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rymdport/portal v0.3.0 h1:QRHcwKwx3kY5JTQcsVhmhC3TGqGQb9LFghVNUy8AdB8= +github.com/rymdport/portal v0.3.0/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/dependency/dependency.go b/internal/dependency/dependency.go new file mode 100644 index 0000000..1a5ab38 --- /dev/null +++ b/internal/dependency/dependency.go @@ -0,0 +1,315 @@ +package dependency + +import ( + "fmt" + osinfo "jws/internal/os" + "jws/pkg/download" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" + "fyne.io/tools" +) + +type Dependency struct { + Name string + Installed bool + Icon fyne.Resource +} + +func CheckDependencies(dependencies []Dependency) { + // 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": + switch index { + case 0: // VSCode + cmd = tools.CommandInShell("winget", "install", "-e", "--id", + "Microsoft.VisualStudioCode") + case 1: // Docker Desktop + wslCheckCmd := tools.CommandInShell("wsl", "--status") + err := wslCheckCmd.Run() + if err != nil { + wslInstallCmd := tools.CommandInShell("wsl", "--install") + err = wslInstallCmd.Run() + if err != nil { + dialog.ShowError(fmt.Errorf("error: installing WSL: %v", err), mainWindow) + return + } + dialog.ShowInformation("WSL wird installiert", "WSL wird installiert. Bitte warten Sie, bis die Installation abgeschlossen ist und starten Sie die Anwendung neu.", mainWindow) + return + } + + cmd = tools.CommandInShell("winget", "install", "-e", "--id", + "Docker.DockerDesktop") + } + case "darwin": + 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 { + dialog.ShowError(fmt.Errorf("error: installing homebrew falied: %v", err), mainWindow) + } + } + switch index { + case 0: // VSCode + cmd = tools.CommandInShell("brew", "install", "--cask", "visual-studio-code") + case 1: // Docker + cmd = tools.CommandInShell("brew", "install", "docker") + } + case "linux": + osInfo, err := osinfo.GetLinuxDistribution() + if err != nil { + log.Println(err) + dialog.ShowError(fmt.Errorf("error getting OS info: %v", err), mainWindow) + return + } + + 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 + } + 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 { + 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 gestartet", + "Die Installation von VSCode wurde gestartet.", mainWindow) + + output, err := cmd.CombinedOutput() + if err != nil { + dialog.ShowError(fmt.Errorf("installation failed:\n%s", output), mainWindow) + return + } else { + dialog.ShowInformation("Erfolg", "VSCode erfolgreich installiert!", mainWindow) + CheckDependencies(dependencies) + } + }() + case 1: // Docker + go func() { + progressBar := widget.NewProgressBar() + progressDialog := dialog.NewCustomWithoutButtons("Docker Installation läuft...", progressBar, mainWindow) + progressDialog.Show() + + osInfo, err := osinfo.GetLinuxDistribution() + if err != nil { + progressDialog.Hide() + dialog.ShowError(fmt.Errorf("error getting os infos: %v", err), mainWindow) + 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": + // Fedora Commands + 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() + dialog.ShowError(fmt.Errorf("error at %s:\n%s", cmd, output), mainWindow) + 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() + dialog.ShowError(fmt.Errorf("cleanup error at %s:\n%s", cmd, output), mainWindow) + 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) + }() + } + } + + if cmd != nil { + err = cmd.Start() + if err != nil { + dialog.ShowError(fmt.Errorf("error starting installation process: %v", err), mainWindow) + } else { + dialog.ShowInformation("Installation started", + fmt.Sprintf("Installation of %s was started", depName), mainWindow) + } + } +} diff --git a/internal/gui/dependency_screen.go b/internal/gui/dependency_screen.go new file mode 100644 index 0000000..d6921ac --- /dev/null +++ b/internal/gui/dependency_screen.go @@ -0,0 +1,92 @@ +package gui + +import ( + "jws/internal/dependency" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// ShowDependencyScreen displays the dependency check screen +func ShowDependencyScreen() { + title := widget.NewLabel("check dependencies") + title.TextStyle = fyne.TextStyle{Bold: true} + title.Alignment = fyne.TextAlignCenter + + description := widget.NewLabel("dependencies needed for dev-environment:") + description.Wrapping = fyne.TextWrapWord + + dependencyContainer := container.NewVBox() + allInstalled := true + + for i, dep := range dependencies { + statusIcon := theme.CancelIcon() + statusText := "not installed" + + if dep.Installed { + statusIcon = theme.ConfirmIcon() + statusText = "installed" + } else { + allInstalled = false + } + + depBox := container.NewHBox( + widget.NewIcon(dep.Icon), + widget.NewLabel(dep.Name), + layout.NewSpacer(), + widget.NewIcon(statusIcon), + widget.NewLabel(statusText), + ) + + dependencyContainer.Add(depBox) + + if !dep.Installed { + installBtn := widget.NewButton("install", func(i int) func() { + return func() { + ShowPasswordDialog(i) + } + }(i)) + + depBox = container.NewStack( + layout.NewSpacer(), + installBtn, + ) + dependencyContainer.Add(depBox) + } + } + + nextBtn := widget.NewButton("go to projects", func() { + if allInstalled { + ShowProjectScreen() + } else { + dialog.ShowInformation("dependencies missing", + "Bitte installieren Sie alle erforderlichen Abhängigkeiten, bevor Sie fortfahren.", mainWindow) + } + }) + + nextBtn.Importance = widget.HighImportance + + recheckBtn := widget.NewButton("check again", func() { + dependency.CheckDependencies(dependencies) + ShowDependencyScreen() + }) + + buttonContainer := container.New(layout.NewGridLayout(2), recheckBtn, nextBtn) + + paddedButtonContainer := container.NewPadded(buttonContainer) + + description.Alignment = fyne.TextAlignCenter + header := container.NewVBox(title, description) + content := container.NewBorder(header, + paddedButtonContainer, + nil, + nil, + dependencyContainer, + ) + + mainWindow.SetContent(content) +} diff --git a/internal/gui/gui.go b/internal/gui/gui.go new file mode 100644 index 0000000..be2ff2a --- /dev/null +++ b/internal/gui/gui.go @@ -0,0 +1,47 @@ +package gui + +import ( + "embed" + "jws/internal/dependency" + "jws/internal/project" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" +) + +var ( + // Global variables available to the package + mainWindow fyne.Window + dependencies []dependency.Dependency + projects []project.Project + projectsFS embed.FS +) + +// Init initializes the GUI package with required dependencies +func Init(window fyne.Window, deps []dependency.Dependency, projs []project.Project, fs embed.FS) { + mainWindow = window + dependencies = deps + projects = projs + projectsFS = fs +} + +// ShowPasswordDialog shows a dialog to enter sudo password for installations +func ShowPasswordDialog(index int) { + password := binding.NewString() + entry := widget.NewEntryWithData(password) + entry.Password = true + + dialog.NewForm("Sudo Password", "ok", "cancel", + []*widget.FormItem{widget.NewFormItem("password", entry)}, + func(b bool) { + pass, err := password.Get() + if err == nil { + if b { + dependency.InstallDependency(index, pass, dependencies, mainWindow) + } + } + }, + mainWindow).Show() +} diff --git a/internal/gui/project_screen.go b/internal/gui/project_screen.go new file mode 100644 index 0000000..96bcc22 --- /dev/null +++ b/internal/gui/project_screen.go @@ -0,0 +1,91 @@ +package gui + +import ( + "jws/internal/project" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +// ShowProjectScreen displays the project selection screen +func ShowProjectScreen() { + title := widget.NewLabel("Project-Selection") + title.TextStyle = fyne.TextStyle{Bold: true} + title.Alignment = fyne.TextAlignCenter + + description := widget.NewLabel("Choose a Starter-Project to deploy:") + description.Wrapping = fyne.TextWrapWord + description.Alignment = fyne.TextAlignCenter + + projectsList := container.NewVBox() + + for i, proj := range projects { + projTitle := widget.NewLabel(proj.Name) + projTitle.TextStyle = fyne.TextStyle{Bold: true} + + projDesc := widget.NewLabel(proj.Description) + projDesc.Wrapping = fyne.TextWrapWord + + deployBtn := widget.NewButton("deploy", func(i int) func() { + return func() { + project.DeployProject(i, projects, projectsFS, mainWindow) + } + }(i)) + deployBtn.Importance = widget.HighImportance + + projectContent := container.NewBorder( + nil, + nil, + nil, + container.NewCenter(deployBtn), + container.NewVBox(projTitle, projDesc), + ) + + projectCard := container.NewBorder( + nil, + nil, + nil, + nil, + container.NewPadded(projectContent), + ) + + bg := canvas.NewRectangle(theme.Color(theme.ColorNameBackground)) + + borderedContainer := container.NewStack( + bg, + projectCard, + ) + + spacedContainer := container.NewVBox( + borderedContainer, + widget.NewSeparator(), + ) + + projectsList.Add(spacedContainer) + } + + backBtn := widget.NewButton("Check Dependecies", func() { + ShowDependencyScreen() + }) + + scrollContainer := container.NewVScroll(projectsList) + + header := container.NewVBox( + title, + description, + widget.NewSeparator(), + ) + + content := container.NewBorder( + header, + backBtn, // footer, + nil, + nil, + scrollContainer, + ) + + mainWindow.SetContent(content) +} diff --git a/internal/os/osinfo.go b/internal/os/osinfo.go new file mode 100644 index 0000000..e5f54da --- /dev/null +++ b/internal/os/osinfo.go @@ -0,0 +1,83 @@ +package os + +import ( + "fmt" + "log" + "os" + "os/exec" + "strings" +) + +type OS struct { + ID string + Name string + Version string + PackageManager string +} + +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 parseOsRelease(osRelease string) *OS { + var result OS + result.ID = "Unknown" + result.Name = "Unknown" + result.Version = "Unknown" + + lines := strings.Split(osRelease, "\n") + + for _, line := range lines { + splitLine := strings.SplitN(line, "=", 2) + if len(splitLine) != 2 { + continue + } + switch splitLine[0] { + case "ID": + result.ID = strings.ToLower(strings.Trim(splitLine[1], "\"")) + case "NAME": + result.Name = strings.Trim(splitLine[1], "\"") + case "VERSION_ID": + result.Version = strings.Trim(splitLine[1], "\"") + } + } + err := result.getPackageManager() + if err != nil { + log.Fatal(err) + } + return &result +} + +func (os *OS) getPackageManager() error { + switch os.ID { + case "debian", "ubuntu", "linuxmint": + 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.Name) + } +} diff --git a/internal/project/project.go b/internal/project/project.go new file mode 100644 index 0000000..6e7dd9b --- /dev/null +++ b/internal/project/project.go @@ -0,0 +1,127 @@ +package project + +import ( + "embed" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "runtime" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" +) + +type Project struct { + Name string `json:"name"` + Description string `json:"description"` + FolderName string `json:"folderName"` +} + +func DeployProject(index int, projects []Project, projectsFS embed.FS, mainWindow fyne.Window) { + project := projects[index] + + homeDir, err := os.UserHomeDir() + if err != nil { + dialog.ShowError(fmt.Errorf("error getting home directory: %v", err), mainWindow) + return + } + + projectPath := filepath.Join(homeDir, "Projects", project.FolderName) + + confirmDialog := dialog.NewConfirm( + "deploying project", + fmt.Sprintf("the project '%s' will be deployed to '%s' and opend in VS Code. Continue?", project.Name, projectPath), + func(ok bool) { + if ok { + progress := widget.NewProgressBar() + progressDiag := dialog.NewCustomWithoutButtons("project will be deployed", progress, mainWindow) + + progressDiag.Show() + + go func() { + defer progressDiag.Hide() + + progress.SetValue(0.1) + err := os.MkdirAll(projectPath, 0755) + if err != nil { + dialog.ShowError(fmt.Errorf("error creating directory: %v", err), mainWindow) + return + } + + progress.SetValue(0.3) + err = CopyEmbeddedProject(projectsFS, project.FolderName, projectPath) + if err != nil { + dialog.ShowError(fmt.Errorf("error copying project files: %v", err), mainWindow) + return + } + + progress.SetValue(0.7) + + var openCmd *exec.Cmd + switch runtime.GOOS { + case "windows": + openCmd = exec.Command("code", projectPath) + case "darwin": + openCmd = exec.Command("open", "-a", "Visual Studio Code", projectPath) + case "linux": + openCmd = exec.Command("code", projectPath) + } + + if openCmd != nil { + progress.SetValue(0.9) + err = openCmd.Run() + if err != nil { + dialog.ShowError(fmt.Errorf("error opening VS Code: %v", err), mainWindow) + return + } + } + + progress.SetValue(1.0) + + dialog.ShowInformation("project deployed", + fmt.Sprintf("the project '%s' was successfully deployed and opend in VS Code.\n\n"+ + "Path: %s\n\n"+ + "Application can be started with Docker Compose.", + project.Name, projectPath), mainWindow) + }() + } + }, + mainWindow, + ) + confirmDialog.Show() +} + +func CopyEmbeddedProject(projectsFS embed.FS, projectFolder string, targetPath string) error { + sourcePath := fmt.Sprintf("projects/%s", projectFolder) + + return fs.WalkDir(projectsFS, sourcePath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(sourcePath, path) + if err != nil { + return err + } + + destPath := filepath.Join(targetPath, relPath) + + if d.IsDir() { + return os.MkdirAll(destPath, 0755) + } + + data, err := projectsFS.ReadFile(path) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { + return err + } + + return os.WriteFile(destPath, data, 0644) + }) +} diff --git a/pkg/download/download.go b/pkg/download/download.go new file mode 100644 index 0000000..45a72dc --- /dev/null +++ b/pkg/download/download.go @@ -0,0 +1,60 @@ +package download + +import ( + "fmt" + "io" + "net/http" + "os" + "time" + + "fyne.io/fyne/v2/widget" +) + +func WithProgressBar(downloadURL, filePath string, progressBar *widget.ProgressBar) error { + client := &http.Client{ + Timeout: 15 * time.Minute, + } + + resp, err := client.Get(downloadURL) + if err != nil { + return fmt.Errorf("download failed: %v", err) + } + defer resp.Body.Close() + + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("unable to create file: %v", err) + } + defer file.Close() + + contentLength := resp.ContentLength + if contentLength <= 0 { + return fmt.Errorf("invalid content length") + } + + progressBar.Max = float64(contentLength) + + buffer := make([]byte, 4096) + var totalBytes int64 + + for { + n, readErr := resp.Body.Read(buffer) + if n > 0 { + totalBytes += int64(n) + progressBar.SetValue(float64(totalBytes)) + + if _, writeErr := file.Write(buffer[:n]); writeErr != nil { + return fmt.Errorf("error writing to file: %v", writeErr) + } + } + + if readErr == io.EOF { + break + } + if readErr != nil { + return fmt.Errorf("download aborted: %v", readErr) + } + } + + return nil +}