@metamask-previews/wallet-cli 0.0.0-preview-1132ac340 → 0.0.0-preview-56b2c8bdd
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/CHANGELOG.md +2 -0
- package/README.md +27 -0
- package/dist/commands/daemon/call.cjs +83 -0
- package/dist/commands/daemon/call.cjs.map +1 -0
- package/dist/commands/daemon/call.d.cts +14 -0
- package/dist/commands/daemon/call.d.cts.map +1 -0
- package/dist/commands/daemon/call.d.mts +14 -0
- package/dist/commands/daemon/call.d.mts.map +1 -0
- package/dist/commands/daemon/call.mjs +81 -0
- package/dist/commands/daemon/call.mjs.map +1 -0
- package/dist/commands/daemon/purge.cjs +58 -0
- package/dist/commands/daemon/purge.cjs.map +1 -0
- package/dist/commands/daemon/purge.d.cts +10 -0
- package/dist/commands/daemon/purge.d.cts.map +1 -0
- package/dist/commands/daemon/purge.d.mts +10 -0
- package/dist/commands/daemon/purge.d.mts.map +1 -0
- package/dist/commands/daemon/purge.mjs +56 -0
- package/dist/commands/daemon/purge.mjs.map +1 -0
- package/dist/commands/daemon/start.cjs +48 -0
- package/dist/commands/daemon/start.cjs.map +1 -0
- package/dist/commands/daemon/start.d.cts +12 -0
- package/dist/commands/daemon/start.d.cts.map +1 -0
- package/dist/commands/daemon/start.d.mts +12 -0
- package/dist/commands/daemon/start.d.mts.map +1 -0
- package/dist/commands/daemon/start.mjs +46 -0
- package/dist/commands/daemon/start.mjs.map +1 -0
- package/dist/commands/daemon/status.cjs +59 -0
- package/dist/commands/daemon/status.cjs.map +1 -0
- package/dist/commands/daemon/status.d.cts +7 -0
- package/dist/commands/daemon/status.d.cts.map +1 -0
- package/dist/commands/daemon/status.d.mts +7 -0
- package/dist/commands/daemon/status.d.mts.map +1 -0
- package/dist/commands/daemon/status.mjs +57 -0
- package/dist/commands/daemon/status.mjs.map +1 -0
- package/dist/commands/daemon/stop.cjs +26 -0
- package/dist/commands/daemon/stop.cjs.map +1 -0
- package/dist/commands/daemon/stop.d.cts +7 -0
- package/dist/commands/daemon/stop.d.cts.map +1 -0
- package/dist/commands/daemon/stop.d.mts +7 -0
- package/dist/commands/daemon/stop.d.mts.map +1 -0
- package/dist/commands/daemon/stop.mjs +24 -0
- package/dist/commands/daemon/stop.mjs.map +1 -0
- package/dist/daemon/daemon-entry.cjs +245 -0
- package/dist/daemon/daemon-entry.cjs.map +1 -0
- package/dist/daemon/daemon-entry.d.cts +2 -0
- package/dist/daemon/daemon-entry.d.cts.map +1 -0
- package/dist/daemon/daemon-entry.d.mts +2 -0
- package/dist/daemon/daemon-entry.d.mts.map +1 -0
- package/dist/daemon/daemon-entry.mjs +243 -0
- package/dist/daemon/daemon-entry.mjs.map +1 -0
- package/dist/daemon/data-dir.cjs +23 -0
- package/dist/daemon/data-dir.cjs.map +1 -0
- package/dist/daemon/data-dir.d.cts +14 -0
- package/dist/daemon/data-dir.d.cts.map +1 -0
- package/dist/daemon/data-dir.d.mts +14 -0
- package/dist/daemon/data-dir.d.mts.map +1 -0
- package/dist/daemon/data-dir.mjs +19 -0
- package/dist/daemon/data-dir.mjs.map +1 -0
- package/dist/daemon/types.cjs.map +1 -1
- package/dist/daemon/types.d.cts +5 -0
- package/dist/daemon/types.d.cts.map +1 -1
- package/dist/daemon/types.d.mts +5 -0
- package/dist/daemon/types.d.mts.map +1 -1
- package/dist/daemon/types.mjs.map +1 -1
- package/dist/daemon/wallet-factory.cjs +234 -0
- package/dist/daemon/wallet-factory.cjs.map +1 -0
- package/dist/daemon/wallet-factory.d.cts +55 -0
- package/dist/daemon/wallet-factory.d.cts.map +1 -0
- package/dist/daemon/wallet-factory.d.mts +55 -0
- package/dist/daemon/wallet-factory.d.mts.map +1 -0
- package/dist/daemon/wallet-factory.mjs +230 -0
- package/dist/daemon/wallet-factory.mjs.map +1 -0
- package/dist/persistence/persistence.cjs.map +1 -1
- package/dist/persistence/persistence.d.cts +1 -1
- package/dist/persistence/persistence.d.cts.map +1 -1
- package/dist/persistence/persistence.d.mts +1 -1
- package/dist/persistence/persistence.d.mts.map +1 -1
- package/dist/persistence/persistence.mjs.map +1 -1
- package/dist/test/run-command.cjs +73 -0
- package/dist/test/run-command.cjs.map +1 -0
- package/dist/test/run-command.d.cts +27 -0
- package/dist/test/run-command.d.cts.map +1 -0
- package/dist/test/run-command.d.mts +27 -0
- package/dist/test/run-command.d.mts.map +1 -0
- package/dist/test/run-command.mjs +69 -0
- package/dist/test/run-command.mjs.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@metamask/utils");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const daemon_client_1 = require("../../daemon/daemon-client.cjs");
|
|
6
|
+
const paths_1 = require("../../daemon/paths.cjs");
|
|
7
|
+
const utils_2 = require("../../daemon/utils.cjs");
|
|
8
|
+
class DaemonStatus extends core_1.Command {
|
|
9
|
+
async run() {
|
|
10
|
+
const { socketPath, pidPath } = (0, paths_1.getDaemonPaths)(this.config.dataDir);
|
|
11
|
+
const pid = await (0, utils_2.readPidFile)(pidPath);
|
|
12
|
+
const ping = await (0, daemon_client_1.pingDaemon)(socketPath);
|
|
13
|
+
if (ping.status === 'absent') {
|
|
14
|
+
// A missing socket alone does not prove the daemon is gone: stopDaemon
|
|
15
|
+
// treats an absent socket plus an alive recorded PID as a daemon that
|
|
16
|
+
// outlived its socket. Surface that orphan here rather than reporting a
|
|
17
|
+
// clean "not running".
|
|
18
|
+
if (pid !== undefined && (0, utils_2.isProcessAlive)(pid)) {
|
|
19
|
+
this.log(`Daemon socket is missing at ${socketPath} but recorded PID ${pid} is still alive. ` +
|
|
20
|
+
`The daemon may be running without its socket; run \`mm daemon stop\` or \`mm daemon purge\`.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.log('Daemon is not running.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (ping.status === 'unreachable') {
|
|
27
|
+
const pidPart = pid === undefined ? '' : ` (recorded PID: ${pid})`;
|
|
28
|
+
this.log(`Daemon socket exists at ${socketPath} but is unresponsive${pidPart} ` +
|
|
29
|
+
`[${ping.reason}]: ${ping.error.message}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let response;
|
|
33
|
+
try {
|
|
34
|
+
response = await (0, daemon_client_1.sendCommand)({
|
|
35
|
+
socketPath,
|
|
36
|
+
method: 'getStatus',
|
|
37
|
+
timeoutMs: 5000,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.log(`Daemon socket is responsive but status request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if ((0, utils_1.isJsonRpcFailure)(response)) {
|
|
45
|
+
this.log(`Daemon is running but returned an error: ${response.error.message}`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const status = response.result;
|
|
49
|
+
if (pid !== undefined && pid !== status.pid) {
|
|
50
|
+
this.log(`Warning: PID file records ${pid} but the running daemon reports ${status.pid}. ` +
|
|
51
|
+
`Local state may be stale; consider \`mm daemon purge\`.`);
|
|
52
|
+
}
|
|
53
|
+
this.log(`Daemon is running. PID: ${status.pid}, Uptime: ${status.uptime}s`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
DaemonStatus.description = 'Check the status of the wallet daemon';
|
|
57
|
+
DaemonStatus.examples = ['<%= config.bin %> daemon status'];
|
|
58
|
+
exports.default = DaemonStatus;
|
|
59
|
+
//# sourceMappingURL=status.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.cjs","sourceRoot":"","sources":["../../../src/commands/daemon/status.ts"],"names":[],"mappings":";;AAAA,2CAAmD;AACnD,sCAAsC;AAEtC,kEAAqE;AACrE,kDAAoD;AAEpD,kDAAiE;AAEjE,MAAqB,YAAa,SAAQ,cAAO;IAKxC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,IAAA,sBAAc,EAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEpE,MAAM,GAAG,GAAG,MAAM,IAAA,mBAAW,EAAC,OAAO,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAU,EAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7B,uEAAuE;YACvE,sEAAsE;YACtE,wEAAwE;YACxE,uBAAuB;YACvB,IAAI,GAAG,KAAK,SAAS,IAAI,IAAA,sBAAc,EAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,GAAG,CACN,+BAA+B,UAAU,qBAAqB,GAAG,mBAAmB;oBAClF,8FAA8F,CACjG,CAAC;gBACF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,GAAG,GAAG,CAAC;YACnE,IAAI,CAAC,GAAG,CACN,2BAA2B,UAAU,uBAAuB,OAAO,GAAG;gBACpE,IAAI,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAC5C,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAA,2BAAW,EAAC;gBAC3B,UAAU;gBACV,MAAM,EAAE,WAAW;gBACnB,SAAS,EAAE,IAAK;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CACN,0DAA0D,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnH,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,IAAA,wBAAgB,EAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CACN,4CAA4C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,CACrE,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,MAA0B,CAAC;QACnD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5C,IAAI,CAAC,GAAG,CACN,6BAA6B,GAAG,mCAAmC,MAAM,CAAC,GAAG,IAAI;gBAC/E,yDAAyD,CAC5D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CACN,2BAA2B,MAAM,CAAC,GAAG,aAAa,MAAM,CAAC,MAAM,GAAG,CACnE,CAAC;IACJ,CAAC;;AAlEe,wBAAW,GAAG,uCAAuC,CAAC;AAEtD,qBAAQ,GAAG,CAAC,iCAAiC,CAAC,CAAC;kBAH5C,YAAY","sourcesContent":["import { isJsonRpcFailure } from '@metamask/utils';\nimport { Command } from '@oclif/core';\n\nimport { pingDaemon, sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport type { DaemonStatusInfo } from '../../daemon/types';\nimport { isProcessAlive, readPidFile } from '../../daemon/utils';\n\nexport default class DaemonStatus extends Command {\n static override description = 'Check the status of the wallet daemon';\n\n static override examples = ['<%= config.bin %> daemon status'];\n\n public async run(): Promise<void> {\n const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);\n\n const pid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n\n if (ping.status === 'absent') {\n // A missing socket alone does not prove the daemon is gone: stopDaemon\n // treats an absent socket plus an alive recorded PID as a daemon that\n // outlived its socket. Surface that orphan here rather than reporting a\n // clean \"not running\".\n if (pid !== undefined && isProcessAlive(pid)) {\n this.log(\n `Daemon socket is missing at ${socketPath} but recorded PID ${pid} is still alive. ` +\n `The daemon may be running without its socket; run \\`mm daemon stop\\` or \\`mm daemon purge\\`.`,\n );\n return;\n }\n this.log('Daemon is not running.');\n return;\n }\n\n if (ping.status === 'unreachable') {\n const pidPart = pid === undefined ? '' : ` (recorded PID: ${pid})`;\n this.log(\n `Daemon socket exists at ${socketPath} but is unresponsive${pidPart} ` +\n `[${ping.reason}]: ${ping.error.message}`,\n );\n return;\n }\n\n let response;\n try {\n response = await sendCommand({\n socketPath,\n method: 'getStatus',\n timeoutMs: 5_000,\n });\n } catch (error) {\n this.log(\n `Daemon socket is responsive but status request failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return;\n }\n\n if (isJsonRpcFailure(response)) {\n this.log(\n `Daemon is running but returned an error: ${response.error.message}`,\n );\n return;\n }\n\n const status = response.result as DaemonStatusInfo;\n if (pid !== undefined && pid !== status.pid) {\n this.log(\n `Warning: PID file records ${pid} but the running daemon reports ${status.pid}. ` +\n `Local state may be stale; consider \\`mm daemon purge\\`.`,\n );\n }\n this.log(\n `Daemon is running. PID: ${status.pid}, Uptime: ${status.uptime}s`,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.cts","sourceRoot":"","sources":["../../../src/commands/daemon/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAOtC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,OAAO;IAC/C,OAAgB,WAAW,SAA2C;IAEtE,OAAgB,QAAQ,WAAuC;IAElD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA+DlC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.mts","sourceRoot":"","sources":["../../../src/commands/daemon/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAOtC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,OAAO;IAC/C,OAAgB,WAAW,SAA2C;IAEtE,OAAgB,QAAQ,WAAuC;IAElD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA+DlC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isJsonRpcFailure } from "@metamask/utils";
|
|
2
|
+
import { Command } from "@oclif/core";
|
|
3
|
+
import { pingDaemon, sendCommand } from "../../daemon/daemon-client.mjs";
|
|
4
|
+
import { getDaemonPaths } from "../../daemon/paths.mjs";
|
|
5
|
+
import { isProcessAlive, readPidFile } from "../../daemon/utils.mjs";
|
|
6
|
+
class DaemonStatus extends Command {
|
|
7
|
+
async run() {
|
|
8
|
+
const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);
|
|
9
|
+
const pid = await readPidFile(pidPath);
|
|
10
|
+
const ping = await pingDaemon(socketPath);
|
|
11
|
+
if (ping.status === 'absent') {
|
|
12
|
+
// A missing socket alone does not prove the daemon is gone: stopDaemon
|
|
13
|
+
// treats an absent socket plus an alive recorded PID as a daemon that
|
|
14
|
+
// outlived its socket. Surface that orphan here rather than reporting a
|
|
15
|
+
// clean "not running".
|
|
16
|
+
if (pid !== undefined && isProcessAlive(pid)) {
|
|
17
|
+
this.log(`Daemon socket is missing at ${socketPath} but recorded PID ${pid} is still alive. ` +
|
|
18
|
+
`The daemon may be running without its socket; run \`mm daemon stop\` or \`mm daemon purge\`.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.log('Daemon is not running.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (ping.status === 'unreachable') {
|
|
25
|
+
const pidPart = pid === undefined ? '' : ` (recorded PID: ${pid})`;
|
|
26
|
+
this.log(`Daemon socket exists at ${socketPath} but is unresponsive${pidPart} ` +
|
|
27
|
+
`[${ping.reason}]: ${ping.error.message}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await sendCommand({
|
|
33
|
+
socketPath,
|
|
34
|
+
method: 'getStatus',
|
|
35
|
+
timeoutMs: 5000,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.log(`Daemon socket is responsive but status request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (isJsonRpcFailure(response)) {
|
|
43
|
+
this.log(`Daemon is running but returned an error: ${response.error.message}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const status = response.result;
|
|
47
|
+
if (pid !== undefined && pid !== status.pid) {
|
|
48
|
+
this.log(`Warning: PID file records ${pid} but the running daemon reports ${status.pid}. ` +
|
|
49
|
+
`Local state may be stale; consider \`mm daemon purge\`.`);
|
|
50
|
+
}
|
|
51
|
+
this.log(`Daemon is running. PID: ${status.pid}, Uptime: ${status.uptime}s`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
DaemonStatus.description = 'Check the status of the wallet daemon';
|
|
55
|
+
DaemonStatus.examples = ['<%= config.bin %> daemon status'];
|
|
56
|
+
export default DaemonStatus;
|
|
57
|
+
//# sourceMappingURL=status.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.mjs","sourceRoot":"","sources":["../../../src/commands/daemon/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,wBAAwB;AACnD,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAEtC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,uCAAmC;AACrE,OAAO,EAAE,cAAc,EAAE,+BAA2B;AAEpD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,+BAA2B;AAEjE,MAAqB,YAAa,SAAQ,OAAO;IAKxC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEpE,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;QAE1C,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7B,uEAAuE;YACvE,sEAAsE;YACtE,wEAAwE;YACxE,uBAAuB;YACvB,IAAI,GAAG,KAAK,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,GAAG,CACN,+BAA+B,UAAU,qBAAqB,GAAG,mBAAmB;oBAClF,8FAA8F,CACjG,CAAC;gBACF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,GAAG,GAAG,CAAC;YACnE,IAAI,CAAC,GAAG,CACN,2BAA2B,UAAU,uBAAuB,OAAO,GAAG;gBACpE,IAAI,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAC5C,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,WAAW,CAAC;gBAC3B,UAAU;gBACV,MAAM,EAAE,WAAW;gBACnB,SAAS,EAAE,IAAK;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CACN,0DAA0D,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnH,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CACN,4CAA4C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,CACrE,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,MAA0B,CAAC;QACnD,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5C,IAAI,CAAC,GAAG,CACN,6BAA6B,GAAG,mCAAmC,MAAM,CAAC,GAAG,IAAI;gBAC/E,yDAAyD,CAC5D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,GAAG,CACN,2BAA2B,MAAM,CAAC,GAAG,aAAa,MAAM,CAAC,MAAM,GAAG,CACnE,CAAC;IACJ,CAAC;;AAlEe,wBAAW,GAAG,uCAAuC,CAAC;AAEtD,qBAAQ,GAAG,CAAC,iCAAiC,CAAC,CAAC;eAH5C,YAAY","sourcesContent":["import { isJsonRpcFailure } from '@metamask/utils';\nimport { Command } from '@oclif/core';\n\nimport { pingDaemon, sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport type { DaemonStatusInfo } from '../../daemon/types';\nimport { isProcessAlive, readPidFile } from '../../daemon/utils';\n\nexport default class DaemonStatus extends Command {\n static override description = 'Check the status of the wallet daemon';\n\n static override examples = ['<%= config.bin %> daemon status'];\n\n public async run(): Promise<void> {\n const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);\n\n const pid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n\n if (ping.status === 'absent') {\n // A missing socket alone does not prove the daemon is gone: stopDaemon\n // treats an absent socket plus an alive recorded PID as a daemon that\n // outlived its socket. Surface that orphan here rather than reporting a\n // clean \"not running\".\n if (pid !== undefined && isProcessAlive(pid)) {\n this.log(\n `Daemon socket is missing at ${socketPath} but recorded PID ${pid} is still alive. ` +\n `The daemon may be running without its socket; run \\`mm daemon stop\\` or \\`mm daemon purge\\`.`,\n );\n return;\n }\n this.log('Daemon is not running.');\n return;\n }\n\n if (ping.status === 'unreachable') {\n const pidPart = pid === undefined ? '' : ` (recorded PID: ${pid})`;\n this.log(\n `Daemon socket exists at ${socketPath} but is unresponsive${pidPart} ` +\n `[${ping.reason}]: ${ping.error.message}`,\n );\n return;\n }\n\n let response;\n try {\n response = await sendCommand({\n socketPath,\n method: 'getStatus',\n timeoutMs: 5_000,\n });\n } catch (error) {\n this.log(\n `Daemon socket is responsive but status request failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return;\n }\n\n if (isJsonRpcFailure(response)) {\n this.log(\n `Daemon is running but returned an error: ${response.error.message}`,\n );\n return;\n }\n\n const status = response.result as DaemonStatusInfo;\n if (pid !== undefined && pid !== status.pid) {\n this.log(\n `Warning: PID file records ${pid} but the running daemon reports ${status.pid}. ` +\n `Local state may be stale; consider \\`mm daemon purge\\`.`,\n );\n }\n this.log(\n `Daemon is running. PID: ${status.pid}, Uptime: ${status.uptime}s`,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const daemon_client_1 = require("../../daemon/daemon-client.cjs");
|
|
5
|
+
const paths_1 = require("../../daemon/paths.cjs");
|
|
6
|
+
const stop_daemon_1 = require("../../daemon/stop-daemon.cjs");
|
|
7
|
+
const utils_1 = require("../../daemon/utils.cjs");
|
|
8
|
+
class DaemonStop extends core_1.Command {
|
|
9
|
+
async run() {
|
|
10
|
+
const { socketPath, pidPath } = (0, paths_1.getDaemonPaths)(this.config.dataDir);
|
|
11
|
+
const ping = await (0, daemon_client_1.pingDaemon)(socketPath);
|
|
12
|
+
const pid = await (0, utils_1.readPidFile)(pidPath);
|
|
13
|
+
if (ping.status === 'absent' && pid === undefined) {
|
|
14
|
+
this.log('Daemon is not running.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const stopped = await (0, stop_daemon_1.stopDaemon)(socketPath, pidPath, (message) => this.log(message));
|
|
18
|
+
if (!stopped) {
|
|
19
|
+
this.error('Daemon did not stop within timeout.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
DaemonStop.description = 'Stop the wallet daemon';
|
|
24
|
+
DaemonStop.examples = ['<%= config.bin %> daemon stop'];
|
|
25
|
+
exports.default = DaemonStop;
|
|
26
|
+
//# sourceMappingURL=stop.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.cjs","sourceRoot":"","sources":["../../../src/commands/daemon/stop.ts"],"names":[],"mappings":";;AAAA,sCAAsC;AAEtC,kEAAwD;AACxD,kDAAoD;AACpD,8DAAsD;AACtD,kDAAiD;AAEjD,MAAqB,UAAW,SAAQ,cAAO;IAKtC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,IAAA,sBAAc,EAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEpE,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAU,EAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,IAAA,mBAAW,EAAC,OAAO,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAClD,IAAI,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAA,wBAAU,EAAC,UAAU,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAChE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAClB,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;;AArBe,sBAAW,GAAG,wBAAwB,CAAC;AAEvC,mBAAQ,GAAG,CAAC,+BAA+B,CAAC,CAAC;kBAH1C,UAAU","sourcesContent":["import { Command } from '@oclif/core';\n\nimport { pingDaemon } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { stopDaemon } from '../../daemon/stop-daemon';\nimport { readPidFile } from '../../daemon/utils';\n\nexport default class DaemonStop extends Command {\n static override description = 'Stop the wallet daemon';\n\n static override examples = ['<%= config.bin %> daemon stop'];\n\n public async run(): Promise<void> {\n const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);\n\n const ping = await pingDaemon(socketPath);\n const pid = await readPidFile(pidPath);\n if (ping.status === 'absent' && pid === undefined) {\n this.log('Daemon is not running.');\n return;\n }\n\n const stopped = await stopDaemon(socketPath, pidPath, (message) =>\n this.log(message),\n );\n\n if (!stopped) {\n this.error('Daemon did not stop within timeout.');\n }\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.d.cts","sourceRoot":"","sources":["../../../src/commands/daemon/stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAOtC,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IAC7C,OAAgB,WAAW,SAA4B;IAEvD,OAAgB,QAAQ,WAAqC;IAEhD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAkBlC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.d.mts","sourceRoot":"","sources":["../../../src/commands/daemon/stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAOtC,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IAC7C,OAAgB,WAAW,SAA4B;IAEvD,OAAgB,QAAQ,WAAqC;IAEhD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAkBlC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import { pingDaemon } from "../../daemon/daemon-client.mjs";
|
|
3
|
+
import { getDaemonPaths } from "../../daemon/paths.mjs";
|
|
4
|
+
import { stopDaemon } from "../../daemon/stop-daemon.mjs";
|
|
5
|
+
import { readPidFile } from "../../daemon/utils.mjs";
|
|
6
|
+
class DaemonStop extends Command {
|
|
7
|
+
async run() {
|
|
8
|
+
const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);
|
|
9
|
+
const ping = await pingDaemon(socketPath);
|
|
10
|
+
const pid = await readPidFile(pidPath);
|
|
11
|
+
if (ping.status === 'absent' && pid === undefined) {
|
|
12
|
+
this.log('Daemon is not running.');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const stopped = await stopDaemon(socketPath, pidPath, (message) => this.log(message));
|
|
16
|
+
if (!stopped) {
|
|
17
|
+
this.error('Daemon did not stop within timeout.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
DaemonStop.description = 'Stop the wallet daemon';
|
|
22
|
+
DaemonStop.examples = ['<%= config.bin %> daemon stop'];
|
|
23
|
+
export default DaemonStop;
|
|
24
|
+
//# sourceMappingURL=stop.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.mjs","sourceRoot":"","sources":["../../../src/commands/daemon/stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB;AAEtC,OAAO,EAAE,UAAU,EAAE,uCAAmC;AACxD,OAAO,EAAE,cAAc,EAAE,+BAA2B;AACpD,OAAO,EAAE,UAAU,EAAE,qCAAiC;AACtD,OAAO,EAAE,WAAW,EAAE,+BAA2B;AAEjD,MAAqB,UAAW,SAAQ,OAAO;IAKtC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEpE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAClD,IAAI,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAChE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAClB,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;;AArBe,sBAAW,GAAG,wBAAwB,CAAC;AAEvC,mBAAQ,GAAG,CAAC,+BAA+B,CAAC,CAAC;eAH1C,UAAU","sourcesContent":["import { Command } from '@oclif/core';\n\nimport { pingDaemon } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { stopDaemon } from '../../daemon/stop-daemon';\nimport { readPidFile } from '../../daemon/utils';\n\nexport default class DaemonStop extends Command {\n static override description = 'Stop the wallet daemon';\n\n static override examples = ['<%= config.bin %> daemon stop'];\n\n public async run(): Promise<void> {\n const { socketPath, pidPath } = getDaemonPaths(this.config.dataDir);\n\n const ping = await pingDaemon(socketPath);\n const pid = await readPidFile(pidPath);\n if (ping.status === 'absent' && pid === undefined) {\n this.log('Daemon is not running.');\n return;\n }\n\n const stopped = await stopDaemon(socketPath, pidPath, (message) =>\n this.log(message),\n );\n\n if (!stopped) {\n this.error('Daemon did not stop within timeout.');\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const promises_1 = require("node:fs/promises");
|
|
4
|
+
const daemon_client_1 = require("./daemon-client.cjs");
|
|
5
|
+
const data_dir_1 = require("./data-dir.cjs");
|
|
6
|
+
const paths_1 = require("./paths.cjs");
|
|
7
|
+
const rpc_socket_server_1 = require("./rpc-socket-server.cjs");
|
|
8
|
+
const utils_1 = require("./utils.cjs");
|
|
9
|
+
const wallet_factory_1 = require("./wallet-factory.cjs");
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
main().catch((error) => {
|
|
12
|
+
process.stderr.write(`Daemon fatal: ${String(error)}\n`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
});
|
|
15
|
+
async function main() {
|
|
16
|
+
const dataDir = process.env.MM_DAEMON_DATA_DIR;
|
|
17
|
+
if (!dataDir) {
|
|
18
|
+
throw new Error('MM_DAEMON_DATA_DIR environment variable is required');
|
|
19
|
+
}
|
|
20
|
+
const infuraProjectId = process.env.INFURA_PROJECT_ID;
|
|
21
|
+
if (!infuraProjectId) {
|
|
22
|
+
throw new Error('INFURA_PROJECT_ID environment variable is required');
|
|
23
|
+
}
|
|
24
|
+
const password = process.env.MM_WALLET_PASSWORD;
|
|
25
|
+
if (!password) {
|
|
26
|
+
throw new Error('MM_WALLET_PASSWORD environment variable is required');
|
|
27
|
+
}
|
|
28
|
+
const srp = process.env.MM_WALLET_SRP;
|
|
29
|
+
if (!srp) {
|
|
30
|
+
throw new Error('MM_WALLET_SRP environment variable is required');
|
|
31
|
+
}
|
|
32
|
+
// Scrub the wallet secrets from the environment now they are captured. The
|
|
33
|
+
// daemon is long-lived and dispatches arbitrary messenger actions over its
|
|
34
|
+
// socket, so leaving the SRP/password in `process.env` for its whole lifetime
|
|
35
|
+
// needlessly widens their exposure to any in-process code.
|
|
36
|
+
delete process.env.MM_WALLET_PASSWORD;
|
|
37
|
+
delete process.env.MM_WALLET_SRP;
|
|
38
|
+
await (0, data_dir_1.ensureOwnerOnlyDirectory)(dataDir);
|
|
39
|
+
const { socketPath: defaultSocketPath, pidPath, logPath, dbPath, } = (0, paths_1.getDaemonPaths)(dataDir);
|
|
40
|
+
const socketPath = process.env.MM_DAEMON_SOCKET_PATH ?? defaultSocketPath;
|
|
41
|
+
const log = makeLogger(logPath);
|
|
42
|
+
log('Starting daemon...');
|
|
43
|
+
// Pre-flight: refuse to take over if a responsive daemon already owns this
|
|
44
|
+
// socket. If the existing PID file is stale (or the socket is dead), clean
|
|
45
|
+
// it up so the exclusive PID-file write below has a chance to succeed.
|
|
46
|
+
await claimDaemonSlot(pidPath, socketPath, log);
|
|
47
|
+
const pidFileContents = `${process.pid}\n${startTime}\n`;
|
|
48
|
+
// Claim the slot atomically BEFORE opening the SQLite database or
|
|
49
|
+
// constructing the Wallet. Two concurrent `daemon start` invocations can
|
|
50
|
+
// both pass `claimDaemonSlot` (the gap between its preflight and the slot
|
|
51
|
+
// write is racy); without this ordering, both would open `wallet.db` and
|
|
52
|
+
// both would run first-run SRP import before one loses the wx race.
|
|
53
|
+
try {
|
|
54
|
+
await (0, promises_1.writeFile)(pidPath, pidFileContents, { flag: 'wx' });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw error instanceof Error
|
|
58
|
+
? Object.assign(error, {
|
|
59
|
+
message: `Failed to claim daemon slot at ${pidPath}: ${error.message}`,
|
|
60
|
+
})
|
|
61
|
+
: /* istanbul ignore next -- node:fs/promises always rejects with an Error */
|
|
62
|
+
new Error(`Failed to claim daemon slot at ${pidPath}: ${String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
let wallet;
|
|
65
|
+
let dispose;
|
|
66
|
+
let handle;
|
|
67
|
+
try {
|
|
68
|
+
({ wallet, dispose } = await (0, wallet_factory_1.createWallet)({
|
|
69
|
+
databasePath: dbPath,
|
|
70
|
+
password,
|
|
71
|
+
srp,
|
|
72
|
+
infuraProjectId,
|
|
73
|
+
log,
|
|
74
|
+
}));
|
|
75
|
+
const constructedWallet = wallet;
|
|
76
|
+
const handlers = {
|
|
77
|
+
getStatus: async () => ({
|
|
78
|
+
pid: process.pid,
|
|
79
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
80
|
+
}),
|
|
81
|
+
// Arbitrary messenger dispatch is intentional: the CLI exposes the full
|
|
82
|
+
// messenger surface over a Unix socket inside the per-user oclif data
|
|
83
|
+
// directory. The dataDir is chmodded to 0o700 above and the socket to
|
|
84
|
+
// 0o600 by the RPC server on bind, so only the owning user can open
|
|
85
|
+
// them, but there is no in-process auth check beyond that
|
|
86
|
+
// filesystem-permission barrier.
|
|
87
|
+
call: async (params) => {
|
|
88
|
+
if (!Array.isArray(params) || typeof params[0] !== 'string') {
|
|
89
|
+
throw new Error('Expected params to be an array with an action name');
|
|
90
|
+
}
|
|
91
|
+
const [action, ...args] = params;
|
|
92
|
+
const result = constructedWallet.messenger.call(action, ...args);
|
|
93
|
+
return (result instanceof Promise ? await result : result);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
// `startRpcSocketServer` restricts the socket to the owner (chmod 0o600)
|
|
97
|
+
// on bind and never leaves a live server/socket behind if it rejects, so
|
|
98
|
+
// the catch below has nothing of its own to close.
|
|
99
|
+
handle = await (0, rpc_socket_server_1.startRpcSocketServer)({
|
|
100
|
+
socketPath,
|
|
101
|
+
handlers,
|
|
102
|
+
onShutdown: async () => shutdown('RPC shutdown'),
|
|
103
|
+
log,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// `dispose` is undefined only when `createWallet` itself threw — it has
|
|
108
|
+
// already torn down its own store in that case.
|
|
109
|
+
if (dispose) {
|
|
110
|
+
await dispose();
|
|
111
|
+
}
|
|
112
|
+
// Only remove the PID file if it's still ours (we may have lost the race
|
|
113
|
+
// and the file now belongs to another daemon).
|
|
114
|
+
await removeOwnedPidFile(pidPath, pidFileContents).catch((rmError) => {
|
|
115
|
+
log(`Failed to remove PID file during cleanup: ${String(rmError)}`);
|
|
116
|
+
});
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
// Stable non-undefined refs for the shutdown closures (TS won't narrow the
|
|
120
|
+
// outer `let`s across closure escape).
|
|
121
|
+
const activeHandle = handle;
|
|
122
|
+
const activeDispose = dispose;
|
|
123
|
+
log(`Daemon started. Socket: ${socketPath}`);
|
|
124
|
+
let shutdownPromise;
|
|
125
|
+
/**
|
|
126
|
+
* Shut down the daemon idempotently. Concurrent calls coalesce.
|
|
127
|
+
*
|
|
128
|
+
* @param reason - A label describing why shutdown was triggered.
|
|
129
|
+
* @returns A promise that resolves when shutdown completes.
|
|
130
|
+
*/
|
|
131
|
+
async function shutdown(reason) {
|
|
132
|
+
if (shutdownPromise === undefined) {
|
|
133
|
+
log(`Shutting down (${reason})...`);
|
|
134
|
+
shutdownPromise = (async () => {
|
|
135
|
+
try {
|
|
136
|
+
await activeHandle.close();
|
|
137
|
+
}
|
|
138
|
+
catch (closeError) {
|
|
139
|
+
log(`handle.close() failed: ${String(closeError)}`);
|
|
140
|
+
}
|
|
141
|
+
await activeDispose();
|
|
142
|
+
await Promise.all([
|
|
143
|
+
removeOwnedPidFile(pidPath, pidFileContents).catch((rmError) => {
|
|
144
|
+
log(`Failed to remove PID file: ${String(rmError)}`);
|
|
145
|
+
}),
|
|
146
|
+
(0, promises_1.rm)(socketPath, { force: true }).catch((rmError) => {
|
|
147
|
+
log(`Failed to remove socket file: ${String(rmError)}`);
|
|
148
|
+
}),
|
|
149
|
+
]);
|
|
150
|
+
})();
|
|
151
|
+
}
|
|
152
|
+
return shutdownPromise;
|
|
153
|
+
}
|
|
154
|
+
process.on('SIGTERM', () => {
|
|
155
|
+
/* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */
|
|
156
|
+
shutdown('SIGTERM').catch(() => undefined);
|
|
157
|
+
});
|
|
158
|
+
process.on('SIGINT', () => {
|
|
159
|
+
/* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */
|
|
160
|
+
shutdown('SIGINT').catch(() => undefined);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Refuse to start if a responsive daemon already owns the socket. Otherwise
|
|
165
|
+
* clear any stale PID/socket files so the exclusive PID-file write can
|
|
166
|
+
* proceed.
|
|
167
|
+
*
|
|
168
|
+
* @param pidPath - The PID file path.
|
|
169
|
+
* @param socketPath - The socket path.
|
|
170
|
+
* @param log - Logger for diagnostic messages.
|
|
171
|
+
*/
|
|
172
|
+
async function claimDaemonSlot(pidPath, socketPath, log) {
|
|
173
|
+
const existingPid = await (0, utils_1.readPidFile)(pidPath);
|
|
174
|
+
const ping = await (0, daemon_client_1.pingDaemon)(socketPath);
|
|
175
|
+
if (ping.status === 'responsive') {
|
|
176
|
+
const pidPart = existingPid === undefined
|
|
177
|
+
? '(no PID file present)'
|
|
178
|
+
: `(pid ${existingPid})`;
|
|
179
|
+
throw new Error(`A daemon is already running on ${socketPath} ${pidPart}`);
|
|
180
|
+
}
|
|
181
|
+
// Refuse to clobber when the recorded PID is still alive, regardless of
|
|
182
|
+
// whether the socket exists. Possible scenarios:
|
|
183
|
+
// - `unreachable`: wedged or mid-startup sibling daemon (socket present
|
|
184
|
+
// but not responding to JSON-RPC).
|
|
185
|
+
// - `absent`: a sibling daemon that hasn't yet bound its socket, or one
|
|
186
|
+
// whose socket was manually removed. In either case, removing its PID
|
|
187
|
+
// file would orphan it from `daemon stop`.
|
|
188
|
+
if (existingPid !== undefined && (0, utils_1.isProcessAlive)(existingPid)) {
|
|
189
|
+
const detail = ping.status === 'unreachable'
|
|
190
|
+
? `socket at ${socketPath} is unresponsive (${ping.error.message})`
|
|
191
|
+
: `no socket at ${socketPath}, but pid is still alive`;
|
|
192
|
+
throw new Error(`A daemon is already running (pid ${existingPid}): ${detail}. ` +
|
|
193
|
+
`Run \`mm daemon stop\` (or \`mm daemon purge\`) before starting a new daemon.`);
|
|
194
|
+
}
|
|
195
|
+
if (ping.status === 'unreachable') {
|
|
196
|
+
log(`Removing stale socket at ${socketPath} (${ping.error.message}).`);
|
|
197
|
+
}
|
|
198
|
+
// Always clear both files before claiming the slot. The PID file may be
|
|
199
|
+
// corrupt (truncated, partial write from a crashed run); without this, the
|
|
200
|
+
// exclusive `wx` write below would fail with EEXIST and the daemon could
|
|
201
|
+
// not start until a human manually deleted the file.
|
|
202
|
+
await Promise.all([
|
|
203
|
+
(0, promises_1.rm)(pidPath, { force: true }),
|
|
204
|
+
(0, promises_1.rm)(socketPath, { force: true }),
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Remove the PID file only if it still contains our exact contents. Guards
|
|
209
|
+
* against a racing daemon's PID file being removed by this daemon during
|
|
210
|
+
* cleanup.
|
|
211
|
+
*
|
|
212
|
+
* @param pidPath - Path to the PID file.
|
|
213
|
+
* @param expectedContents - The contents we wrote when claiming the slot.
|
|
214
|
+
*/
|
|
215
|
+
async function removeOwnedPidFile(pidPath, expectedContents) {
|
|
216
|
+
let actual;
|
|
217
|
+
try {
|
|
218
|
+
actual = await (0, promises_1.readFile)(pidPath, 'utf-8');
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if ((0, utils_1.isErrorWithCode)(error, 'ENOENT')) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
if (actual === expectedContents) {
|
|
227
|
+
await (0, promises_1.rm)(pidPath, { force: true });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create a file logger that appends timestamped lines to `logPath`, falling
|
|
232
|
+
* back to stderr if the append fails.
|
|
233
|
+
*
|
|
234
|
+
* @param logPath - The log file path.
|
|
235
|
+
* @returns A logging function.
|
|
236
|
+
*/
|
|
237
|
+
function makeLogger(logPath) {
|
|
238
|
+
return (message) => {
|
|
239
|
+
const line = `[${new Date().toISOString()}] ${message}\n`;
|
|
240
|
+
(0, promises_1.appendFile)(logPath, line).catch((error) => {
|
|
241
|
+
process.stderr.write(`[log write failed: ${String(error)}] ${message}\n`);
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=daemon-entry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-entry.cjs","sourceRoot":"","sources":["../../src/daemon/daemon-entry.ts"],"names":[],"mappings":";;AAEA,+CAAuE;AAEvE,uDAA6C;AAC7C,6CAAsD;AACtD,uCAAyC;AACzC,+DAA2D;AAG3D,uCAAuE;AACvE,yDAAgD;AAEhD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAE7B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACtD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IAED,2EAA2E;IAC3E,2EAA2E;IAC3E,8EAA8E;IAC9E,2DAA2D;IAC3D,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACtC,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAEjC,MAAM,IAAA,mCAAwB,EAAC,OAAO,CAAC,CAAC;IAExC,MAAM,EACJ,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EACP,OAAO,EACP,MAAM,GACP,GAAG,IAAA,sBAAc,EAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,iBAAiB,CAAC;IAE1E,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAChC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAE1B,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IAEhD,MAAM,eAAe,GAAG,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC;IAEzD,kEAAkE;IAClE,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,oEAAoE;IACpE,IAAI,CAAC;QACH,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,YAAY,KAAK;YAC1B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;gBACnB,OAAO,EAAE,kCAAkC,OAAO,KAAK,KAAK,CAAC,OAAO,EAAE;aACvE,CAAC;YACJ,CAAC,CAAC,2EAA2E;gBAC3E,IAAI,KAAK,CACP,kCAAkC,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAC9D,CAAC;IACR,CAAC;IAED,IAAI,MAA0B,CAAC;IAC/B,IAAI,OAA0C,CAAC;IAC/C,IAAI,MAAyC,CAAC;IAE9C,IAAI,CAAC;QACH,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAA,6BAAY,EAAC;YACxC,YAAY,EAAE,MAAM;YACpB,QAAQ;YACR,GAAG;YACH,eAAe;YACf,GAAG;SACJ,CAAC,CAAC,CAAC;QAEJ,MAAM,iBAAiB,GAAG,MAAM,CAAC;QACjC,MAAM,QAAQ,GAAkB;YAC9B,SAAS,EAAE,KAAK,IAA+B,EAAE,CAAC,CAAC;gBACjD,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;aACpD,CAAC;YACF,wEAAwE;YACxE,sEAAsE;YACtE,sEAAsE;YACtE,oEAAoE;YACpE,0DAA0D;YAC1D,iCAAiC;YACjC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;oBAC5D,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;gBACxE,CAAC;gBACD,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,MAA6B,CAAC;gBAQxD,MAAM,MAAM,GACV,iBAAiB,CAAC,SACnB,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;gBACxB,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,MAAM,CAAS,CAAC;YACrE,CAAC;SACF,CAAC;QAEF,yEAAyE;QACzE,yEAAyE;QACzE,mDAAmD;QACnD,MAAM,GAAG,MAAM,IAAA,wCAAoB,EAAC;YAClC,UAAU;YACV,QAAQ;YACR,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAChD,GAAG;SACJ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,gDAAgD;QAChD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,yEAAyE;QACzE,+CAA+C;QAC/C,MAAM,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,KAAK,CACtD,CAAC,OAAgB,EAAE,EAAE;YACnB,GAAG,CAAC,6CAA6C,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC,CACF,CAAC;QACF,MAAM,KAAK,CAAC;IACd,CAAC;IAED,2EAA2E;IAC3E,uCAAuC;IACvC,MAAM,YAAY,GAAG,MAAM,CAAC;IAC5B,MAAM,aAAa,GAAG,OAAO,CAAC;IAE9B,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IAE7C,IAAI,eAA0C,CAAC;IAE/C;;;;;OAKG;IACH,KAAK,UAAU,QAAQ,CAAC,MAAc;QACpC,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAClC,GAAG,CAAC,kBAAkB,MAAM,MAAM,CAAC,CAAC;YACpC,eAAe,GAAG,CAAC,KAAK,IAAmB,EAAE;gBAC3C,IAAI,CAAC;oBACH,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;gBAC7B,CAAC;gBAAC,OAAO,UAAU,EAAE,CAAC;oBACpB,GAAG,CAAC,0BAA0B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACtD,CAAC;gBACD,MAAM,aAAa,EAAE,CAAC;gBACtB,MAAM,OAAO,CAAC,GAAG,CAAC;oBAChB,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,KAAK,CAChD,CAAC,OAAgB,EAAE,EAAE;wBACnB,GAAG,CAAC,8BAA8B,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACvD,CAAC,CACF;oBACD,IAAA,aAAE,EAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,OAAgB,EAAE,EAAE;wBACzD,GAAG,CAAC,iCAAiC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBAC1D,CAAC,CAAC;iBACH,CAAC,CAAC;YACL,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,iJAAiJ;QACjJ,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,iJAAiJ;QACjJ,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,eAAe,CAC5B,OAAe,EACf,UAAkB,EAClB,GAAW;IAEX,MAAM,WAAW,GAAG,MAAM,IAAA,mBAAW,EAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAU,EAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACjC,MAAM,OAAO,GACX,WAAW,KAAK,SAAS;YACvB,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,QAAQ,WAAW,GAAG,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,IAAI,OAAO,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,wEAAwE;IACxE,iDAAiD;IACjD,wEAAwE;IACxE,qCAAqC;IACrC,wEAAwE;IACxE,wEAAwE;IACxE,6CAA6C;IAC7C,IAAI,WAAW,KAAK,SAAS,IAAI,IAAA,sBAAc,EAAC,WAAW,CAAC,EAAE,CAAC;QAC7D,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,KAAK,aAAa;YAC3B,CAAC,CAAC,aAAa,UAAU,qBAAqB,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG;YACnE,CAAC,CAAC,gBAAgB,UAAU,0BAA0B,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,oCAAoC,WAAW,MAAM,MAAM,IAAI;YAC7D,+EAA+E,CAClF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QAClC,GAAG,CAAC,4BAA4B,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACzE,CAAC;IACD,wEAAwE;IACxE,2EAA2E;IAC3E,yEAAyE;IACzE,qDAAqD;IACrD,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,IAAA,aAAE,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC5B,IAAA,aAAE,EAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,kBAAkB,CAC/B,OAAe,EACf,gBAAwB;IAExB,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,IAAA,uBAAe,EAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAChC,MAAM,IAAA,aAAE,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,CAAC,OAAe,EAAQ,EAAE;QAC/B,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,IAAI,CAAC;QAC1D,IAAA,qBAAU,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { Json } from '@metamask/utils';\nimport type { Wallet } from '@metamask/wallet';\nimport { appendFile, readFile, rm, writeFile } from 'node:fs/promises';\n\nimport { pingDaemon } from './daemon-client';\nimport { ensureOwnerOnlyDirectory } from './data-dir';\nimport { getDaemonPaths } from './paths';\nimport { startRpcSocketServer } from './rpc-socket-server';\nimport type { RpcSocketServerHandle } from './rpc-socket-server';\nimport type { DaemonStatusInfo, Logger, RpcHandlerMap } from './types';\nimport { isErrorWithCode, isProcessAlive, readPidFile } from './utils';\nimport { createWallet } from './wallet-factory';\n\nconst startTime = Date.now();\n\nmain().catch((error: unknown) => {\n process.stderr.write(`Daemon fatal: ${String(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const dataDir = process.env.MM_DAEMON_DATA_DIR;\n if (!dataDir) {\n throw new Error('MM_DAEMON_DATA_DIR environment variable is required');\n }\n\n const infuraProjectId = process.env.INFURA_PROJECT_ID;\n if (!infuraProjectId) {\n throw new Error('INFURA_PROJECT_ID environment variable is required');\n }\n\n const password = process.env.MM_WALLET_PASSWORD;\n if (!password) {\n throw new Error('MM_WALLET_PASSWORD environment variable is required');\n }\n\n const srp = process.env.MM_WALLET_SRP;\n if (!srp) {\n throw new Error('MM_WALLET_SRP environment variable is required');\n }\n\n // Scrub the wallet secrets from the environment now they are captured. The\n // daemon is long-lived and dispatches arbitrary messenger actions over its\n // socket, so leaving the SRP/password in `process.env` for its whole lifetime\n // needlessly widens their exposure to any in-process code.\n delete process.env.MM_WALLET_PASSWORD;\n delete process.env.MM_WALLET_SRP;\n\n await ensureOwnerOnlyDirectory(dataDir);\n\n const {\n socketPath: defaultSocketPath,\n pidPath,\n logPath,\n dbPath,\n } = getDaemonPaths(dataDir);\n const socketPath = process.env.MM_DAEMON_SOCKET_PATH ?? defaultSocketPath;\n\n const log = makeLogger(logPath);\n log('Starting daemon...');\n\n // Pre-flight: refuse to take over if a responsive daemon already owns this\n // socket. If the existing PID file is stale (or the socket is dead), clean\n // it up so the exclusive PID-file write below has a chance to succeed.\n await claimDaemonSlot(pidPath, socketPath, log);\n\n const pidFileContents = `${process.pid}\\n${startTime}\\n`;\n\n // Claim the slot atomically BEFORE opening the SQLite database or\n // constructing the Wallet. Two concurrent `daemon start` invocations can\n // both pass `claimDaemonSlot` (the gap between its preflight and the slot\n // write is racy); without this ordering, both would open `wallet.db` and\n // both would run first-run SRP import before one loses the wx race.\n try {\n await writeFile(pidPath, pidFileContents, { flag: 'wx' });\n } catch (error) {\n throw error instanceof Error\n ? Object.assign(error, {\n message: `Failed to claim daemon slot at ${pidPath}: ${error.message}`,\n })\n : /* istanbul ignore next -- node:fs/promises always rejects with an Error */\n new Error(\n `Failed to claim daemon slot at ${pidPath}: ${String(error)}`,\n );\n }\n\n let wallet: Wallet | undefined;\n let dispose: (() => Promise<void>) | undefined;\n let handle: RpcSocketServerHandle | undefined;\n\n try {\n ({ wallet, dispose } = await createWallet({\n databasePath: dbPath,\n password,\n srp,\n infuraProjectId,\n log,\n }));\n\n const constructedWallet = wallet;\n const handlers: RpcHandlerMap = {\n getStatus: async (): Promise<DaemonStatusInfo> => ({\n pid: process.pid,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n }),\n // Arbitrary messenger dispatch is intentional: the CLI exposes the full\n // messenger surface over a Unix socket inside the per-user oclif data\n // directory. The dataDir is chmodded to 0o700 above and the socket to\n // 0o600 by the RPC server on bind, so only the owning user can open\n // them, but there is no in-process auth check beyond that\n // filesystem-permission barrier.\n call: async (params) => {\n if (!Array.isArray(params) || typeof params[0] !== 'string') {\n throw new Error('Expected params to be an array with an action name');\n }\n const [action, ...args] = params as [string, ...Json[]];\n // The messenger's `call` is typed to a literal action-name union; the\n // daemon dispatches arbitrary action names from RPC. Cast to a\n // string-keyed `call` (which preserves arity) rather than to `any`, so\n // the only untyped value is the `unknown` result narrowed below.\n type ArbitraryDispatch = {\n call: (actionName: string, ...callArgs: Json[]) => unknown;\n };\n const result = (\n constructedWallet.messenger as unknown as ArbitraryDispatch\n ).call(action, ...args);\n return (result instanceof Promise ? await result : result) as Json;\n },\n };\n\n // `startRpcSocketServer` restricts the socket to the owner (chmod 0o600)\n // on bind and never leaves a live server/socket behind if it rejects, so\n // the catch below has nothing of its own to close.\n handle = await startRpcSocketServer({\n socketPath,\n handlers,\n onShutdown: async () => shutdown('RPC shutdown'),\n log,\n });\n } catch (error) {\n // `dispose` is undefined only when `createWallet` itself threw — it has\n // already torn down its own store in that case.\n if (dispose) {\n await dispose();\n }\n // Only remove the PID file if it's still ours (we may have lost the race\n // and the file now belongs to another daemon).\n await removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file during cleanup: ${String(rmError)}`);\n },\n );\n throw error;\n }\n\n // Stable non-undefined refs for the shutdown closures (TS won't narrow the\n // outer `let`s across closure escape).\n const activeHandle = handle;\n const activeDispose = dispose;\n\n log(`Daemon started. Socket: ${socketPath}`);\n\n let shutdownPromise: Promise<void> | undefined;\n\n /**\n * Shut down the daemon idempotently. Concurrent calls coalesce.\n *\n * @param reason - A label describing why shutdown was triggered.\n * @returns A promise that resolves when shutdown completes.\n */\n async function shutdown(reason: string): Promise<void> {\n if (shutdownPromise === undefined) {\n log(`Shutting down (${reason})...`);\n shutdownPromise = (async (): Promise<void> => {\n try {\n await activeHandle.close();\n } catch (closeError) {\n log(`handle.close() failed: ${String(closeError)}`);\n }\n await activeDispose();\n await Promise.all([\n removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file: ${String(rmError)}`);\n },\n ),\n rm(socketPath, { force: true }).catch((rmError: unknown) => {\n log(`Failed to remove socket file: ${String(rmError)}`);\n }),\n ]);\n })();\n }\n return shutdownPromise;\n }\n\n process.on('SIGTERM', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGTERM').catch(() => undefined);\n });\n process.on('SIGINT', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGINT').catch(() => undefined);\n });\n}\n\n/**\n * Refuse to start if a responsive daemon already owns the socket. Otherwise\n * clear any stale PID/socket files so the exclusive PID-file write can\n * proceed.\n *\n * @param pidPath - The PID file path.\n * @param socketPath - The socket path.\n * @param log - Logger for diagnostic messages.\n */\nasync function claimDaemonSlot(\n pidPath: string,\n socketPath: string,\n log: Logger,\n): Promise<void> {\n const existingPid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n\n if (ping.status === 'responsive') {\n const pidPart =\n existingPid === undefined\n ? '(no PID file present)'\n : `(pid ${existingPid})`;\n throw new Error(`A daemon is already running on ${socketPath} ${pidPart}`);\n }\n\n // Refuse to clobber when the recorded PID is still alive, regardless of\n // whether the socket exists. Possible scenarios:\n // - `unreachable`: wedged or mid-startup sibling daemon (socket present\n // but not responding to JSON-RPC).\n // - `absent`: a sibling daemon that hasn't yet bound its socket, or one\n // whose socket was manually removed. In either case, removing its PID\n // file would orphan it from `daemon stop`.\n if (existingPid !== undefined && isProcessAlive(existingPid)) {\n const detail =\n ping.status === 'unreachable'\n ? `socket at ${socketPath} is unresponsive (${ping.error.message})`\n : `no socket at ${socketPath}, but pid is still alive`;\n throw new Error(\n `A daemon is already running (pid ${existingPid}): ${detail}. ` +\n `Run \\`mm daemon stop\\` (or \\`mm daemon purge\\`) before starting a new daemon.`,\n );\n }\n\n if (ping.status === 'unreachable') {\n log(`Removing stale socket at ${socketPath} (${ping.error.message}).`);\n }\n // Always clear both files before claiming the slot. The PID file may be\n // corrupt (truncated, partial write from a crashed run); without this, the\n // exclusive `wx` write below would fail with EEXIST and the daemon could\n // not start until a human manually deleted the file.\n await Promise.all([\n rm(pidPath, { force: true }),\n rm(socketPath, { force: true }),\n ]);\n}\n\n/**\n * Remove the PID file only if it still contains our exact contents. Guards\n * against a racing daemon's PID file being removed by this daemon during\n * cleanup.\n *\n * @param pidPath - Path to the PID file.\n * @param expectedContents - The contents we wrote when claiming the slot.\n */\nasync function removeOwnedPidFile(\n pidPath: string,\n expectedContents: string,\n): Promise<void> {\n let actual: string;\n try {\n actual = await readFile(pidPath, 'utf-8');\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return;\n }\n throw error;\n }\n if (actual === expectedContents) {\n await rm(pidPath, { force: true });\n }\n}\n\n/**\n * Create a file logger that appends timestamped lines to `logPath`, falling\n * back to stderr if the append fails.\n *\n * @param logPath - The log file path.\n * @returns A logging function.\n */\nfunction makeLogger(logPath: string): Logger {\n return (message: string): void => {\n const line = `[${new Date().toISOString()}] ${message}\\n`;\n appendFile(logPath, line).catch((error: unknown) => {\n process.stderr.write(`[log write failed: ${String(error)}] ${message}\\n`);\n });\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-entry.d.cts","sourceRoot":"","sources":["../../src/daemon/daemon-entry.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-entry.d.mts","sourceRoot":"","sources":["../../src/daemon/daemon-entry.ts"],"names":[],"mappings":""}
|