dank-ai 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/docker/manager.js +1014 -420
- package/package.json +1 -1
package/lib/docker/manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Docker Container Manager
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Manages Docker containers for Dank agents including:
|
|
5
5
|
* - Building agent images
|
|
6
6
|
* - Starting/stopping containers
|
|
@@ -8,32 +8,68 @@
|
|
|
8
8
|
* - Managing Docker resources
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const Docker = require(
|
|
12
|
-
const fs = require(
|
|
13
|
-
const path = require(
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
18
|
-
const {
|
|
19
|
-
const {
|
|
11
|
+
const Docker = require("dockerode");
|
|
12
|
+
const fs = require("fs-extra");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const tar = require("tar");
|
|
16
|
+
const winston = require("winston");
|
|
17
|
+
const { spawn, exec } = require("child_process");
|
|
18
|
+
const { promisify } = require("util");
|
|
19
|
+
const { DOCKER_CONFIG } = require("../constants");
|
|
20
|
+
const { AgentConfig } = require("../config");
|
|
20
21
|
|
|
21
22
|
const execAsync = promisify(exec);
|
|
22
23
|
|
|
23
24
|
class DockerManager {
|
|
24
25
|
constructor(options = {}) {
|
|
25
26
|
this.docker = new Docker(options.dockerOptions || {});
|
|
26
|
-
this.logger =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
this.logger =
|
|
28
|
+
options.logger ||
|
|
29
|
+
winston.createLogger({
|
|
30
|
+
level: "info",
|
|
31
|
+
format: winston.format.simple(),
|
|
32
|
+
transports: [new winston.transports.Console()],
|
|
33
|
+
});
|
|
34
|
+
|
|
32
35
|
this.defaultBaseImageName = `${DOCKER_CONFIG.baseImagePrefix}:${DOCKER_CONFIG.defaultTag}`;
|
|
33
36
|
this.networkName = DOCKER_CONFIG.networkName;
|
|
34
37
|
this.containers = new Map();
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Create Docker client with proper socket detection
|
|
42
|
+
*/
|
|
43
|
+
createDockerClient() {
|
|
44
|
+
const fs = require("fs-extra");
|
|
45
|
+
const path = require("path");
|
|
46
|
+
|
|
47
|
+
// Common Docker socket locations
|
|
48
|
+
const socketPaths = [
|
|
49
|
+
"/var/run/docker.sock", // Standard Linux
|
|
50
|
+
"//var/run/docker.sock", // Windows WSL2
|
|
51
|
+
"\\\\.\\pipe\\docker_engine", // Windows named pipe
|
|
52
|
+
path.join(os.homedir(), ".docker", "run", "docker.sock"), // macOS Docker Desktop
|
|
53
|
+
"/Users/Shared/docker.sock", // Alternative macOS location
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Find the first available socket
|
|
57
|
+
for (const socketPath of socketPaths) {
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(socketPath)) {
|
|
60
|
+
this.logger.info(`Found Docker socket at: ${socketPath}`);
|
|
61
|
+
return new Docker({ socketPath });
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
// Continue to next socket path
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// If no socket found, try default connection (will use environment variables)
|
|
69
|
+
this.logger.info("No Docker socket found, using default connection");
|
|
70
|
+
return new Docker();
|
|
71
|
+
}
|
|
72
|
+
|
|
37
73
|
/**
|
|
38
74
|
* Initialize Docker environment
|
|
39
75
|
*/
|
|
@@ -41,21 +77,25 @@ class DockerManager {
|
|
|
41
77
|
try {
|
|
42
78
|
// Ensure Docker is available and running
|
|
43
79
|
await this.ensureDockerAvailable();
|
|
44
|
-
|
|
80
|
+
|
|
81
|
+
// Initialize Docker client with proper socket detection
|
|
82
|
+
this.docker = this.createDockerClient();
|
|
83
|
+
|
|
45
84
|
// Check Docker connection
|
|
46
85
|
await this.docker.ping();
|
|
47
|
-
this.logger.info(
|
|
86
|
+
this.logger.info("Docker connection established");
|
|
48
87
|
|
|
49
88
|
// Create network if it doesn't exist
|
|
50
89
|
await this.ensureNetwork();
|
|
51
|
-
|
|
90
|
+
|
|
52
91
|
// Check if default base image exists, pull if not found
|
|
53
92
|
const hasBaseImage = await this.hasImage(this.defaultBaseImageName);
|
|
54
93
|
if (!hasBaseImage) {
|
|
55
|
-
this.logger.info(
|
|
94
|
+
this.logger.info(
|
|
95
|
+
`Default base image '${this.defaultBaseImageName}' not found. Pulling from registry...`
|
|
96
|
+
);
|
|
56
97
|
await this.pullBaseImage();
|
|
57
98
|
}
|
|
58
|
-
|
|
59
99
|
} catch (error) {
|
|
60
100
|
throw new Error(`Failed to initialize Docker: ${error.message}`);
|
|
61
101
|
}
|
|
@@ -66,22 +106,27 @@ class DockerManager {
|
|
|
66
106
|
*/
|
|
67
107
|
async ensureDockerAvailable() {
|
|
68
108
|
try {
|
|
109
|
+
// Initialize Docker client with proper socket detection
|
|
110
|
+
this.docker = this.createDockerClient();
|
|
111
|
+
|
|
69
112
|
// First, try to ping Docker to see if it's running
|
|
70
113
|
await this.docker.ping();
|
|
71
|
-
this.logger.info(
|
|
114
|
+
this.logger.info("Docker is running and accessible");
|
|
72
115
|
return;
|
|
73
116
|
} catch (error) {
|
|
74
|
-
this.logger.info(
|
|
117
|
+
this.logger.info("Docker is not accessible, checking installation...");
|
|
75
118
|
}
|
|
76
119
|
|
|
77
120
|
// Check if Docker is installed
|
|
78
121
|
const isInstalled = await this.isDockerInstalled();
|
|
79
|
-
|
|
122
|
+
|
|
80
123
|
if (!isInstalled) {
|
|
81
|
-
this.logger.info(
|
|
124
|
+
this.logger.info("Docker is not installed. Installing Docker...");
|
|
82
125
|
await this.installDocker();
|
|
83
126
|
} else {
|
|
84
|
-
this.logger.info(
|
|
127
|
+
this.logger.info(
|
|
128
|
+
"Docker is installed but not running. Starting Docker..."
|
|
129
|
+
);
|
|
85
130
|
await this.startDocker();
|
|
86
131
|
}
|
|
87
132
|
|
|
@@ -94,33 +139,140 @@ class DockerManager {
|
|
|
94
139
|
*/
|
|
95
140
|
async isDockerInstalled() {
|
|
96
141
|
try {
|
|
97
|
-
|
|
142
|
+
// Check if docker command is available using multiple paths
|
|
143
|
+
const dockerPaths = [
|
|
144
|
+
"/usr/local/bin/docker",
|
|
145
|
+
"/opt/homebrew/bin/docker",
|
|
146
|
+
"/usr/bin/docker",
|
|
147
|
+
"docker", // fallback to PATH
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
let dockerFound = false;
|
|
151
|
+
for (const path of dockerPaths) {
|
|
152
|
+
try {
|
|
153
|
+
await execAsync(`${path} --version`);
|
|
154
|
+
dockerFound = true;
|
|
155
|
+
break;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Try next path
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!dockerFound) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// On macOS, also check if Docker Desktop is installed
|
|
166
|
+
if (process.platform === "darwin") {
|
|
167
|
+
const dockerAppPath = "/Applications/Docker.app";
|
|
168
|
+
const fs = require("fs-extra");
|
|
169
|
+
if (!(await fs.pathExists(dockerAppPath))) {
|
|
170
|
+
this.logger.info("Docker CLI found but Docker Desktop not installed");
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
98
175
|
return true;
|
|
99
176
|
} catch (error) {
|
|
100
177
|
return false;
|
|
101
178
|
}
|
|
102
179
|
}
|
|
103
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Check if Docker is accessible and running
|
|
183
|
+
*/
|
|
184
|
+
async checkDockerAccess() {
|
|
185
|
+
try {
|
|
186
|
+
// Try to run a simple docker command to check if Docker is accessible
|
|
187
|
+
// Use full path to docker command to avoid PATH issues after installation
|
|
188
|
+
const dockerPaths = [
|
|
189
|
+
"/usr/local/bin/docker",
|
|
190
|
+
"/opt/homebrew/bin/docker",
|
|
191
|
+
"/usr/bin/docker",
|
|
192
|
+
"docker", // fallback to PATH
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
let dockerCommand = "docker";
|
|
196
|
+
let dockerFound = false;
|
|
197
|
+
|
|
198
|
+
for (const path of dockerPaths) {
|
|
199
|
+
try {
|
|
200
|
+
this.logger.info(`Trying Docker path: ${path}`);
|
|
201
|
+
const result = await execAsync(`${path} --version`);
|
|
202
|
+
this.logger.info(`✅ Docker found at: ${path}`);
|
|
203
|
+
this.logger.info(`Docker version: ${result.stdout.trim()}`);
|
|
204
|
+
dockerCommand = path;
|
|
205
|
+
dockerFound = true;
|
|
206
|
+
break;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.logger.debug(
|
|
209
|
+
`❌ Docker not found at: ${path} - ${error.message}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!dockerFound) {
|
|
215
|
+
throw new Error("Docker executable not found in any expected location");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Test if Docker daemon service is accessible and running
|
|
219
|
+
this.logger.info("🔍 Testing Docker daemon service...");
|
|
220
|
+
try {
|
|
221
|
+
const result = await execAsync(`${dockerCommand} ps`);
|
|
222
|
+
this.logger.info("✅ Docker daemon service is running and accessible");
|
|
223
|
+
this.logger.debug(`Docker ps output: ${result.stdout.trim()}`);
|
|
224
|
+
return true;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
this.logger.error(
|
|
227
|
+
`❌ Docker daemon service not accessible: ${error.message}`
|
|
228
|
+
);
|
|
229
|
+
this.logger.error(`STDERR: ${error.stderr || "No stderr"}`);
|
|
230
|
+
|
|
231
|
+
// Provide helpful error information
|
|
232
|
+
if (error.message.includes("Cannot connect to the Docker daemon")) {
|
|
233
|
+
this.logger.info(
|
|
234
|
+
"💡 Docker daemon is not running. Please start Docker Desktop or Docker service."
|
|
235
|
+
);
|
|
236
|
+
} else if (error.message.includes("permission denied")) {
|
|
237
|
+
this.logger.info(
|
|
238
|
+
"💡 Permission denied. You may need to add your user to the docker group or restart your terminal."
|
|
239
|
+
);
|
|
240
|
+
} else if (error.message.includes("connection refused")) {
|
|
241
|
+
this.logger.info(
|
|
242
|
+
"💡 Connection refused. Docker daemon may not be started yet."
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Docker daemon service is not accessible: ${error.message}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
this.logger.error(`Docker access check failed: ${error.message}`);
|
|
252
|
+
throw new Error(`Docker is not accessible: ${error.message}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
104
256
|
/**
|
|
105
257
|
* Install Docker on the system
|
|
106
258
|
*/
|
|
107
259
|
async installDocker() {
|
|
108
260
|
const platform = process.platform;
|
|
109
|
-
|
|
261
|
+
|
|
110
262
|
this.logger.info(`Installing Docker for ${platform}...`);
|
|
111
|
-
|
|
263
|
+
|
|
112
264
|
try {
|
|
113
|
-
if (platform ===
|
|
265
|
+
if (platform === "darwin") {
|
|
114
266
|
await this.installDockerMacOS();
|
|
115
|
-
} else if (platform ===
|
|
267
|
+
} else if (platform === "linux") {
|
|
116
268
|
await this.installDockerLinux();
|
|
117
|
-
} else if (platform ===
|
|
269
|
+
} else if (platform === "win32") {
|
|
118
270
|
await this.installDockerWindows();
|
|
119
271
|
} else {
|
|
120
272
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
121
273
|
}
|
|
122
|
-
|
|
123
|
-
this.logger.info(
|
|
274
|
+
|
|
275
|
+
this.logger.info("Docker installation completed");
|
|
124
276
|
} catch (error) {
|
|
125
277
|
throw new Error(`Failed to install Docker: ${error.message}`);
|
|
126
278
|
}
|
|
@@ -130,121 +282,421 @@ class DockerManager {
|
|
|
130
282
|
* Install Docker on macOS
|
|
131
283
|
*/
|
|
132
284
|
async installDockerMacOS() {
|
|
133
|
-
this.logger.info(
|
|
134
|
-
|
|
135
|
-
// Check if Homebrew is available with multiple methods
|
|
136
|
-
let homebrewAvailable = false;
|
|
137
|
-
let homebrewPath = 'brew';
|
|
138
|
-
|
|
285
|
+
this.logger.info("Installing Docker Desktop for macOS...");
|
|
286
|
+
|
|
139
287
|
try {
|
|
140
|
-
//
|
|
141
|
-
await
|
|
142
|
-
|
|
143
|
-
|
|
288
|
+
// Download Docker Desktop installer
|
|
289
|
+
await this.downloadDockerInstaller();
|
|
290
|
+
|
|
291
|
+
// Launch the installer
|
|
292
|
+
await this.launchDockerInstaller();
|
|
293
|
+
|
|
294
|
+
// Prompt user to complete installation
|
|
295
|
+
this.promptUserToCompleteInstallation();
|
|
296
|
+
|
|
297
|
+
// Wait for Docker to be installed and available
|
|
298
|
+
await this.waitForDockerInstallation();
|
|
144
299
|
} catch (error) {
|
|
300
|
+
this.logger.error("Docker installation failed:", error.message);
|
|
301
|
+
throw new Error(`Docker Desktop installation failed: ${error.message}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Wait for Docker to be ready and accessible
|
|
307
|
+
*/
|
|
308
|
+
async waitForDockerReady(maxWaitTime = 120000) {
|
|
309
|
+
// 2 minutes max wait
|
|
310
|
+
const startTime = Date.now();
|
|
311
|
+
const checkInterval = 5000; // Check every 5 seconds
|
|
312
|
+
|
|
313
|
+
this.logger.info("Checking if Docker is ready...");
|
|
314
|
+
|
|
315
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
145
316
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
homebrewPath = '/usr/local/bin/brew';
|
|
156
|
-
} catch (error3) {
|
|
157
|
-
// Try to find brew in PATH
|
|
158
|
-
try {
|
|
159
|
-
const { stdout } = await execAsync('which brew');
|
|
160
|
-
if (stdout.trim()) {
|
|
161
|
-
await execAsync(`${stdout.trim()} --version`);
|
|
162
|
-
homebrewAvailable = true;
|
|
163
|
-
homebrewPath = stdout.trim();
|
|
164
|
-
}
|
|
165
|
-
} catch (error4) {
|
|
166
|
-
// Homebrew not found
|
|
167
|
-
this.logger.debug('Homebrew detection failed:', error4.message);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
317
|
+
await this.checkDockerAccess();
|
|
318
|
+
this.logger.info("Docker is ready and accessible!");
|
|
319
|
+
return true;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
322
|
+
this.logger.info(
|
|
323
|
+
`Docker not ready yet (${elapsed}s elapsed), waiting...`
|
|
324
|
+
);
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
170
326
|
}
|
|
171
327
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
328
|
+
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Docker did not become ready within ${maxWaitTime / 1000} seconds`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Download a file using Node.js built-in modules with proper binary handling
|
|
336
|
+
*/
|
|
337
|
+
async downloadFile(url, filePath) {
|
|
338
|
+
this.logger.info(`Downloading file from ${url} to ${filePath}`);
|
|
339
|
+
|
|
340
|
+
const https = require("https");
|
|
341
|
+
const http = require("http");
|
|
342
|
+
const fs = require("fs-extra");
|
|
343
|
+
const { URL } = require("url");
|
|
344
|
+
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
const parsedUrl = new URL(url);
|
|
347
|
+
const client = parsedUrl.protocol === "https:" ? https : http;
|
|
348
|
+
|
|
349
|
+
const request = client.get(url, (response) => {
|
|
350
|
+
if (response.statusCode === 200) {
|
|
351
|
+
const file = fs.createWriteStream(filePath);
|
|
352
|
+
|
|
353
|
+
// Handle binary data properly
|
|
354
|
+
response.setEncoding("binary");
|
|
355
|
+
|
|
356
|
+
response.on("data", (chunk) => {
|
|
357
|
+
file.write(chunk, "binary");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
response.on("end", () => {
|
|
361
|
+
file.end();
|
|
362
|
+
this.logger.info("File downloaded successfully");
|
|
363
|
+
resolve();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
file.on("error", (error) => {
|
|
367
|
+
fs.unlink(filePath, () => {}); // Delete the file on error
|
|
368
|
+
reject(new Error(`File write error: ${error.message}`));
|
|
369
|
+
});
|
|
370
|
+
} else if (response.statusCode === 302 || response.statusCode === 301) {
|
|
371
|
+
// Handle redirects
|
|
372
|
+
this.logger.info(
|
|
373
|
+
`Following redirect to: ${response.headers.location}`
|
|
374
|
+
);
|
|
375
|
+
this.downloadFile(response.headers.location, filePath)
|
|
376
|
+
.then(resolve)
|
|
377
|
+
.catch(reject);
|
|
378
|
+
} else {
|
|
379
|
+
reject(
|
|
380
|
+
new Error(
|
|
381
|
+
`HTTP error: ${response.statusCode} ${response.statusMessage}`
|
|
382
|
+
)
|
|
383
|
+
);
|
|
185
384
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
request.on("error", (error) => {
|
|
388
|
+
reject(new Error(`Download error: ${error.message}`));
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Download Docker Desktop installer for the current platform
|
|
395
|
+
*/
|
|
396
|
+
async downloadDockerInstaller() {
|
|
397
|
+
this.logger.info("Downloading Docker Desktop installer...");
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
// Determine the correct download URL based on platform and architecture
|
|
401
|
+
let downloadUrl, installerName, installerPath;
|
|
402
|
+
|
|
403
|
+
if (process.platform === "darwin") {
|
|
404
|
+
const arch = process.arch === "arm64" ? "arm64" : "amd64";
|
|
405
|
+
downloadUrl = `https://desktop.docker.com/mac/main/${arch}/Docker.dmg`;
|
|
406
|
+
installerName = "Docker.dmg";
|
|
407
|
+
installerPath = path.join(os.homedir(), "Downloads", installerName);
|
|
408
|
+
} else if (process.platform === "win32") {
|
|
409
|
+
downloadUrl =
|
|
410
|
+
"https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe";
|
|
411
|
+
installerName = "Docker Desktop Installer.exe";
|
|
412
|
+
installerPath = path.join(os.homedir(), "Downloads", installerName);
|
|
413
|
+
} else if (process.platform === "linux") {
|
|
414
|
+
downloadUrl =
|
|
415
|
+
"https://desktop.docker.com/linux/main/amd64/docker-desktop-4.46.0-amd64.deb";
|
|
416
|
+
installerName = "docker-desktop.deb";
|
|
417
|
+
installerPath = path.join(os.homedir(), "Downloads", installerName);
|
|
418
|
+
} else {
|
|
419
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
this.logger.info(
|
|
423
|
+
`Downloading Docker Desktop for ${process.platform} (${process.arch})...`
|
|
424
|
+
);
|
|
425
|
+
this.logger.info(`URL: ${downloadUrl}`);
|
|
426
|
+
this.logger.info(`Destination: ${installerPath}`);
|
|
427
|
+
|
|
428
|
+
// Download the installer using Node.js built-in modules
|
|
429
|
+
await this.downloadFile(downloadUrl, installerPath);
|
|
430
|
+
|
|
431
|
+
this.logger.info("Docker Desktop installer downloaded successfully");
|
|
432
|
+
return { installerPath, installerName };
|
|
433
|
+
} catch (error) {
|
|
434
|
+
this.logger.error(
|
|
435
|
+
"Failed to download Docker Desktop installer:",
|
|
436
|
+
error.message
|
|
437
|
+
);
|
|
438
|
+
throw new Error(
|
|
439
|
+
`Failed to download Docker Desktop installer: ${error.message}`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Launch the Docker Desktop installer
|
|
446
|
+
*/
|
|
447
|
+
async launchDockerInstaller() {
|
|
448
|
+
this.logger.info("Launching Docker Desktop installer...");
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
let launchCommand;
|
|
452
|
+
|
|
453
|
+
if (process.platform === "darwin") {
|
|
454
|
+
// Mount DMG and copy Docker.app to Applications
|
|
455
|
+
const dmgPath = path.join(os.homedir(), "Downloads", "Docker.dmg");
|
|
456
|
+
|
|
457
|
+
// Mount the DMG
|
|
458
|
+
const mountCommand = `hdiutil attach "${dmgPath}"`;
|
|
459
|
+
this.logger.info(`🔧 About to run command: ${mountCommand}`);
|
|
460
|
+
await this.runCommand(mountCommand, "Mounting Docker Desktop DMG");
|
|
461
|
+
|
|
462
|
+
// Wait a moment for the mount to complete
|
|
463
|
+
await this.sleep(2000);
|
|
464
|
+
|
|
465
|
+
// Check if the volume was mounted and find the correct path
|
|
466
|
+
const fs = require("fs-extra");
|
|
467
|
+
const possiblePaths = [
|
|
468
|
+
"/Volumes/Docker/Docker.app",
|
|
469
|
+
"/Volumes/Docker.app",
|
|
470
|
+
"/Volumes/Docker Desktop/Docker.app",
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
let dockerAppPath = null;
|
|
474
|
+
for (const path of possiblePaths) {
|
|
475
|
+
if (await fs.pathExists(path)) {
|
|
476
|
+
dockerAppPath = path;
|
|
477
|
+
break;
|
|
221
478
|
}
|
|
222
479
|
}
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
this.logger.info('Starting Docker Desktop...');
|
|
227
|
-
try {
|
|
228
|
-
const startCommand = 'open -a Docker';
|
|
229
|
-
this.logger.info(`🔧 About to run command: ${startCommand}`);
|
|
230
|
-
await this.runCommandWithEnv(startCommand, 'Starting Docker Desktop');
|
|
231
|
-
} catch (startError) {
|
|
232
|
-
this.logger.warn('Could not start Docker Desktop automatically. Please start it manually from Applications.');
|
|
233
|
-
}
|
|
480
|
+
|
|
481
|
+
if (!dockerAppPath) {
|
|
482
|
+
throw new Error("Could not find Docker.app in mounted volume");
|
|
234
483
|
}
|
|
235
|
-
|
|
484
|
+
|
|
485
|
+
// Copy Docker.app to Applications folder
|
|
486
|
+
this.logger.info("Copying Docker.app to Applications folder...");
|
|
487
|
+
const copyCommand = `cp -R "${dockerAppPath}" /Applications/`;
|
|
488
|
+
this.logger.info(`🔧 About to run command: ${copyCommand}`);
|
|
489
|
+
await this.runCommand(
|
|
490
|
+
copyCommand,
|
|
491
|
+
"Copying Docker.app to Applications"
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
// Unmount the DMG
|
|
495
|
+
this.logger.info("Unmounting DMG...");
|
|
496
|
+
const unmountCommand = "hdiutil detach /Volumes/Docker";
|
|
497
|
+
this.logger.info(`🔧 About to run command: ${unmountCommand}`);
|
|
498
|
+
try {
|
|
499
|
+
await this.runCommand(unmountCommand, "Unmounting DMG");
|
|
500
|
+
} catch (error) {
|
|
501
|
+
this.logger.warn(
|
|
502
|
+
"Could not unmount DMG (may already be unmounted):",
|
|
503
|
+
error.message
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Launch Docker Desktop from Applications
|
|
508
|
+
launchCommand = "open -a Docker";
|
|
509
|
+
} else if (process.platform === "win32") {
|
|
510
|
+
const installerPath = path.join(
|
|
511
|
+
os.homedir(),
|
|
512
|
+
"Downloads",
|
|
513
|
+
"Docker Desktop Installer.exe"
|
|
514
|
+
);
|
|
515
|
+
launchCommand = `"${installerPath}"`;
|
|
516
|
+
} else if (process.platform === "linux") {
|
|
517
|
+
const installerPath = path.join(
|
|
518
|
+
os.homedir(),
|
|
519
|
+
"Downloads",
|
|
520
|
+
"docker-desktop.deb"
|
|
521
|
+
);
|
|
522
|
+
launchCommand = `sudo dpkg -i "${installerPath}"`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.logger.info(`🔧 About to run command: ${launchCommand}`);
|
|
526
|
+
await this.runCommand(
|
|
527
|
+
launchCommand,
|
|
528
|
+
"Launching Docker Desktop installer"
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
this.logger.info("Docker Desktop installer launched successfully");
|
|
532
|
+
} catch (error) {
|
|
533
|
+
this.logger.error(
|
|
534
|
+
"Failed to launch Docker Desktop installer:",
|
|
535
|
+
error.message
|
|
536
|
+
);
|
|
537
|
+
throw new Error(
|
|
538
|
+
`Failed to launch Docker Desktop installer: ${error.message}`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Prompt user to complete Docker installation
|
|
545
|
+
*/
|
|
546
|
+
promptUserToCompleteInstallation() {
|
|
547
|
+
console.log("\n" + "=".repeat(60));
|
|
548
|
+
console.log("🐳 DOCKER INSTALLATION REQUIRED");
|
|
549
|
+
console.log("=".repeat(60));
|
|
550
|
+
console.log("");
|
|
551
|
+
console.log("The Docker Desktop installer has been launched.");
|
|
552
|
+
console.log("Please follow these steps to complete the installation:");
|
|
553
|
+
console.log("");
|
|
554
|
+
|
|
555
|
+
if (process.platform === "darwin") {
|
|
556
|
+
console.log(
|
|
557
|
+
"1. Docker.app has been automatically copied to Applications"
|
|
558
|
+
);
|
|
559
|
+
console.log("2. Docker Desktop should launch automatically");
|
|
560
|
+
console.log("3. Follow the setup wizard to complete installation");
|
|
561
|
+
console.log("4. Start Docker Desktop when prompted");
|
|
562
|
+
} else if (process.platform === "win32") {
|
|
563
|
+
console.log("1. The Docker Desktop installer should open automatically");
|
|
564
|
+
console.log("2. Follow the installation wizard");
|
|
565
|
+
console.log("3. Restart your computer if prompted");
|
|
566
|
+
console.log("4. Launch Docker Desktop from Start Menu");
|
|
567
|
+
} else if (process.platform === "linux") {
|
|
568
|
+
console.log("1. The Docker Desktop package has been installed");
|
|
569
|
+
console.log("2. Launch Docker Desktop from your applications menu");
|
|
570
|
+
console.log("3. Follow the setup wizard to complete installation");
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
console.log("");
|
|
574
|
+
console.log("⏳ Waiting for Docker to be installed and started...");
|
|
575
|
+
console.log(" (This may take a few minutes)");
|
|
576
|
+
console.log("");
|
|
577
|
+
console.log("=".repeat(60));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Wait for Docker to be installed and available
|
|
582
|
+
*/
|
|
583
|
+
async waitForDockerInstallation() {
|
|
584
|
+
this.logger.info("Waiting for Docker to be installed and available...");
|
|
585
|
+
|
|
586
|
+
const maxWaitTime = 300000; // 5 minutes max wait
|
|
587
|
+
const checkInterval = 5000; // Check every 5 seconds
|
|
588
|
+
const startTime = Date.now();
|
|
589
|
+
|
|
590
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
591
|
+
try {
|
|
592
|
+
// Check if Docker is installed and running
|
|
593
|
+
await this.checkDockerAccess();
|
|
594
|
+
this.logger.info("Docker is installed and running!");
|
|
595
|
+
return true;
|
|
236
596
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
597
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
598
|
+
const remaining = Math.round(
|
|
599
|
+
(maxWaitTime - (Date.now() - startTime)) / 1000
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
this.logger.info(
|
|
603
|
+
`Docker not ready yet (${elapsed}s elapsed, ${remaining}s remaining)...`
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
// Show progress every 30 seconds
|
|
607
|
+
if (elapsed % 30 === 0) {
|
|
608
|
+
console.log(`⏳ Still waiting for Docker... (${elapsed}s elapsed)`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
243
612
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
throw new Error(
|
|
616
|
+
`Docker did not become available within ${
|
|
617
|
+
maxWaitTime / 1000
|
|
618
|
+
} seconds. Please ensure Docker Desktop is installed and running.`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Install Docker Desktop manually by downloading and installing the DMG
|
|
624
|
+
*/
|
|
625
|
+
async installDockerDesktopManually() {
|
|
626
|
+
this.logger.info("Downloading Docker Desktop manually...");
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
// Determine the correct download URL based on architecture
|
|
630
|
+
const arch = process.arch === "arm64" ? "arm64" : "amd64";
|
|
631
|
+
const downloadUrl = `https://desktop.docker.com/mac/main/${arch}/Docker.dmg`;
|
|
632
|
+
const dmgPath = path.join(os.homedir(), "Downloads", "Docker.dmg");
|
|
633
|
+
|
|
634
|
+
this.logger.info(`Downloading Docker Desktop for ${arch}...`);
|
|
635
|
+
this.logger.info(`URL: ${downloadUrl}`);
|
|
636
|
+
this.logger.info(`Destination: ${dmgPath}`);
|
|
637
|
+
|
|
638
|
+
// Download the DMG file
|
|
639
|
+
const downloadCommand = `curl -L "${downloadUrl}" -o "${dmgPath}"`;
|
|
640
|
+
this.logger.info(`🔧 About to run command: ${downloadCommand}`);
|
|
641
|
+
await this.runCommandWithEnv(
|
|
642
|
+
downloadCommand,
|
|
643
|
+
"Downloading Docker Desktop"
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
// Mount the DMG
|
|
647
|
+
this.logger.info("Mounting Docker Desktop DMG...");
|
|
648
|
+
const mountCommand = `hdiutil attach "${dmgPath}"`;
|
|
649
|
+
this.logger.info(`🔧 About to run command: ${mountCommand}`);
|
|
650
|
+
await this.runCommandWithEnv(mountCommand, "Mounting Docker Desktop DMG");
|
|
651
|
+
|
|
652
|
+
// Copy Docker.app to Applications
|
|
653
|
+
this.logger.info("Installing Docker Desktop to Applications...");
|
|
654
|
+
const copyCommand = 'cp -R "/Volumes/Docker/Docker.app" /Applications/';
|
|
655
|
+
this.logger.info(`🔧 About to run command: ${copyCommand}`);
|
|
656
|
+
await this.runCommandWithEnv(copyCommand, "Installing Docker Desktop");
|
|
657
|
+
|
|
658
|
+
// Unmount the DMG
|
|
659
|
+
this.logger.info("Unmounting Docker Desktop DMG...");
|
|
660
|
+
const unmountCommand = "hdiutil detach /Volumes/Docker";
|
|
661
|
+
this.logger.info(`🔧 About to run command: ${unmountCommand}`);
|
|
662
|
+
await this.runCommandWithEnv(
|
|
663
|
+
unmountCommand,
|
|
664
|
+
"Unmounting Docker Desktop DMG"
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// Clean up the DMG file
|
|
668
|
+
this.logger.info("Cleaning up downloaded DMG...");
|
|
669
|
+
const cleanupCommand = `rm "${dmgPath}"`;
|
|
670
|
+
this.logger.info(`🔧 About to run command: ${cleanupCommand}`);
|
|
671
|
+
await this.runCommandWithEnv(cleanupCommand, "Cleaning up DMG file");
|
|
672
|
+
|
|
673
|
+
this.logger.info(
|
|
674
|
+
"Docker Desktop installed successfully via manual download"
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
// Try to start Docker Desktop
|
|
678
|
+
this.logger.info("Starting Docker Desktop...");
|
|
679
|
+
try {
|
|
680
|
+
const startCommand = "open -a Docker";
|
|
681
|
+
this.logger.info(`🔧 About to run command: ${startCommand}`);
|
|
682
|
+
await this.runCommandWithEnv(startCommand, "Starting Docker Desktop");
|
|
683
|
+
|
|
684
|
+
// Wait for Docker to be ready
|
|
685
|
+
this.logger.info("Waiting for Docker Desktop to start...");
|
|
686
|
+
await this.waitForDockerReady();
|
|
687
|
+
} catch (startError) {
|
|
688
|
+
this.logger.warn(
|
|
689
|
+
"Could not start Docker Desktop automatically. Please start it manually from Applications."
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
} catch (error) {
|
|
693
|
+
this.logger.error(
|
|
694
|
+
"Manual Docker Desktop installation failed:",
|
|
695
|
+
error.message
|
|
696
|
+
);
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Manual Docker Desktop installation failed: ${error.message}`
|
|
699
|
+
);
|
|
248
700
|
}
|
|
249
701
|
}
|
|
250
702
|
|
|
@@ -252,34 +704,23 @@ class DockerManager {
|
|
|
252
704
|
* Install Docker on Linux
|
|
253
705
|
*/
|
|
254
706
|
async installDockerLinux() {
|
|
255
|
-
this.logger.info(
|
|
256
|
-
|
|
707
|
+
this.logger.info("Installing Docker on Linux...");
|
|
708
|
+
|
|
257
709
|
try {
|
|
258
|
-
//
|
|
259
|
-
await this.
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
await this.
|
|
263
|
-
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
//
|
|
268
|
-
await this.
|
|
269
|
-
|
|
270
|
-
// Update package index again
|
|
271
|
-
await this.runCommand('sudo apt-get update', 'Updating package index with Docker repository');
|
|
272
|
-
|
|
273
|
-
// Install Docker
|
|
274
|
-
await this.runCommand('sudo apt-get install -y docker-ce docker-ce-cli containerd.io', 'Installing Docker');
|
|
275
|
-
|
|
276
|
-
// Add current user to docker groupl
|
|
277
|
-
await this.runCommand('sudo usermod -aG docker $USER', 'Adding user to docker group');
|
|
278
|
-
|
|
279
|
-
this.logger.info('Docker installation completed. You may need to log out and back in for group changes to take effect.');
|
|
280
|
-
|
|
710
|
+
// Download Docker Desktop installer
|
|
711
|
+
await this.downloadDockerInstaller();
|
|
712
|
+
|
|
713
|
+
// Launch the installer
|
|
714
|
+
await this.launchDockerInstaller();
|
|
715
|
+
|
|
716
|
+
// Prompt user to complete installation
|
|
717
|
+
this.promptUserToCompleteInstallation();
|
|
718
|
+
|
|
719
|
+
// Wait for Docker to be installed and available
|
|
720
|
+
await this.waitForDockerInstallation();
|
|
281
721
|
} catch (error) {
|
|
282
|
-
|
|
722
|
+
this.logger.error("Docker installation failed:", error.message);
|
|
723
|
+
throw new Error(`Docker Desktop installation failed: ${error.message}`);
|
|
283
724
|
}
|
|
284
725
|
}
|
|
285
726
|
|
|
@@ -287,19 +728,23 @@ class DockerManager {
|
|
|
287
728
|
* Install Docker on Windows
|
|
288
729
|
*/
|
|
289
730
|
async installDockerWindows() {
|
|
290
|
-
this.logger.info(
|
|
291
|
-
|
|
292
|
-
// Check if Chocolatey is available
|
|
731
|
+
this.logger.info("Installing Docker Desktop for Windows...");
|
|
732
|
+
|
|
293
733
|
try {
|
|
294
|
-
|
|
295
|
-
this.
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
await this.
|
|
299
|
-
|
|
734
|
+
// Download Docker Desktop installer
|
|
735
|
+
await this.downloadDockerInstaller();
|
|
736
|
+
|
|
737
|
+
// Launch the installer
|
|
738
|
+
await this.launchDockerInstaller();
|
|
739
|
+
|
|
740
|
+
// Prompt user to complete installation
|
|
741
|
+
this.promptUserToCompleteInstallation();
|
|
742
|
+
|
|
743
|
+
// Wait for Docker to be installed and available
|
|
744
|
+
await this.waitForDockerInstallation();
|
|
300
745
|
} catch (error) {
|
|
301
|
-
this.logger.
|
|
302
|
-
throw new Error(
|
|
746
|
+
this.logger.error("Docker installation failed:", error.message);
|
|
747
|
+
throw new Error(`Docker Desktop installation failed: ${error.message}`);
|
|
303
748
|
}
|
|
304
749
|
}
|
|
305
750
|
|
|
@@ -308,24 +753,33 @@ class DockerManager {
|
|
|
308
753
|
*/
|
|
309
754
|
async startDocker() {
|
|
310
755
|
const platform = process.platform;
|
|
311
|
-
|
|
756
|
+
|
|
312
757
|
try {
|
|
313
|
-
if (platform ===
|
|
758
|
+
if (platform === "darwin") {
|
|
314
759
|
// On macOS, try to start Docker Desktop
|
|
315
|
-
await this.runCommand(
|
|
316
|
-
} else if (platform ===
|
|
760
|
+
await this.runCommand("open -a Docker", "Starting Docker Desktop");
|
|
761
|
+
} else if (platform === "linux") {
|
|
317
762
|
// On Linux, start Docker service
|
|
318
|
-
await this.runCommand(
|
|
319
|
-
|
|
320
|
-
|
|
763
|
+
await this.runCommand(
|
|
764
|
+
"sudo systemctl start docker",
|
|
765
|
+
"Starting Docker service"
|
|
766
|
+
);
|
|
767
|
+
await this.runCommand(
|
|
768
|
+
"sudo systemctl enable docker",
|
|
769
|
+
"Enabling Docker service"
|
|
770
|
+
);
|
|
771
|
+
} else if (platform === "win32") {
|
|
321
772
|
// On Windows, try to start Docker Desktop
|
|
322
|
-
await this.runCommand(
|
|
773
|
+
await this.runCommand(
|
|
774
|
+
'start "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"',
|
|
775
|
+
"Starting Docker Desktop"
|
|
776
|
+
);
|
|
323
777
|
}
|
|
324
|
-
|
|
325
|
-
this.logger.info(
|
|
778
|
+
|
|
779
|
+
this.logger.info("Docker service started");
|
|
326
780
|
} catch (error) {
|
|
327
781
|
this.logger.warn(`Failed to start Docker service: ${error.message}`);
|
|
328
|
-
this.logger.info(
|
|
782
|
+
this.logger.info("Please start Docker manually and try again");
|
|
329
783
|
throw error;
|
|
330
784
|
}
|
|
331
785
|
}
|
|
@@ -334,25 +788,85 @@ class DockerManager {
|
|
|
334
788
|
* Wait for Docker to become available
|
|
335
789
|
*/
|
|
336
790
|
async waitForDocker() {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
791
|
+
const maxWaitTime = 300000; // 5 minutes
|
|
792
|
+
const checkInterval = 3000; // 3 seconds
|
|
793
|
+
const stabilityCheckDuration = 10000; // 10 seconds of consistent readiness
|
|
794
|
+
const stabilityCheckInterval = 2000; // Check every 2 seconds during stability phase
|
|
795
|
+
let elapsedTime = 0;
|
|
796
|
+
|
|
797
|
+
this.logger.info(
|
|
798
|
+
"⏳ Waiting for Docker daemon service to become available..."
|
|
799
|
+
);
|
|
800
|
+
this.logger.info("This may take a few minutes if Docker is starting up...");
|
|
801
|
+
|
|
802
|
+
while (elapsedTime < maxWaitTime) {
|
|
343
803
|
try {
|
|
344
|
-
await this.
|
|
345
|
-
this.logger.info(
|
|
804
|
+
await this.checkDockerAccess();
|
|
805
|
+
this.logger.info("✅ Docker daemon detected! Verifying stability...");
|
|
806
|
+
|
|
807
|
+
// Wait for Docker to be consistently ready for stabilityCheckDuration
|
|
808
|
+
const stabilityStartTime = Date.now();
|
|
809
|
+
let consecutiveSuccessfulChecks = 0;
|
|
810
|
+
const requiredConsecutiveChecks = Math.ceil(
|
|
811
|
+
stabilityCheckDuration / stabilityCheckInterval
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
while (Date.now() - stabilityStartTime < stabilityCheckDuration) {
|
|
815
|
+
try {
|
|
816
|
+
await this.checkDockerAccess();
|
|
817
|
+
consecutiveSuccessfulChecks++;
|
|
818
|
+
this.logger.info(
|
|
819
|
+
`Stability check ${consecutiveSuccessfulChecks}/${requiredConsecutiveChecks} passed`
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
if (consecutiveSuccessfulChecks >= requiredConsecutiveChecks) {
|
|
823
|
+
this.logger.info("✅ Docker daemon is stable and ready!");
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
} catch (error) {
|
|
827
|
+
this.logger.info(
|
|
828
|
+
"Docker daemon became unstable, restarting stability check..."
|
|
829
|
+
);
|
|
830
|
+
consecutiveSuccessfulChecks = 0;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
await this.sleep(stabilityCheckInterval);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// If we get here, stability check timed out
|
|
837
|
+
this.logger.warn(
|
|
838
|
+
"Docker daemon stability check timed out, continuing anyway..."
|
|
839
|
+
);
|
|
346
840
|
return;
|
|
347
841
|
} catch (error) {
|
|
348
|
-
|
|
349
|
-
|
|
842
|
+
const remainingTime = Math.round((maxWaitTime - elapsedTime) / 1000);
|
|
843
|
+
const elapsedSeconds = Math.round(elapsedTime / 1000);
|
|
844
|
+
|
|
845
|
+
if (elapsedSeconds % 30 === 0) {
|
|
846
|
+
// Log every 30 seconds
|
|
847
|
+
this.logger.info(
|
|
848
|
+
`Docker daemon service not ready yet (${elapsedSeconds}s elapsed, ${remainingTime}s remaining)...`
|
|
849
|
+
);
|
|
850
|
+
this.logger.info(`Last error: ${error.message}`);
|
|
350
851
|
}
|
|
351
|
-
|
|
352
|
-
this.
|
|
353
|
-
|
|
852
|
+
|
|
853
|
+
await this.sleep(checkInterval);
|
|
854
|
+
elapsedTime += checkInterval;
|
|
354
855
|
}
|
|
355
856
|
}
|
|
857
|
+
|
|
858
|
+
this.logger.error(
|
|
859
|
+
"❌ Docker daemon service did not become available within the expected time"
|
|
860
|
+
);
|
|
861
|
+
this.logger.error("Please check:");
|
|
862
|
+
this.logger.error(
|
|
863
|
+
"1. Docker is installed and the daemon service is running"
|
|
864
|
+
);
|
|
865
|
+
this.logger.error("2. Docker daemon has finished starting up");
|
|
866
|
+
this.logger.error("3. You have permission to access Docker");
|
|
867
|
+
this.logger.error('4. Try running "docker ps" manually to test');
|
|
868
|
+
|
|
869
|
+
throw new Error("Docker did not become available within the expected time");
|
|
356
870
|
}
|
|
357
871
|
|
|
358
872
|
/**
|
|
@@ -361,36 +875,38 @@ class DockerManager {
|
|
|
361
875
|
async runCommand(command, description) {
|
|
362
876
|
this.logger.info(`${description}...`);
|
|
363
877
|
this.logger.debug(`Executing command: ${command}`);
|
|
364
|
-
|
|
878
|
+
|
|
365
879
|
return new Promise((resolve, reject) => {
|
|
366
|
-
const child = spawn(
|
|
367
|
-
stdio: [
|
|
368
|
-
shell: true
|
|
880
|
+
const child = spawn(command, {
|
|
881
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
882
|
+
shell: true,
|
|
369
883
|
});
|
|
370
|
-
|
|
371
|
-
let stdout =
|
|
372
|
-
let stderr =
|
|
373
|
-
|
|
374
|
-
child.stdout.on(
|
|
884
|
+
|
|
885
|
+
let stdout = "";
|
|
886
|
+
let stderr = "";
|
|
887
|
+
|
|
888
|
+
child.stdout.on("data", (data) => {
|
|
375
889
|
const output = data.toString();
|
|
376
890
|
stdout += output;
|
|
377
891
|
// Log output in debug mode
|
|
378
892
|
this.logger.debug(`STDOUT: ${output.trim()}`);
|
|
379
893
|
});
|
|
380
|
-
|
|
381
|
-
child.stderr.on(
|
|
894
|
+
|
|
895
|
+
child.stderr.on("data", (data) => {
|
|
382
896
|
const output = data.toString();
|
|
383
897
|
stderr += output;
|
|
384
898
|
// Log stderr in debug mode
|
|
385
899
|
this.logger.debug(`STDERR: ${output.trim()}`);
|
|
386
900
|
});
|
|
387
|
-
|
|
388
|
-
child.on(
|
|
901
|
+
|
|
902
|
+
child.on("close", (code) => {
|
|
389
903
|
if (code === 0) {
|
|
390
904
|
this.logger.info(`${description} completed successfully`);
|
|
391
905
|
resolve({ stdout, stderr });
|
|
392
906
|
} else {
|
|
393
|
-
const error = new Error(
|
|
907
|
+
const error = new Error(
|
|
908
|
+
`Command failed with exit code ${code}: ${stderr}`
|
|
909
|
+
);
|
|
394
910
|
this.logger.error(`${description} failed: ${error.message}`);
|
|
395
911
|
this.logger.error(`Command: ${command}`);
|
|
396
912
|
this.logger.error(`STDOUT: ${stdout}`);
|
|
@@ -398,8 +914,8 @@ class DockerManager {
|
|
|
398
914
|
reject(error);
|
|
399
915
|
}
|
|
400
916
|
});
|
|
401
|
-
|
|
402
|
-
child.on(
|
|
917
|
+
|
|
918
|
+
child.on("error", (error) => {
|
|
403
919
|
this.logger.error(`${description} failed: ${error.message}`);
|
|
404
920
|
this.logger.error(`Command: ${command}`);
|
|
405
921
|
reject(error);
|
|
@@ -413,51 +929,74 @@ class DockerManager {
|
|
|
413
929
|
async runCommandWithEnv(command, description) {
|
|
414
930
|
this.logger.info(`${description}...`);
|
|
415
931
|
this.logger.debug(`Executing command: ${command}`);
|
|
416
|
-
|
|
932
|
+
|
|
417
933
|
return new Promise((resolve, reject) => {
|
|
418
934
|
// Set up environment for Homebrew
|
|
935
|
+
const homebrewPaths = [
|
|
936
|
+
"/opt/homebrew/bin",
|
|
937
|
+
"/usr/local/bin",
|
|
938
|
+
"/opt/homebrew/sbin",
|
|
939
|
+
"/usr/local/sbin",
|
|
940
|
+
];
|
|
941
|
+
|
|
942
|
+
const existingPath = process.env.PATH || "";
|
|
943
|
+
const allPaths = [...homebrewPaths, ...existingPath.split(":")];
|
|
944
|
+
const uniquePaths = [...new Set(allPaths.filter((p) => p))];
|
|
945
|
+
const effectivePath = uniquePaths.join(":");
|
|
946
|
+
|
|
419
947
|
const env = {
|
|
420
948
|
...process.env,
|
|
421
|
-
PATH:
|
|
422
|
-
HOMEBREW_NO_AUTO_UPDATE:
|
|
423
|
-
HOMEBREW_NO_INSTALL_CLEANUP:
|
|
949
|
+
PATH: effectivePath,
|
|
950
|
+
HOMEBREW_NO_AUTO_UPDATE: "1",
|
|
951
|
+
HOMEBREW_NO_INSTALL_CLEANUP: "1",
|
|
952
|
+
HOMEBREW_PREFIX: "/opt/homebrew",
|
|
953
|
+
HOMEBREW_CELLAR: "/opt/homebrew/Cellar",
|
|
954
|
+
HOMEBREW_REPOSITORY: "/opt/homebrew",
|
|
955
|
+
HOMEBREW_SHELLENV_PREFIX: "/opt/homebrew",
|
|
424
956
|
};
|
|
425
|
-
|
|
957
|
+
|
|
426
958
|
// Debug: Log environment details
|
|
427
959
|
this.logger.debug(`Environment PATH: ${env.PATH}`);
|
|
428
|
-
this.logger.debug(`
|
|
429
|
-
this.logger.debug(
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
960
|
+
this.logger.debug(`HOMEBREW_PREFIX: ${env.HOMEBREW_PREFIX}`);
|
|
961
|
+
this.logger.debug(
|
|
962
|
+
`HOMEBREW_NO_AUTO_UPDATE: ${env.HOMEBREW_NO_AUTO_UPDATE}`
|
|
963
|
+
);
|
|
964
|
+
this.logger.debug(
|
|
965
|
+
`HOMEBREW_NO_INSTALL_CLEANUP: ${env.HOMEBREW_NO_INSTALL_CLEANUP}`
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
// Use zsh with login shell for better environment support on macOS
|
|
969
|
+
const child = spawn("zsh", ["-l", "-c", command], {
|
|
970
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
971
|
+
shell: false,
|
|
972
|
+
env: env,
|
|
436
973
|
});
|
|
437
|
-
|
|
438
|
-
let stdout =
|
|
439
|
-
let stderr =
|
|
440
|
-
|
|
441
|
-
child.stdout.on(
|
|
974
|
+
|
|
975
|
+
let stdout = "";
|
|
976
|
+
let stderr = "";
|
|
977
|
+
|
|
978
|
+
child.stdout.on("data", (data) => {
|
|
442
979
|
const output = data.toString();
|
|
443
980
|
stdout += output;
|
|
444
981
|
// Log output in debug mode
|
|
445
982
|
this.logger.debug(`STDOUT: ${output.trim()}`);
|
|
446
983
|
});
|
|
447
|
-
|
|
448
|
-
child.stderr.on(
|
|
984
|
+
|
|
985
|
+
child.stderr.on("data", (data) => {
|
|
449
986
|
const output = data.toString();
|
|
450
987
|
stderr += output;
|
|
451
988
|
// Log stderr in debug mode
|
|
452
989
|
this.logger.debug(`STDERR: ${output.trim()}`);
|
|
453
990
|
});
|
|
454
|
-
|
|
455
|
-
child.on(
|
|
991
|
+
|
|
992
|
+
child.on("close", (code) => {
|
|
456
993
|
if (code === 0) {
|
|
457
994
|
this.logger.info(`${description} completed successfully`);
|
|
458
995
|
resolve({ stdout, stderr });
|
|
459
996
|
} else {
|
|
460
|
-
const error = new Error(
|
|
997
|
+
const error = new Error(
|
|
998
|
+
`Command failed with exit code ${code}: ${stderr}`
|
|
999
|
+
);
|
|
461
1000
|
this.logger.error(`${description} failed: ${error.message}`);
|
|
462
1001
|
this.logger.error(`Command: ${command}`);
|
|
463
1002
|
this.logger.error(`STDOUT: ${stdout}`);
|
|
@@ -465,8 +1004,8 @@ class DockerManager {
|
|
|
465
1004
|
reject(error);
|
|
466
1005
|
}
|
|
467
1006
|
});
|
|
468
|
-
|
|
469
|
-
child.on(
|
|
1007
|
+
|
|
1008
|
+
child.on("error", (error) => {
|
|
470
1009
|
this.logger.error(`${description} failed: ${error.message}`);
|
|
471
1010
|
this.logger.error(`Command: ${command}`);
|
|
472
1011
|
reject(error);
|
|
@@ -478,7 +1017,7 @@ class DockerManager {
|
|
|
478
1017
|
* Sleep utility
|
|
479
1018
|
*/
|
|
480
1019
|
sleep(ms) {
|
|
481
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1020
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
482
1021
|
}
|
|
483
1022
|
|
|
484
1023
|
/**
|
|
@@ -487,20 +1026,21 @@ class DockerManager {
|
|
|
487
1026
|
async pullBaseImage(baseImageName = null, options = {}) {
|
|
488
1027
|
const imageName = baseImageName || this.defaultBaseImageName;
|
|
489
1028
|
this.logger.info(`Pulling base Docker image: ${imageName}`);
|
|
490
|
-
|
|
1029
|
+
|
|
491
1030
|
try {
|
|
492
1031
|
const stream = await this.docker.pull(imageName);
|
|
493
|
-
|
|
494
|
-
await this.followPullProgress(stream,
|
|
495
|
-
|
|
1032
|
+
|
|
1033
|
+
await this.followPullProgress(stream, "Base image pull");
|
|
1034
|
+
|
|
496
1035
|
// Verify the image was pulled
|
|
497
1036
|
const hasImage = await this.hasImage(imageName);
|
|
498
1037
|
if (hasImage) {
|
|
499
1038
|
this.logger.info(`Base image '${imageName}' pulled successfully`);
|
|
500
1039
|
} else {
|
|
501
|
-
throw new Error(
|
|
1040
|
+
throw new Error(
|
|
1041
|
+
`Base image '${imageName}' was not pulled successfully`
|
|
1042
|
+
);
|
|
502
1043
|
}
|
|
503
|
-
|
|
504
1044
|
} catch (error) {
|
|
505
1045
|
throw new Error(`Failed to pull base image: ${error.message}`);
|
|
506
1046
|
}
|
|
@@ -510,55 +1050,61 @@ class DockerManager {
|
|
|
510
1050
|
* Clean up existing containers from previous runs
|
|
511
1051
|
*/
|
|
512
1052
|
async cleanupExistingContainers(agents) {
|
|
513
|
-
this.logger.info(
|
|
514
|
-
|
|
1053
|
+
this.logger.info("Cleaning up existing containers from previous runs...");
|
|
1054
|
+
|
|
515
1055
|
try {
|
|
516
1056
|
// Get all containers (running and stopped) that match our agent naming pattern
|
|
517
1057
|
const containers = await this.docker.listContainers({ all: true });
|
|
518
|
-
|
|
519
|
-
const agentNames = agents.map(agent => agent.name.toLowerCase());
|
|
520
|
-
const containersToCleanup = containers.filter(container => {
|
|
1058
|
+
|
|
1059
|
+
const agentNames = agents.map((agent) => agent.name.toLowerCase());
|
|
1060
|
+
const containersToCleanup = containers.filter((container) => {
|
|
521
1061
|
// Check if container name matches our dank agent pattern
|
|
522
|
-
const containerName = container.Names[0].replace(/^\//,
|
|
523
|
-
return agentNames.some(
|
|
524
|
-
|
|
525
|
-
|
|
1062
|
+
const containerName = container.Names[0].replace(/^\//, ""); // Remove leading slash
|
|
1063
|
+
return agentNames.some(
|
|
1064
|
+
(agentName) =>
|
|
1065
|
+
containerName.startsWith(`dank-${agentName}-`) ||
|
|
1066
|
+
containerName === `dank-${agentName}`
|
|
526
1067
|
);
|
|
527
1068
|
});
|
|
528
1069
|
|
|
529
1070
|
if (containersToCleanup.length === 0) {
|
|
530
|
-
this.logger.info(
|
|
1071
|
+
this.logger.info("No existing containers found to cleanup");
|
|
531
1072
|
return;
|
|
532
1073
|
}
|
|
533
1074
|
|
|
534
|
-
this.logger.info(
|
|
1075
|
+
this.logger.info(
|
|
1076
|
+
`Found ${containersToCleanup.length} existing containers to cleanup`
|
|
1077
|
+
);
|
|
535
1078
|
|
|
536
1079
|
// Stop and remove each container
|
|
537
1080
|
for (const containerInfo of containersToCleanup) {
|
|
538
1081
|
const container = this.docker.getContainer(containerInfo.Id);
|
|
539
|
-
const containerName = containerInfo.Names[0].replace(/^\//,
|
|
540
|
-
|
|
1082
|
+
const containerName = containerInfo.Names[0].replace(/^\//, "");
|
|
1083
|
+
|
|
541
1084
|
try {
|
|
542
1085
|
// Stop container if running
|
|
543
|
-
if (containerInfo.State ===
|
|
1086
|
+
if (containerInfo.State === "running") {
|
|
544
1087
|
this.logger.info(`Stopping container: ${containerName}`);
|
|
545
1088
|
await container.stop({ t: 10 }); // 10 second timeout
|
|
546
1089
|
}
|
|
547
|
-
|
|
1090
|
+
|
|
548
1091
|
// Remove container
|
|
549
1092
|
this.logger.info(`Removing container: ${containerName}`);
|
|
550
1093
|
await container.remove({ force: true });
|
|
551
|
-
|
|
552
1094
|
} catch (error) {
|
|
553
1095
|
// Log but don't fail if we can't clean up a specific container
|
|
554
|
-
this.logger.warn(
|
|
1096
|
+
this.logger.warn(
|
|
1097
|
+
`Failed to cleanup container ${containerName}: ${error.message}`
|
|
1098
|
+
);
|
|
555
1099
|
}
|
|
556
1100
|
}
|
|
557
1101
|
|
|
558
|
-
this.logger.info(
|
|
559
|
-
|
|
1102
|
+
this.logger.info("Container cleanup completed");
|
|
560
1103
|
} catch (error) {
|
|
561
|
-
this.logger.error(
|
|
1104
|
+
this.logger.error(
|
|
1105
|
+
"Failed to cleanup existing containers:",
|
|
1106
|
+
error.message
|
|
1107
|
+
);
|
|
562
1108
|
// Don't throw - we want to continue even if cleanup fails
|
|
563
1109
|
}
|
|
564
1110
|
}
|
|
@@ -572,22 +1118,21 @@ class DockerManager {
|
|
|
572
1118
|
|
|
573
1119
|
try {
|
|
574
1120
|
const buildContext = await this.createAgentBuildContext(agent);
|
|
575
|
-
|
|
1121
|
+
|
|
576
1122
|
const stream = await this.docker.buildImage(buildContext, {
|
|
577
1123
|
t: imageName,
|
|
578
|
-
dockerfile:
|
|
579
|
-
nocache: options.rebuild || options.noCache || false
|
|
1124
|
+
dockerfile: "Dockerfile",
|
|
1125
|
+
nocache: options.rebuild || options.noCache || false,
|
|
580
1126
|
});
|
|
581
1127
|
|
|
582
1128
|
await this.followBuildProgress(stream, `Agent ${agent.name} build`);
|
|
583
|
-
|
|
1129
|
+
|
|
584
1130
|
this.logger.info(`Agent image '${imageName}' built successfully`);
|
|
585
|
-
|
|
1131
|
+
|
|
586
1132
|
// Clean up build context
|
|
587
1133
|
await fs.remove(buildContext);
|
|
588
|
-
|
|
1134
|
+
|
|
589
1135
|
return imageName;
|
|
590
|
-
|
|
591
1136
|
} catch (error) {
|
|
592
1137
|
throw new Error(`Failed to build agent image: ${error.message}`);
|
|
593
1138
|
}
|
|
@@ -598,49 +1143,54 @@ class DockerManager {
|
|
|
598
1143
|
*/
|
|
599
1144
|
async buildProductionImage(agent, options = {}) {
|
|
600
1145
|
const {
|
|
601
|
-
tag =
|
|
1146
|
+
tag = "latest",
|
|
602
1147
|
registry,
|
|
603
1148
|
namespace,
|
|
604
1149
|
force = false,
|
|
605
|
-
push = false
|
|
1150
|
+
push = false,
|
|
606
1151
|
} = options;
|
|
607
1152
|
|
|
608
1153
|
// Construct production image name
|
|
609
1154
|
let imageName = agent.name.toLowerCase();
|
|
610
|
-
|
|
1155
|
+
|
|
611
1156
|
// Add namespace if provided
|
|
612
1157
|
if (namespace) {
|
|
613
1158
|
imageName = `${namespace}/${imageName}`;
|
|
614
1159
|
}
|
|
615
|
-
|
|
1160
|
+
|
|
616
1161
|
// Add registry if provided
|
|
617
1162
|
if (registry) {
|
|
618
1163
|
imageName = `${registry}/${imageName}`;
|
|
619
1164
|
}
|
|
620
|
-
|
|
1165
|
+
|
|
621
1166
|
// Add tag
|
|
622
1167
|
imageName = `${imageName}:${tag}`;
|
|
623
1168
|
|
|
624
|
-
this.logger.info(
|
|
1169
|
+
this.logger.info(
|
|
1170
|
+
`Building production image for agent: ${agent.name} -> ${imageName}`
|
|
1171
|
+
);
|
|
625
1172
|
|
|
626
1173
|
try {
|
|
627
1174
|
const buildContext = await this.createAgentBuildContext(agent);
|
|
628
|
-
|
|
1175
|
+
|
|
629
1176
|
const stream = await this.docker.buildImage(buildContext, {
|
|
630
1177
|
t: imageName,
|
|
631
|
-
dockerfile:
|
|
632
|
-
nocache: force
|
|
1178
|
+
dockerfile: "Dockerfile",
|
|
1179
|
+
nocache: force,
|
|
633
1180
|
});
|
|
634
1181
|
|
|
635
|
-
await this.followBuildProgress(
|
|
636
|
-
|
|
1182
|
+
await this.followBuildProgress(
|
|
1183
|
+
stream,
|
|
1184
|
+
`Production build for ${agent.name}`
|
|
1185
|
+
);
|
|
1186
|
+
|
|
637
1187
|
this.logger.info(`Production image '${imageName}' built successfully`);
|
|
638
|
-
|
|
1188
|
+
|
|
639
1189
|
// Clean up build context
|
|
640
1190
|
await fs.remove(buildContext);
|
|
641
|
-
|
|
1191
|
+
|
|
642
1192
|
let pushed = false;
|
|
643
|
-
|
|
1193
|
+
|
|
644
1194
|
// Push to registry if requested
|
|
645
1195
|
if (push) {
|
|
646
1196
|
try {
|
|
@@ -650,16 +1200,17 @@ class DockerManager {
|
|
|
650
1200
|
this.logger.info(`Successfully pushed image: ${imageName}`);
|
|
651
1201
|
pushed = true;
|
|
652
1202
|
} catch (pushError) {
|
|
653
|
-
this.logger.warn(
|
|
1203
|
+
this.logger.warn(
|
|
1204
|
+
`Failed to push image ${imageName}: ${pushError.message}`
|
|
1205
|
+
);
|
|
654
1206
|
// Don't fail the build if push fails
|
|
655
1207
|
}
|
|
656
1208
|
}
|
|
657
|
-
|
|
1209
|
+
|
|
658
1210
|
return {
|
|
659
1211
|
imageName,
|
|
660
|
-
pushed
|
|
1212
|
+
pushed,
|
|
661
1213
|
};
|
|
662
|
-
|
|
663
1214
|
} catch (error) {
|
|
664
1215
|
throw new Error(`Failed to build production image: ${error.message}`);
|
|
665
1216
|
}
|
|
@@ -670,21 +1221,21 @@ class DockerManager {
|
|
|
670
1221
|
*/
|
|
671
1222
|
generateHandlersCode(agent) {
|
|
672
1223
|
const handlers = {};
|
|
673
|
-
|
|
1224
|
+
|
|
674
1225
|
// Add default handlers
|
|
675
1226
|
handlers.output = ['(data) => console.log("Output:", data)'];
|
|
676
1227
|
handlers.error = ['(error) => console.error("Error:", error)'];
|
|
677
|
-
|
|
1228
|
+
|
|
678
1229
|
// Add custom handlers from agent configuration
|
|
679
1230
|
if (agent.handlers && agent.handlers.size > 0) {
|
|
680
1231
|
for (const [eventName, handlerList] of agent.handlers) {
|
|
681
1232
|
if (!handlers[eventName]) {
|
|
682
1233
|
handlers[eventName] = [];
|
|
683
1234
|
}
|
|
684
|
-
|
|
1235
|
+
|
|
685
1236
|
// Convert handler functions to string representations
|
|
686
|
-
handlerList.forEach(handlerObj => {
|
|
687
|
-
if (handlerObj && typeof handlerObj.handler ===
|
|
1237
|
+
handlerList.forEach((handlerObj) => {
|
|
1238
|
+
if (handlerObj && typeof handlerObj.handler === "function") {
|
|
688
1239
|
// Convert function to string, handling the function properly
|
|
689
1240
|
const handlerStr = handlerObj.handler.toString();
|
|
690
1241
|
handlers[eventName].push(handlerStr);
|
|
@@ -692,15 +1243,19 @@ class DockerManager {
|
|
|
692
1243
|
});
|
|
693
1244
|
}
|
|
694
1245
|
}
|
|
695
|
-
|
|
1246
|
+
|
|
696
1247
|
// Generate the JavaScript object code
|
|
697
|
-
const handlersEntries = Object.entries(handlers)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1248
|
+
const handlersEntries = Object.entries(handlers)
|
|
1249
|
+
.map(([eventName, handlerArray]) => {
|
|
1250
|
+
const handlersStr = handlerArray.join(",\n ");
|
|
1251
|
+
// Quote event names that contain special characters (like colons)
|
|
1252
|
+
const quotedEventName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(eventName)
|
|
1253
|
+
? eventName
|
|
1254
|
+
: `"${eventName}"`;
|
|
1255
|
+
return ` ${quotedEventName}: [\n ${handlersStr}\n ]`;
|
|
1256
|
+
})
|
|
1257
|
+
.join(",\n");
|
|
1258
|
+
|
|
704
1259
|
return `{\n${handlersEntries}\n }`;
|
|
705
1260
|
}
|
|
706
1261
|
|
|
@@ -710,16 +1265,21 @@ class DockerManager {
|
|
|
710
1265
|
async startAgent(agent, options = {}) {
|
|
711
1266
|
// Finalize agent configuration (auto-detect features)
|
|
712
1267
|
agent.finalize();
|
|
713
|
-
|
|
1268
|
+
|
|
714
1269
|
const imageName = `dank-agent-${agent.name.toLowerCase()}`;
|
|
715
|
-
const containerName = `dank-${agent.name.toLowerCase()}-${agent.id
|
|
716
|
-
|
|
1270
|
+
const containerName = `dank-${agent.name.toLowerCase()}-${agent.id
|
|
1271
|
+
.split("_")
|
|
1272
|
+
.pop()}`;
|
|
1273
|
+
const baseImageName =
|
|
1274
|
+
agent.config.docker?.baseImage || this.defaultBaseImageName;
|
|
717
1275
|
|
|
718
1276
|
try {
|
|
719
1277
|
// Ensure base image exists
|
|
720
1278
|
const hasBaseImage = await this.hasImage(baseImageName);
|
|
721
1279
|
if (!hasBaseImage) {
|
|
722
|
-
this.logger.info(
|
|
1280
|
+
this.logger.info(
|
|
1281
|
+
`Base image '${baseImageName}' not found for agent ${agent.name}. Pulling...`
|
|
1282
|
+
);
|
|
723
1283
|
await this.pullBaseImage(baseImageName);
|
|
724
1284
|
}
|
|
725
1285
|
|
|
@@ -739,44 +1299,48 @@ class DockerManager {
|
|
|
739
1299
|
CpuQuota: Math.floor(agent.config.resources.cpu * 100000),
|
|
740
1300
|
CpuPeriod: 100000,
|
|
741
1301
|
RestartPolicy: {
|
|
742
|
-
Name:
|
|
743
|
-
MaximumRetryCount: agent.config.resources.maxRestarts || 3
|
|
1302
|
+
Name: "on-failure",
|
|
1303
|
+
MaximumRetryCount: agent.config.resources.maxRestarts || 3,
|
|
744
1304
|
},
|
|
745
1305
|
NetworkMode: this.networkName,
|
|
746
|
-
...this.preparePortConfiguration(agent)
|
|
1306
|
+
...this.preparePortConfiguration(agent),
|
|
747
1307
|
},
|
|
748
1308
|
NetworkingConfig: {
|
|
749
1309
|
EndpointsConfig: {
|
|
750
|
-
[this.networkName]: {}
|
|
751
|
-
}
|
|
1310
|
+
[this.networkName]: {},
|
|
1311
|
+
},
|
|
752
1312
|
},
|
|
753
|
-
...this.prepareExposedPorts(agent)
|
|
1313
|
+
...this.prepareExposedPorts(agent),
|
|
754
1314
|
};
|
|
755
1315
|
|
|
756
1316
|
// Create container
|
|
757
1317
|
this.logger.info(`Creating container for agent: ${agent.name}`);
|
|
758
1318
|
const container = await this.docker.createContainer(containerConfig);
|
|
759
|
-
|
|
1319
|
+
|
|
760
1320
|
// Start container
|
|
761
1321
|
await container.start();
|
|
762
|
-
|
|
1322
|
+
|
|
763
1323
|
// Store container reference
|
|
764
1324
|
this.containers.set(agent.name, {
|
|
765
1325
|
container,
|
|
766
1326
|
agent,
|
|
767
1327
|
startTime: new Date(),
|
|
768
|
-
status:
|
|
1328
|
+
status: "running",
|
|
769
1329
|
});
|
|
770
1330
|
|
|
771
1331
|
agent.containerId = container.id;
|
|
772
|
-
agent.status =
|
|
1332
|
+
agent.status = "running";
|
|
1333
|
+
|
|
1334
|
+
this.logger.info(
|
|
1335
|
+
`Agent ${agent.name} started successfully (${container.id.substring(
|
|
1336
|
+
0,
|
|
1337
|
+
12
|
|
1338
|
+
)})`
|
|
1339
|
+
);
|
|
773
1340
|
|
|
774
|
-
this.logger.info(`Agent ${agent.name} started successfully (${container.id.substring(0, 12)})`);
|
|
775
|
-
|
|
776
1341
|
return container;
|
|
777
|
-
|
|
778
1342
|
} catch (error) {
|
|
779
|
-
agent.status =
|
|
1343
|
+
agent.status = "error";
|
|
780
1344
|
throw new Error(`Failed to start agent ${agent.name}: ${error.message}`);
|
|
781
1345
|
}
|
|
782
1346
|
}
|
|
@@ -792,23 +1356,22 @@ class DockerManager {
|
|
|
792
1356
|
|
|
793
1357
|
try {
|
|
794
1358
|
const { container, agent } = containerInfo;
|
|
795
|
-
|
|
1359
|
+
|
|
796
1360
|
this.logger.info(`Stopping agent: ${agentName}`);
|
|
797
|
-
|
|
1361
|
+
|
|
798
1362
|
if (options.force) {
|
|
799
1363
|
await container.kill();
|
|
800
1364
|
} else {
|
|
801
1365
|
await container.stop({ t: 10 }); // 10 second timeout
|
|
802
1366
|
}
|
|
803
|
-
|
|
1367
|
+
|
|
804
1368
|
await container.remove();
|
|
805
|
-
|
|
1369
|
+
|
|
806
1370
|
this.containers.delete(agentName);
|
|
807
|
-
agent.status =
|
|
1371
|
+
agent.status = "stopped";
|
|
808
1372
|
agent.containerId = null;
|
|
809
|
-
|
|
1373
|
+
|
|
810
1374
|
this.logger.info(`Agent ${agentName} stopped successfully`);
|
|
811
|
-
|
|
812
1375
|
} catch (error) {
|
|
813
1376
|
throw new Error(`Failed to stop agent ${agentName}: ${error.message}`);
|
|
814
1377
|
}
|
|
@@ -820,28 +1383,27 @@ class DockerManager {
|
|
|
820
1383
|
async getAgentStatus(agentName) {
|
|
821
1384
|
const containerInfo = this.containers.get(agentName);
|
|
822
1385
|
if (!containerInfo) {
|
|
823
|
-
return { status:
|
|
1386
|
+
return { status: "not_running" };
|
|
824
1387
|
}
|
|
825
1388
|
|
|
826
1389
|
try {
|
|
827
1390
|
const { container, agent, startTime } = containerInfo;
|
|
828
1391
|
const containerData = await container.inspect();
|
|
829
|
-
|
|
1392
|
+
|
|
830
1393
|
return {
|
|
831
|
-
status: containerData.State.Running ?
|
|
1394
|
+
status: containerData.State.Running ? "running" : "stopped",
|
|
832
1395
|
containerId: container.id,
|
|
833
1396
|
startTime,
|
|
834
1397
|
uptime: Date.now() - startTime.getTime(),
|
|
835
|
-
health: containerData.State.Health?.Status ||
|
|
1398
|
+
health: containerData.State.Health?.Status || "unknown",
|
|
836
1399
|
restartCount: containerData.RestartCount,
|
|
837
1400
|
resources: {
|
|
838
1401
|
memory: agent.config.resources.memory,
|
|
839
|
-
cpu: agent.config.resources.cpu
|
|
840
|
-
}
|
|
1402
|
+
cpu: agent.config.resources.cpu,
|
|
1403
|
+
},
|
|
841
1404
|
};
|
|
842
|
-
|
|
843
1405
|
} catch (error) {
|
|
844
|
-
return { status:
|
|
1406
|
+
return { status: "error", error: error.message };
|
|
845
1407
|
}
|
|
846
1408
|
}
|
|
847
1409
|
|
|
@@ -855,14 +1417,14 @@ class DockerManager {
|
|
|
855
1417
|
}
|
|
856
1418
|
|
|
857
1419
|
const { container } = containerInfo;
|
|
858
|
-
|
|
1420
|
+
|
|
859
1421
|
const logStream = await container.logs({
|
|
860
1422
|
stdout: true,
|
|
861
1423
|
stderr: true,
|
|
862
1424
|
follow: options.follow || false,
|
|
863
1425
|
tail: options.tail || 100,
|
|
864
1426
|
since: options.since || undefined,
|
|
865
|
-
timestamps: true
|
|
1427
|
+
timestamps: true,
|
|
866
1428
|
});
|
|
867
1429
|
|
|
868
1430
|
return logStream;
|
|
@@ -872,22 +1434,25 @@ class DockerManager {
|
|
|
872
1434
|
* Create build context for base image
|
|
873
1435
|
*/
|
|
874
1436
|
async createBaseBuildContext() {
|
|
875
|
-
const contextDir = path.join(__dirname,
|
|
1437
|
+
const contextDir = path.join(__dirname, "../../.build-context-base");
|
|
876
1438
|
await fs.ensureDir(contextDir);
|
|
877
1439
|
|
|
878
1440
|
// Copy Docker files
|
|
879
|
-
await fs.copy(path.join(__dirname,
|
|
880
|
-
|
|
1441
|
+
await fs.copy(path.join(__dirname, "../../docker"), contextDir);
|
|
1442
|
+
|
|
881
1443
|
// Create runtime directory
|
|
882
|
-
const runtimeDir = path.join(contextDir,
|
|
1444
|
+
const runtimeDir = path.join(contextDir, "runtime");
|
|
883
1445
|
await fs.ensureDir(runtimeDir);
|
|
884
|
-
|
|
1446
|
+
|
|
885
1447
|
// Create tarball
|
|
886
|
-
const tarPath = path.join(__dirname,
|
|
887
|
-
await tar.create(
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1448
|
+
const tarPath = path.join(__dirname, "../../.base-build-context.tar");
|
|
1449
|
+
await tar.create(
|
|
1450
|
+
{
|
|
1451
|
+
file: tarPath,
|
|
1452
|
+
cwd: contextDir,
|
|
1453
|
+
},
|
|
1454
|
+
["."]
|
|
1455
|
+
);
|
|
891
1456
|
|
|
892
1457
|
return tarPath;
|
|
893
1458
|
}
|
|
@@ -896,28 +1461,32 @@ class DockerManager {
|
|
|
896
1461
|
* Create build context for agent
|
|
897
1462
|
*/
|
|
898
1463
|
async createAgentBuildContext(agent) {
|
|
899
|
-
const contextDir = path.join(
|
|
1464
|
+
const contextDir = path.join(
|
|
1465
|
+
__dirname,
|
|
1466
|
+
`../../.build-context-${agent.name}`
|
|
1467
|
+
);
|
|
900
1468
|
await fs.ensureDir(contextDir);
|
|
901
1469
|
|
|
902
1470
|
// Get the base image for this agent
|
|
903
|
-
const baseImageName =
|
|
1471
|
+
const baseImageName =
|
|
1472
|
+
agent.config.docker?.baseImage || this.defaultBaseImageName;
|
|
904
1473
|
|
|
905
1474
|
// Create Dockerfile for agent
|
|
906
1475
|
const dockerfile = `FROM ${baseImageName}
|
|
907
1476
|
COPY agent-code/ /app/agent-code/
|
|
908
1477
|
USER dankuser
|
|
909
1478
|
`;
|
|
910
|
-
|
|
911
|
-
await fs.writeFile(path.join(contextDir,
|
|
912
|
-
|
|
1479
|
+
|
|
1480
|
+
await fs.writeFile(path.join(contextDir, "Dockerfile"), dockerfile);
|
|
1481
|
+
|
|
913
1482
|
// Copy agent code if it exists
|
|
914
|
-
const agentCodeDir = path.join(contextDir,
|
|
1483
|
+
const agentCodeDir = path.join(contextDir, "agent-code");
|
|
915
1484
|
await fs.ensureDir(agentCodeDir);
|
|
916
|
-
|
|
1485
|
+
|
|
917
1486
|
// Create basic agent code structure
|
|
918
1487
|
// Generate handlers from agent configuration
|
|
919
1488
|
const handlersCode = this.generateHandlersCode(agent);
|
|
920
|
-
|
|
1489
|
+
|
|
921
1490
|
const agentCode = `
|
|
922
1491
|
// Agent: ${agent.name}
|
|
923
1492
|
// Generated by Dank Agent Service
|
|
@@ -961,15 +1530,21 @@ module.exports = {
|
|
|
961
1530
|
handlers: ${handlersCode}
|
|
962
1531
|
};
|
|
963
1532
|
`;
|
|
964
|
-
|
|
965
|
-
await fs.writeFile(path.join(agentCodeDir,
|
|
966
|
-
|
|
1533
|
+
|
|
1534
|
+
await fs.writeFile(path.join(agentCodeDir, "index.js"), agentCode);
|
|
1535
|
+
|
|
967
1536
|
// Create tarball
|
|
968
|
-
const tarPath = path.join(
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1537
|
+
const tarPath = path.join(
|
|
1538
|
+
__dirname,
|
|
1539
|
+
`../../.agent-${agent.name}-context.tar`
|
|
1540
|
+
);
|
|
1541
|
+
await tar.create(
|
|
1542
|
+
{
|
|
1543
|
+
file: tarPath,
|
|
1544
|
+
cwd: contextDir,
|
|
1545
|
+
},
|
|
1546
|
+
["."]
|
|
1547
|
+
);
|
|
973
1548
|
|
|
974
1549
|
return tarPath;
|
|
975
1550
|
}
|
|
@@ -988,11 +1563,11 @@ module.exports = {
|
|
|
988
1563
|
preparePortConfiguration(agent) {
|
|
989
1564
|
const portConfig = {};
|
|
990
1565
|
const portBindings = {};
|
|
991
|
-
|
|
1566
|
+
|
|
992
1567
|
// Always bind the main agent port
|
|
993
1568
|
const mainPort = agent.config.docker?.port || DOCKER_CONFIG.defaultPort;
|
|
994
1569
|
portBindings[`${mainPort}/tcp`] = [{ HostPort: mainPort.toString() }];
|
|
995
|
-
|
|
1570
|
+
|
|
996
1571
|
// Also bind HTTP port if HTTP is enabled and different from main port
|
|
997
1572
|
if (agent.config.http && agent.config.http.enabled) {
|
|
998
1573
|
const httpPort = agent.config.http.port;
|
|
@@ -1000,11 +1575,11 @@ module.exports = {
|
|
|
1000
1575
|
portBindings[`${httpPort}/tcp`] = [{ HostPort: httpPort.toString() }];
|
|
1001
1576
|
}
|
|
1002
1577
|
}
|
|
1003
|
-
|
|
1578
|
+
|
|
1004
1579
|
// Always bind health check port
|
|
1005
1580
|
const healthPort = DOCKER_CONFIG.healthCheckPort;
|
|
1006
1581
|
portBindings[`${healthPort}/tcp`] = [{ HostPort: healthPort.toString() }];
|
|
1007
|
-
|
|
1582
|
+
|
|
1008
1583
|
portConfig.PortBindings = portBindings;
|
|
1009
1584
|
return portConfig;
|
|
1010
1585
|
}
|
|
@@ -1014,11 +1589,11 @@ module.exports = {
|
|
|
1014
1589
|
*/
|
|
1015
1590
|
prepareExposedPorts(agent) {
|
|
1016
1591
|
const exposedPorts = {};
|
|
1017
|
-
|
|
1592
|
+
|
|
1018
1593
|
// Always expose the main agent port
|
|
1019
1594
|
const mainPort = agent.config.docker?.port || DOCKER_CONFIG.defaultPort;
|
|
1020
1595
|
exposedPorts[`${mainPort}/tcp`] = {};
|
|
1021
|
-
|
|
1596
|
+
|
|
1022
1597
|
// Also expose HTTP port if HTTP is enabled and different from main port
|
|
1023
1598
|
if (agent.config.http && agent.config.http.enabled) {
|
|
1024
1599
|
const httpPort = agent.config.http.port;
|
|
@@ -1026,11 +1601,11 @@ module.exports = {
|
|
|
1026
1601
|
exposedPorts[`${httpPort}/tcp`] = {};
|
|
1027
1602
|
}
|
|
1028
1603
|
}
|
|
1029
|
-
|
|
1604
|
+
|
|
1030
1605
|
// Always expose health check port
|
|
1031
1606
|
const healthPort = DOCKER_CONFIG.healthCheckPort;
|
|
1032
1607
|
exposedPorts[`${healthPort}/tcp`] = {};
|
|
1033
|
-
|
|
1608
|
+
|
|
1034
1609
|
return { ExposedPorts: exposedPorts };
|
|
1035
1610
|
}
|
|
1036
1611
|
|
|
@@ -1046,7 +1621,7 @@ module.exports = {
|
|
|
1046
1621
|
this.logger.info(`Creating Docker network: ${this.networkName}`);
|
|
1047
1622
|
await this.docker.createNetwork({
|
|
1048
1623
|
Name: this.networkName,
|
|
1049
|
-
Driver:
|
|
1624
|
+
Driver: "bridge",
|
|
1050
1625
|
});
|
|
1051
1626
|
} else {
|
|
1052
1627
|
throw error;
|
|
@@ -1074,19 +1649,23 @@ module.exports = {
|
|
|
1074
1649
|
*/
|
|
1075
1650
|
async followBuildProgress(stream, buildName) {
|
|
1076
1651
|
return new Promise((resolve, reject) => {
|
|
1077
|
-
this.docker.modem.followProgress(
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1652
|
+
this.docker.modem.followProgress(
|
|
1653
|
+
stream,
|
|
1654
|
+
(err, res) => {
|
|
1655
|
+
if (err) {
|
|
1656
|
+
reject(err);
|
|
1657
|
+
} else {
|
|
1658
|
+
resolve(res);
|
|
1659
|
+
}
|
|
1660
|
+
},
|
|
1661
|
+
(event) => {
|
|
1662
|
+
if (event.stream) {
|
|
1663
|
+
process.stdout.write(event.stream);
|
|
1664
|
+
} else if (event.status) {
|
|
1665
|
+
this.logger.debug(`${buildName}: ${event.status}`);
|
|
1666
|
+
}
|
|
1088
1667
|
}
|
|
1089
|
-
|
|
1668
|
+
);
|
|
1090
1669
|
});
|
|
1091
1670
|
}
|
|
1092
1671
|
|
|
@@ -1095,21 +1674,27 @@ module.exports = {
|
|
|
1095
1674
|
*/
|
|
1096
1675
|
async followPullProgress(stream, pullName) {
|
|
1097
1676
|
return new Promise((resolve, reject) => {
|
|
1098
|
-
this.docker.modem.followProgress(
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
}, (event) => {
|
|
1105
|
-
if (event.status) {
|
|
1106
|
-
if (event.progress) {
|
|
1107
|
-
process.stdout.write(`\r${pullName}: ${event.status} ${event.progress}`);
|
|
1677
|
+
this.docker.modem.followProgress(
|
|
1678
|
+
stream,
|
|
1679
|
+
(err, res) => {
|
|
1680
|
+
if (err) {
|
|
1681
|
+
reject(err);
|
|
1108
1682
|
} else {
|
|
1109
|
-
|
|
1683
|
+
resolve(res);
|
|
1684
|
+
}
|
|
1685
|
+
},
|
|
1686
|
+
(event) => {
|
|
1687
|
+
if (event.status) {
|
|
1688
|
+
if (event.progress) {
|
|
1689
|
+
process.stdout.write(
|
|
1690
|
+
`\r${pullName}: ${event.status} ${event.progress}`
|
|
1691
|
+
);
|
|
1692
|
+
} else {
|
|
1693
|
+
this.logger.info(`${pullName}: ${event.status}`);
|
|
1694
|
+
}
|
|
1110
1695
|
}
|
|
1111
1696
|
}
|
|
1112
|
-
|
|
1697
|
+
);
|
|
1113
1698
|
});
|
|
1114
1699
|
}
|
|
1115
1700
|
|
|
@@ -1117,26 +1702,28 @@ module.exports = {
|
|
|
1117
1702
|
* Clean up Docker resources
|
|
1118
1703
|
*/
|
|
1119
1704
|
async cleanup(options = {}) {
|
|
1120
|
-
this.logger.info(
|
|
1705
|
+
this.logger.info("Cleaning up Docker resources...");
|
|
1121
1706
|
|
|
1122
1707
|
try {
|
|
1123
1708
|
if (options.containers || options.all) {
|
|
1124
1709
|
// Stop and remove all Dank containers
|
|
1125
1710
|
const containers = await this.docker.listContainers({
|
|
1126
1711
|
all: true,
|
|
1127
|
-
filters: { name: [
|
|
1712
|
+
filters: { name: ["dank-"] },
|
|
1128
1713
|
});
|
|
1129
1714
|
|
|
1130
1715
|
for (const containerInfo of containers) {
|
|
1131
1716
|
const container = this.docker.getContainer(containerInfo.Id);
|
|
1132
1717
|
try {
|
|
1133
|
-
if (containerInfo.State ===
|
|
1718
|
+
if (containerInfo.State === "running") {
|
|
1134
1719
|
await container.stop();
|
|
1135
1720
|
}
|
|
1136
1721
|
await container.remove();
|
|
1137
1722
|
this.logger.info(`Removed container: ${containerInfo.Names[0]}`);
|
|
1138
1723
|
} catch (error) {
|
|
1139
|
-
this.logger.warn(
|
|
1724
|
+
this.logger.warn(
|
|
1725
|
+
`Failed to remove container ${containerInfo.Names[0]}: ${error.message}`
|
|
1726
|
+
);
|
|
1140
1727
|
}
|
|
1141
1728
|
}
|
|
1142
1729
|
}
|
|
@@ -1144,14 +1731,16 @@ module.exports = {
|
|
|
1144
1731
|
if (options.images || options.all) {
|
|
1145
1732
|
// Remove Dank images
|
|
1146
1733
|
const images = await this.docker.listImages({
|
|
1147
|
-
filters: { reference: [
|
|
1734
|
+
filters: { reference: ["dank-*"] },
|
|
1148
1735
|
});
|
|
1149
1736
|
|
|
1150
1737
|
for (const imageInfo of images) {
|
|
1151
1738
|
const image = this.docker.getImage(imageInfo.Id);
|
|
1152
1739
|
try {
|
|
1153
1740
|
await image.remove();
|
|
1154
|
-
this.logger.info(
|
|
1741
|
+
this.logger.info(
|
|
1742
|
+
`Removed image: ${imageInfo.RepoTags?.[0] || imageInfo.Id}`
|
|
1743
|
+
);
|
|
1155
1744
|
} catch (error) {
|
|
1156
1745
|
this.logger.warn(`Failed to remove image: ${error.message}`);
|
|
1157
1746
|
}
|
|
@@ -1163,8 +1752,7 @@ module.exports = {
|
|
|
1163
1752
|
await this.cleanupBuildContexts();
|
|
1164
1753
|
}
|
|
1165
1754
|
|
|
1166
|
-
this.logger.info(
|
|
1167
|
-
|
|
1755
|
+
this.logger.info("Cleanup completed");
|
|
1168
1756
|
} catch (error) {
|
|
1169
1757
|
throw new Error(`Cleanup failed: ${error.message}`);
|
|
1170
1758
|
}
|
|
@@ -1174,13 +1762,15 @@ module.exports = {
|
|
|
1174
1762
|
* Clean up build context directories and tarballs
|
|
1175
1763
|
*/
|
|
1176
1764
|
async cleanupBuildContexts() {
|
|
1177
|
-
const projectRoot = path.join(__dirname,
|
|
1178
|
-
|
|
1765
|
+
const projectRoot = path.join(__dirname, "../..");
|
|
1766
|
+
|
|
1179
1767
|
try {
|
|
1180
1768
|
// Find all build context directories
|
|
1181
1769
|
const entries = await fs.readdir(projectRoot);
|
|
1182
|
-
const buildContextDirs = entries.filter(entry =>
|
|
1183
|
-
|
|
1770
|
+
const buildContextDirs = entries.filter((entry) =>
|
|
1771
|
+
entry.startsWith(".build-context-")
|
|
1772
|
+
);
|
|
1773
|
+
|
|
1184
1774
|
// Remove build context directories
|
|
1185
1775
|
for (const dir of buildContextDirs) {
|
|
1186
1776
|
const dirPath = path.join(projectRoot, dir);
|
|
@@ -1188,29 +1778,33 @@ module.exports = {
|
|
|
1188
1778
|
await fs.remove(dirPath);
|
|
1189
1779
|
this.logger.info(`Removed build context directory: ${dir}`);
|
|
1190
1780
|
} catch (error) {
|
|
1191
|
-
this.logger.warn(
|
|
1781
|
+
this.logger.warn(
|
|
1782
|
+
`Failed to remove build context directory ${dir}: ${error.message}`
|
|
1783
|
+
);
|
|
1192
1784
|
}
|
|
1193
1785
|
}
|
|
1194
|
-
|
|
1786
|
+
|
|
1195
1787
|
// Find and remove tarball files
|
|
1196
|
-
const tarballs = entries.filter(
|
|
1197
|
-
entry
|
|
1788
|
+
const tarballs = entries.filter(
|
|
1789
|
+
(entry) =>
|
|
1790
|
+
entry.endsWith("-context.tar") || entry.endsWith("-build-context.tar")
|
|
1198
1791
|
);
|
|
1199
|
-
|
|
1792
|
+
|
|
1200
1793
|
for (const tarball of tarballs) {
|
|
1201
1794
|
const tarballPath = path.join(projectRoot, tarball);
|
|
1202
1795
|
try {
|
|
1203
1796
|
await fs.remove(tarballPath);
|
|
1204
1797
|
this.logger.info(`Removed build context tarball: ${tarball}`);
|
|
1205
1798
|
} catch (error) {
|
|
1206
|
-
this.logger.warn(
|
|
1799
|
+
this.logger.warn(
|
|
1800
|
+
`Failed to remove tarball ${tarball}: ${error.message}`
|
|
1801
|
+
);
|
|
1207
1802
|
}
|
|
1208
1803
|
}
|
|
1209
|
-
|
|
1804
|
+
|
|
1210
1805
|
if (buildContextDirs.length === 0 && tarballs.length === 0) {
|
|
1211
|
-
this.logger.info(
|
|
1806
|
+
this.logger.info("No build context files found to clean up");
|
|
1212
1807
|
}
|
|
1213
|
-
|
|
1214
1808
|
} catch (error) {
|
|
1215
1809
|
this.logger.warn(`Error during build context cleanup: ${error.message}`);
|
|
1216
1810
|
}
|