@agenticmail/core 0.5.23 → 0.5.25
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 +19 -55
- package/dist/index.js +223 -470
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1412,86 +1412,50 @@ 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.
|
|
1433
|
+
* macOS: Install Docker via Colima (CLI-only, no GUI, no license dialogs).
|
|
1434
|
+
* Installs colima + docker + docker-compose via Homebrew, then starts Colima.
|
|
1441
1435
|
*/
|
|
1442
|
-
private
|
|
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;
|
|
1436
|
+
private installDockerMac;
|
|
1466
1437
|
/**
|
|
1467
|
-
*
|
|
1468
|
-
* Called after starting Docker to ensure the user never sees any Docker UI.
|
|
1438
|
+
* Start Colima and wait for Docker to be ready.
|
|
1469
1439
|
*/
|
|
1470
|
-
private
|
|
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.
|
|
1476
|
-
*/
|
|
1477
|
-
private installDockerMac;
|
|
1440
|
+
private startColima;
|
|
1478
1441
|
/**
|
|
1479
1442
|
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
1480
1443
|
* Also adds the current user to the docker group for rootless usage.
|
|
1481
1444
|
*/
|
|
1482
1445
|
private installDockerLinux;
|
|
1483
1446
|
/**
|
|
1484
|
-
*
|
|
1485
|
-
|
|
1486
|
-
|
|
1447
|
+
* Wait for Docker daemon on Linux.
|
|
1448
|
+
*/
|
|
1449
|
+
private waitForDockerLinux;
|
|
1450
|
+
/**
|
|
1451
|
+
* Windows: Install Docker via WSL2 + Docker Engine, or guide user to Docker Desktop.
|
|
1452
|
+
* Prefers WSL2 with Docker Engine (no GUI needed).
|
|
1487
1453
|
*/
|
|
1488
|
-
private
|
|
1454
|
+
private installDockerWindows;
|
|
1489
1455
|
/**
|
|
1490
|
-
* Wait for Docker daemon
|
|
1491
|
-
* Cycles through multiple start strategies automatically.
|
|
1492
|
-
* Reports progress as a percentage (0-100).
|
|
1456
|
+
* Wait for Docker daemon on Windows.
|
|
1493
1457
|
*/
|
|
1494
|
-
private
|
|
1458
|
+
private waitForDockerWindows;
|
|
1495
1459
|
/**
|
|
1496
1460
|
* Start the Stalwart mail server Docker container.
|
|
1497
1461
|
*/
|
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,288 +6088,92 @@ 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
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
"/opt/homebrew/Caskroom",
|
|
6134
|
-
"/usr/local/Caskroom"
|
|
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
|
-
}
|
|
6095
|
+
async installDockerMac() {
|
|
6096
|
+
if (hasCommand("colima") && hasCommand("docker")) {
|
|
6097
|
+
this.onProgress("__progress__:10:Starting container engine...");
|
|
6098
|
+
await this.startColima();
|
|
6099
|
+
return;
|
|
6150
6100
|
}
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
* Returns true if we're confident the user has accepted before.
|
|
6156
|
-
*/
|
|
6157
|
-
isDockerLicenseAccepted() {
|
|
6158
|
-
try {
|
|
6159
|
-
const systemFile = "/Library/Application Support/com.docker.docker/install-settings.json";
|
|
6160
|
-
if (existsSync4(systemFile)) {
|
|
6161
|
-
const raw = readFileSync2(systemFile, "utf-8");
|
|
6162
|
-
const data = JSON.parse(raw);
|
|
6163
|
-
if (data.acceptLicense) return true;
|
|
6164
|
-
}
|
|
6165
|
-
} catch {
|
|
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
|
+
);
|
|
6166
6105
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
}
|
|
6172
|
-
|
|
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
|
+
);
|
|
6173
6116
|
}
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
* Call this AFTER Docker has been started and license accepted.
|
|
6179
|
-
* Suppresses UI, dock icon, tips, analytics, and auto-updates.
|
|
6180
|
-
*/
|
|
6181
|
-
configureDockerHeadless() {
|
|
6182
|
-
try {
|
|
6183
|
-
const userDockerDir = join6(process.env.HOME || "", "Library/Group Containers/group.com.docker");
|
|
6184
|
-
const settingsFile = join6(userDockerDir, "settings-store.json");
|
|
6185
|
-
const headlessDefaults = {
|
|
6186
|
-
AutoStart: false,
|
|
6187
|
-
// Don't auto-start on login (we start it ourselves)
|
|
6188
|
-
OpenUIOnStartupDisabled: true,
|
|
6189
|
-
// Don't show dashboard on start
|
|
6190
|
-
TipShown: true,
|
|
6191
|
-
// Don't show tips/welcome
|
|
6192
|
-
SUAutomaticallyUpdate: false,
|
|
6193
|
-
// Don't nag about updates
|
|
6194
|
-
AnalyticsEnabled: false
|
|
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));
|
|
6215
|
-
}
|
|
6216
|
-
} 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
|
+
);
|
|
6217
6121
|
}
|
|
6122
|
+
this.onProgress("__progress__:50:Starting container engine...");
|
|
6123
|
+
await this.startColima();
|
|
6218
6124
|
}
|
|
6219
6125
|
/**
|
|
6220
|
-
*
|
|
6221
|
-
* Runs the built-in installer silently (--accept-license to link CLI tools),
|
|
6222
|
-
* then starts Docker Desktop hidden (no GUI window).
|
|
6126
|
+
* Start Colima and wait for Docker to be ready.
|
|
6223
6127
|
*/
|
|
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.
|
|
6229
|
-
*/
|
|
6230
|
-
async setupExistingDockerApp(_appPath) {
|
|
6128
|
+
async startColima() {
|
|
6231
6129
|
if (this.isDockerReady()) {
|
|
6232
6130
|
this.onProgress("__progress__:100:Engine is ready!");
|
|
6233
6131
|
return;
|
|
6234
6132
|
}
|
|
6235
|
-
|
|
6236
|
-
|
|
6133
|
+
const startResult = await runSilent(
|
|
6134
|
+
"colima",
|
|
6135
|
+
["start", "--cpu", "2", "--memory", "2", "--disk", "10"],
|
|
6136
|
+
{ timeout: 3e5 }
|
|
6137
|
+
);
|
|
6138
|
+
if (startResult.exitCode !== 0) {
|
|
6139
|
+
throw new Error(
|
|
6140
|
+
"Failed to start Colima. Try running manually: colima start\n" + startResult.fullOutput.slice(-500)
|
|
6141
|
+
);
|
|
6142
|
+
}
|
|
6143
|
+
const totalTime = 6e4;
|
|
6237
6144
|
const start = Date.now();
|
|
6238
6145
|
while (Date.now() - start < totalTime) {
|
|
6239
|
-
|
|
6240
|
-
execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
|
|
6146
|
+
if (this.isDockerReady()) {
|
|
6241
6147
|
this.onProgress("__progress__:100:Engine is ready!");
|
|
6242
|
-
this.configureDockerHeadless();
|
|
6243
|
-
this.hideDockerWindow();
|
|
6244
6148
|
return;
|
|
6245
|
-
} catch {
|
|
6246
6149
|
}
|
|
6247
6150
|
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));
|
|
6151
|
+
const pct = Math.min(95, 50 + Math.round(elapsed / totalTime * 50));
|
|
6152
|
+
this.onProgress(`__progress__:${pct}:Waiting for engine...`);
|
|
6153
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
6258
6154
|
}
|
|
6259
6155
|
throw new Error(
|
|
6260
|
-
"Docker did not start
|
|
6156
|
+
"Docker engine did not start in time. Try running manually: colima start"
|
|
6261
6157
|
);
|
|
6262
6158
|
}
|
|
6263
6159
|
/**
|
|
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.
|
|
6160
|
+
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
6161
|
+
* Also adds the current user to the docker group for rootless usage.
|
|
6314
6162
|
*/
|
|
6315
|
-
async
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
this.onProgress("__progress__:5:Installing Docker Desktop...");
|
|
6319
|
-
try {
|
|
6320
|
-
const brewResult = await runSilent(
|
|
6321
|
-
"brew",
|
|
6322
|
-
["install", "--cask", "docker"],
|
|
6323
|
-
{ timeout: 6e5 }
|
|
6324
|
-
);
|
|
6325
|
-
if (brewResult.exitCode === 0 && this.findDockerApp()) {
|
|
6326
|
-
installed = true;
|
|
6327
|
-
}
|
|
6328
|
-
} catch {
|
|
6329
|
-
}
|
|
6330
|
-
}
|
|
6331
|
-
if (!installed) {
|
|
6332
|
-
const cpu = arch2();
|
|
6333
|
-
const archName = cpu === "arm64" ? "arm64" : "amd64";
|
|
6334
|
-
const dmgUrl = `https://desktop.docker.com/mac/main/${archName}/Docker.dmg`;
|
|
6335
|
-
const dmgPath = "/tmp/Docker.dmg";
|
|
6336
|
-
this.onProgress("__progress__:5:Downloading Docker Desktop...");
|
|
6337
|
-
const dlResult = await runSilent("curl", [
|
|
6338
|
-
"-fSL",
|
|
6339
|
-
"-o",
|
|
6340
|
-
dmgPath,
|
|
6341
|
-
dmgUrl
|
|
6342
|
-
], { timeout: 6e5 });
|
|
6343
|
-
if (dlResult.exitCode !== 0) {
|
|
6344
|
-
throw new Error("Failed to download Docker Desktop. Check your internet connection and try again.");
|
|
6345
|
-
}
|
|
6346
|
-
this.onProgress("__progress__:40:Installing Docker Desktop...");
|
|
6163
|
+
async installDockerLinux() {
|
|
6164
|
+
if (hasCommand("docker")) {
|
|
6165
|
+
this.onProgress("__progress__:10:Starting Docker service...");
|
|
6347
6166
|
try {
|
|
6348
|
-
|
|
6349
|
-
execSync("hdiutil detach /Volumes/Docker 2>/dev/null", { timeout: 1e4, stdio: "ignore" });
|
|
6350
|
-
} catch {
|
|
6351
|
-
}
|
|
6352
|
-
execSync(`hdiutil attach "${dmgPath}" -nobrowse -quiet`, { timeout: 3e4, stdio: "ignore" });
|
|
6167
|
+
execSync("sudo systemctl start docker", { timeout: 15e3, stdio: "ignore" });
|
|
6353
6168
|
} catch {
|
|
6354
|
-
throw new Error("Failed to mount Docker DMG. The download may be corrupted \u2014 try again.");
|
|
6355
|
-
}
|
|
6356
|
-
if (!existsSync4("/Applications/Docker.app")) {
|
|
6357
6169
|
try {
|
|
6358
|
-
execSync(
|
|
6170
|
+
execSync("sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6359
6171
|
} catch {
|
|
6360
|
-
throw new Error("Failed to install. You may need to run this with admin privileges.");
|
|
6361
6172
|
}
|
|
6362
6173
|
}
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
} catch {
|
|
6366
|
-
}
|
|
6367
|
-
try {
|
|
6368
|
-
await unlink(dmgPath);
|
|
6369
|
-
} catch {
|
|
6370
|
-
}
|
|
6371
|
-
}
|
|
6372
|
-
try {
|
|
6373
|
-
execSync(`osascript -e 'quit app "Docker Desktop"' 2>/dev/null`, { timeout: 5e3, stdio: "ignore" });
|
|
6374
|
-
} catch {
|
|
6375
|
-
}
|
|
6376
|
-
try {
|
|
6377
|
-
execSync(`osascript -e 'quit app "Docker"' 2>/dev/null`, { timeout: 5e3, stdio: "ignore" });
|
|
6378
|
-
} catch {
|
|
6379
|
-
}
|
|
6380
|
-
try {
|
|
6381
|
-
execSync("pkill -f Docker.app 2>/dev/null", { timeout: 5e3, stdio: "ignore" });
|
|
6382
|
-
} catch {
|
|
6383
|
-
}
|
|
6384
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
6385
|
-
const appPath = this.findDockerApp();
|
|
6386
|
-
if (!appPath) {
|
|
6387
|
-
throw new Error("Docker Desktop was installed but could not be found. Try again.");
|
|
6174
|
+
await this.waitForDockerLinux();
|
|
6175
|
+
return;
|
|
6388
6176
|
}
|
|
6389
|
-
await this.setupExistingDockerApp(appPath);
|
|
6390
|
-
}
|
|
6391
|
-
/**
|
|
6392
|
-
* Install Docker Engine on Linux using Docker's official convenience script.
|
|
6393
|
-
* Also adds the current user to the docker group for rootless usage.
|
|
6394
|
-
*/
|
|
6395
|
-
async installDockerLinux() {
|
|
6396
6177
|
this.onProgress("__progress__:5:Installing Docker Engine...");
|
|
6397
6178
|
const dlResult = await runShellWithRollingOutput(
|
|
6398
6179
|
"curl -fsSL https://get.docker.com -o /tmp/install-docker.sh && sudo sh /tmp/install-docker.sh",
|
|
@@ -6424,172 +6205,126 @@ var DependencyInstaller = class {
|
|
|
6424
6205
|
} catch {
|
|
6425
6206
|
}
|
|
6426
6207
|
}
|
|
6427
|
-
await this.
|
|
6208
|
+
await this.waitForDockerLinux();
|
|
6428
6209
|
}
|
|
6429
6210
|
/**
|
|
6430
|
-
*
|
|
6431
|
-
* On macOS: tries Docker Desktop app via `open`, direct binary launch, etc.
|
|
6432
|
-
* On Linux: tries systemctl, service, snap.
|
|
6211
|
+
* Wait for Docker daemon on Linux.
|
|
6433
6212
|
*/
|
|
6434
|
-
|
|
6435
|
-
const
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
if (existsSync4(appBin)) {
|
|
6470
|
-
execSync(`"${appBin}" &`, { timeout: 5e3, stdio: "ignore", shell: "sh" });
|
|
6471
|
-
}
|
|
6472
|
-
} catch {
|
|
6473
|
-
}
|
|
6474
|
-
break;
|
|
6475
|
-
}
|
|
6476
|
-
case "install": {
|
|
6477
|
-
const installBin = dockerApp ? join6(dockerApp, "Contents", "MacOS", "install") : "/Applications/Docker.app/Contents/MacOS/install";
|
|
6478
|
-
if (existsSync4(installBin)) {
|
|
6479
|
-
const user = process.env.USER || "nobody";
|
|
6480
|
-
try {
|
|
6481
|
-
execSync(`"${installBin}" --accept-license --user=${user}`, { timeout: 6e4, stdio: "ignore" });
|
|
6482
|
-
} catch {
|
|
6483
|
-
}
|
|
6484
|
-
}
|
|
6485
|
-
if (dockerApp) {
|
|
6486
|
-
try {
|
|
6487
|
-
execFileSync2("open", ["-g", "-j", dockerApp], { timeout: 1e4, stdio: "ignore" });
|
|
6488
|
-
} catch {
|
|
6489
|
-
}
|
|
6490
|
-
}
|
|
6491
|
-
break;
|
|
6213
|
+
async waitForDockerLinux() {
|
|
6214
|
+
const totalTime = 6e4;
|
|
6215
|
+
const start = Date.now();
|
|
6216
|
+
while (Date.now() - start < totalTime) {
|
|
6217
|
+
if (this.isDockerReady()) {
|
|
6218
|
+
this.onProgress("__progress__:100:Docker is ready!");
|
|
6219
|
+
return;
|
|
6220
|
+
}
|
|
6221
|
+
const elapsed = Date.now() - start;
|
|
6222
|
+
const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
|
|
6223
|
+
this.onProgress(`__progress__:${pct}:Waiting for Docker...`);
|
|
6224
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
6225
|
+
}
|
|
6226
|
+
throw new Error("Docker did not start. Try: sudo systemctl start docker");
|
|
6227
|
+
}
|
|
6228
|
+
/**
|
|
6229
|
+
* Windows: Install Docker via WSL2 + Docker Engine, or guide user to Docker Desktop.
|
|
6230
|
+
* Prefers WSL2 with Docker Engine (no GUI needed).
|
|
6231
|
+
*/
|
|
6232
|
+
async installDockerWindows() {
|
|
6233
|
+
if (hasCommand("docker")) {
|
|
6234
|
+
if (this.isDockerReady()) {
|
|
6235
|
+
this.onProgress("__progress__:100:Engine is ready!");
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
this.onProgress("__progress__:10:Starting Docker...");
|
|
6239
|
+
try {
|
|
6240
|
+
execSync("net start com.docker.service", { timeout: 3e4, stdio: "ignore" });
|
|
6241
|
+
} catch {
|
|
6242
|
+
}
|
|
6243
|
+
try {
|
|
6244
|
+
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6245
|
+
const dockerExe = join6(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6246
|
+
if (existsSync4(dockerExe)) {
|
|
6247
|
+
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6492
6248
|
}
|
|
6493
|
-
|
|
6494
|
-
if (dockerApp) {
|
|
6495
|
-
try {
|
|
6496
|
-
execFileSync2("open", ["-g", "-j", dockerApp], { timeout: 1e4, stdio: "ignore" });
|
|
6497
|
-
} catch {
|
|
6498
|
-
}
|
|
6499
|
-
} else {
|
|
6500
|
-
try {
|
|
6501
|
-
execFileSync2("open", ["-g", "-j", "-a", "Docker"], { timeout: 1e4, stdio: "ignore" });
|
|
6502
|
-
} catch {
|
|
6503
|
-
}
|
|
6504
|
-
}
|
|
6249
|
+
} catch {
|
|
6505
6250
|
}
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6251
|
+
await this.waitForDockerWindows();
|
|
6252
|
+
return;
|
|
6253
|
+
}
|
|
6254
|
+
let hasWsl = false;
|
|
6255
|
+
try {
|
|
6256
|
+
const wslResult = execSync("wsl --status", { timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
6257
|
+
hasWsl = wslResult.length > 0;
|
|
6258
|
+
} catch {
|
|
6259
|
+
}
|
|
6260
|
+
if (hasWsl) {
|
|
6261
|
+
this.onProgress("__progress__:5:Installing Docker Engine in WSL...");
|
|
6262
|
+
try {
|
|
6263
|
+
const wslResult = await runSilent(
|
|
6264
|
+
"wsl",
|
|
6265
|
+
["-e", "sh", "-c", "curl -fsSL https://get.docker.com | sh"],
|
|
6266
|
+
{ timeout: 3e5 }
|
|
6267
|
+
);
|
|
6268
|
+
if (wslResult.exitCode === 0) {
|
|
6515
6269
|
try {
|
|
6516
|
-
|
|
6270
|
+
execSync("wsl -e sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6517
6271
|
} catch {
|
|
6518
6272
|
}
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6273
|
+
await this.waitForDockerWindows();
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
} catch {
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
let hasWinget = false;
|
|
6280
|
+
try {
|
|
6281
|
+
execSync("winget --version", { timeout: 5e3, stdio: "ignore" });
|
|
6282
|
+
hasWinget = true;
|
|
6283
|
+
} catch {
|
|
6284
|
+
}
|
|
6285
|
+
if (hasWinget) {
|
|
6286
|
+
this.onProgress("__progress__:5:Installing Docker Desktop...");
|
|
6287
|
+
const wingetResult = await runSilent(
|
|
6288
|
+
"winget",
|
|
6289
|
+
["install", "-e", "--id", "Docker.DockerDesktop", "--accept-source-agreements", "--accept-package-agreements"],
|
|
6290
|
+
{ timeout: 6e5 }
|
|
6291
|
+
);
|
|
6292
|
+
if (wingetResult.exitCode === 0) {
|
|
6293
|
+
this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
|
|
6294
|
+
try {
|
|
6295
|
+
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
6296
|
+
const dockerExe = join6(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
6297
|
+
if (existsSync4(dockerExe)) {
|
|
6298
|
+
execSync(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
6524
6299
|
}
|
|
6300
|
+
} catch {
|
|
6301
|
+
}
|
|
6302
|
+
await this.waitForDockerWindows();
|
|
6303
|
+
return;
|
|
6525
6304
|
}
|
|
6526
6305
|
}
|
|
6306
|
+
throw new Error(
|
|
6307
|
+
'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"'
|
|
6308
|
+
);
|
|
6527
6309
|
}
|
|
6528
6310
|
/**
|
|
6529
|
-
* Wait for Docker daemon
|
|
6530
|
-
* Cycles through multiple start strategies automatically.
|
|
6531
|
-
* Reports progress as a percentage (0-100).
|
|
6311
|
+
* Wait for Docker daemon on Windows.
|
|
6532
6312
|
*/
|
|
6533
|
-
async
|
|
6534
|
-
const
|
|
6535
|
-
const strategies = os === "darwin" ? ["default", "cli", "reopen", "background", "install"] : ["default", "service", "snap"];
|
|
6536
|
-
const totalTime = 3e5;
|
|
6537
|
-
const perStrategyTime = Math.floor(totalTime / strategies.length);
|
|
6313
|
+
async waitForDockerWindows() {
|
|
6314
|
+
const totalTime = 12e4;
|
|
6538
6315
|
const start = Date.now();
|
|
6539
|
-
let strategyIdx = 0;
|
|
6540
|
-
this.onProgress("__progress__:0:Starting Docker...");
|
|
6541
6316
|
while (Date.now() - start < totalTime) {
|
|
6542
|
-
|
|
6543
|
-
execFileSync2("docker", ["info"], { timeout: 5e3, stdio: "ignore" });
|
|
6317
|
+
if (this.isDockerReady()) {
|
|
6544
6318
|
this.onProgress("__progress__:100:Docker is ready!");
|
|
6545
6319
|
return;
|
|
6546
|
-
} catch {
|
|
6547
6320
|
}
|
|
6548
6321
|
const elapsed = Date.now() - start;
|
|
6549
6322
|
const pct = Math.min(95, Math.round(elapsed / totalTime * 100));
|
|
6550
|
-
|
|
6551
|
-
const msgs = [
|
|
6552
|
-
"Please accept the license agreement in the Docker window...",
|
|
6553
|
-
"Waiting for you to accept the terms...",
|
|
6554
|
-
"Accept the agreement to continue...",
|
|
6555
|
-
"Still waiting..."
|
|
6556
|
-
];
|
|
6557
|
-
const msgIdx = Math.floor(elapsed / 8e3) % msgs.length;
|
|
6558
|
-
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
6559
|
-
} else {
|
|
6560
|
-
const currentStrategyElapsed = elapsed - strategyIdx * perStrategyTime;
|
|
6561
|
-
if (currentStrategyElapsed >= perStrategyTime && strategyIdx < strategies.length - 1) {
|
|
6562
|
-
strategyIdx++;
|
|
6563
|
-
const strategy = strategies[strategyIdx];
|
|
6564
|
-
const msgs = {
|
|
6565
|
-
cli: "Checking engine...",
|
|
6566
|
-
reopen: "Restarting engine...",
|
|
6567
|
-
background: "Trying direct launch...",
|
|
6568
|
-
install: "Re-running installer...",
|
|
6569
|
-
service: "Trying service command...",
|
|
6570
|
-
snap: "Trying snap..."
|
|
6571
|
-
};
|
|
6572
|
-
this.onProgress(`__progress__:${pct}:${msgs[strategy] || "Trying another approach..."}`);
|
|
6573
|
-
this.startDockerDaemon(strategy);
|
|
6574
|
-
} else {
|
|
6575
|
-
const msgs = [
|
|
6576
|
-
"Starting engine...",
|
|
6577
|
-
"Waiting for engine...",
|
|
6578
|
-
"Loading...",
|
|
6579
|
-
"Almost there...",
|
|
6580
|
-
"Still starting up...",
|
|
6581
|
-
"First launch takes a bit longer...",
|
|
6582
|
-
"Hang tight..."
|
|
6583
|
-
];
|
|
6584
|
-
const msgIdx = Math.floor(elapsed / 1e4) % msgs.length;
|
|
6585
|
-
this.onProgress(`__progress__:${pct}:${msgs[msgIdx]}`);
|
|
6586
|
-
}
|
|
6587
|
-
if (os === "darwin") this.hideDockerWindow();
|
|
6588
|
-
}
|
|
6323
|
+
this.onProgress(`__progress__:${pct}:Waiting for Docker...`);
|
|
6589
6324
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
6590
6325
|
}
|
|
6591
6326
|
throw new Error(
|
|
6592
|
-
"
|
|
6327
|
+
"Docker did not start in time.\nMake sure Docker Desktop is running, then try again."
|
|
6593
6328
|
);
|
|
6594
6329
|
}
|
|
6595
6330
|
/**
|
|
@@ -6599,12 +6334,24 @@ var DependencyInstaller = class {
|
|
|
6599
6334
|
if (!existsSync4(composePath)) {
|
|
6600
6335
|
throw new Error(`docker-compose.yml not found at: ${composePath}`);
|
|
6601
6336
|
}
|
|
6602
|
-
|
|
6603
|
-
execFileSync2("docker", ["info"], { timeout: 1e4, stdio: "ignore" });
|
|
6604
|
-
} catch {
|
|
6337
|
+
if (!this.isDockerReady()) {
|
|
6605
6338
|
this.onProgress("Starting Docker...");
|
|
6606
|
-
|
|
6607
|
-
|
|
6339
|
+
const os = platform2();
|
|
6340
|
+
if (os === "darwin") {
|
|
6341
|
+
await this.startColima();
|
|
6342
|
+
} else if (os === "win32") {
|
|
6343
|
+
await this.waitForDockerWindows();
|
|
6344
|
+
} else {
|
|
6345
|
+
try {
|
|
6346
|
+
execSync("sudo systemctl start docker", { timeout: 15e3, stdio: "ignore" });
|
|
6347
|
+
} catch {
|
|
6348
|
+
try {
|
|
6349
|
+
execSync("sudo service docker start", { timeout: 15e3, stdio: "ignore" });
|
|
6350
|
+
} catch {
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
await this.waitForDockerLinux();
|
|
6354
|
+
}
|
|
6608
6355
|
}
|
|
6609
6356
|
this.onProgress("__progress__:10:Pulling mail server image...");
|
|
6610
6357
|
const composeResult = await runWithRollingOutput("docker", ["compose", "-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
@@ -6638,25 +6385,31 @@ var DependencyInstaller = class {
|
|
|
6638
6385
|
* Returns the path to the installed binary.
|
|
6639
6386
|
*/
|
|
6640
6387
|
async installCloudflared() {
|
|
6388
|
+
const os = platform2();
|
|
6641
6389
|
const binDir = join6(homedir5(), ".agenticmail", "bin");
|
|
6642
|
-
const
|
|
6390
|
+
const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
6391
|
+
const binPath = join6(binDir, binName);
|
|
6643
6392
|
if (existsSync4(binPath)) {
|
|
6644
6393
|
return binPath;
|
|
6645
6394
|
}
|
|
6646
6395
|
try {
|
|
6647
|
-
const
|
|
6396
|
+
const whichCmd = os === "win32" ? "where" : "which";
|
|
6397
|
+
const sysPath = execFileSync2(whichCmd, ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
|
|
6648
6398
|
if (sysPath && existsSync4(sysPath)) return sysPath;
|
|
6649
6399
|
} catch {
|
|
6650
6400
|
}
|
|
6651
6401
|
this.onProgress("Downloading cloudflared...");
|
|
6652
6402
|
await mkdir2(binDir, { recursive: true });
|
|
6653
|
-
const os = platform2();
|
|
6654
6403
|
const cpu = arch2();
|
|
6655
6404
|
const archName = cpu === "arm64" ? "arm64" : "amd64";
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
if (os
|
|
6405
|
+
let downloadUrl;
|
|
6406
|
+
if (os === "darwin") {
|
|
6407
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-${archName}.tgz`;
|
|
6408
|
+
} else if (os === "linux") {
|
|
6409
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${archName}`;
|
|
6410
|
+
} else if (os === "win32") {
|
|
6411
|
+
downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-${archName}.exe`;
|
|
6412
|
+
} else {
|
|
6660
6413
|
throw new Error(`Unsupported platform: ${os}/${cpu}`);
|
|
6661
6414
|
}
|
|
6662
6415
|
const response = await fetch(downloadUrl);
|
|
@@ -6664,7 +6417,7 @@ var DependencyInstaller = class {
|
|
|
6664
6417
|
throw new Error(`Failed to download cloudflared: ${response.statusText}`);
|
|
6665
6418
|
}
|
|
6666
6419
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
6667
|
-
if (
|
|
6420
|
+
if (os === "darwin") {
|
|
6668
6421
|
const tgzPath = join6(binDir, "cloudflared.tgz");
|
|
6669
6422
|
await writeFile(tgzPath, buffer);
|
|
6670
6423
|
try {
|
|
@@ -6679,7 +6432,7 @@ var DependencyInstaller = class {
|
|
|
6679
6432
|
} else {
|
|
6680
6433
|
const tmpPath = binPath + ".tmp";
|
|
6681
6434
|
await writeFile(tmpPath, buffer);
|
|
6682
|
-
await chmod2(tmpPath, 493);
|
|
6435
|
+
if (os !== "win32") await chmod2(tmpPath, 493);
|
|
6683
6436
|
await rename(tmpPath, binPath);
|
|
6684
6437
|
}
|
|
6685
6438
|
if (!existsSync4(binPath)) {
|
|
@@ -6700,7 +6453,7 @@ var DependencyInstaller = class {
|
|
|
6700
6453
|
|
|
6701
6454
|
// src/setup/service.ts
|
|
6702
6455
|
import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
|
|
6703
|
-
import { existsSync as existsSync5, readFileSync as
|
|
6456
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
|
|
6704
6457
|
import { join as join7 } from "path";
|
|
6705
6458
|
import { homedir as homedir6, platform as platform3 } from "os";
|
|
6706
6459
|
var PLIST_LABEL = "com.agenticmail.server";
|
|
@@ -6763,7 +6516,7 @@ var ServiceManager = class {
|
|
|
6763
6516
|
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6764
6517
|
const entryCache = join7(dataDir, "api-entry.path");
|
|
6765
6518
|
if (existsSync5(entryCache)) {
|
|
6766
|
-
const cached =
|
|
6519
|
+
const cached = readFileSync2(entryCache, "utf-8").trim();
|
|
6767
6520
|
if (existsSync5(cached)) return cached;
|
|
6768
6521
|
}
|
|
6769
6522
|
throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
|
|
@@ -6773,8 +6526,8 @@ var ServiceManager = class {
|
|
|
6773
6526
|
*/
|
|
6774
6527
|
cacheApiEntryPath(entryPath) {
|
|
6775
6528
|
const dataDir = join7(homedir6(), ".agenticmail");
|
|
6776
|
-
if (!existsSync5(dataDir))
|
|
6777
|
-
|
|
6529
|
+
if (!existsSync5(dataDir)) mkdirSync3(dataDir, { recursive: true });
|
|
6530
|
+
writeFileSync3(join7(dataDir, "api-entry.path"), entryPath);
|
|
6778
6531
|
}
|
|
6779
6532
|
/**
|
|
6780
6533
|
* Get the current package version.
|
|
@@ -6792,7 +6545,7 @@ var ServiceManager = class {
|
|
|
6792
6545
|
}
|
|
6793
6546
|
for (const p of pkgPaths) {
|
|
6794
6547
|
if (existsSync5(p)) {
|
|
6795
|
-
const pkg = JSON.parse(
|
|
6548
|
+
const pkg = JSON.parse(readFileSync2(p, "utf-8"));
|
|
6796
6549
|
if (pkg.version) return pkg.version;
|
|
6797
6550
|
}
|
|
6798
6551
|
}
|
|
@@ -6807,7 +6560,7 @@ var ServiceManager = class {
|
|
|
6807
6560
|
generateStartScript(nodePath, apiEntry) {
|
|
6808
6561
|
const scriptPath = join7(homedir6(), ".agenticmail", "bin", "start-server.sh");
|
|
6809
6562
|
const scriptDir = join7(homedir6(), ".agenticmail", "bin");
|
|
6810
|
-
if (!existsSync5(scriptDir))
|
|
6563
|
+
if (!existsSync5(scriptDir)) mkdirSync3(scriptDir, { recursive: true });
|
|
6811
6564
|
const script = [
|
|
6812
6565
|
"#!/bin/bash",
|
|
6813
6566
|
"# AgenticMail auto-start script",
|
|
@@ -6858,7 +6611,7 @@ var ServiceManager = class {
|
|
|
6858
6611
|
`log "Starting API server: ${nodePath} ${apiEntry}"`,
|
|
6859
6612
|
`exec "${nodePath}" "${apiEntry}"`
|
|
6860
6613
|
].join("\n") + "\n";
|
|
6861
|
-
|
|
6614
|
+
writeFileSync3(scriptPath, script, { mode: 493 });
|
|
6862
6615
|
return scriptPath;
|
|
6863
6616
|
}
|
|
6864
6617
|
/**
|
|
@@ -6871,9 +6624,9 @@ var ServiceManager = class {
|
|
|
6871
6624
|
* - Service version tracking in env vars
|
|
6872
6625
|
*/
|
|
6873
6626
|
generatePlist(nodePath, apiEntry, configPath) {
|
|
6874
|
-
const config = JSON.parse(
|
|
6627
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
6875
6628
|
const logDir = join7(homedir6(), ".agenticmail", "logs");
|
|
6876
|
-
if (!existsSync5(logDir))
|
|
6629
|
+
if (!existsSync5(logDir)) mkdirSync3(logDir, { recursive: true });
|
|
6877
6630
|
const version = this.getVersion();
|
|
6878
6631
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
6879
6632
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -6969,7 +6722,7 @@ var ServiceManager = class {
|
|
|
6969
6722
|
* - Proper dependency ordering
|
|
6970
6723
|
*/
|
|
6971
6724
|
generateSystemdUnit(nodePath, apiEntry, configPath) {
|
|
6972
|
-
const config = JSON.parse(
|
|
6725
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
6973
6726
|
const dataDir = config.dataDir || join7(homedir6(), ".agenticmail");
|
|
6974
6727
|
const version = this.getVersion();
|
|
6975
6728
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
@@ -7024,7 +6777,7 @@ WantedBy=default.target
|
|
|
7024
6777
|
const servicePath = this.getServicePath();
|
|
7025
6778
|
if (this.os === "darwin") {
|
|
7026
6779
|
const dir = join7(homedir6(), "Library", "LaunchAgents");
|
|
7027
|
-
if (!existsSync5(dir))
|
|
6780
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
7028
6781
|
if (existsSync5(servicePath)) {
|
|
7029
6782
|
try {
|
|
7030
6783
|
execFileSync3("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -7032,7 +6785,7 @@ WantedBy=default.target
|
|
|
7032
6785
|
}
|
|
7033
6786
|
}
|
|
7034
6787
|
const plist = this.generatePlist(nodePath, apiEntry, configPath);
|
|
7035
|
-
|
|
6788
|
+
writeFileSync3(servicePath, plist);
|
|
7036
6789
|
try {
|
|
7037
6790
|
execFileSync3("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
7038
6791
|
} catch (err) {
|
|
@@ -7041,9 +6794,9 @@ WantedBy=default.target
|
|
|
7041
6794
|
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
7042
6795
|
} else if (this.os === "linux") {
|
|
7043
6796
|
const dir = join7(homedir6(), ".config", "systemd", "user");
|
|
7044
|
-
if (!existsSync5(dir))
|
|
6797
|
+
if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
|
|
7045
6798
|
const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
|
|
7046
|
-
|
|
6799
|
+
writeFileSync3(servicePath, unit);
|
|
7047
6800
|
try {
|
|
7048
6801
|
execFileSync3("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
7049
6802
|
execFileSync3("systemctl", ["--user", "enable", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -7194,14 +6947,14 @@ var SetupManager = class {
|
|
|
7194
6947
|
const envPath = join8(dataDir, ".env");
|
|
7195
6948
|
if (existsSync6(configPath)) {
|
|
7196
6949
|
try {
|
|
7197
|
-
const existing = JSON.parse(
|
|
6950
|
+
const existing = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
7198
6951
|
this.generateDockerFiles(existing);
|
|
7199
6952
|
return { configPath, envPath, config: existing, isNew: false };
|
|
7200
6953
|
} catch {
|
|
7201
6954
|
}
|
|
7202
6955
|
}
|
|
7203
6956
|
if (!existsSync6(dataDir)) {
|
|
7204
|
-
|
|
6957
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
7205
6958
|
}
|
|
7206
6959
|
const masterKey = `mk_${randomBytes2(24).toString("hex")}`;
|
|
7207
6960
|
const stalwartPassword = randomBytes2(16).toString("hex");
|
|
@@ -7217,7 +6970,7 @@ var SetupManager = class {
|
|
|
7217
6970
|
api: { port: 3100, host: "127.0.0.1" },
|
|
7218
6971
|
dataDir
|
|
7219
6972
|
};
|
|
7220
|
-
|
|
6973
|
+
writeFileSync4(configPath, JSON.stringify(config, null, 2));
|
|
7221
6974
|
chmodSync(configPath, 384);
|
|
7222
6975
|
const envContent = `# Auto-generated by agenticmail setup
|
|
7223
6976
|
STALWART_ADMIN_USER=admin
|
|
@@ -7233,7 +6986,7 @@ SMTP_PORT=587
|
|
|
7233
6986
|
IMAP_HOST=localhost
|
|
7234
6987
|
IMAP_PORT=143
|
|
7235
6988
|
`;
|
|
7236
|
-
|
|
6989
|
+
writeFileSync4(envPath, envContent);
|
|
7237
6990
|
chmodSync(envPath, 384);
|
|
7238
6991
|
this.generateDockerFiles(config);
|
|
7239
6992
|
return { configPath, envPath, config, isNew: true };
|
|
@@ -7245,11 +6998,11 @@ IMAP_PORT=143
|
|
|
7245
6998
|
generateDockerFiles(config) {
|
|
7246
6999
|
const dataDir = config.dataDir || join8(homedir7(), ".agenticmail");
|
|
7247
7000
|
if (!existsSync6(dataDir)) {
|
|
7248
|
-
|
|
7001
|
+
mkdirSync4(dataDir, { recursive: true });
|
|
7249
7002
|
}
|
|
7250
7003
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
7251
7004
|
const composePath = join8(dataDir, "docker-compose.yml");
|
|
7252
|
-
|
|
7005
|
+
writeFileSync4(composePath, `services:
|
|
7253
7006
|
stalwart:
|
|
7254
7007
|
image: stalwartlabs/stalwart:latest
|
|
7255
7008
|
container_name: agenticmail-stalwart
|
|
@@ -7272,7 +7025,7 @@ volumes:
|
|
|
7272
7025
|
stalwart-data:
|
|
7273
7026
|
`);
|
|
7274
7027
|
const tomlPath = join8(dataDir, "stalwart.toml");
|
|
7275
|
-
|
|
7028
|
+
writeFileSync4(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
7276
7029
|
|
|
7277
7030
|
[server]
|
|
7278
7031
|
hostname = "localhost"
|