lnlink-server 1.1.2 → 1.1.4

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.js CHANGED
@@ -122,6 +122,15 @@ var require_linkLogger = __commonJS({
122
122
  }
123
123
  // Cache for logger instances to avoid creating multiple instances with the same logType
124
124
  static instances = /* @__PURE__ */ new Map();
125
+ // Silent mode flag - when true, all logging is suppressed (used during graceful shutdown)
126
+ static silentMode = false;
127
+ /**
128
+ * Set silent mode to suppress all logging (used during graceful shutdown)
129
+ * @param {boolean} value - Whether to enable silent mode
130
+ */
131
+ static setSilentMode(value) {
132
+ _Logger.silentMode = value;
133
+ }
125
134
  constructor(logType) {
126
135
  if (_Logger.instances.has(logType)) {
127
136
  return _Logger.instances.get(logType);
@@ -131,21 +140,27 @@ var require_linkLogger = __commonJS({
131
140
  _Logger.instances.set(logType, this);
132
141
  }
133
142
  debug(message, ...args) {
143
+ if (_Logger.silentMode) return;
134
144
  this.logger.debug({ msg: message }, ...args);
135
145
  }
136
146
  info(message, ...args) {
147
+ if (_Logger.silentMode) return;
137
148
  this.logger.info({ msg: message }, ...args);
138
149
  }
139
150
  warn(message, ...args) {
151
+ if (_Logger.silentMode) return;
140
152
  this.logger.warn({ msg: message }, ...args);
141
153
  }
142
154
  error(message, ...args) {
155
+ if (_Logger.silentMode) return;
143
156
  this.logger.error({ msg: message }, ...args);
144
157
  }
145
158
  fatal(message, ...args) {
159
+ if (_Logger.silentMode) return;
146
160
  this.logger.fatal({ msg: message }, ...args);
147
161
  }
148
162
  logWithData(level, message, data) {
163
+ if (_Logger.silentMode) return;
149
164
  this.logger[level]({ ...data, msg: message });
150
165
  }
151
166
  };
@@ -2720,7 +2735,6 @@ var require_getConfig = __commonJS({
2720
2735
  "LINK_DEBUG",
2721
2736
  "LINK_ENABLE_TOR",
2722
2737
  "LINK_NODE_ENV",
2723
- "LINK_AUTO_ASSIGN_PORTS",
2724
2738
  "LINK_HTTPS_PROXY",
2725
2739
  "LINK_DATABASE_URL",
2726
2740
  "LINK_REPORT_BASE_URL",
@@ -2759,7 +2773,7 @@ var require_getConfig = __commonJS({
2759
2773
  });
2760
2774
  }
2761
2775
  __name(isPortAvailable, "isPortAvailable");
2762
- async function findAvailablePort(basePort) {
2776
+ async function findAvailablePort(basePort, assignedPorts = /* @__PURE__ */ new Set()) {
2763
2777
  const portRanges = {
2764
2778
  10009: [10009, 20009, 30009, 40009, 50009],
2765
2779
  // LINK_LND_RPC_PORT: 10009 -> 20009 -> 30009...
@@ -2778,13 +2792,13 @@ var require_getConfig = __commonJS({
2778
2792
  };
2779
2793
  if (portRanges[basePort]) {
2780
2794
  for (const port2 of portRanges[basePort]) {
2781
- if (await isPortAvailable(port2)) {
2795
+ if (!assignedPorts.has(port2) && await isPortAvailable(port2)) {
2782
2796
  return port2;
2783
2797
  }
2784
2798
  }
2785
2799
  let port = portRanges[basePort][portRanges[basePort].length - 1] + 1;
2786
2800
  while (port < 65535) {
2787
- if (await isPortAvailable(port)) {
2801
+ if (!assignedPorts.has(port) && await isPortAvailable(port)) {
2788
2802
  return port;
2789
2803
  }
2790
2804
  port++;
@@ -2792,7 +2806,7 @@ var require_getConfig = __commonJS({
2792
2806
  } else {
2793
2807
  let port = basePort;
2794
2808
  while (port < 65535) {
2795
- if (await isPortAvailable(port)) {
2809
+ if (!assignedPorts.has(port) && await isPortAvailable(port)) {
2796
2810
  return port;
2797
2811
  }
2798
2812
  port++;
@@ -2812,16 +2826,18 @@ var require_getConfig = __commonJS({
2812
2826
  "LINK_HTTP_PORT"
2813
2827
  ];
2814
2828
  const updatedConfig = { ...config };
2829
+ const assignedPorts = /* @__PURE__ */ new Set();
2815
2830
  for (const key of portKeys) {
2816
2831
  if (updatedConfig[key]) {
2817
2832
  const basePort = Number.parseInt(updatedConfig[key]);
2818
2833
  if (!Number.isNaN(basePort)) {
2819
2834
  try {
2820
- const availablePort = await findAvailablePort(basePort);
2835
+ const availablePort = await findAvailablePort(basePort, assignedPorts);
2821
2836
  if (availablePort !== basePort) {
2822
2837
  console.log(`[port-assign] Port ${basePort} for ${key} is occupied, using ${availablePort} instead`);
2823
2838
  updatedConfig[key] = availablePort;
2824
2839
  }
2840
+ assignedPorts.add(availablePort);
2825
2841
  } catch (error) {
2826
2842
  console.warn(`[port-assign] Failed to find available port for ${key}: ${error?.message}`);
2827
2843
  }
@@ -2885,9 +2901,7 @@ var require_getConfig = __commonJS({
2885
2901
  combinedConfig.LINK_TOR_SOCKS_PORT = combinedConfig.LINK_TOR_SOCKS_PORT || 9050;
2886
2902
  combinedConfig.LINK_TOR_CONTROL_PORT = combinedConfig.LINK_TOR_CONTROL_PORT || 9051;
2887
2903
  try {
2888
- if (processConfig.LINK_AUTO_ASSIGN_PORTS !== "false") {
2889
- combinedConfig = await assignAvailablePorts(combinedConfig);
2890
- }
2904
+ combinedConfig = await assignAvailablePorts(combinedConfig);
2891
2905
  } catch (portError) {
2892
2906
  console.warn(
2893
2907
  "Port assignment failed, using default ports:",
@@ -5577,7 +5591,6 @@ var require_processManager = __commonJS({
5577
5591
  "business/service/nodeManage/processManager.js"(exports2, module2) {
5578
5592
  var { spawn } = require("node:child_process");
5579
5593
  var findProcess = require("find-process").default;
5580
- var { getLightningService } = require_common();
5581
5594
  var { getConfig: getConfig2 } = require_getConfig();
5582
5595
  var Logger = require_linkLogger();
5583
5596
  var { sleep } = require_timeUtils();
@@ -5587,6 +5600,11 @@ var require_processManager = __commonJS({
5587
5600
  return config.LINK_EXTERNAL_NODES === "true" || config.LINK_EXTERNAL_NODES === true;
5588
5601
  }
5589
5602
  __name(isExternalNodesMode, "isExternalNodesMode");
5603
+ function shouldDetach() {
5604
+ const config = getConfig2();
5605
+ return config.LINK_NODE_ENV !== "app";
5606
+ }
5607
+ __name(shouldDetach, "shouldDetach");
5590
5608
  function terminateProcess(pid, force = false) {
5591
5609
  const logger2 = new Logger("processManager");
5592
5610
  try {
@@ -5601,9 +5619,19 @@ var require_processManager = __commonJS({
5601
5619
  }
5602
5620
  __name(terminateProcess, "terminateProcess");
5603
5621
  var serviceState = {
5604
- services: null
5622
+ services: null,
5605
5623
  // Will be initialized when needed
5624
+ isShuttingDown: false
5625
+ // Flag to suppress async logs during shutdown
5606
5626
  };
5627
+ function setShuttingDown(value) {
5628
+ serviceState.isShuttingDown = value;
5629
+ }
5630
+ __name(setShuttingDown, "setShuttingDown");
5631
+ function isShuttingDown() {
5632
+ return serviceState.isShuttingDown;
5633
+ }
5634
+ __name(isShuttingDown, "isShuttingDown");
5607
5635
  function initializeServices() {
5608
5636
  if (!serviceState.services) {
5609
5637
  serviceState.services = getServiceConfig();
@@ -5654,7 +5682,8 @@ var require_processManager = __commonJS({
5654
5682
  }
5655
5683
  }, "settleReject");
5656
5684
  const proc = spawn(service.command, args, {
5657
- stdio: "pipe"
5685
+ stdio: "pipe",
5686
+ detached: shouldDetach()
5658
5687
  });
5659
5688
  setServiceProcess("litd", proc);
5660
5689
  timeoutId = setTimeout(() => {
@@ -5664,7 +5693,9 @@ var require_processManager = __commonJS({
5664
5693
  }
5665
5694
  }, LITD_STARTUP_TIMEOUT);
5666
5695
  proc.once("exit", (code, signal) => {
5667
- logger2.info(`litd process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5696
+ if (!isShuttingDown()) {
5697
+ logger2.info(`litd process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5698
+ }
5668
5699
  const currentService = getService("litd");
5669
5700
  if (currentService?.process === proc) {
5670
5701
  setServiceProcess("litd", null);
@@ -5727,7 +5758,8 @@ var require_processManager = __commonJS({
5727
5758
  }, "settleReject");
5728
5759
  logger2.info(`Tor config: ${JSON.stringify(args, null, 2)}`);
5729
5760
  const proc = spawn(service.command, args, {
5730
- stdio: "pipe"
5761
+ stdio: "pipe",
5762
+ detached: shouldDetach()
5731
5763
  });
5732
5764
  setServiceProcess("tor", proc);
5733
5765
  timeoutId = setTimeout(() => {
@@ -5737,7 +5769,9 @@ var require_processManager = __commonJS({
5737
5769
  }
5738
5770
  }, TOR_STARTUP_TIMEOUT);
5739
5771
  proc.once("exit", (code, signal) => {
5740
- logger2.info(`tor process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5772
+ if (!isShuttingDown()) {
5773
+ logger2.info(`tor process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5774
+ }
5741
5775
  const currentService = getService("tor");
5742
5776
  if (currentService?.process === proc) {
5743
5777
  setServiceProcess("tor", null);
@@ -5805,7 +5839,8 @@ var require_processManager = __commonJS({
5805
5839
  }
5806
5840
  }, "settleReject");
5807
5841
  const proc = spawn(service.command, args, {
5808
- stdio: "pipe"
5842
+ stdio: "pipe",
5843
+ detached: shouldDetach()
5809
5844
  });
5810
5845
  setServiceProcess("rgb", proc);
5811
5846
  timeoutId = setTimeout(() => {
@@ -5815,7 +5850,9 @@ var require_processManager = __commonJS({
5815
5850
  }
5816
5851
  }, RGB_STARTUP_TIMEOUT);
5817
5852
  proc.once("exit", (code, signal) => {
5818
- logger2.info(`rgb process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5853
+ if (!isShuttingDown()) {
5854
+ logger2.info(`rgb process exited with code ${code ?? "null"}, signal ${signal ?? "null"}`);
5855
+ }
5819
5856
  const currentService = getService("rgb");
5820
5857
  if (currentService?.process === proc) {
5821
5858
  setServiceProcess("rgb", null);
@@ -5923,35 +5960,12 @@ var require_processManager = __commonJS({
5923
5960
  const trackedProcess = initialService.process;
5924
5961
  const processAlive = trackedProcess && !trackedProcess.killed && trackedProcess.exitCode === null;
5925
5962
  if (processAlive) {
5926
- try {
5927
- logger2.info("Attempting graceful shutdown via RPC...");
5928
- const lightningService = getLightningService();
5929
- await lightningService.stopDaemon();
5930
- logger2.info("LND daemon stopped gracefully via RPC");
5931
- let waitCount = 0;
5932
- const maxWait = 10;
5933
- while (waitCount < maxWait) {
5934
- const currentProcess = getService("litd")?.process ?? trackedProcess;
5935
- if (!currentProcess || currentProcess.killed || currentProcess.exitCode !== null) {
5936
- setServiceProcess("litd", null);
5937
- return true;
5938
- }
5939
- await sleep(1e3);
5940
- waitCount++;
5941
- }
5942
- logger2.warn("Process did not exit after RPC shutdown, proceeding with fallback...");
5943
- } catch (error) {
5944
- logger2.warn(`RPC shutdown failed: ${error.message}, falling back to process termination`);
5945
- }
5946
- } else {
5947
- logger2.info("litd process already exited, skipping RPC shutdown");
5948
- setServiceProcess("litd", null);
5949
- }
5950
- const fallbackService = getService("litd") ?? initialService;
5951
- if (fallbackService.process) {
5952
- return await killProcess(fallbackService.process, "litd");
5963
+ logger2.info("Stopping litd via SIGTERM...");
5964
+ return await killProcess(trackedProcess, "litd", 5e3);
5953
5965
  }
5954
- return await findAndKillProcess(fallbackService.command, "litd");
5966
+ logger2.info("litd process already exited, cleaning up...");
5967
+ setServiceProcess("litd", null);
5968
+ return await findAndKillProcess(initialService.command, "litd");
5955
5969
  }
5956
5970
  __name(stopLitdService, "stopLitdService");
5957
5971
  async function stopTorService() {
@@ -6007,32 +6021,12 @@ var require_processManager = __commonJS({
6007
6021
  if (!service) {
6008
6022
  throw new Error("rgb service not found");
6009
6023
  }
6010
- try {
6011
- logger2.info("Attempting graceful shutdown of RGB via SIGTERM...");
6012
- const processList = await findProcess("name", "rgb-lightning-node");
6013
- if (processList.length > 0) {
6014
- processList.forEach((p) => terminateProcess(p.pid, false));
6015
- await sleep(2e3);
6016
- setServiceProcess("rgb", null);
6017
- const remaining = await findProcess("name", "rgb-lightning-node");
6018
- if (remaining.length === 0) {
6019
- logger2.info("RGB stopped gracefully");
6020
- return true;
6021
- }
6022
- logger2.warn(`${remaining.length} RGB process(es) still running after SIGTERM, force killing...`);
6023
- remaining.forEach((p) => terminateProcess(p.pid, true));
6024
- setServiceProcess("rgb", null);
6025
- return true;
6026
- }
6027
- logger2.info("No RGB processes found via find-process, trying tracked process...");
6028
- } catch (error) {
6029
- logger2.warn(`RGB graceful shutdown failed: ${error.message}, falling back to process termination`);
6030
- }
6031
6024
  if (service.process) {
6032
- return await killProcess(service.process, "rgb");
6033
- } else {
6034
- return await findAndKillProcess("rgb-lightning-node", "rgb");
6025
+ logger2.info("Stopping RGB via tracked process...");
6026
+ return await killProcess(service.process, "rgb", 5e3);
6035
6027
  }
6028
+ logger2.info("No tracked RGB process, falling back to system scan...");
6029
+ return await findAndKillProcess("rgb-lightning-node", "rgb");
6036
6030
  }
6037
6031
  __name(stopRgbService, "stopRgbService");
6038
6032
  async function stopService(name) {
@@ -6079,13 +6073,24 @@ var require_processManager = __commonJS({
6079
6073
  };
6080
6074
  }
6081
6075
  __name(getServicesState, "getServicesState");
6076
+ function getServicePids() {
6077
+ const services = initializeServices();
6078
+ return {
6079
+ litd: services.litd?.process?.pid || null,
6080
+ tor: services.tor?.process?.pid || null,
6081
+ rgb: services.rgb?.process?.pid || null
6082
+ };
6083
+ }
6084
+ __name(getServicePids, "getServicePids");
6082
6085
  module2.exports = {
6083
6086
  startService,
6084
6087
  stopService,
6085
6088
  stopLitdService,
6086
6089
  stopTorService,
6087
6090
  stopRgbService,
6088
- getServicesState
6091
+ getServicesState,
6092
+ getServicePids,
6093
+ setShuttingDown
6089
6094
  };
6090
6095
  }
6091
6096
  });
@@ -6202,21 +6207,11 @@ var require_node = __commonJS({
6202
6207
  tag: TASKS.EnableRGBNode
6203
6208
  });
6204
6209
  }
6205
- const unlockPromise = rgbClient.node.unlockNode(unlockNodeParams);
6206
- const timeoutMs = 3e4;
6207
- let timeoutId;
6208
- const timeoutPromise = new Promise((resolve) => {
6209
- timeoutId = setTimeout(async () => {
6210
- const status = await getNodeState();
6211
- resolve({
6212
- message: "Request submitted, current node status:",
6213
- status
6214
- });
6215
- }, timeoutMs);
6216
- });
6217
- const result = await Promise.race([unlockPromise, timeoutPromise]);
6218
- clearTimeout(timeoutId);
6219
- return result || true;
6210
+ rgbClient.node.unlockNode(unlockNodeParams).then(() => {
6211
+ }).catch((err) => console.error("[rgb] unlockNode background error:", err.message));
6212
+ const linkCache = require_linkCache();
6213
+ linkCache.set("rgbState", 3);
6214
+ return { state: 3, message: "Unlock submitted, syncing..." };
6220
6215
  }
6221
6216
  __name(unlockNode, "unlockNode");
6222
6217
  async function backupNode({ password }) {
@@ -6276,6 +6271,7 @@ var require_statusChecker = __commonJS({
6276
6271
  var { getNodeState } = require_node();
6277
6272
  var { getServiceConfig } = require_config2();
6278
6273
  var { getServicesState } = require_processManager();
6274
+ var normalizePath = /* @__PURE__ */ __name((s) => s ? s.replace(/\\/g, "/") : "", "normalizePath");
6279
6275
  function isExternalNodesMode() {
6280
6276
  const config = getConfig2();
6281
6277
  return config.LINK_EXTERNAL_NODES === "true" || config.LINK_EXTERNAL_NODES === true;
@@ -6313,10 +6309,26 @@ var require_statusChecker = __commonJS({
6313
6309
  }
6314
6310
  return ServiceStatus.STOPPED;
6315
6311
  }
6312
+ const { LINK_DATA_PATH } = getConfig2();
6316
6313
  const processList = await findProcess("name", "litd");
6314
+ if (!LINK_DATA_PATH) {
6315
+ if (processList.length > 0) {
6316
+ logger2.warn("LINK_DATA_PATH not set, cannot filter litd by ownership. Returning RUNNING based on process name match.");
6317
+ return ServiceStatus.RUNNING;
6318
+ }
6319
+ return ServiceStatus.STOPPED;
6320
+ }
6321
+ const lnlinkLitdDataDir = `${LINK_DATA_PATH}/.litd`;
6317
6322
  if (processList.length > 0) {
6318
- logger2.info(`Found litd process(es): ${processList.map((p) => p.pid).join(", ")}`);
6319
- return ServiceStatus.RUNNING;
6323
+ const targetDir = normalizePath(lnlinkLitdDataDir);
6324
+ const lnlinkLitdProcess = processList.find((p) => p.cmd && normalizePath(p.cmd).includes(targetDir));
6325
+ if (lnlinkLitdProcess) {
6326
+ logger2.info(`Found lnlink litd process: PID ${lnlinkLitdProcess.pid}`);
6327
+ return ServiceStatus.RUNNING;
6328
+ } else {
6329
+ logger2.debug(`Found ${processList.length} litd process(es), but none belong to lnlink (data dir: ${lnlinkLitdDataDir})`);
6330
+ return ServiceStatus.STOPPED;
6331
+ }
6320
6332
  }
6321
6333
  return ServiceStatus.STOPPED;
6322
6334
  } catch (error) {
@@ -6341,10 +6353,26 @@ var require_statusChecker = __commonJS({
6341
6353
  }
6342
6354
  return ServiceStatus.STOPPED;
6343
6355
  }
6356
+ const { LINK_DATA_PATH } = getConfig2();
6344
6357
  const processList = await findProcess("name", "rgb-lightning-node");
6358
+ if (!LINK_DATA_PATH) {
6359
+ if (processList.length > 0) {
6360
+ logger2.warn("LINK_DATA_PATH not set, cannot filter RGB by ownership. Returning RUNNING based on process name match.");
6361
+ return ServiceStatus.RUNNING;
6362
+ }
6363
+ return ServiceStatus.STOPPED;
6364
+ }
6365
+ const lnlinkRgbDataDir = `${LINK_DATA_PATH}/.rgb`;
6345
6366
  if (processList.length > 0) {
6346
- logger2.info(`Found rgb-lightning-node process(es): ${processList.map((p) => p.pid).join(", ")}`);
6347
- return ServiceStatus.RUNNING;
6367
+ const targetDir = normalizePath(lnlinkRgbDataDir);
6368
+ const lnlinkRgbProcess = processList.find((p) => p.cmd && normalizePath(p.cmd).includes(targetDir));
6369
+ if (lnlinkRgbProcess) {
6370
+ logger2.info(`Found lnlink RGB process: PID ${lnlinkRgbProcess.pid}`);
6371
+ return ServiceStatus.RUNNING;
6372
+ } else {
6373
+ logger2.debug(`Found ${processList.length} rgb-lightning-node process(es), but none belong to lnlink (data dir: ${lnlinkRgbDataDir})`);
6374
+ return ServiceStatus.STOPPED;
6375
+ }
6348
6376
  }
6349
6377
  return ServiceStatus.STOPPED;
6350
6378
  } catch (error) {
@@ -6383,12 +6411,25 @@ var require_statusChecker = __commonJS({
6383
6411
  }
6384
6412
  }
6385
6413
  __name(checkProcess, "checkProcess");
6386
- function getTerminalStatus(filter = "") {
6387
- const services = getServiceConfig();
6414
+ var terminalStatusCache = {
6415
+ data: null,
6416
+ timestamp: 0,
6417
+ TTL_MS: 5e3
6418
+ // 5 seconds cache
6419
+ };
6420
+ async function getTerminalStatus(filter = "") {
6388
6421
  const logger2 = new Logger("statusChecker");
6422
+ if (!filter && terminalStatusCache.data) {
6423
+ const age = Date.now() - terminalStatusCache.timestamp;
6424
+ if (age < terminalStatusCache.TTL_MS) {
6425
+ logger2.debug(`getTerminalStatus: returning cached result (age: ${age}ms)`);
6426
+ return [...terminalStatusCache.data];
6427
+ }
6428
+ }
6429
+ const services = getServiceConfig();
6389
6430
  const shouldInclude = /* @__PURE__ */ __name((name) => name !== "rgb" && (!filter || name.includes(filter)), "shouldInclude");
6390
6431
  const filteredServices = Object.keys(services).filter(shouldInclude);
6391
- return Promise.all(
6432
+ const result = await Promise.all(
6392
6433
  filteredServices.map(async (name) => {
6393
6434
  try {
6394
6435
  const state = await checkProcess(name);
@@ -6403,8 +6444,18 @@ var require_statusChecker = __commonJS({
6403
6444
  }
6404
6445
  })
6405
6446
  );
6447
+ if (!filter) {
6448
+ terminalStatusCache.data = result;
6449
+ terminalStatusCache.timestamp = Date.now();
6450
+ }
6451
+ return result;
6406
6452
  }
6407
6453
  __name(getTerminalStatus, "getTerminalStatus");
6454
+ function invalidateTerminalStatusCache() {
6455
+ terminalStatusCache.data = null;
6456
+ terminalStatusCache.timestamp = 0;
6457
+ }
6458
+ __name(invalidateTerminalStatusCache, "invalidateTerminalStatusCache");
6408
6459
  async function getRGBStatus() {
6409
6460
  const logger2 = new Logger("statusChecker");
6410
6461
  try {
@@ -6461,6 +6512,7 @@ var require_statusChecker = __commonJS({
6461
6512
  checkProcess,
6462
6513
  getRGBStatus,
6463
6514
  getTerminalStatus,
6515
+ invalidateTerminalStatusCache,
6464
6516
  isServiceRunning,
6465
6517
  waitForServiceStatus
6466
6518
  };
@@ -6486,6 +6538,7 @@ var require_nodeManage = __commonJS({
6486
6538
  var { startService, stopService } = require_processManager();
6487
6539
  var {
6488
6540
  getTerminalStatus,
6541
+ invalidateTerminalStatusCache,
6489
6542
  isServiceRunning,
6490
6543
  getRGBStatus,
6491
6544
  ServiceStatus
@@ -6546,22 +6599,30 @@ var require_nodeManage = __commonJS({
6546
6599
  return { matched: false, statuses: lastStatuses };
6547
6600
  }
6548
6601
  __name(waitForRgbStatus, "waitForRgbStatus");
6602
+ var isStartingTerminal = false;
6603
+ var isStartingRGB = false;
6549
6604
  async function startTerminal({ waitForRunning = true } = {}) {
6550
6605
  const logger2 = new Logger("litdService");
6551
- const { LINK_ENABLE_TOR } = getConfig2();
6552
- const isStartTor = LINK_ENABLE_TOR === "true" || LINK_ENABLE_TOR === true;
6553
- logger2.info("Starting terminal services...");
6554
- const startStatus = await getTerminalStatus();
6555
- const litdStatus = startStatus.find((item) => item.service_name === "litd");
6556
- const torStatus = startStatus.find((item) => item.service_name === "tor");
6557
- logger2.info(`Current status - Litd: ${litdStatus?.status}, Tor: ${torStatus?.status}`);
6558
- let torSucceeded = !isStartTor;
6606
+ if (isStartingTerminal) {
6607
+ logger2.warn("Terminal start already in progress, returning current status");
6608
+ return await getTerminalStatus();
6609
+ }
6610
+ isStartingTerminal = true;
6559
6611
  try {
6612
+ const { LINK_ENABLE_TOR } = getConfig2();
6613
+ const isStartTor = LINK_ENABLE_TOR === "true" || LINK_ENABLE_TOR === true;
6614
+ logger2.info("Starting terminal services...");
6615
+ const startStatus = await getTerminalStatus();
6616
+ const litdStatus = startStatus.find((item) => item.service_name === "litd");
6617
+ const torStatus = startStatus.find((item) => item.service_name === "tor");
6618
+ logger2.info(`Current status - Litd: ${litdStatus?.status}, Tor: ${torStatus?.status}`);
6619
+ let torSucceeded = !isStartTor;
6560
6620
  if (isStartTor && torStatus?.status === ServiceStatus.STOPPED) {
6561
6621
  logger2.info("Starting Tor service...");
6562
6622
  try {
6563
6623
  const torArgs = buildTorArgs();
6564
6624
  await startService("tor", torArgs);
6625
+ invalidateTerminalStatusCache();
6565
6626
  torSucceeded = true;
6566
6627
  logger2.info("Tor service started successfully");
6567
6628
  } catch (torError) {
@@ -6579,6 +6640,7 @@ var require_nodeManage = __commonJS({
6579
6640
  logger2.info(`Starting Litd service (Tor: ${useTor})...`);
6580
6641
  const litdArgs = buildLitdArgs(useTor);
6581
6642
  await startService("litd", litdArgs);
6643
+ invalidateTerminalStatusCache();
6582
6644
  logger2.info("Litd service started successfully");
6583
6645
  }
6584
6646
  const endStatus = await getTerminalStatus();
@@ -6600,6 +6662,8 @@ var require_nodeManage = __commonJS({
6600
6662
  } catch (error) {
6601
6663
  logger2.error(`Failed to start terminal services: ${error.message}`);
6602
6664
  throw error;
6665
+ } finally {
6666
+ isStartingTerminal = false;
6603
6667
  }
6604
6668
  }
6605
6669
  __name(startTerminal, "startTerminal");
@@ -6613,6 +6677,7 @@ var require_nodeManage = __commonJS({
6613
6677
  logger2.info("Stopping terminal services...");
6614
6678
  try {
6615
6679
  const stopRet = await stopService("litd");
6680
+ invalidateTerminalStatusCache();
6616
6681
  if (!stopRet) {
6617
6682
  logger2.warn("stopService returned a falsy result");
6618
6683
  return false;
@@ -6706,6 +6771,11 @@ var require_nodeManage = __commonJS({
6706
6771
  async function startRGB(args = {}) {
6707
6772
  const { waitForRunning = true, lnlinkUser } = args;
6708
6773
  const logger2 = new Logger("rgbService");
6774
+ if (isStartingRGB) {
6775
+ logger2.warn("RGB start already in progress, returning current status");
6776
+ return await getRGBStatus();
6777
+ }
6778
+ isStartingRGB = true;
6709
6779
  logger2.info("Starting RGB service...");
6710
6780
  try {
6711
6781
  const startStatus = await getRGBStatus();
@@ -6740,6 +6810,8 @@ var require_nodeManage = __commonJS({
6740
6810
  } catch (error) {
6741
6811
  logger2.error(`Failed to start RGB service: ${error.message}`);
6742
6812
  throw error;
6813
+ } finally {
6814
+ isStartingRGB = false;
6743
6815
  }
6744
6816
  }
6745
6817
  __name(startRGB, "startRGB");
@@ -6848,24 +6920,6 @@ var require_nodeManage = __commonJS({
6848
6920
  }
6849
6921
  }
6850
6922
  __name(updateNodeName, "updateNodeName");
6851
- if (globalThis.addCleanupFunction) {
6852
- const logger2 = new Logger("nodeManage");
6853
- globalThis.addCleanupFunction(async () => {
6854
- logger2.info("Cleaning up node processes...");
6855
- try {
6856
- for (const name of ["litd", "tor", "rgb"]) {
6857
- try {
6858
- await stopService(name);
6859
- logger2.info(`${name} process stopped`);
6860
- } catch (err) {
6861
- logger2.warn(`Failed to stop ${name}: ${err.message}`);
6862
- }
6863
- }
6864
- } catch (error) {
6865
- logger2.error(`Error during node cleanup: ${error.message}`);
6866
- }
6867
- });
6868
- }
6869
6923
  module2.exports = {
6870
6924
  // Terminal (Litd + Tor) management
6871
6925
  startTerminal,
@@ -7127,7 +7181,7 @@ var require_package = __commonJS({
7127
7181
  "package.json"(exports2, module2) {
7128
7182
  module2.exports = {
7129
7183
  name: "lnlink-server",
7130
- version: "1.1.2",
7184
+ version: "1.1.4",
7131
7185
  private: false,
7132
7186
  main: "dist/index.js",
7133
7187
  files: [
@@ -7137,13 +7191,13 @@ var require_package = __commonJS({
7137
7191
  ],
7138
7192
  scripts: {
7139
7193
  build: "node build.js && node build.js --mode development --external all --entry electron",
7140
- "start:bin": "dotenv -e .env.bin -- node scripts/start-bin.js",
7194
+ "start:bin": "node scripts/start-bin.js",
7141
7195
  "start:docker:dev": "dotenv -e .env.dev -- docker compose -f ./docker-compose.dev.yml up --build",
7142
7196
  "start:dev": 'dotenv -e .env.dev -- sh -c "prisma generate && (prisma migrate dev --name auto_update || prisma db push) && clinic heapprof -- node ./app.js"',
7143
7197
  "start:regtest": "docker compose --env-file ./.env.regtest -f ./docker-compose-lnlink.yml up --build",
7144
7198
  "start:testnet": "docker compose --env-file ./.env.testnet -f ./docker-compose-lnlink.yml up --build",
7145
7199
  "start:mainnet": "docker compose --env-file ./.env.mainnet -f ./docker-compose-lnlink.yml up --build",
7146
- "start:bin:dist": "dotenv -e .env.bin -- node scripts/start-bin.js --dist",
7200
+ "start:bin:dist": "node scripts/start-bin.js --dist",
7147
7201
  lint: "eslint .",
7148
7202
  "lint:fix": "eslint . --fix",
7149
7203
  "lint:staged": "lint-staged",
@@ -7462,13 +7516,9 @@ var require_lndService = __commonJS({
7462
7516
  LINK_NODE_ADDR,
7463
7517
  LINK_LND_PEER_LISTEN_PORT
7464
7518
  } = getConfig2();
7465
- const currentWalletState = await getWalletState(true);
7466
- const terminalStatus = await getTerminalStatus();
7467
- const linkStatusObj = {};
7468
- terminalStatus.forEach((item) => {
7469
- linkStatusObj[item.service_name] = item.status;
7470
- });
7471
7519
  const logger2 = new Logger("lnd");
7520
+ const version = packageJSON.version;
7521
+ const currentWalletState = await getWalletState(true);
7472
7522
  const timeoutMs = 15e3;
7473
7523
  const fallbackCombineInfo = linkCache.get("combineNodeInfo") || {};
7474
7524
  let timeoutId;
@@ -7481,16 +7531,34 @@ var require_lndService = __commonJS({
7481
7531
  });
7482
7532
  }, timeoutMs);
7483
7533
  });
7484
- const result = await Promise.race([
7485
- combineNodeInfoAsync(currentWalletState).then((data2) => ({ fromCache: false, data: data2 })),
7486
- timeoutPromise
7487
- ]);
7488
- clearTimeout(timeoutId);
7489
- const combineInfo = result.data || {};
7490
- const version = packageJSON.version;
7491
- const taprootAssetsEnabled = await checkTprEnabled().catch(() => false);
7492
- const readOnlyAccount = await getLnlinkUser({
7493
- account_type: ACCOUNT_TYPE.READ_ONLY
7534
+ let results;
7535
+ try {
7536
+ results = await Promise.allSettled([
7537
+ Promise.race([
7538
+ combineNodeInfoAsync(currentWalletState).then((data2) => ({ fromCache: false, data: data2 })),
7539
+ timeoutPromise
7540
+ ]),
7541
+ getTerminalStatus(),
7542
+ checkTprEnabled(),
7543
+ getLnlinkUser({ account_type: ACCOUNT_TYPE.READ_ONLY })
7544
+ ]);
7545
+ } finally {
7546
+ clearTimeout(timeoutId);
7547
+ }
7548
+ const [combineSettled, terminalSettled, tprSettled, userSettled] = results;
7549
+ const combineInfo = combineSettled.value?.data || {};
7550
+ const terminalStatus = terminalSettled.status === "fulfilled" ? terminalSettled.value : [];
7551
+ const taprootAssetsEnabled = tprSettled.status === "fulfilled" ? tprSettled.value : false;
7552
+ const readOnlyAccount = userSettled.status === "fulfilled" ? userSettled.value : null;
7553
+ if (terminalSettled.status === "rejected") {
7554
+ logger2.warn(`getNodeInfo: getTerminalStatus failed: ${terminalSettled.reason?.message}`);
7555
+ }
7556
+ if (userSettled.status === "rejected") {
7557
+ logger2.warn(`getNodeInfo: getLnlinkUser failed: ${userSettled.reason?.message}`);
7558
+ }
7559
+ const linkStatusObj = {};
7560
+ terminalStatus.forEach((item) => {
7561
+ linkStatusObj[item.service_name] = item.status;
7494
7562
  });
7495
7563
  const uri = combineInfo?.uris?.[0];
7496
7564
  const data = {
@@ -7722,6 +7790,9 @@ var require_lndService = __commonJS({
7722
7790
  async function combineNodeInfoAsync(walletState) {
7723
7791
  let retInfo = {};
7724
7792
  const logger2 = new Logger("lnd");
7793
+ const CACHE_KEY = "combineNodeInfo";
7794
+ const CACHE_TTL_KEY = "combineNodeInfo_ts";
7795
+ const CACHE_TTL_MS = 5e3;
7725
7796
  try {
7726
7797
  const { isMacaroonDecrypted } = getCacheMacaroon();
7727
7798
  let state = walletState;
@@ -7730,18 +7801,33 @@ var require_lndService = __commonJS({
7730
7801
  state = WALLET_STATE_CODE.LOCKED;
7731
7802
  }
7732
7803
  }
7804
+ const cachedInfo = linkCache.get(CACHE_KEY);
7805
+ const cacheTimestamp = linkCache.get(CACHE_TTL_KEY);
7806
+ if (cachedInfo && cacheTimestamp && cachedInfo.state === state) {
7807
+ const age = Date.now() - cacheTimestamp;
7808
+ if (age < CACHE_TTL_MS) {
7809
+ logger2.debug(`combineNodeInfoAsync: returning cached result (age: ${age}ms)`);
7810
+ return cachedInfo;
7811
+ }
7812
+ }
7733
7813
  if (state >= WALLET_STATE_CODE.RPC_ACTIVE && isMacaroonDecrypted && state !== WALLET_STATE_CODE.WAITING_TO_START) {
7734
7814
  const lightningService = getLightningService();
7735
- const { settings } = await getMainLnlinkConfig();
7736
7815
  const ret = await Promise.allSettled([
7816
+ getMainLnlinkConfig(),
7737
7817
  lightningService.getInfo(),
7738
7818
  lightningService.walletBalance(),
7739
7819
  lightningService.listPeers({})
7740
7820
  ]);
7741
- const [infoResult, balanceResult, peersResult] = ret;
7742
- const nodeInfo = infoResult.value;
7743
- const balance = balanceResult.value;
7744
- const retListpeers = peersResult.value;
7821
+ const [configResult, infoResult, balanceResult, peersResult] = ret;
7822
+ let settings = null;
7823
+ if (configResult.status === "fulfilled") {
7824
+ settings = configResult.value?.settings;
7825
+ } else {
7826
+ logger2.warn(`combineNodeInfoAsync: getMainLnlinkConfig failed: ${configResult.reason?.message}`);
7827
+ }
7828
+ const nodeInfo = infoResult.status === "fulfilled" ? infoResult.value : null;
7829
+ const balance = balanceResult.status === "fulfilled" ? balanceResult.value : null;
7830
+ const retListpeers = peersResult.status === "fulfilled" ? peersResult.value : null;
7745
7831
  const peers = retListpeers?.peers || [];
7746
7832
  const mapPeers = peers.map((peer) => {
7747
7833
  const isLnfiPeer = peer.pub_key === settings?.officialLndPeer;
@@ -7771,7 +7857,8 @@ var require_lndService = __commonJS({
7771
7857
  ...retInfo,
7772
7858
  state
7773
7859
  };
7774
- linkCache.set("combineNodeInfo", retInfo);
7860
+ linkCache.set(CACHE_KEY, retInfo);
7861
+ linkCache.set(CACHE_TTL_KEY, Date.now());
7775
7862
  return retInfo;
7776
7863
  } catch (e) {
7777
7864
  logger2.warn(`LND lndService combineNodeInfoAsync peer combineNodeInfo warn--->${e.message}`);
@@ -7931,6 +8018,10 @@ var require_lndService = __commonJS({
7931
8018
  cipher_seed_mnemonic: seed
7932
8019
  });
7933
8020
  linkCache.set("tempPassword", password);
8021
+ try {
8022
+ await getWalletState(false);
8023
+ } catch (_refreshErr) {
8024
+ }
7934
8025
  const cipherSeedMnemonic = linkCache.get("cipherSeedMnemonic");
7935
8026
  const ret = {
7936
8027
  message: "Init wallet success.",
@@ -8064,6 +8155,10 @@ var require_lndService = __commonJS({
8064
8155
  });
8065
8156
  setCacheMacaroon(decodeWalletPassword);
8066
8157
  linkCache.set("tempPassword", wallet_password);
8158
+ try {
8159
+ await getWalletState(false);
8160
+ } catch (_refreshErr) {
8161
+ }
8067
8162
  }
8068
8163
  const ret = {
8069
8164
  msg: "Unlock success."
@@ -8291,6 +8386,15 @@ var require_lndService = __commonJS({
8291
8386
  }
8292
8387
  logger2.info("Stopping litd before config reload (to free ports)...");
8293
8388
  await stopTerminal({ lnlinkUser, waitForStopped: true });
8389
+ if (!enableTor && torRunning) {
8390
+ logger2.info("Stopping Tor service (was running standalone)...");
8391
+ try {
8392
+ await stopService("tor");
8393
+ logger2.info("Tor service stopped successfully");
8394
+ } catch (torError) {
8395
+ logger2.warn(`Failed to stop Tor service: ${torError.message}`);
8396
+ }
8397
+ }
8294
8398
  logger2.info("Reloading config after services stopped...");
8295
8399
  await reloadConfig();
8296
8400
  logger2.info("Starting litd with new Tor configuration...");
@@ -11908,12 +12012,17 @@ var require_info = __commonJS({
11908
12012
  LINK_RGB_LDK_PEER_LISTENING_PORT
11909
12013
  } = getConfig2();
11910
12014
  const logger2 = new Logger("rgb");
11911
- let statusEntries = [];
11912
- try {
11913
- statusEntries = await getRGBStatus();
11914
- } catch (error) {
11915
- logger2.warn(`getCombinedNodeInfo - getRGBStatus failed: ${error.message}`);
12015
+ const [statusResult, readOnlyAccountResult] = await Promise.allSettled([
12016
+ getRGBStatus(),
12017
+ getLnlinkUser({ account_type: ACCOUNT_TYPE.READ_ONLY })
12018
+ ]);
12019
+ let statusEntries = {};
12020
+ if (statusResult.status === "fulfilled") {
12021
+ statusEntries = statusResult.value;
12022
+ } else {
12023
+ logger2.warn(`getCombinedNodeInfo - getRGBStatus failed: ${statusResult.reason?.message}`);
11916
12024
  }
12025
+ const readOnlyAccount = readOnlyAccountResult.status === "fulfilled" ? readOnlyAccountResult.value : null;
11917
12026
  const serviceRunning = statusEntries.status === ServiceStatus.RUNNING;
11918
12027
  let nodeInfo = null;
11919
12028
  let balance = {
@@ -11945,7 +12054,8 @@ var require_info = __commonJS({
11945
12054
  ] = await Promise.allSettled([
11946
12055
  rgbClient.node.getNodeInfo(),
11947
12056
  rgbClient.onchain.getBtcBalance({
11948
- skip_sync: false
12057
+ skip_sync: true
12058
+ // Skip sync for faster response
11949
12059
  }),
11950
12060
  rgbClient.lightning.listPeers({})
11951
12061
  ]);
@@ -11964,14 +12074,11 @@ var require_info = __commonJS({
11964
12074
  } else {
11965
12075
  logger2.warn(`getCombinedNodeInfo - listPeers failed: ${peersResult.reason?.message || peersResult.reason}`);
11966
12076
  }
11967
- rgbClient.rgb.refreshTransfers({ skip_sync: false }).catch((error) => {
12077
+ rgbClient.rgb.refreshTransfers({ skip_sync: true }).catch((error) => {
11968
12078
  logger2.warn(`getCombinedNodeInfo - refreshTransfers failed: ${error.message}`);
11969
12079
  });
11970
12080
  }
11971
12081
  }
11972
- const readOnlyAccount = await getLnlinkUser({
11973
- account_type: ACCOUNT_TYPE.READ_ONLY
11974
- });
11975
12082
  const data = {
11976
12083
  pubkey: nodeInfo?.pubkey ?? null,
11977
12084
  host: `${LINK_RGB_HOST}:${LINK_RGB_LDK_PEER_LISTENING_PORT}`,
@@ -19001,17 +19108,6 @@ var require_job = __commonJS({
19001
19108
  logger2.info("Job system graceful shutdown completed");
19002
19109
  }
19003
19110
  __name(gracefulShutdown, "gracefulShutdown");
19004
- process.on("SIGINT", gracefulShutdown);
19005
- process.on("SIGTERM", gracefulShutdown);
19006
- process.on("uncaughtException", (error) => {
19007
- const logger2 = new Logger("job_uncaught");
19008
- logger2.error(`Uncaught exception: ${error.message}`);
19009
- gracefulShutdown();
19010
- });
19011
- process.on("unhandledRejection", (reason, promise) => {
19012
- const logger2 = new Logger("job_unhandled");
19013
- logger2.error(`Unhandled rejection at: ${promise}, reason: ${reason}`);
19014
- });
19015
19111
  module2.exports = {
19016
19112
  initJobSystem,
19017
19113
  startJobSystem,
@@ -19511,20 +19607,25 @@ var LnLink = class extends EventEmitter {
19511
19607
  */
19512
19608
  async stop() {
19513
19609
  try {
19514
- try {
19515
- const { stopService } = require_processManager();
19516
- for (const name of ["rgb", "litd", "tor"]) {
19610
+ const { stopService, setShuttingDown } = require_processManager();
19611
+ setShuttingDown(true);
19612
+ console.info("Stopping LN-Link (jobs + rgb + litd + tor)...");
19613
+ await Promise.allSettled([
19614
+ // Job system shutdown (cron tasks, state monitoring)
19615
+ (async () => {
19616
+ const { gracefulShutdown: shutdownJobs } = require_job();
19617
+ await shutdownJobs();
19618
+ })().catch((err) => console.warn(`Job system shutdown failed: ${err.message}`)),
19619
+ // Stop all child processes in parallel
19620
+ ...["rgb", "litd", "tor"].map(async (name) => {
19517
19621
  try {
19518
- console.info(`Stopping ${name} process...`);
19519
19622
  await stopService(name);
19520
- console.info(`${name} process stopped`);
19623
+ console.info(`${name} stopped`);
19521
19624
  } catch (err) {
19522
19625
  console.warn(`Failed to stop ${name}: ${err.message}`);
19523
19626
  }
19524
- }
19525
- } catch (err) {
19526
- console.warn(`Child process cleanup failed: ${err.message}`);
19527
- }
19627
+ })
19628
+ ]);
19528
19629
  if (this.server) {
19529
19630
  await new Promise((resolve) => {
19530
19631
  this.server.close(() => {
@@ -19564,6 +19665,14 @@ var LnLink = class extends EventEmitter {
19564
19665
  port: this.isRunning ? this.options.httpPort : null
19565
19666
  };
19566
19667
  }
19668
+ /**
19669
+ * Get PIDs of all managed child processes (litd, tor, rgb)
19670
+ * @returns {{ litd: number|null, tor: number|null, rgb: number|null }}
19671
+ */
19672
+ getServicePids() {
19673
+ const { getServicePids } = require_processManager();
19674
+ return getServicePids();
19675
+ }
19567
19676
  /**
19568
19677
  * Get configuration
19569
19678
  */