diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index 79cdff16b2..847152b258 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -131,6 +131,7 @@ public CommandletManagerImpl(IdeContext context) { add(new UninstallPluginCommandlet(context)); add(new UpgradeCommandlet(context)); add(new TruststoreCommandlet(context)); + add(new ContainerCommandlet(context)); add(new Gh(context)); add(new Helm(context)); add(new Java(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/ContainerCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ContainerCommandlet.java new file mode 100644 index 0000000000..06795c1c40 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/ContainerCommandlet.java @@ -0,0 +1,198 @@ +package com.devonfw.tools.ide.commandlet; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.BooleanProperty; + +/** + * {@link Commandlet} to print the environment variables. + */ +public class ContainerCommandlet extends Commandlet { + + private static final String IMAGE_NAME = "ideasy-linux"; + + private final BooleanProperty rebuild; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public ContainerCommandlet(IdeContext context) { + + super(context); + + addKeyword("container"); + + this.rebuild = add(new BooleanProperty("--rebuild", false, "-r")); + } + + @Override + public String getName() { + + return "container"; + } + + @Override + protected void doRun() { + + try { + + verifyDocker(); + verifySshAgent(); + + Path dockerfile = Paths.get("IDEasy", "docker", "Dockerfile").toAbsolutePath(); + Path buildContext = Paths.get("IDEasy").toAbsolutePath(); + + if (Boolean.TRUE.equals(this.rebuild.getValue())) { + removeImage(); + } + + if (!imageExists()) { + buildImage( + toWslPath(dockerfile), + toWslPath(buildContext)); + } + startContainer(); + + } catch (Exception e) { + + throw new RuntimeException(e); + } + } + + private void verifyDocker() throws Exception { + + int exitCode = runAndWaitSilent( + "wsl", + "docker", + "--version"); + + if (exitCode != 0) { + throw new IllegalStateException("Docker is not available."); + } + } + + private void verifySshAgent() throws Exception { + + int exitCode = runAndWaitSilent( + "C:\\Windows\\System32\\OpenSSH\\ssh-add.exe", + "-l"); + + if (exitCode != 0) { + + throw new IllegalStateException( + "No SSH identity loaded in Windows OpenSSH agent."); + } + } + + private boolean imageExists() throws Exception { + + return runAndWaitSilent( + "wsl", + "docker", + "image", + "inspect", + IMAGE_NAME) == 0; + } + + private void removeImage() throws Exception { + + runAndWaitSilent( + "wsl", + "docker", + "rmi", + "-f", + IMAGE_NAME); + } + + private void buildImage(String dockerfile, String context) throws Exception { + + int exitCode = runAndWait( + "wsl", + "docker", + "build", + "-t", + IMAGE_NAME, + "-f", + dockerfile, + context); + + if (exitCode != 0) { + + throw new RuntimeException( + "Docker build failed with exit code " + exitCode); + } + } + + private String getWslHome() throws Exception { + + Process process = new ProcessBuilder( + "wsl", + "sh", + "-c", + "printf $HOME") + .start(); + + return new String(process.getInputStream().readAllBytes()).trim(); + } + + private void startContainer() throws Exception { + + String wslHome = getWslHome(); + + new ProcessBuilder( + "cmd", + "/c", + "start", + "wsl", + "docker", + "run", + "--rm", + "-it", + + "-e", "DISPLAY", + "-e", "WAYLAND_DISPLAY", + "-e", "XDG_RUNTIME_DIR", + "-e", "PULSE_SERVER", + + "-v", "/tmp/.X11-unix:/tmp/.X11-unix", + "-v", "/mnt/wslg:/mnt/wslg", + + "-v", wslHome + "/.ssh:/root/.ssh", + + "-v", "ideasy-data:/projects", + "-v", "ideasy-home:/root", + + IMAGE_NAME) + .start(); + } + + private int runAndWait(String... command) throws Exception { + + ProcessBuilder pb = new ProcessBuilder(command); + + pb.inheritIO(); + + return pb.start().waitFor(); + } + + private int runAndWaitSilent(String... command) throws Exception { + + ProcessBuilder pb = new ProcessBuilder(command); + + pb.redirectOutput(ProcessBuilder.Redirect.DISCARD); + pb.redirectError(ProcessBuilder.Redirect.DISCARD); + + return pb.start().waitFor(); + } + + private String toWslPath(Path path) { + + return path.toString() + .replace("\\", "/") + .replace("C:", "/mnt/c"); + } +} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..8629089674 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:24.04 +ARG IDEASY_VERSION=2026.08.001 +ENV IDE_ROOT=/projects +RUN apt-get update \ +&& apt-get install -y --no-install-recommends \ +ca-certificates \ +curl \ +git \ +gzip \ +tar \ +bash \ +libfreetype6 \ +fontconfig \ +libx11-6 \ +libxext6 \ +libxrender1 \ +libxtst6 \ +libxi6 \ +libxrandr2 \ +libxinerama1 \ +libxcursor1 \ +libxcomposite1 \ +libxdamage1 \ +openssh-client \ +libgtk-3-0 \ +libglib2.0-0 \ +libatk1.0-0 \ +libatk-bridge2.0-0 \ +libcairo2 \ +libpango-1.0-0 \ +libgdk-pixbuf-2.0-0 \ +libnss3 \ +libgbm1 \ +libasound2t64 \ +&& rm -rf /var/lib/apt/lists/* + +# GitHub Copilot CLI + +RUN apt-get update \ +&& apt-get install -y nodejs npm \ +&& npm install -g @github/copilot \ +&& rm -rf /var/lib/apt/lists/* + +# IDEasy + +RUN mkdir -p /opt/ideasy "${IDE_ROOT}" \ +&& curl -fsSL \ +"https://repo1.maven.org/maven2/com/devonfw/tools/IDEasy/ide-cli/${IDEASY_VERSION}/ide-cli-${IDEASY_VERSION}-linux-x64.tar.gz" \ +-o /tmp/ideasy.tar.gz \ +&& tar --extract --gzip --no-same-owner \ +--file /tmp/ideasy.tar.gz \ +--directory /opt/ideasy \ +&& cd /opt/ideasy \ +&& ./bin/ideasy --batch --force install \ +&& rm /tmp/ideasy.tar.gz + +ENV PATH="${IDE_ROOT}/_ide/installation/bin:${PATH}" + +RUN ideasy --batch create basic-project - + +WORKDIR /projects/basic-project + +CMD ["bash"]