@agenticmail/enterprise 0.5.287 → 0.5.288
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/chunk-P6YLZXKZ.js +1434 -0
- package/dist/cli-recover-ARHVBGYO.js +392 -0
- package/dist/cli-serve-BDMXATDJ.js +143 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +1 -1
- package/dist/setup-VXBO4VN5.js +20 -0
- package/package.json +1 -1
- package/src/cli-serve.ts +34 -0
- package/src/domain-lock/cli-recover.ts +198 -0
- package/src/setup/deployment.ts +16 -3
|
@@ -37,6 +37,28 @@ export async function runRecover(args: string[]): Promise<void> {
|
|
|
37
37
|
const { default: chalk } = await import('chalk');
|
|
38
38
|
const { default: ora } = await import('ora');
|
|
39
39
|
|
|
40
|
+
// ─── Detect recovery type ─────────────────────────
|
|
41
|
+
const isCloud = hasFlag(args, '--cloud') || hasFlag(args, '-c');
|
|
42
|
+
if (isCloud || (!getFlag(args, '--domain') && !getFlag(args, '--key'))) {
|
|
43
|
+
// Ask which type
|
|
44
|
+
if (!isCloud && !getFlag(args, '--domain')) {
|
|
45
|
+
const { recoveryType } = await inquirer.prompt([{
|
|
46
|
+
type: 'list',
|
|
47
|
+
name: 'recoveryType',
|
|
48
|
+
message: 'What are you recovering?',
|
|
49
|
+
choices: [
|
|
50
|
+
{ name: `AgenticMail Cloud ${chalk.dim('(yourname.agenticmail.io)')}`, value: 'cloud' },
|
|
51
|
+
{ name: `Custom Domain ${chalk.dim('(your own domain)')}`, value: 'domain' },
|
|
52
|
+
],
|
|
53
|
+
}]);
|
|
54
|
+
if (recoveryType === 'cloud') {
|
|
55
|
+
return runCloudRecover(args, inquirer, chalk, ora);
|
|
56
|
+
}
|
|
57
|
+
} else if (isCloud) {
|
|
58
|
+
return runCloudRecover(args, inquirer, chalk, ora);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
console.log('');
|
|
41
63
|
console.log(chalk.bold(' AgenticMail Enterprise — Domain Recovery'));
|
|
42
64
|
console.log(chalk.dim(' Recover your domain registration on a new machine.'));
|
|
@@ -276,6 +298,182 @@ export async function runRecover(args: string[]): Promise<void> {
|
|
|
276
298
|
}
|
|
277
299
|
}
|
|
278
300
|
|
|
301
|
+
// ─── AgenticMail Cloud Recovery ─────────────────────
|
|
302
|
+
|
|
303
|
+
const REGISTRY_URL = process.env.AGENTICMAIL_SUBDOMAIN_REGISTRY_URL
|
|
304
|
+
|| 'https://registry.agenticmail.io';
|
|
305
|
+
|
|
306
|
+
async function runCloudRecover(args: string[], inquirer: any, chalk: any, ora: any): Promise<void> {
|
|
307
|
+
console.log('');
|
|
308
|
+
console.log(chalk.bold(' AgenticMail Cloud — Recovery'));
|
|
309
|
+
console.log(chalk.dim(' Recover your agenticmail.io subdomain on a new machine.'));
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(chalk.dim(' You will need your AGENTICMAIL_VAULT_KEY — the key from your'));
|
|
312
|
+
console.log(chalk.dim(' original installation\'s ~/.agenticmail/.env file.'));
|
|
313
|
+
console.log('');
|
|
314
|
+
|
|
315
|
+
// Step 1: Get vault key
|
|
316
|
+
let vaultKey = getFlag(args, '--vault-key') || process.env.AGENTICMAIL_VAULT_KEY;
|
|
317
|
+
if (!vaultKey) {
|
|
318
|
+
const answer = await inquirer.prompt([{
|
|
319
|
+
type: 'password',
|
|
320
|
+
name: 'vaultKey',
|
|
321
|
+
message: 'Your AGENTICMAIL_VAULT_KEY:',
|
|
322
|
+
mask: '*',
|
|
323
|
+
validate: (v: string) => v.trim().length >= 16 ? true : 'Key seems too short — check your backup',
|
|
324
|
+
}]);
|
|
325
|
+
vaultKey = answer.vaultKey.trim();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Step 2: Optionally get subdomain name (speeds up recovery)
|
|
329
|
+
let subdomain = getFlag(args, '--name') || getFlag(args, '--subdomain');
|
|
330
|
+
if (!subdomain) {
|
|
331
|
+
const answer = await inquirer.prompt([{
|
|
332
|
+
type: 'input',
|
|
333
|
+
name: 'subdomain',
|
|
334
|
+
message: 'Your subdomain (optional — press Enter to auto-detect):',
|
|
335
|
+
suffix: chalk.dim('.agenticmail.io'),
|
|
336
|
+
}]);
|
|
337
|
+
subdomain = answer.subdomain?.trim() || undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Step 3: Recover from registry
|
|
341
|
+
const { createHash } = await import('crypto');
|
|
342
|
+
const vaultKeyHash = createHash('sha256').update(vaultKey!).digest('hex');
|
|
343
|
+
|
|
344
|
+
const spinner = ora('Recovering subdomain credentials...').start();
|
|
345
|
+
try {
|
|
346
|
+
const body: any = { vaultKeyHash };
|
|
347
|
+
if (subdomain) body.name = subdomain;
|
|
348
|
+
|
|
349
|
+
const resp = await fetch(`${REGISTRY_URL}/recover`, {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: { 'Content-Type': 'application/json' },
|
|
352
|
+
body: JSON.stringify(body),
|
|
353
|
+
});
|
|
354
|
+
const data = await resp.json() as any;
|
|
355
|
+
|
|
356
|
+
if (!data.success) {
|
|
357
|
+
spinner.fail(data.error || 'Recovery failed');
|
|
358
|
+
console.log('');
|
|
359
|
+
console.log(chalk.dim(' Make sure you are using the exact AGENTICMAIL_VAULT_KEY from'));
|
|
360
|
+
console.log(chalk.dim(' your original installation (~/.agenticmail/.env).'));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
spinner.succeed(`Recovered: ${chalk.bold(data.fqdn)}`);
|
|
365
|
+
|
|
366
|
+
// Step 4: Save to .env
|
|
367
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import('fs');
|
|
368
|
+
const { join } = await import('path');
|
|
369
|
+
const { homedir } = await import('os');
|
|
370
|
+
|
|
371
|
+
const amDir = join(homedir(), '.agenticmail');
|
|
372
|
+
mkdirSync(amDir, { recursive: true });
|
|
373
|
+
const envPath = join(amDir, '.env');
|
|
374
|
+
|
|
375
|
+
let envContent = '';
|
|
376
|
+
if (existsSync(envPath)) {
|
|
377
|
+
envContent = readFileSync(envPath, 'utf8');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Update or add each key
|
|
381
|
+
const updates: Record<string, string> = {
|
|
382
|
+
AGENTICMAIL_VAULT_KEY: vaultKey!,
|
|
383
|
+
AGENTICMAIL_SUBDOMAIN: data.subdomain,
|
|
384
|
+
AGENTICMAIL_DOMAIN: data.fqdn,
|
|
385
|
+
CLOUDFLARED_TOKEN: data.tunnelToken,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
for (const [key, val] of Object.entries(updates)) {
|
|
389
|
+
if (!val) continue;
|
|
390
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
391
|
+
if (regex.test(envContent)) {
|
|
392
|
+
envContent = envContent.replace(regex, `${key}=${val}`);
|
|
393
|
+
} else {
|
|
394
|
+
envContent += `${envContent.endsWith('\n') ? '' : '\n'}${key}=${val}\n`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
writeFileSync(envPath, envContent, { mode: 0o600 });
|
|
399
|
+
|
|
400
|
+
console.log('');
|
|
401
|
+
console.log(chalk.green.bold(' Recovery complete!'));
|
|
402
|
+
console.log('');
|
|
403
|
+
console.log(` Subdomain: ${chalk.bold(data.fqdn)}`);
|
|
404
|
+
console.log(` Tunnel Token: ${chalk.dim('saved to ~/.agenticmail/.env')}`);
|
|
405
|
+
console.log(` Vault Key: ${chalk.dim('saved to ~/.agenticmail/.env')}`);
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(chalk.bold(' Next steps:'));
|
|
408
|
+
console.log('');
|
|
409
|
+
console.log(` 1. Set your DATABASE_URL in ${chalk.dim('~/.agenticmail/.env')}`);
|
|
410
|
+
console.log(` ${chalk.dim('(same database from your original installation)')}`);
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log(` 2. Start your instance:`);
|
|
413
|
+
console.log(` ${chalk.cyan('npx @agenticmail/enterprise start')}`);
|
|
414
|
+
console.log('');
|
|
415
|
+
console.log(chalk.dim(' The server will auto-start cloudflared with your tunnel token.'));
|
|
416
|
+
console.log(chalk.dim(' Your dashboard will be live again at https://' + data.fqdn));
|
|
417
|
+
console.log('');
|
|
418
|
+
|
|
419
|
+
// Step 5: Offer to install cloudflared now
|
|
420
|
+
const { doInstall } = await inquirer.prompt([{
|
|
421
|
+
type: 'confirm',
|
|
422
|
+
name: 'doInstall',
|
|
423
|
+
message: 'Install cloudflared and start the tunnel now?',
|
|
424
|
+
default: true,
|
|
425
|
+
}]);
|
|
426
|
+
|
|
427
|
+
if (doInstall) {
|
|
428
|
+
const { execSync } = await import('child_process');
|
|
429
|
+
const { platform, arch } = await import('os');
|
|
430
|
+
|
|
431
|
+
// Install cloudflared if needed
|
|
432
|
+
try {
|
|
433
|
+
execSync('which cloudflared', { timeout: 3000 });
|
|
434
|
+
console.log(chalk.green(' cloudflared already installed'));
|
|
435
|
+
} catch {
|
|
436
|
+
const spinner2 = ora('Installing cloudflared...').start();
|
|
437
|
+
try {
|
|
438
|
+
const os = platform();
|
|
439
|
+
if (os === 'darwin') {
|
|
440
|
+
try {
|
|
441
|
+
execSync('brew install cloudflared', { stdio: 'pipe', timeout: 120000 });
|
|
442
|
+
} catch {
|
|
443
|
+
const cfArch = arch() === 'arm64' ? 'arm64' : 'amd64';
|
|
444
|
+
execSync(`curl -L -o /usr/local/bin/cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-${cfArch} && chmod +x /usr/local/bin/cloudflared`, { timeout: 60000 });
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
const cfArch = arch() === 'arm64' ? 'arm64' : 'amd64';
|
|
448
|
+
execSync(`curl -L -o /usr/local/bin/cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${cfArch} && chmod +x /usr/local/bin/cloudflared`, { timeout: 60000 });
|
|
449
|
+
}
|
|
450
|
+
spinner2.succeed('cloudflared installed');
|
|
451
|
+
} catch (e: any) {
|
|
452
|
+
spinner2.fail('Could not install cloudflared: ' + e.message);
|
|
453
|
+
console.log(chalk.dim(' Install manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Start via PM2
|
|
458
|
+
try {
|
|
459
|
+
const { execSync: ex } = await import('child_process');
|
|
460
|
+
ex('which pm2', { timeout: 3000 });
|
|
461
|
+
try { ex('pm2 delete cloudflared 2>/dev/null', { timeout: 5000 }); } catch {}
|
|
462
|
+
ex(`pm2 start cloudflared --name cloudflared -- tunnel --no-autoupdate run --token ${data.tunnelToken}`, { timeout: 15000 });
|
|
463
|
+
try { ex('pm2 save 2>/dev/null', { timeout: 5000 }); } catch {}
|
|
464
|
+
console.log(chalk.green(' Tunnel running via PM2'));
|
|
465
|
+
} catch {
|
|
466
|
+
console.log(chalk.dim(' Start the tunnel manually:'));
|
|
467
|
+
console.log(chalk.cyan(` cloudflared tunnel --no-autoupdate run --token ${data.tunnelToken}`));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
} catch (err: any) {
|
|
472
|
+
spinner.fail('Recovery failed: ' + err.message);
|
|
473
|
+
console.log(chalk.dim(' Check your internet connection and try again.'));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
279
477
|
/** Detect DB type from connection string */
|
|
280
478
|
function detectDbType(url: string): string {
|
|
281
479
|
const u = url.toLowerCase().trim();
|
package/src/setup/deployment.ts
CHANGED
|
@@ -17,7 +17,7 @@ const execP = promisify(execCb);
|
|
|
17
17
|
export type DeployTarget = 'cloud' | 'cloudflare-tunnel' | 'fly' | 'railway' | 'docker' | 'local';
|
|
18
18
|
|
|
19
19
|
const SUBDOMAIN_REGISTRY_URL = process.env.AGENTICMAIL_SUBDOMAIN_REGISTRY_URL
|
|
20
|
-
|| 'https://
|
|
20
|
+
|| 'https://registry.agenticmail.io';
|
|
21
21
|
|
|
22
22
|
export interface DeploymentSelection {
|
|
23
23
|
target: DeployTarget;
|
|
@@ -52,11 +52,11 @@ export async function promptDeployment(
|
|
|
52
52
|
message: 'Deploy to:',
|
|
53
53
|
choices: [
|
|
54
54
|
{
|
|
55
|
-
name: `AgenticMail Cloud ${chalk.dim('(
|
|
55
|
+
name: `AgenticMail Cloud ${chalk.green('← recommended')} ${chalk.dim('(instant URL, zero config)')}`,
|
|
56
56
|
value: 'cloud',
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
|
-
name: `Cloudflare Tunnel ${chalk.
|
|
59
|
+
name: `Cloudflare Tunnel ${chalk.dim('(self-hosted, free, no ports)')}`,
|
|
60
60
|
value: 'cloudflare-tunnel',
|
|
61
61
|
},
|
|
62
62
|
{
|
|
@@ -253,6 +253,19 @@ async function runCloudSetup(
|
|
|
253
253
|
console.log(chalk.bold.green(' ✓ Setup Complete!'));
|
|
254
254
|
console.log('');
|
|
255
255
|
console.log(` Your dashboard: ${chalk.bold.cyan('https://' + fqdn)}`);
|
|
256
|
+
|
|
257
|
+
// ── CRITICAL: Vault key backup warning ─────────
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(chalk.bgYellow.black.bold(' ⚠ SAVE YOUR RECOVERY KEY ⚠ '));
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(chalk.yellow(' If your computer crashes, you need this key to recover your subdomain.'));
|
|
262
|
+
console.log(chalk.yellow(' It is stored in ~/.agenticmail/.env — back up this file somewhere safe!'));
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(` ${chalk.bold('AGENTICMAIL_VAULT_KEY=')}${chalk.dim(process.env.AGENTICMAIL_VAULT_KEY || '(check ~/.agenticmail/.env)')}`);
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(chalk.dim(' To recover on a new machine, run:'));
|
|
267
|
+
console.log(chalk.dim(' npx @agenticmail/enterprise recover --cloud'));
|
|
268
|
+
console.log('');
|
|
256
269
|
console.log('');
|
|
257
270
|
console.log(chalk.dim(' To start your instance, run these two processes:'));
|
|
258
271
|
console.log('');
|