@agenticmail/core 0.5.24 → 0.5.26
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/dist/index.d.ts +23 -64
- package/dist/index.js +245 -510
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1412,97 +1412,56 @@ declare class DependencyChecker {
|
|
|
1412
1412
|
type InstallProgress = (message: string) => void;
|
|
1413
1413
|
/**
|
|
1414
1414
|
* DependencyInstaller handles installing all external dependencies.
|
|
1415
|
-
*
|
|
1415
|
+
* Uses Colima + Docker CLI on macOS (no Docker Desktop GUI).
|
|
1416
|
+
* Uses Docker Engine directly on Linux.
|
|
1416
1417
|
*/
|
|
1417
1418
|
declare class DependencyInstaller {
|
|
1418
1419
|
private onProgress;
|
|
1419
|
-
private _firstLaunchMode;
|
|
1420
1420
|
constructor(onProgress?: InstallProgress);
|
|
1421
1421
|
/**
|
|
1422
1422
|
* Ensure Docker is installed AND the daemon is running.
|
|
1423
|
-
* Fully automatic — installs, accepts license, starts daemon, waits for ready.
|
|
1424
|
-
* Never asks the user to do anything manually.
|
|
1425
1423
|
*
|
|
1426
1424
|
* Flow:
|
|
1427
|
-
* 1. docker
|
|
1428
|
-
* 2. docker CLI
|
|
1429
|
-
* 3.
|
|
1430
|
-
* 4. Nothing installed? → install via Homebrew or DMG download, then start, wait
|
|
1425
|
+
* 1. docker info works? → done
|
|
1426
|
+
* 2. docker CLI + colima exist but not running? → colima start
|
|
1427
|
+
* 3. Nothing installed? → install via brew (colima + docker) or Docker Engine (Linux)
|
|
1431
1428
|
*/
|
|
1432
1429
|
installDocker(): Promise<void>;
|
|
1433
1430
|
/** Check if `docker info` succeeds (CLI + daemon both working). */
|
|
1434
1431
|
private isDockerReady;
|
|
1435
|
-
/** Check if `docker` CLI is in PATH (daemon may be stopped). */
|
|
1436
|
-
private isDockerCliInstalled;
|
|
1437
1432
|
/**
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1440
|
-
* Validates symlinks aren't broken.
|
|
1441
|
-
*/
|
|
1442
|
-
private findDockerApp;
|
|
1443
|
-
/**
|
|
1444
|
-
* Check if Docker Desktop license has already been accepted.
|
|
1445
|
-
* Returns true if we're confident the user has accepted before.
|
|
1446
|
-
*/
|
|
1447
|
-
private isDockerLicenseAccepted;
|
|
1448
|
-
/**
|
|
1449
|
-
* Configure Docker Desktop to run headless after first-time setup.
|
|
1450
|
-
* Call this AFTER Docker has been started and license accepted.
|
|
1451
|
-
* Suppresses UI, dock icon, tips, analytics, and auto-updates.
|
|
1452
|
-
*/
|
|
1453
|
-
private configureDockerHeadless;
|
|
1454
|
-
/**
|
|
1455
|
-
* Docker.app exists but CLI isn't available or daemon isn't running.
|
|
1456
|
-
* Runs the built-in installer silently (--accept-license to link CLI tools),
|
|
1457
|
-
* then starts Docker Desktop hidden (no GUI window).
|
|
1458
|
-
*/
|
|
1459
|
-
/**
|
|
1460
|
-
* Docker.app exists but daemon isn't running.
|
|
1461
|
-
* Simple approach: if Docker was already running, great. If not, just
|
|
1462
|
-
* tell the user to open Docker and accept the terms themselves.
|
|
1463
|
-
* No GUI manipulation, no programmatic license tricks — just wait.
|
|
1464
|
-
*/
|
|
1465
|
-
private setupExistingDockerApp;
|
|
1466
|
-
/**
|
|
1467
|
-
* Hide Docker Desktop completely — close all windows, hide from dock, make invisible.
|
|
1468
|
-
* Called after starting Docker to ensure the user never sees any Docker UI.
|
|
1469
|
-
*/
|
|
1470
|
-
private hideDockerWindow;
|
|
1471
|
-
/**
|
|
1472
|
-
* Full Docker Desktop install on macOS.
|
|
1473
|
-
* Tries Homebrew first (cleaner), falls back to DMG download.
|
|
1474
|
-
* After install, tells the user to open Docker and accept the terms.
|
|
1475
|
-
* No GUI manipulation — the user handles Docker themselves.
|
|
1433
|
+
* macOS: Install Docker via Colima (CLI-only, no GUI, no license dialogs).
|
|
1434
|
+
* Installs colima + docker + docker-compose via Homebrew, then starts Colima.
|
|
1476
1435
|
*/
|
|
1436
|
+
private installDockerMac;
|
|
1477
1437
|
/**
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1438
|
+
* Link docker-compose as a Docker CLI plugin so `docker compose` (v2 syntax) works.
|
|
1439
|
+
* Brew installs docker-compose as a standalone binary, but many tools expect
|
|
1440
|
+
* the `docker compose` subcommand.
|
|
1481
1441
|
*/
|
|
1482
|
-
private
|
|
1442
|
+
private linkComposePlugin;
|
|
1483
1443
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
* automatically, but the DMG path doesn't).
|
|
1444
|
+
* Start Colima and wait for Docker to be ready.
|
|
1486
1445
|
*/
|
|
1487
|
-
private
|
|
1488
|
-
private installDockerMac;
|
|
1446
|
+
private startColima;
|
|
1489
1447
|
/**
|
|
1490
1448
|
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
1491
1449
|
* Also adds the current user to the docker group for rootless usage.
|
|
1492
1450
|
*/
|
|
1493
1451
|
private installDockerLinux;
|
|
1494
1452
|
/**
|
|
1495
|
-
*
|
|
1496
|
-
|
|
1497
|
-
|
|
1453
|
+
* Wait for Docker daemon on Linux.
|
|
1454
|
+
*/
|
|
1455
|
+
private waitForDockerLinux;
|
|
1456
|
+
/**
|
|
1457
|
+
* Windows: Install Docker via WSL2 + Docker Engine, or guide user to Docker Desktop.
|
|
1458
|
+
* Prefers WSL2 with Docker Engine (no GUI needed).
|
|
1498
1459
|
*/
|
|
1499
|
-
private
|
|
1460
|
+
private installDockerWindows;
|
|
1500
1461
|
/**
|
|
1501
|
-
* Wait for Docker daemon
|
|
1502
|
-
* Cycles through multiple start strategies automatically.
|
|
1503
|
-
* Reports progress as a percentage (0-100).
|
|
1462
|
+
* Wait for Docker daemon on Windows.
|
|
1504
1463
|
*/
|
|
1505
|
-
private
|
|
1464
|
+
private waitForDockerWindows;
|
|
1506
1465
|
/**
|
|
1507
1466
|
* Start the Stalwart mail server Docker container.
|
|
1508
1467
|
*/
|
package/dist/index.js
CHANGED
|
@@ -850,14 +850,14 @@ var StalwartAdmin = class {
|
|
|
850
850
|
* Critical for email deliverability — must match the sending domain.
|
|
851
851
|
*/
|
|
852
852
|
async setHostname(domain) {
|
|
853
|
-
const { readFileSync:
|
|
853
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync5 } = await import("fs");
|
|
854
854
|
const { homedir: homedir8 } = await import("os");
|
|
855
855
|
const { join: join9 } = await import("path");
|
|
856
856
|
const configPath = join9(homedir8(), ".agenticmail", "stalwart.toml");
|
|
857
857
|
try {
|
|
858
|
-
let config =
|
|
858
|
+
let config = readFileSync4(configPath, "utf-8");
|
|
859
859
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${domain}"`);
|
|
860
|
-
|
|
860
|
+
writeFileSync5(configPath, config);
|
|
861
861
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
862
862
|
} catch (err) {
|
|
863
863
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -975,12 +975,12 @@ var StalwartAdmin = class {
|
|
|
975
975
|
* This bypasses the need for a PTR record on the sending IP.
|
|
976
976
|
*/
|
|
977
977
|
async configureOutboundRelay(config) {
|
|
978
|
-
const { readFileSync:
|
|
978
|
+
const { readFileSync: readFileSync4, writeFileSync: writeFileSync5 } = await import("fs");
|
|
979
979
|
const { homedir: homedir8 } = await import("os");
|
|
980
980
|
const { join: join9 } = await import("path");
|
|
981
981
|
const routeName = config.routeName ?? "gmail";
|
|
982
982
|
const tomlPath = join9(homedir8(), ".agenticmail", "stalwart.toml");
|
|
983
|
-
let toml =
|
|
983
|
+
let toml = readFileSync4(tomlPath, "utf-8");
|
|
984
984
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
985
985
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
986
986
|
toml += `
|
|
@@ -999,7 +999,7 @@ auth.secret = "${config.password}"
|
|
|
999
999
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
1000
1000
|
{ else = "'${routeName}'" } ]
|
|
1001
1001
|
`;
|
|
1002
|
-
|
|
1002
|
+
writeFileSync5(tomlPath, toml, "utf-8");
|
|
1003
1003
|
await this.restartContainer();
|
|
1004
1004
|
}
|
|
1005
1005
|
};
|
|
@@ -5180,9 +5180,9 @@ var GatewayManager = class {
|
|
|
5180
5180
|
const { homedir: homedir8 } = await import("os");
|
|
5181
5181
|
const backupDir = join4(homedir8(), ".agenticmail");
|
|
5182
5182
|
const backupPath = join4(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
5183
|
-
const { writeFileSync:
|
|
5184
|
-
|
|
5185
|
-
|
|
5183
|
+
const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync5 } = await import("fs");
|
|
5184
|
+
mkdirSync5(backupDir, { recursive: true });
|
|
5185
|
+
writeFileSync5(backupPath, JSON.stringify({
|
|
5186
5186
|
domain,
|
|
5187
5187
|
zoneId: zone.id,
|
|
5188
5188
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5813,7 +5813,7 @@ var RELAY_PRESETS = {
|
|
|
5813
5813
|
|
|
5814
5814
|
// src/setup/index.ts
|
|
5815
5815
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5816
|
-
import { existsSync as existsSync6, readFileSync as
|
|
5816
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, chmodSync } from "fs";
|
|
5817
5817
|
import { join as join8 } from "path";
|
|
5818
5818
|
import { homedir as homedir7 } from "os";
|
|
5819
5819
|
|
|
@@ -5894,7 +5894,7 @@ var DependencyChecker = class {
|
|
|
5894
5894
|
|
|
5895
5895
|
// src/setup/installer.ts
|
|
5896
5896
|
import { execFileSync as execFileSync2, execSync, spawn as spawnChild } from "child_process";
|
|
5897
|
-
import { existsSync as existsSync4
|
|
5897
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5898
5898
|
import { writeFile, rename, chmod as chmod2, mkdir as mkdir2, unlink } from "fs/promises";
|
|
5899
5899
|
import { join as join6 } from "path";
|
|
5900
5900
|
import { homedir as homedir5, platform as platform2, arch as arch2 } from "os";
|
|
@@ -6034,26 +6034,17 @@ function runSilent(command, args, opts = {}) {
|
|
|
6034
6034
|
child.on("error", reject);
|
|
6035
6035
|
});
|
|
6036
6036
|
}
|
|
6037
|
-
function
|
|
6037
|
+
function hasHomebrew() {
|
|
6038
6038
|
try {
|
|
6039
|
-
|
|
6040
|
-
if (stat.isSymbolicLink()) {
|
|
6041
|
-
try {
|
|
6042
|
-
const target = readlinkSync(p);
|
|
6043
|
-
const resolved = target.startsWith("/") ? target : join6(p, "..", target);
|
|
6044
|
-
return existsSync4(resolved);
|
|
6045
|
-
} catch {
|
|
6046
|
-
return false;
|
|
6047
|
-
}
|
|
6048
|
-
}
|
|
6039
|
+
execFileSync2("brew", ["--version"], { timeout: 5e3, stdio: "ignore" });
|
|
6049
6040
|
return true;
|
|
6050
6041
|
} catch {
|
|
6051
6042
|
return false;
|
|
6052
6043
|
}
|
|
6053
6044
|
}
|
|
6054
|
-
function
|
|
6045
|
+
function hasCommand(cmd) {
|
|
6055
6046
|
try {
|
|
6056
|
-
execFileSync2("
|
|
6047
|
+
execFileSync2("which", [cmd], { timeout: 5e3, stdio: "ignore" });
|
|
6057
6048
|
return true;
|
|
6058
6049
|
} catch {
|
|
6059
6050
|
return false;
|
|
@@ -6061,41 +6052,27 @@ function hasHomebrew() {
|
|
|
6061
6052
|
}
|
|
6062
6053
|
var DependencyInstaller = class {
|
|
6063
6054
|
onProgress;
|
|
6064
|
-
_firstLaunchMode = false;
|
|
6065
6055
|
constructor(onProgress) {
|
|
6066
6056
|
this.onProgress = onProgress ?? (() => {
|
|
6067
6057
|
});
|
|
6068
6058
|
}
|
|
6069
6059
|
/**
|
|
6070
6060
|
* Ensure Docker is installed AND the daemon is running.
|
|
6071
|
-
* Fully automatic — installs, accepts license, starts daemon, waits for ready.
|
|
6072
|
-
* Never asks the user to do anything manually.
|
|
6073
6061
|
*
|
|
6074
6062
|
* Flow:
|
|
6075
|
-
* 1. docker
|
|
6076
|
-
* 2. docker CLI
|
|
6077
|
-
* 3.
|
|
6078
|
-
* 4. Nothing installed? → install via Homebrew or DMG download, then start, wait
|
|
6063
|
+
* 1. docker info works? → done
|
|
6064
|
+
* 2. docker CLI + colima exist but not running? → colima start
|
|
6065
|
+
* 3. Nothing installed? → install via brew (colima + docker) or Docker Engine (Linux)
|
|
6079
6066
|
*/
|
|
6080
6067
|
async installDocker() {
|
|
6081
6068
|
if (this.isDockerReady()) return;
|
|
6082
|
-
if (this.isDockerCliInstalled()) {
|
|
6083
|
-
this.onProgress("__progress__:10:Docker installed \u2014 starting the engine...");
|
|
6084
|
-
this.startDockerDaemon();
|
|
6085
|
-
await this.waitForDocker();
|
|
6086
|
-
return;
|
|
6087
|
-
}
|
|
6088
|
-
const dockerApp = this.findDockerApp();
|
|
6089
|
-
if (dockerApp) {
|
|
6090
|
-
this.onProgress("__progress__:10:Setting up mail server engine...");
|
|
6091
|
-
await this.setupExistingDockerApp(dockerApp);
|
|
6092
|
-
return;
|
|
6093
|
-
}
|
|
6094
6069
|
const os = platform2();
|
|
6095
6070
|
if (os === "darwin") {
|
|
6096
6071
|
await this.installDockerMac();
|
|
6097
6072
|
} else if (os === "linux") {
|
|
6098
6073
|
await this.installDockerLinux();
|
|
6074
|
+
} else if (os === "win32") {
|
|
6075
|
+
await this.installDockerWindows();
|
|
6099
6076
|
} else {
|
|
6100
6077
|
throw new Error(
|
|
6101
6078
|
`Docker auto-install isn't supported on ${os} yet. Install it from https://docs.docker.com/get-docker/ and try again.`
|
|
@@ -6111,334 +6088,116 @@ var DependencyInstaller = class {
|
|
|
6111
6088
|
return false;
|
|
6112
6089
|
}
|
|
6113
6090
|
}
|
|
6114
|
-
/** Check if `docker` CLI is in PATH (daemon may be stopped). */
|
|
6115
|
-
isDockerCliInstalled() {
|
|
6116
|
-
try {
|
|
6117
|
-
execFileSync2("docker", ["--version"], { timeout: 5e3, stdio: "ignore" });
|
|
6118
|
-
return true;
|
|
6119
|
-
} catch {
|
|
6120
|
-
return false;
|
|
6121
|
-
}
|
|
6122
|
-
}
|
|
6123
6091
|
/**
|
|
6124
|
-
*
|
|
6125
|
-
*
|
|
6126
|
-
* Validates symlinks aren't broken.
|
|
6092
|
+
* macOS: Install Docker via Colima (CLI-only, no GUI, no license dialogs).
|
|
6093
|
+
* Installs colima + docker + docker-compose via Homebrew, then starts Colima.
|
|
6127
6094
|
*/
|
|
6128
|
-
|
|
6129
|
-
if (
|
|
6130
|
-
|
|
6095
|
+
async installDockerMac() {
|
|
6096
|
+
if (hasCommand("colima") && hasCommand("docker")) {
|
|
6097
|
+
this.onProgress("__progress__:10:Starting container engine...");
|
|
6098
|
+
await this.startColima();
|
|
6099
|
+
return;
|
|
6131
6100
|
}
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
const caskNames = ["docker-desktop", "docker"];
|
|
6137
|
-
for (const base of caskroomBases) {
|
|
6138
|
-
for (const name of caskNames) {
|
|
6139
|
-
const caskroom = join6(base, name);
|
|
6140
|
-
if (!existsSync4(caskroom)) continue;
|
|
6141
|
-
try {
|
|
6142
|
-
const versions = readdirSync(caskroom).filter((d) => !d.startsWith("."));
|
|
6143
|
-
for (const ver of versions) {
|
|
6144
|
-
const appPath = join6(caskroom, ver, "Docker.app");
|
|
6145
|
-
if (existsReal(appPath)) return appPath;
|
|
6146
|
-
}
|
|
6147
|
-
} catch {
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6101
|
+
if (!hasHomebrew()) {
|
|
6102
|
+
throw new Error(
|
|
6103
|
+
"Homebrew is required to install Docker on macOS.\nInstall it from https://brew.sh and try again."
|
|
6104
|
+
);
|
|
6150
6105
|
}
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
const raw = readFileSync2(systemFile, "utf-8");
|
|
6162
|
-
const data = JSON.parse(raw);
|
|
6163
|
-
if (data.acceptLicense) return true;
|
|
6164
|
-
}
|
|
6165
|
-
} catch {
|
|
6106
|
+
this.onProgress("__progress__:5:Installing container engine...");
|
|
6107
|
+
const brewResult = await runSilent(
|
|
6108
|
+
"brew",
|
|
6109
|
+
["install", "colima", "docker", "docker-compose"],
|
|
6110
|
+
{ timeout: 6e5 }
|
|
6111
|
+
);
|
|
6112
|
+
if (brewResult.exitCode !== 0) {
|
|
6113
|
+
throw new Error(
|
|
6114
|
+
"Failed to install Docker via Homebrew.\nTry running manually: brew install colima docker docker-compose\n" + brewResult.fullOutput.slice(-500)
|
|
6115
|
+
);
|
|
6166
6116
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
}
|
|
6172
|
-
} catch {
|
|
6117
|
+
if (!hasCommand("colima") || !hasCommand("docker")) {
|
|
6118
|
+
throw new Error(
|
|
6119
|
+
"Installation completed but colima/docker not found in PATH.\nTry running manually: brew install colima docker docker-compose"
|
|
6120
|
+
);
|
|
6173
6121
|
}
|
|
6174
|
-
|
|
6122
|
+
this.linkComposePlugin();
|
|
6123
|
+
this.onProgress("__progress__:50:Starting container engine...");
|
|
6124
|
+
await this.startColima();
|
|
6175
6125
|
}
|
|
6176
6126
|
/**
|
|
6177
|
-
*
|
|
6178
|
-
*
|
|
6179
|
-
*
|
|
6127
|
+
* Link docker-compose as a Docker CLI plugin so `docker compose` (v2 syntax) works.
|
|
6128
|
+
* Brew installs docker-compose as a standalone binary, but many tools expect
|
|
6129
|
+
* the `docker compose` subcommand.
|
|
6180
6130
|
*/
|
|
6181
|
-
|
|
6131
|
+
linkComposePlugin() {
|
|
6182
6132
|
try {
|
|
6183
|
-
const
|
|
6184
|
-
|
|
6185
|
-
const
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
// No analytics prompts
|
|
6196
|
-
};
|
|
6197
|
-
if (existsSync4(settingsFile)) {
|
|
6198
|
-
const raw = readFileSync2(settingsFile, "utf-8");
|
|
6199
|
-
try {
|
|
6200
|
-
const settings = JSON.parse(raw);
|
|
6201
|
-
let changed = false;
|
|
6202
|
-
for (const [key, val] of Object.entries(headlessDefaults)) {
|
|
6203
|
-
if (settings[key] !== val) {
|
|
6204
|
-
settings[key] = val;
|
|
6205
|
-
changed = true;
|
|
6206
|
-
}
|
|
6207
|
-
}
|
|
6208
|
-
if (changed) {
|
|
6209
|
-
writeFileSync3(settingsFile, JSON.stringify(settings, null, 2));
|
|
6210
|
-
}
|
|
6211
|
-
} catch {
|
|
6212
|
-
}
|
|
6213
|
-
} else if (existsSync4(userDockerDir)) {
|
|
6214
|
-
writeFileSync3(settingsFile, JSON.stringify(headlessDefaults, null, 2));
|
|
6133
|
+
const composeBin = execFileSync2("which", ["docker-compose"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6134
|
+
if (!composeBin) return;
|
|
6135
|
+
const pluginDir = join6(homedir5(), ".docker", "cli-plugins");
|
|
6136
|
+
const pluginPath = join6(pluginDir, "docker-compose");
|
|
6137
|
+
if (existsSync4(pluginPath)) return;
|
|
6138
|
+
try {
|
|
6139
|
+
execSync(`mkdir -p "${pluginDir}"`, { timeout: 5e3, stdio: "ignore" });
|
|
6140
|
+
} catch {
|
|
6141
|
+
}
|
|
6142
|
+
try {
|
|
6143
|
+
execSync(`ln -sf "${composeBin}" "${pluginPath}"`, { timeout: 5e3, stdio: "ignore" });
|
|
6144
|
+
} catch {
|
|
6215
6145
|
}
|
|
6216
6146
|
} catch {
|
|
6217
6147
|
}
|
|
6218
6148
|
}
|
|
6219
6149
|
/**
|
|
6220
|
-
*
|
|
6221
|
-
* Runs the built-in installer silently (--accept-license to link CLI tools),
|
|
6222
|
-
* then starts Docker Desktop hidden (no GUI window).
|
|
6223
|
-
*/
|
|
6224
|
-
/**
|
|
6225
|
-
* Docker.app exists but daemon isn't running.
|
|
6226
|
-
* Simple approach: if Docker was already running, great. If not, just
|
|
6227
|
-
* tell the user to open Docker and accept the terms themselves.
|
|
6228
|
-
* No GUI manipulation, no programmatic license tricks — just wait.
|
|
6150
|
+
* Start Colima and wait for Docker to be ready.
|
|
6229
6151
|
*/
|
|
6230
|
-
async
|
|
6152
|
+
async startColima() {
|
|
6231
6153
|
if (this.isDockerReady()) {
|
|
6232
6154
|
this.onProgress("__progress__:100:Engine is ready!");
|
|
6233
6155
|
return;
|
|
6234
6156
|
}
|
|
6235
|
-
|
|
6236
|
-
|
|
6157
|
+
const startResult = await runSilent(
|
|
6158
|
+
"colima",
|
|
6159
|
+
["start", "--cpu", "2", "--memory", "2", "--disk", "10"],
|
|
6160
|
+
{ timeout: 3e5 }
|
|
6161
|
+
);
|
|
6162
|
+
if (startResult.exitCode !== 0) {
|
|
6163
|
+
throw new Error(
|
|
6164
|
+
"Failed to start Colima. Try running manually: colima start\n" + startResult.fullOutput.slice(-500)
|
|
6165
|
+
);
|
|
6166
|
+
}
|
|
6167
|
+
const totalTime = 6e4;
|
|
6237
6168
|
const start = Date.now();
|
|
6238
6169
|
while (Date.now() - start < totalTime) {
|
|
6239
|
-
|
|
6240
|
-
execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
|
|
6170
|
+
if (this.isDockerReady()) {
|
|
6241
6171
|
this.onProgress("__progress__:100:Engine is ready!");
|
|
6242
|
-
this.configureDockerHeadless();
|
|
6243
|
-
this.hideDockerWindow();
|
|
6244
6172
|
return;
|
|
6245
|
-
} catch {
|
|
6246
6173
|
}
|
|
6247
6174
|
const elapsed = Date.now() - start;
|
|
6248
|
-
const pct = Math.min(95, Math.round(elapsed / totalTime *
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
"Waiting for Docker to be ready...",
|
|
6252
|
-
"Accept the terms in Docker Desktop to continue...",
|
|
6253
|
-
"Still waiting for Docker..."
|
|
6254
|
-
];
|
|
6255
|
-
const msgIdx = Math.floor(elapsed / 8e3) % msgs.length;
|
|
6256
|
-
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
6257
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
6175
|
+
const pct = Math.min(95, 50 + Math.round(elapsed / totalTime * 50));
|
|
6176
|
+
this.onProgress(`__progress__:${pct}:Waiting for engine...`);
|
|
6177
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
6258
6178
|
}
|
|
6259
6179
|
throw new Error(
|
|
6260
|
-
"Docker did not start
|
|
6180
|
+
"Docker engine did not start in time. Try running manually: colima start"
|
|
6261
6181
|
);
|
|
6262
6182
|
}
|
|
6263
6183
|
/**
|
|
6264
|
-
*
|
|
6265
|
-
*
|
|
6266
|
-
*/
|
|
6267
|
-
hideDockerWindow() {
|
|
6268
|
-
try {
|
|
6269
|
-
execSync(
|
|
6270
|
-
`osascript -e 'tell application "System Events" to tell process "Docker Desktop" to set visible to false' 2>/dev/null`,
|
|
6271
|
-
{ timeout: 5e3, stdio: "ignore" }
|
|
6272
|
-
);
|
|
6273
|
-
} catch {
|
|
6274
|
-
}
|
|
6275
|
-
try {
|
|
6276
|
-
execSync(
|
|
6277
|
-
`osascript -e 'tell application "Docker Desktop" to close every window' 2>/dev/null`,
|
|
6278
|
-
{ timeout: 5e3, stdio: "ignore" }
|
|
6279
|
-
);
|
|
6280
|
-
} catch {
|
|
6281
|
-
}
|
|
6282
|
-
try {
|
|
6283
|
-
execSync(
|
|
6284
|
-
`osascript -e 'tell application "Docker" to close every window' 2>/dev/null`,
|
|
6285
|
-
{ timeout: 5e3, stdio: "ignore" }
|
|
6286
|
-
);
|
|
6287
|
-
} catch {
|
|
6288
|
-
}
|
|
6289
|
-
const dockerApp = this.findDockerApp();
|
|
6290
|
-
if (dockerApp) {
|
|
6291
|
-
const plistPath = join6(dockerApp, "Contents", "Info.plist");
|
|
6292
|
-
try {
|
|
6293
|
-
const plistContent = readFileSync2(plistPath, "utf-8");
|
|
6294
|
-
if (!plistContent.includes("LSUIElement")) {
|
|
6295
|
-
execSync(
|
|
6296
|
-
`/usr/libexec/PlistBuddy -c "Add :LSUIElement bool true" "${plistPath}" 2>/dev/null`,
|
|
6297
|
-
{ timeout: 5e3, stdio: "ignore" }
|
|
6298
|
-
);
|
|
6299
|
-
} else if (!plistContent.includes("<key>LSUIElement</key>\n <true/>") && !plistContent.includes("<key>LSUIElement</key><true/>")) {
|
|
6300
|
-
execSync(
|
|
6301
|
-
`/usr/libexec/PlistBuddy -c "Set :LSUIElement true" "${plistPath}" 2>/dev/null`,
|
|
6302
|
-
{ timeout: 5e3, stdio: "ignore" }
|
|
6303
|
-
);
|
|
6304
|
-
}
|
|
6305
|
-
} catch {
|
|
6306
|
-
}
|
|
6307
|
-
}
|
|
6308
|
-
}
|
|
6309
|
-
/**
|
|
6310
|
-
* Full Docker Desktop install on macOS.
|
|
6311
|
-
* Tries Homebrew first (cleaner), falls back to DMG download.
|
|
6312
|
-
* After install, tells the user to open Docker and accept the terms.
|
|
6313
|
-
* No GUI manipulation — the user handles Docker themselves.
|
|
6314
|
-
*/
|
|
6315
|
-
/**
|
|
6316
|
-
* Ensure /usr/local/bin and /usr/local/cli-plugins are writable by the
|
|
6317
|
-
* current user. Brew needs to create symlinks there and will fail silently
|
|
6318
|
-
* if they're root-owned (it tries sudo which fails non-interactively).
|
|
6319
|
-
*/
|
|
6320
|
-
fixLocalDirPermissions() {
|
|
6321
|
-
const user = process.env.USER || "";
|
|
6322
|
-
if (!user) return;
|
|
6323
|
-
const dirs = ["/usr/local/bin", "/usr/local/cli-plugins"];
|
|
6324
|
-
for (const dir of dirs) {
|
|
6325
|
-
try {
|
|
6326
|
-
if (!existsSync4(dir)) {
|
|
6327
|
-
execSync(`mkdir -p "${dir}"`, { timeout: 5e3, stdio: "ignore" });
|
|
6328
|
-
}
|
|
6329
|
-
const stat = execSync(`stat -f '%Su' "${dir}"`, { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
6330
|
-
if (stat !== user) {
|
|
6331
|
-
try {
|
|
6332
|
-
execSync(`find "${dir}" -maxdepth 1 -type l ! -exec test -e {} \\; -delete 2>/dev/null`, { timeout: 5e3, stdio: "ignore" });
|
|
6333
|
-
} catch {
|
|
6334
|
-
}
|
|
6335
|
-
try {
|
|
6336
|
-
execSync(`chown -R ${user} "${dir}"`, { timeout: 5e3, stdio: "ignore" });
|
|
6337
|
-
} catch {
|
|
6338
|
-
}
|
|
6339
|
-
}
|
|
6340
|
-
} catch {
|
|
6341
|
-
}
|
|
6342
|
-
}
|
|
6343
|
-
}
|
|
6344
|
-
/**
|
|
6345
|
-
* After DMG install, manually link Docker CLI tools (brew does this
|
|
6346
|
-
* automatically, but the DMG path doesn't).
|
|
6184
|
+
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
6185
|
+
* Also adds the current user to the docker group for rootless usage.
|
|
6347
6186
|
*/
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
[join6(appPath, "Contents/Resources/bin/docker-credential-desktop"), "/usr/local/bin/docker-credential-desktop"],
|
|
6352
|
-
[join6(appPath, "Contents/Resources/bin/docker-credential-ecr-login"), "/usr/local/bin/docker-credential-ecr-login"],
|
|
6353
|
-
[join6(appPath, "Contents/Resources/bin/docker-credential-osxkeychain"), "/usr/local/bin/docker-credential-osxkeychain"],
|
|
6354
|
-
[join6(appPath, "Contents/Resources/cli-plugins/docker-compose"), "/usr/local/cli-plugins/docker-compose"]
|
|
6355
|
-
];
|
|
6356
|
-
for (const [src, dest] of links) {
|
|
6357
|
-
try {
|
|
6358
|
-
if (existsSync4(src)) {
|
|
6359
|
-
const destDir = join6(dest, "..");
|
|
6360
|
-
if (!existsSync4(destDir)) mkdirSync3(destDir, { recursive: true });
|
|
6361
|
-
try {
|
|
6362
|
-
execSync(`rm -f "${dest}"`, { timeout: 3e3, stdio: "ignore" });
|
|
6363
|
-
} catch {
|
|
6364
|
-
}
|
|
6365
|
-
execSync(`ln -s "${src}" "${dest}"`, { timeout: 3e3, stdio: "ignore" });
|
|
6366
|
-
}
|
|
6367
|
-
} catch {
|
|
6368
|
-
}
|
|
6369
|
-
}
|
|
6370
|
-
}
|
|
6371
|
-
async installDockerMac() {
|
|
6372
|
-
let installed = false;
|
|
6373
|
-
this.fixLocalDirPermissions();
|
|
6374
|
-
if (hasHomebrew()) {
|
|
6375
|
-
this.onProgress("__progress__:5:Installing Docker Desktop...");
|
|
6187
|
+
async installDockerLinux() {
|
|
6188
|
+
if (hasCommand("docker")) {
|
|
6189
|
+
this.onProgress("__progress__:10:Starting Docker service...");
|
|
6376
6190
|
try {
|
|
6377
|
-
|
|
6378
|
-
"brew",
|
|
6379
|
-
["install", "--cask", "docker"],
|
|
6380
|
-
{ timeout: 6e5 }
|
|
6381
|
-
);
|
|
6382
|
-
if (brewResult.exitCode === 0 && this.findDockerApp()) {
|
|
6383
|
-
installed = true;
|
|
6384
|
-
}
|
|
6191
|
+
execSync("sudo systemctl start docker", { timeout: 15e3, stdio: "ignore" });
|
|
6385
6192
|
} catch {
|
|
6386
|
-
}
|
|
6387
|
-
}
|
|
6388
|
-
if (!installed) {
|
|
6389
|
-
const cpu = arch2();
|
|
6390
|
-
const archName = cpu === "arm64" ? "arm64" : "amd64";
|
|
6391
|
-
const dmgUrl = `https://desktop.docker.com/mac/main/${archName}/Docker.dmg`;
|
|
6392
|
-
const dmgPath = "/tmp/Docker.dmg";
|
|
6393
|
-
this.onProgress("__progress__:5:Downloading Docker Desktop...");
|
|
6394
|
-
const dlResult = await runSilent("curl", [
|
|
6395
|
-
"-fSL",
|
|
6396
|
-
"-o",
|
|
6397
|
-
dmgPath,
|
|
6398
|
-
dmgUrl
|
|
6399
|
-
], { timeout: 6e5 });
|
|
6400
|
-
if (dlResult.exitCode !== 0) {
|
|
6401
|
-
throw new Error("Failed to download Docker Desktop. Check your internet connection and try again.");
|
|
6402
|
-
}
|
|
6403
|
-
this.onProgress("__progress__:40:Installing Docker Desktop...");
|
|
6404
|
-
try {
|
|
6405
6193
|
try {
|
|
6406
|
-
execSync("
|
|
6194
|
+
execSync("sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6407
6195
|
} catch {
|
|
6408
6196
|
}
|
|
6409
|
-
execSync(`hdiutil attach "${dmgPath}" -nobrowse -quiet`, { timeout: 3e4, stdio: "ignore" });
|
|
6410
|
-
} catch {
|
|
6411
|
-
throw new Error("Failed to mount Docker DMG. The download may be corrupted \u2014 try again.");
|
|
6412
6197
|
}
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
execSync('cp -R "/Volumes/Docker/Docker.app" /Applications/', { timeout: 6e4, stdio: "ignore" });
|
|
6416
|
-
} catch {
|
|
6417
|
-
throw new Error("Failed to install. You may need to run this with admin privileges.");
|
|
6418
|
-
}
|
|
6419
|
-
}
|
|
6420
|
-
try {
|
|
6421
|
-
execSync("hdiutil detach /Volumes/Docker -quiet", { timeout: 15e3, stdio: "ignore" });
|
|
6422
|
-
} catch {
|
|
6423
|
-
}
|
|
6424
|
-
try {
|
|
6425
|
-
await unlink(dmgPath);
|
|
6426
|
-
} catch {
|
|
6427
|
-
}
|
|
6428
|
-
const appPath2 = this.findDockerApp();
|
|
6429
|
-
if (appPath2) this.linkDockerCli(appPath2);
|
|
6430
|
-
}
|
|
6431
|
-
const appPath = this.findDockerApp();
|
|
6432
|
-
if (!appPath) {
|
|
6433
|
-
throw new Error("Docker Desktop was installed but could not be found. Try again.");
|
|
6198
|
+
await this.waitForDockerLinux();
|
|
6199
|
+
return;
|
|
6434
6200
|
}
|
|
6435
|
-
await this.setupExistingDockerApp(appPath);
|
|
6436
|
-
}
|
|
6437
|
-
/**
|
|
6438
|
-
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
6439
|
-
* Also adds the current user to the docker group for rootless usage.
|
|
6440
|
-
*/
|
|
6441
|
-
async installDockerLinux() {
|
|
6442
6201
|
this.onProgress("__progress__:5:Installing Docker Engine...");
|
|
6443
6202
|
const dlResult = await runShellWithRollingOutput(
|
|
6444
6203
|
"curl -fsSL https://get.docker.com -o /tmp/install-docker.sh && sudo sh /tmp/install-docker.sh",
|
|
@@ -6470,172 +6229,126 @@ var DependencyInstaller = class {
|
|
|
6470
6229
|
} catch {
|
|
6471
6230
|
}
|
|
6472
6231
|
}
|
|
6473
|
-
await this.
|
|
6232
|
+
await this.waitForDockerLinux();
|
|
6474
6233
|
}
|
|
6475
6234
|
/**
|
|
6476
|
-
*
|
|
6477
|
-
* On macOS: tries Docker Desktop app via `open`, direct binary launch, etc.
|
|
6478
|
-
* On Linux: tries systemctl, service, snap.
|
|
6235
|
+
* Wait for Docker daemon on Linux.
|
|
6479
6236
|
*/
|
|
6480
|
-
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
if (existsSync4(appBin)) {
|
|
6516
|
-
execSync(`"${appBin}" &`, { timeout: 5e3, stdio: "ignore", shell: "sh" });
|
|
6517
|
-
}
|
|
6518
|
-
} catch {
|
|
6519
|
-
}
|
|
6520
|
-
break;
|
|
6521
|
-
}
|
|
6522
|
-
case "install": {
|
|
6523
|
-
const installBin = dockerApp ? join6(dockerApp, "Contents", "MacOS", "install") : "/Applications/Docker.app/Contents/MacOS/install";
|
|
6524
|
-
if (existsSync4(installBin)) {
|
|
6525
|
-
const user = process.env.USER || "nobody";
|
|
6526
|
-
try {
|
|
6527
|
-
execSync(`"${installBin}" --accept-license --user=${user}`, { timeout: 6e4, stdio: "ignore" });
|
|
6528
|
-
} catch {
|
|
6529
|
-
}
|
|
6530
|
-
}
|
|
6531
|
-
if (dockerApp) {
|
|
6532
|
-
try {
|
|
6533
|
-
execFileSync2("open", ["-g", "-j", dockerApp], { timeout: 1e4, stdio: "ignore" });
|
|
6534
|
-
} catch {
|
|
6535
|
-
}
|
|
6536
|
-
}
|
|
6537
|
-
break;
|
|
6237
|
+
async waitForDockerLinux() {
|
|
6238
|
+
const totalTime = 6e4;
|
|
6239
|
+
const start = Date.now();
|
|
6240
|
+
while (Date.now() - start < totalTime) {
|
|
6241
|
+
if (this.isDockerReady()) {
|
|
6242
|
+
this.onProgress("__progress__:100:Docker is ready!");
|
|
6243
|
+
return;
|
|
6244
|
+
}
|
|
6245
|
+
const elapsed = Date.now() - start;
|
|
6246
|
+
const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
|
|
6247
|
+
this.onProgress(`__progress__:${pct}:Waiting for Docker...`);
|
|
6248
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
6249
|
+
}
|
|
6250
|
+
throw new Error("Docker did not start. Try: sudo systemctl start docker");
|
|
6251
|
+
}
|
|
6252
|
+
/**
|
|
6253
|
+
* Windows: Install Docker via WSL2 + Docker Engine, or guide user to Docker Desktop.
|
|
6254
|
+
* Prefers WSL2 with Docker Engine (no GUI needed).
|
|
6255
|
+
*/
|
|
6256
|
+
async installDockerWindows() {
|
|
6257
|
+
if (hasCommand("docker")) {
|
|
6258
|
+
if (this.isDockerReady()) {
|
|
6259
|
+
this.onProgress("__progress__:100:Engine is ready!");
|
|
6260
|
+
return;
|
|
6261
|
+
}
|
|
6262
|
+
this.onProgress("__progress__:10:Starting Docker...");
|
|
6263
|
+
try {
|
|
6264
|
+
execSync("net start com.docker.service", { timeout: 3e4, stdio: "ignore" });
|
|
6265
|
+
} catch {
|
|
6266
|
+
}
|
|
6267
|
+
try {
|
|
6268
|
+
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6269
|
+
const dockerExe = join6(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6270
|
+
if (existsSync4(dockerExe)) {
|
|
6271
|
+
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6538
6272
|
}
|
|
6539
|
-
|
|
6540
|
-
if (dockerApp) {
|
|
6541
|
-
try {
|
|
6542
|
-
execFileSync2("open", ["-g", "-j", dockerApp], { timeout: 1e4, stdio: "ignore" });
|
|
6543
|
-
} catch {
|
|
6544
|
-
}
|
|
6545
|
-
} else {
|
|
6546
|
-
try {
|
|
6547
|
-
execFileSync2("open", ["-g", "-j", "-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
|
|
6548
|
-
} catch {
|
|
6549
|
-
}
|
|
6550
|
-
}
|
|
6273
|
+
} catch {
|
|
6551
6274
|
}
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6275
|
+
await this.waitForDockerWindows();
|
|
6276
|
+
return;
|
|
6277
|
+
}
|
|
6278
|
+
let hasWsl = false;
|
|
6279
|
+
try {
|
|
6280
|
+
const wslResult = execSync("wsl --status", { timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
6281
|
+
hasWsl = wslResult.length > 0;
|
|
6282
|
+
} catch {
|
|
6283
|
+
}
|
|
6284
|
+
if (hasWsl) {
|
|
6285
|
+
this.onProgress("__progress__:5:Installing Docker Engine in WSL...");
|
|
6286
|
+
try {
|
|
6287
|
+
const wslResult = await runSilent(
|
|
6288
|
+
"wsl",
|
|
6289
|
+
["-e", "sh", "-c", "curl -fsSL https://get.docker.com | sh"],
|
|
6290
|
+
{ timeout: 3e5 }
|
|
6291
|
+
);
|
|
6292
|
+
if (wslResult.exitCode === 0) {
|
|
6561
6293
|
try {
|
|
6562
|
-
|
|
6294
|
+
execSync("wsl -e sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6563
6295
|
} catch {
|
|
6564
6296
|
}
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6297
|
+
await this.waitForDockerWindows();
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
} catch {
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
let hasWinget = false;
|
|
6304
|
+
try {
|
|
6305
|
+
execSync("winget --version", { timeout: 5e3, stdio: "ignore" });
|
|
6306
|
+
hasWinget = true;
|
|
6307
|
+
} catch {
|
|
6308
|
+
}
|
|
6309
|
+
if (hasWinget) {
|
|
6310
|
+
this.onProgress("__progress__:5:Installing Docker Desktop...");
|
|
6311
|
+
const wingetResult = await runSilent(
|
|
6312
|
+
"winget",
|
|
6313
|
+
["install", "-e", "--id", "Docker.DockerDesktop", "--accept-source-agreements", "--accept-package-agreements"],
|
|
6314
|
+
{ timeout: 6e5 }
|
|
6315
|
+
);
|
|
6316
|
+
if (wingetResult.exitCode === 0) {
|
|
6317
|
+
this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
|
|
6318
|
+
try {
|
|
6319
|
+
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6320
|
+
const dockerExe = join6(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6321
|
+
if (existsSync4(dockerExe)) {
|
|
6322
|
+
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6570
6323
|
}
|
|
6324
|
+
} catch {
|
|
6325
|
+
}
|
|
6326
|
+
await this.waitForDockerWindows();
|
|
6327
|
+
return;
|
|
6571
6328
|
}
|
|
6572
6329
|
}
|
|
6330
|
+
throw new Error(
|
|
6331
|
+
'Could not auto-install Docker on Windows.\nPlease install Docker Desktop from https://docs.docker.com/desktop/install/windows-install/\nOr install WSL2 and Docker Engine: wsl --install && wsl -e sh -c "curl -fsSL https://get.docker.com | sh"'
|
|
6332
|
+
);
|
|
6573
6333
|
}
|
|
6574
6334
|
/**
|
|
6575
|
-
* Wait for Docker daemon
|
|
6576
|
-
* Cycles through multiple start strategies automatically.
|
|
6577
|
-
* Reports progress as a percentage (0-100).
|
|
6335
|
+
* Wait for Docker daemon on Windows.
|
|
6578
6336
|
*/
|
|
6579
|
-
async
|
|
6580
|
-
const
|
|
6581
|
-
const strategies = os === "darwin" ? ["default", "cli", "reopen", "background", "install"] : ["default", "service", "snap"];
|
|
6582
|
-
const totalTime = 3e5;
|
|
6583
|
-
const perStrategyTime = Math.floor(totalTime / strategies.length);
|
|
6337
|
+
async waitForDockerWindows() {
|
|
6338
|
+
const totalTime = 12e4;
|
|
6584
6339
|
const start = Date.now();
|
|
6585
|
-
let strategyIdx = 0;
|
|
6586
|
-
this.onProgress("__progress__:0:Starting Docker...");
|
|
6587
6340
|
while (Date.now() - start < totalTime) {
|
|
6588
|
-
|
|
6589
|
-
execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
|
|
6341
|
+
if (this.isDockerReady()) {
|
|
6590
6342
|
this.onProgress("__progress__:100:Docker is ready!");
|
|
6591
6343
|
return;
|
|
6592
|
-
} catch {
|
|
6593
6344
|
}
|
|
6594
6345
|
const elapsed = Date.now() - start;
|
|
6595
6346
|
const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
|
|
6596
|
-
|
|
6597
|
-
const msgs = [
|
|
6598
|
-
"Please accept the license agreement in the Docker window...",
|
|
6599
|
-
"Waiting for you to accept the terms...",
|
|
6600
|
-
"Accept the agreement to continue...",
|
|
6601
|
-
"Still waiting..."
|
|
6602
|
-
];
|
|
6603
|
-
const msgIdx = Math.floor(elapsed / 8e3) % msgs.length;
|
|
6604
|
-
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
6605
|
-
} else {
|
|
6606
|
-
const currentStrategyElapsed = elapsed - strategyIdx * perStrategyTime;
|
|
6607
|
-
if (currentStrategyElapsed >= perStrategyTime && strategyIdx < strategies.length - 1) {
|
|
6608
|
-
strategyIdx++;
|
|
6609
|
-
const strategy = strategies[strategyIdx];
|
|
6610
|
-
const msgs = {
|
|
6611
|
-
cli: "Checking engine...",
|
|
6612
|
-
reopen: "Restarting engine...",
|
|
6613
|
-
background: "Trying direct launch...",
|
|
6614
|
-
install: "Re-running installer...",
|
|
6615
|
-
service: "Trying service command...",
|
|
6616
|
-
snap: "Trying snap..."
|
|
6617
|
-
};
|
|
6618
|
-
this.onProgress(`__progress__:${pct}:${msgs[strategy] || "Trying another approach..."}`);
|
|
6619
|
-
this.startDockerDaemon(strategy);
|
|
6620
|
-
} else {
|
|
6621
|
-
const msgs = [
|
|
6622
|
-
"Starting engine...",
|
|
6623
|
-
"Waiting for engine...",
|
|
6624
|
-
"Loading...",
|
|
6625
|
-
"Almost there...",
|
|
6626
|
-
"Still starting up...",
|
|
6627
|
-
"First launch takes a bit longer...",
|
|
6628
|
-
"Hang tight..."
|
|
6629
|
-
];
|
|
6630
|
-
const msgIdx = Math.floor(elapsed / 1e4) % msgs.length;
|
|
6631
|
-
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
6632
|
-
}
|
|
6633
|
-
if (os === "darwin") this.hideDockerWindow();
|
|
6634
|
-
}
|
|
6347
|
+
this.onProgress(`__progress__:${pct}:Waiting for Docker...`);
|
|
6635
6348
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
6636
6349
|
}
|
|
6637
6350
|
throw new Error(
|
|
6638
|
-
"
|
|
6351
|
+
"Docker did not start in time.\nMake sure Docker Desktop is running, then try again."
|
|
6639
6352
|
);
|
|
6640
6353
|
}
|
|
6641
6354
|
/**
|
|
@@ -6645,15 +6358,31 @@ var DependencyInstaller = class {
|
|
|
6645
6358
|
if (!existsSync4(composePath)) {
|
|
6646
6359
|
throw new Error(`docker-compose.yml not found at: ${composePath}`);
|
|
6647
6360
|
}
|
|
6648
|
-
|
|
6649
|
-
execFileSync2("docker", ["info"], { timeout: 1e4, stdio: "ignore" });
|
|
6650
|
-
} catch {
|
|
6361
|
+
if (!this.isDockerReady()) {
|
|
6651
6362
|
this.onProgress("Starting Docker...");
|
|
6652
|
-
|
|
6653
|
-
|
|
6363
|
+
const os = platform2();
|
|
6364
|
+
if (os === "darwin") {
|
|
6365
|
+
await this.startColima();
|
|
6366
|
+
} else if (os === "win32") {
|
|
6367
|
+
await this.waitForDockerWindows();
|
|
6368
|
+
} else {
|
|
6369
|
+
try {
|
|
6370
|
+
execSync("sudo systemctl start docker", { timeout: 15e3, stdio: "ignore" });
|
|
6371
|
+
} catch {
|
|
6372
|
+
try {
|
|
6373
|
+
execSync("sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6374
|
+
} catch {
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
await this.waitForDockerLinux();
|
|
6378
|
+
}
|
|
6654
6379
|
}
|
|
6655
6380
|
this.onProgress("__progress__:10:Pulling mail server image...");
|
|
6656
|
-
|
|
6381
|
+
if (platform2() === "darwin") this.linkComposePlugin();
|
|
6382
|
+
let composeResult = await runWithRollingOutput("docker", ["compose", "-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
6383
|
+
if (composeResult.exitCode !== 0 && hasCommand("docker-compose")) {
|
|
6384
|
+
composeResult = await runWithRollingOutput("docker-compose", ["-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
6385
|
+
}
|
|
6657
6386
|
if (composeResult.exitCode !== 0) {
|
|
6658
6387
|
throw new Error("Failed to start mail server container. Check Docker is running.");
|
|
6659
6388
|
}
|
|
@@ -6684,25 +6413,31 @@ var DependencyInstaller = class {
|
|
|
6684
6413
|
* Returns the path to the installed binary.
|
|
6685
6414
|
*/
|
|
6686
6415
|
async installCloudflared() {
|
|
6416
|
+
const os = platform2();
|
|
6687
6417
|
const binDir = join6(homedir5(), ".agenticmail", "bin");
|
|
6688
|
-
const
|
|
6418
|
+
const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
6419
|
+
const binPath = join6(binDir, binName);
|
|
6689
6420
|
if (existsSync4(binPath)) {
|
|
6690
6421
|
return binPath;
|
|
6691
6422
|
}
|
|
6692
6423
|
try {
|
|
6693
|
-
const
|
|
6424
|
+
const whichCmd = os === "win32" ? "where" : "which";
|
|
6425
|
+
const sysPath = execFileSync2(whichCmd, ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
|
|
6694
6426
|
if (sysPath && existsSync4(sysPath)) return sysPath;
|
|
6695
6427
|
} catch {
|
|
6696
6428
|
}
|
|
6697
6429
|
this.onProgress("Downloading cloudflared...");
|
|
6698
6430
|
await mkdir2(binDir, { recursive: true });
|
|
6699
|
-
const os = platform2();
|
|
6700
6431
|
const cpu = arch2();
|
|
6701
6432
|
const archName = cpu === "arm64" ? "arm64" : "amd64";
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
if (os
|
|
6433
|
+
let downloadUrl;
|
|
6434
|
+
if (os === "darwin") {
|
|
6435
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-${archName}.tgz`;
|
|
6436
|
+
} else if (os === "linux") {
|
|
6437
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${archName}`;
|
|
6438
|
+
} else if (os === "win32") {
|
|
6439
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-${archName}.exe`;
|
|
6440
|
+
} else {
|
|
6706
6441
|
throw new Error(`Unsupported platform: ${os}/${cpu}`);
|
|
6707
6442
|
}
|
|
6708
6443
|
const response = await fetch(downloadUrl);
|
|
@@ -6710,7 +6445,7 @@ var DependencyInstaller = class {
|
|
|
6710
6445
|
throw new Error(`Failed to download cloudflared: ${response.statusText}`);
|
|
6711
6446
|
}
|
|
6712
6447
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
6713
|
-
if (
|
|
6448
|
+
if (os === "darwin") {
|
|
6714
6449
|
const tgzPath = join6(binDir, "cloudflared.tgz");
|
|
6715
6450
|
await writeFile(tgzPath, buffer);
|
|
6716
6451
|
try {
|
|
@@ -6725,7 +6460,7 @@ var DependencyInstaller = class {
|
|
|
6725
6460
|
} else {
|
|
6726
6461
|
const tmpPath = binPath + ".tmp";
|
|
6727
6462
|
await writeFile(tmpPath, buffer);
|
|
6728
|
-
await chmod2(tmpPath, 493);
|
|
6463
|
+
if (os !== "win32") await chmod2(tmpPath, 493);
|
|
6729
6464
|
await rename(tmpPath, binPath);
|
|
6730
6465
|
}
|
|
6731
6466
|
if (!existsSync4(binPath)) {
|
|
@@ -6746,7 +6481,7 @@ var DependencyInstaller = class {
|
|
|
6746
6481
|
|
|
6747
6482
|
// src/setup/service.ts
|
|
6748
6483
|
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
6749
|
-
import { existsSync as existsSync5, readFileSync as
|
|
6484
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6750
6485
|
import { join as join7 } from "path";
|
|
6751
6486
|
import { homedir as homedir6, platform as platform3 } from "os";
|
|
6752
6487
|
var PLIST_LABEL = "com.agenticmail.server";
|
|
@@ -6809,7 +6544,7 @@ var ServiceManager = class {
|
|
|
6809
6544
|
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6810
6545
|
const entryCache = join7(dataDir, "api-entry.path");
|
|
6811
6546
|
if (existsSync5(entryCache)) {
|
|
6812
|
-
const cached =
|
|
6547
|
+
const cached = readFileSync2(entryCache, "utf-8").trim();
|
|
6813
6548
|
if (existsSync5(cached)) return cached;
|
|
6814
6549
|
}
|
|
6815
6550
|
throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
|
|
@@ -6819,8 +6554,8 @@ var ServiceManager = class {
|
|
|
6819
6554
|
*/
|
|
6820
6555
|
cacheApiEntryPath(entryPath) {
|
|
6821
6556
|
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6822
|
-
if (!existsSync5(dataDir))
|
|
6823
|
-
|
|
6557
|
+
if (!existsSync5(dataDir)) mkdirSync3(dataDir, { recursive: true });
|
|
6558
|
+
writeFileSync3(join7(dataDir, "api-entry.path"), entryPath);
|
|
6824
6559
|
}
|
|
6825
6560
|
/**
|
|
6826
6561
|
* Get the current package version.
|
|
@@ -6838,7 +6573,7 @@ var ServiceManager = class {
|
|
|
6838
6573
|
}
|
|
6839
6574
|
for (const p of pkgPaths) {
|
|
6840
6575
|
if (existsSync5(p)) {
|
|
6841
|
-
const pkg = JSON.parse(
|
|
6576
|
+
const pkg = JSON.parse(readFileSync2(p, "utf-8"));
|
|
6842
6577
|
if (pkg.version) return pkg.version;
|
|
6843
6578
|
}
|
|
6844
6579
|
}
|
|
@@ -6853,7 +6588,7 @@ var ServiceManager = class {
|
|
|
6853
6588
|
generateStartScript(nodePath, apiEntry) {
|
|
6854
6589
|
const scriptPath = join7(homedir6(), ".agenticmail", "bin", "start-server.sh");
|
|
6855
6590
|
const scriptDir = join7(homedir6(), ".agenticmail", "bin");
|
|
6856
|
-
if (!existsSync5(scriptDir))
|
|
6591
|
+
if (!existsSync5(scriptDir)) mkdirSync3(scriptDir, { recursive: true });
|
|
6857
6592
|
const script = [
|
|
6858
6593
|
"#!/bin/bash",
|
|
6859
6594
|
"# AgenticMail auto-start script",
|
|
@@ -6904,7 +6639,7 @@ var ServiceManager = class {
|
|
|
6904
6639
|
`log "Starting API server: ${nodePath} ${apiEntry}"`,
|
|
6905
6640
|
`exec "${nodePath}" "${apiEntry}"`
|
|
6906
6641
|
].join("\n") + "\n";
|
|
6907
|
-
|
|
6642
|
+
writeFileSync3(scriptPath, script, { mode: 493 });
|
|
6908
6643
|
return scriptPath;
|
|
6909
6644
|
}
|
|
6910
6645
|
/**
|
|
@@ -6917,9 +6652,9 @@ var ServiceManager = class {
|
|
|
6917
6652
|
* - Service version tracking in env vars
|
|
6918
6653
|
*/
|
|
6919
6654
|
generatePlist(nodePath, apiEntry, configPath) {
|
|
6920
|
-
const config = JSON.parse(
|
|
6655
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
6921
6656
|
const logDir = join7(homedir6(), ".agenticmail", "logs");
|
|
6922
|
-
if (!existsSync5(logDir))
|
|
6657
|
+
if (!existsSync5(logDir)) mkdirSync3(logDir, { recursive: true });
|
|
6923
6658
|
const version = this.getVersion();
|
|
6924
6659
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6925
6660
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -7015,7 +6750,7 @@ var ServiceManager = class {
|
|
|
7015
6750
|
* - Proper dependency ordering
|
|
7016
6751
|
*/
|
|
7017
6752
|
generateSystemdUnit(nodePath, apiEntry, configPath) {
|
|
7018
|
-
const config = JSON.parse(
|
|
6753
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
7019
6754
|
const dataDir = config.dataDir || join7(homedir6(), ".agenticmail");
|
|
7020
6755
|
const version = this.getVersion();
|
|
7021
6756
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
@@ -7070,7 +6805,7 @@ WantedBy=default.target
|
|
|
7070
6805
|
const servicePath = this.getServicePath();
|
|
7071
6806
|
if (this.os === "darwin") {
|
|
7072
6807
|
const dir = join7(homedir6(), "Library", "LaunchAgents");
|
|
7073
|
-
if (!existsSync5(dir))
|
|
6808
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
7074
6809
|
if (existsSync5(servicePath)) {
|
|
7075
6810
|
try {
|
|
7076
6811
|
execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -7078,7 +6813,7 @@ WantedBy=default.target
|
|
|
7078
6813
|
}
|
|
7079
6814
|
}
|
|
7080
6815
|
const plist = this.generatePlist(nodePath, apiEntry, configPath);
|
|
7081
|
-
|
|
6816
|
+
writeFileSync3(servicePath, plist);
|
|
7082
6817
|
try {
|
|
7083
6818
|
execFileSync3("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
7084
6819
|
} catch (err) {
|
|
@@ -7087,9 +6822,9 @@ WantedBy=default.target
|
|
|
7087
6822
|
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
7088
6823
|
} else if (this.os === "linux") {
|
|
7089
6824
|
const dir = join7(homedir6(), ".config", "systemd", "user");
|
|
7090
|
-
if (!existsSync5(dir))
|
|
6825
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
7091
6826
|
const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
|
|
7092
|
-
|
|
6827
|
+
writeFileSync3(servicePath, unit);
|
|
7093
6828
|
try {
|
|
7094
6829
|
execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
7095
6830
|
execFileSync3("systemctl", ["--user", "enable", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -7240,14 +6975,14 @@ var SetupManager = class {
|
|
|
7240
6975
|
const envPath = join8(dataDir, ".env");
|
|
7241
6976
|
if (existsSync6(configPath)) {
|
|
7242
6977
|
try {
|
|
7243
|
-
const existing = JSON.parse(
|
|
6978
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
7244
6979
|
this.generateDockerFiles(existing);
|
|
7245
6980
|
return { configPath, envPath, config: existing, isNew: false };
|
|
7246
6981
|
} catch {
|
|
7247
6982
|
}
|
|
7248
6983
|
}
|
|
7249
6984
|
if (!existsSync6(dataDir)) {
|
|
7250
|
-
|
|
6985
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
7251
6986
|
}
|
|
7252
6987
|
const masterKey = `mk_${randomBytes2(24).toString("hex")}`;
|
|
7253
6988
|
const stalwartPassword = randomBytes2(16).toString("hex");
|
|
@@ -7263,7 +6998,7 @@ var SetupManager = class {
|
|
|
7263
6998
|
api: { port: 3100, host: "127.0.0.1" },
|
|
7264
6999
|
dataDir
|
|
7265
7000
|
};
|
|
7266
|
-
|
|
7001
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
7267
7002
|
chmodSync(configPath, 384);
|
|
7268
7003
|
const envContent = `# Auto-generated by agenticmail setup
|
|
7269
7004
|
STALWART_ADMIN_USER=admin
|
|
@@ -7279,7 +7014,7 @@ SMTP_PORT=587
|
|
|
7279
7014
|
IMAP_HOST=localhost
|
|
7280
7015
|
IMAP_PORT=143
|
|
7281
7016
|
`;
|
|
7282
|
-
|
|
7017
|
+
writeFileSync4(envPath, envContent);
|
|
7283
7018
|
chmodSync(envPath, 384);
|
|
7284
7019
|
this.generateDockerFiles(config);
|
|
7285
7020
|
return { configPath, envPath, config, isNew: true };
|
|
@@ -7291,11 +7026,11 @@ IMAP_PORT=143
|
|
|
7291
7026
|
generateDockerFiles(config) {
|
|
7292
7027
|
const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
|
|
7293
7028
|
if (!existsSync6(dataDir)) {
|
|
7294
|
-
|
|
7029
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
7295
7030
|
}
|
|
7296
7031
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
7297
7032
|
const composePath = join8(dataDir, "docker-compose.yml");
|
|
7298
|
-
|
|
7033
|
+
writeFileSync4(composePath, `services:
|
|
7299
7034
|
stalwart:
|
|
7300
7035
|
image: stalwartlabs/stalwart:latest
|
|
7301
7036
|
container_name: agenticmail-stalwart
|
|
@@ -7318,7 +7053,7 @@ volumes:
|
|
|
7318
7053
|
stalwart-data:
|
|
7319
7054
|
`);
|
|
7320
7055
|
const tomlPath = join8(dataDir, "stalwart.toml");
|
|
7321
|
-
|
|
7056
|
+
writeFileSync4(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
7322
7057
|
|
|
7323
7058
|
[server]
|
|
7324
7059
|
hostname = "localhost"
|