Skip to content

Commit 6b2c4c9

Browse files
committed
Merge branch 'development'
2 parents 069690b + 6bf1f5b commit 6b2c4c9

3 files changed

Lines changed: 377 additions & 62 deletions

File tree

src/main/java/io/github/intisy/docker/MacDockerProvider.java

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class MacDockerProvider extends DockerProvider {
4646

4747
private final String instanceId;
4848
private final String vmName;
49-
49+
5050
private final Path instanceDir;
5151

5252
private DockerClient dockerClient;
@@ -72,11 +72,11 @@ public void ensureInstalled() throws IOException {
7272
log.info("Lima not found. Downloading Lima {}...", LIMA_VERSION);
7373
downloadAndInstallLima();
7474
}
75-
75+
7676
if (!isLimaInstalled()) {
7777
throw new IOException("Failed to install Lima. Please install manually: brew install lima");
7878
}
79-
79+
8080
log.info("Lima is ready");
8181
}
8282

@@ -101,7 +101,7 @@ private boolean isLimaInstalled() {
101101
Process process = pb.start();
102102
byte[] output = readAllBytes(process.getInputStream());
103103
int exitCode = process.waitFor();
104-
104+
105105
if (exitCode == 0) {
106106
log.debug("Local Lima version: {}", new String(output).trim());
107107
return true;
@@ -110,14 +110,14 @@ private boolean isLimaInstalled() {
110110
log.debug("Local Lima check failed: {}", e.getMessage());
111111
}
112112
}
113-
113+
114114
try {
115115
ProcessBuilder pb = new ProcessBuilder("limactl", "--version");
116116
pb.redirectErrorStream(true);
117117
Process process = pb.start();
118118
byte[] output = readAllBytes(process.getInputStream());
119119
int exitCode = process.waitFor();
120-
120+
121121
if (exitCode == 0) {
122122
log.debug("System Lima version: {}", new String(output).trim());
123123
return true;
@@ -136,9 +136,9 @@ private boolean isLimaInstalled() {
136136
private void downloadAndInstallLima() throws IOException {
137137
String arch = getArch();
138138
String downloadUrl = String.format(LIMA_DOWNLOAD_URL, LIMA_VERSION, LIMA_VERSION, arch);
139-
139+
140140
log.info("Downloading Lima from {}...", downloadUrl);
141-
141+
142142
if (Files.exists(LIMA_DIR)) {
143143
try (java.util.stream.Stream<Path> walk = Files.walk(LIMA_DIR)) {
144144
walk.sorted(java.util.Comparator.reverseOrder())
@@ -147,17 +147,17 @@ private void downloadAndInstallLima() throws IOException {
147147
}
148148
}
149149
Files.createDirectories(LIMA_DIR);
150-
150+
151151
URL url = new URL(downloadUrl);
152152
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
153153
connection.setInstanceFollowRedirects(true);
154154
connection.setRequestMethod("GET");
155-
155+
156156
int responseCode = connection.getResponseCode();
157157
if (responseCode != 200) {
158158
throw new IOException("Failed to download Lima from " + downloadUrl + ". Status code: " + responseCode);
159159
}
160-
160+
161161
try (InputStream is = connection.getInputStream();
162162
GzipCompressorInputStream gzis = new GzipCompressorInputStream(is);
163163
TarArchiveInputStream tis = new TarArchiveInputStream(gzis)) {
@@ -176,9 +176,9 @@ private void downloadAndInstallLima() throws IOException {
176176
}
177177
}
178178
}
179-
179+
180180
Files.write(LIMA_VERSION_FILE, LIMA_VERSION.getBytes());
181-
181+
182182
log.info("Lima {} installed successfully", LIMA_VERSION);
183183
}
184184

@@ -202,20 +202,20 @@ private String getArch() {
202202
@Override
203203
public void start() throws IOException, InterruptedException {
204204
log.info("Starting Docker via Lima VM (instance: {})...", instanceId);
205-
205+
206206
ensureInstalled();
207207
Files.createDirectories(instanceDir);
208-
208+
209209
dockerPort = 2375 + Math.abs(instanceId.hashCode() % 1000);
210-
210+
211211
createAndStartLimaVm();
212-
212+
213213
if (!waitForDocker()) {
214214
String logs = getLimaLogs();
215215
log.error("Lima VM logs:\n{}", logs);
216216
throw new RuntimeException("Docker daemon in Lima VM failed to start. See logs above.");
217217
}
218-
218+
219219
log.info("Docker daemon started in Lima VM (instance: {}, port: {})", instanceId, dockerPort);
220220
}
221221

@@ -224,10 +224,10 @@ public void start() throws IOException, InterruptedException {
224224
*/
225225
private void createAndStartLimaVm() throws IOException, InterruptedException {
226226
String limactl = getLimactlPath();
227-
227+
228228
String vmStatus = runLimaCommand(limactl, "list", "--format", "{{.Name}}:{{.Status}}");
229229
boolean vmExists = vmStatus.contains(vmName + ":");
230-
230+
231231
if (vmExists) {
232232
log.info("Lima VM {} already exists, checking status...", vmName);
233233
if (vmStatus.contains(vmName + ":Running")) {
@@ -243,30 +243,30 @@ private void createAndStartLimaVm() throws IOException, InterruptedException {
243243
return;
244244
}
245245
}
246-
246+
247247
Path configPath = instanceDir.resolve("lima.yaml");
248248
String limaConfig = createLimaConfig();
249249
Files.write(configPath, limaConfig.getBytes());
250-
250+
251251
log.info("Creating Lima VM {} with Docker...", vmName);
252-
252+
253253
ProcessBuilder pb = new ProcessBuilder(limactl, "start", "--name=" + vmName, configPath.toString());
254254
pb.redirectErrorStream(true);
255255
pb.inheritIO();
256256
Process process = pb.start();
257-
257+
258258
boolean completed = process.waitFor(10, TimeUnit.MINUTES);
259259
if (!completed) {
260260
process.destroyForcibly();
261261
throw new RuntimeException("Lima VM creation timed out after 10 minutes");
262262
}
263-
263+
264264
if (process.exitValue() != 0) {
265265
throw new RuntimeException("Failed to create Lima VM. Exit code: " + process.exitValue());
266266
}
267-
267+
268268
vmStartedByUs = true;
269-
269+
270270
ensureDockerRunning();
271271
}
272272

@@ -336,13 +336,13 @@ private String createLimaConfig() {
336336
*/
337337
private void ensureDockerRunning() throws IOException, InterruptedException {
338338
log.debug("Ensuring Docker is running in Lima VM {}...", vmName);
339-
339+
340340
String result = runLimaShellCommand("systemctl is-active docker || true");
341341
if (!"active".equals(result.trim())) {
342342
log.info("Starting Docker in Lima VM...");
343343
runLimaShellCommand("sudo systemctl start docker");
344344
}
345-
345+
346346
String tcpCheck = runLimaShellCommand("ss -tlnp | grep 2375 || true");
347347
if (tcpCheck.trim().isEmpty()) {
348348
log.info("Configuring Docker to listen on TCP...");
@@ -401,16 +401,16 @@ private boolean waitForDocker() throws InterruptedException {
401401
long timeoutMillis = TimeUnit.SECONDS.toMillis(120);
402402
long startTime = System.currentTimeMillis();
403403
int attempts = 0;
404-
404+
405405
while (System.currentTimeMillis() - startTime < timeoutMillis) {
406406
attempts++;
407-
407+
408408
try {
409409
Socket socket = new Socket();
410410
socket.connect(new InetSocketAddress("localhost", dockerPort), 1000);
411411
socket.close();
412412
log.debug("Docker daemon is listening on localhost:{} after {} attempts", dockerPort, attempts);
413-
413+
414414
try {
415415
DockerClient testClient = DockerClient.builder()
416416
.withHost("tcp://localhost:" + dockerPort)
@@ -423,12 +423,12 @@ private boolean waitForDocker() throws InterruptedException {
423423
}
424424
} catch (IOException e) {
425425
}
426-
426+
427427
if (attempts % 20 == 0) {
428-
log.debug("Still waiting for Docker... ({} seconds elapsed)",
428+
log.debug("Still waiting for Docker... ({} seconds elapsed)",
429429
(System.currentTimeMillis() - startTime) / 1000);
430430
}
431-
431+
432432
Thread.sleep(500);
433433
}
434434

@@ -458,17 +458,17 @@ public void stop() {
458458
}
459459
dockerClient = null;
460460
}
461-
461+
462462
if (vmStartedByUs) {
463463
try {
464464
String limactl = getLimactlPath();
465-
465+
466466
log.info("Stopping Lima VM {}...", vmName);
467467
ProcessBuilder pb = new ProcessBuilder(limactl, "stop", vmName);
468468
pb.redirectErrorStream(true);
469469
Process process = pb.start();
470470
process.waitFor(30, TimeUnit.SECONDS);
471-
471+
472472
log.info("Deleting Lima VM {}...", vmName);
473473
pb = new ProcessBuilder(limactl, "delete", vmName, "--force");
474474
pb.redirectErrorStream(true);

src/main/java/io/github/intisy/docker/WindowsDockerProvider.java

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -169,63 +169,99 @@ public void installNvidiaContainerToolkit() throws IOException {
169169
log.info("Installing NVIDIA Container Toolkit in WSL2 (distro: {})...", wslDistro);
170170
log.info("This may take a few minutes...");
171171

172-
String distroId = runWslCommand("cat /etc/os-release | grep '^ID=' | cut -d= -f2 | tr -d '\"'", false, 5).trim();
173-
String distroVersion = runWslCommand("cat /etc/os-release | grep '^VERSION_ID=' | cut -d= -f2 | tr -d '\"'", false, 5).trim();
174-
log.debug("Detected distro: {} {}", distroId, distroVersion);
175-
176-
String distribution = distroId + distroVersion;
172+
if (!checkPasswordlessSudoForApt()) {
173+
log.error("NVIDIA Container Toolkit installation requires passwordless sudo.");
174+
log.error("Please run this ONE-TIME setup in WSL ({}):", wslDistro);
175+
log.error("");
176+
log.error(" wsl -d {}", wslDistro);
177+
log.error(" sudo bash -c 'echo \"$USER ALL=(ALL) NOPASSWD: ALL\" > /etc/sudoers.d/nopasswd-$USER'");
178+
log.error(" sudo chmod 440 /etc/sudoers.d/nopasswd-$USER");
179+
log.error(" exit");
180+
log.error("");
181+
log.error("Or install manually:");
182+
printManualInstallInstructions();
183+
throw new IOException("Passwordless sudo is required. Run the setup commands above in WSL.");
184+
}
177185

178186
try {
179187
log.info("Adding NVIDIA GPG key...");
180188
String gpgKeyCmd = "curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | " +
181-
"sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg --yes";
182-
String gpgResult = runWslCommandWithTimeout(gpgKeyCmd, true, 60);
189+
"sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg --yes 2>&1";
190+
String gpgResult = runWslCommandWithTimeout(gpgKeyCmd, false, 60);
183191
log.debug("GPG key result: {}", gpgResult);
184192

185193
log.info("Adding NVIDIA repository...");
186-
String repoCmd = String.format(
187-
"curl -s -L https://nvidia.github.io/libnvidia-container/%s/libnvidia-container.list | " +
194+
String repoCmd = "curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | " +
188195
"sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | " +
189-
"sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null",
190-
distribution);
196+
"sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null 2>&1 && echo ok";
191197
String repoResult = runWslCommandWithTimeout(repoCmd, false, 60);
192198
log.debug("Repo add result: {}", repoResult);
193199

194200
log.info("Updating package lists...");
195-
String updateResult = runWslCommandWithTimeout("sudo apt-get update", false, 120);
201+
String updateResult = runWslCommandWithTimeout("sudo apt-get update 2>&1", false, 180);
196202
log.debug("apt update result: {}", updateResult);
197203

198204
log.info("Installing nvidia-container-toolkit package...");
199205
String installResult = runWslCommandWithTimeout(
200-
"sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nvidia-container-toolkit", false, 300);
206+
"sudo apt-get install -y nvidia-container-toolkit 2>&1", false, 600);
201207
log.debug("Install result: {}", installResult);
202208

203209
log.info("Configuring Docker to use NVIDIA runtime...");
204-
String configResult = runWslCommandWithTimeout("sudo nvidia-ctk runtime configure --runtime=docker", false, 30);
210+
String configResult = runWslCommandWithTimeout("sudo nvidia-ctk runtime configure --runtime=docker 2>&1", false, 60);
205211
log.debug("Config result: {}", configResult);
206212

207213
if (isNvidiaContainerToolkitInstalled()) {
208214
log.info("NVIDIA Container Toolkit installed successfully!");
209215
} else {
210-
throw new IOException("Installation completed but toolkit not found. Please check manually.");
216+
throw new IOException("Installation completed but toolkit not found. Check output above for errors.");
211217
}
212218

213219
} catch (Exception e) {
214220
log.error("Failed to install NVIDIA Container Toolkit: {}", e.getMessage());
215221
log.error("");
216-
log.error("You can install it manually by running these commands in WSL ({}):", wslDistro);
217-
log.error(" distribution=$(. /etc/os-release;echo $ID$VERSION_ID)");
218-
log.error(" curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg");
219-
log.error(" curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \\");
220-
log.error(" sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \\");
221-
log.error(" sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list");
222-
log.error(" sudo apt-get update");
223-
log.error(" sudo apt-get install -y nvidia-container-toolkit");
224-
log.error(" sudo nvidia-ctk runtime configure --runtime=docker");
222+
printManualInstallInstructions();
225223
throw new IOException("Failed to install NVIDIA Container Toolkit: " + e.getMessage(), e);
226224
}
227225
}
228226

227+
/**
228+
* Check if passwordless sudo is available (tests with 'sudo -n true').
229+
*/
230+
private boolean checkPasswordlessSudoForApt() {
231+
try {
232+
ProcessBuilder pb = new ProcessBuilder("wsl", "-d", wslDistro, "-e", "bash", "-c",
233+
"sudo -n true 2>/dev/null && echo yes || echo no");
234+
pb.redirectErrorStream(true);
235+
Process process = pb.start();
236+
byte[] output = readAllBytes(process.getInputStream());
237+
boolean completed = process.waitFor(5, TimeUnit.SECONDS);
238+
239+
if (!completed) {
240+
log.debug("sudo check timed out");
241+
return false;
242+
}
243+
244+
String result = new String(output).trim();
245+
boolean hasPasswordlessSudo = "yes".equals(result);
246+
log.debug("Passwordless sudo available: {}", hasPasswordlessSudo);
247+
return hasPasswordlessSudo;
248+
} catch (IOException | InterruptedException e) {
249+
log.debug("Failed to check passwordless sudo: {}", e.getMessage());
250+
return false;
251+
}
252+
}
253+
254+
private void printManualInstallInstructions() {
255+
log.error("You can install it manually by running these commands in WSL ({}):", wslDistro);
256+
log.error(" curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg --yes");
257+
log.error(" curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \\");
258+
log.error(" sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \\");
259+
log.error(" sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list");
260+
log.error(" sudo apt-get update");
261+
log.error(" sudo apt-get install -y nvidia-container-toolkit");
262+
log.error(" sudo nvidia-ctk runtime configure --runtime=docker");
263+
}
264+
229265
/**
230266
* Ensure NVIDIA Container Toolkit is set up for GPU support.
231267
* Automatically installs if NVIDIA GPU is detected but toolkit is not installed.

0 commit comments

Comments
 (0)