@whyour/qinglong 2.20.2-2 → 2.21.0

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.
Files changed (218) hide show
  1. package/.env.example +5 -0
  2. package/AGENTS.md +43 -0
  3. package/CLAUDE.md +43 -0
  4. package/README-en.md +11 -0
  5. package/README.md +11 -0
  6. package/docker/Dockerfile +51 -69
  7. package/docker/Dockerfile.310 +94 -0
  8. package/docker/Dockerfile.debian +120 -0
  9. package/docker/{310.Dockerfile → Dockerfile.debian310} +13 -5
  10. package/docker/docker-entrypoint.sh +95 -3
  11. package/docs/PROJECT_ARCHITECTURE.md +550 -0
  12. package/package.json +28 -25
  13. package/sample/config.sample.sh +12 -1
  14. package/sample/notify.js +81 -9
  15. package/sample/notify.py +54 -11
  16. package/shell/api.sh +44 -20
  17. package/shell/bot.sh +30 -16
  18. package/shell/check.sh +21 -19
  19. package/shell/lang/en.sh +105 -0
  20. package/shell/lang/zh.sh +105 -0
  21. package/shell/otask.sh +114 -28
  22. package/shell/preload/client.js +20 -2
  23. package/shell/preload/esm-loader.mjs +41 -0
  24. package/shell/preload/sitecustomize.js +36 -0
  25. package/shell/pub.sh +8 -8
  26. package/shell/rmlog.sh +5 -5
  27. package/shell/share.sh +49 -30
  28. package/shell/start.sh +40 -27
  29. package/shell/task.sh +10 -2
  30. package/shell/update.sh +22 -22
  31. package/static/build/api/config.js +17 -7
  32. package/static/build/api/cron.js +38 -1
  33. package/static/build/api/dashboard.js +337 -0
  34. package/static/build/api/env.js +40 -2
  35. package/static/build/api/index.js +2 -0
  36. package/static/build/api/log.js +5 -4
  37. package/static/build/api/script.js +34 -9
  38. package/static/build/api/system.js +16 -1
  39. package/static/build/api/user.js +6 -5
  40. package/static/build/app.js +3 -0
  41. package/static/build/config/const.js +7 -6
  42. package/static/build/config/container.js +12 -0
  43. package/static/build/config/grpcCerts.js +150 -0
  44. package/static/build/config/index.js +6 -0
  45. package/static/build/config/util.js +66 -15
  46. package/static/build/data/cron.js +3 -1
  47. package/static/build/data/cronStats.js +59 -0
  48. package/static/build/data/env.js +2 -0
  49. package/static/build/data/notify.js +11 -1
  50. package/static/build/data/runningInstance.js +57 -0
  51. package/static/build/loaders/app.js +1 -1
  52. package/static/build/loaders/db.js +6 -0
  53. package/static/build/loaders/express.js +2 -1
  54. package/static/build/loaders/initData.js +19 -2
  55. package/static/build/loaders/initFile.js +2 -0
  56. package/static/build/schedule/api.js +1 -1
  57. package/static/build/schedule/client.js +9 -1
  58. package/static/build/schedule/health.js +1 -1
  59. package/static/build/services/config.js +19 -5
  60. package/static/build/services/cron.js +118 -8
  61. package/static/build/services/env.js +31 -3
  62. package/static/build/services/grpc.js +32 -6
  63. package/static/build/services/http.js +33 -18
  64. package/static/build/services/notify.js +40 -3
  65. package/static/build/services/open.js +2 -1
  66. package/static/build/services/sshKey.js +6 -3
  67. package/static/build/services/subscription.js +2 -1
  68. package/static/build/services/system.js +22 -7
  69. package/static/build/services/user.js +8 -7
  70. package/static/build/shared/i18n.js +105 -0
  71. package/static/build/shared/pLimit.js +12 -1
  72. package/static/build/shared/runCron.js +5 -0
  73. package/static/build/validation/schedule.js +1 -0
  74. package/static/dist/1089.0ecc5383.async.js +1 -0
  75. package/static/dist/1147.c420132e.async.js +1 -0
  76. package/static/dist/1192.4e5740f2.async.js +1 -0
  77. package/static/dist/1214.c6469b53.async.js +1 -0
  78. package/static/dist/134.54df9e38.async.js +1 -0
  79. package/static/dist/1352.96a77e1d.async.js +1 -0
  80. package/static/dist/{1765.d8e002d7.async.js → 1765.5ac01a93.async.js} +1 -1
  81. package/static/dist/1836.f0b74cf1.async.js +1 -0
  82. package/static/dist/{1885.e0d00d2d.async.js → 1885.e1ea09f6.async.js} +1 -1
  83. package/static/dist/1967.3f3945d0.async.js +1 -0
  84. package/static/dist/{2096.383c1047.async.js → 2096.6d98fd12.async.js} +1 -1
  85. package/static/dist/2208.f7ba3dfa.async.js +1 -0
  86. package/static/dist/2286.164bb089.async.js +1 -0
  87. package/static/dist/2476.4e9b0992.async.js +1 -0
  88. package/static/dist/2537.2b262ee0.async.js +1 -0
  89. package/static/dist/{255.12f03ab2.async.js → 255.8a80b983.async.js} +1 -1
  90. package/static/dist/2635.d4c59d23.async.js +1 -0
  91. package/static/dist/2808.cdc0995c.async.js +1 -0
  92. package/static/dist/{2821.be3dc88e.async.js → 2821.651f31e5.async.js} +1 -1
  93. package/static/dist/2986.4c49eef7.async.js +1 -0
  94. package/static/dist/300.08ac9875.async.js +1 -0
  95. package/static/dist/{3191.70bc19db.async.js → 3191.63263871.async.js} +1 -1
  96. package/static/dist/3714.8d6dba9e.async.js +1 -0
  97. package/static/dist/3906.a5eee612.async.js +1 -0
  98. package/static/dist/{6541.a6d499de.async.js → 3948.4fe809fd.async.js} +1 -1
  99. package/static/dist/{5171.7fc6d0a2.async.js → 4573.16f19278.async.js} +1 -1
  100. package/static/dist/{4859.93e63ea6.async.js → 4859.6592cebb.async.js} +1 -1
  101. package/static/dist/4887.8e3ae573.async.js +1 -0
  102. package/static/dist/{4902.54ecbdb5.async.js → 4902.555f92ab.async.js} +1 -1
  103. package/static/dist/{4934.1ca6b6b0.async.js → 4934.f3539d08.async.js} +1 -1
  104. package/static/dist/5077.cbd111cd.async.js +1 -0
  105. package/static/dist/{6247.b550d996.async.js → 5157.7c5af144.async.js} +1 -1
  106. package/static/dist/{540.481d4708.async.js → 540.19ddf020.async.js} +1 -1
  107. package/static/dist/{5970.10ac4f16.async.js → 5970.d3a6e7bd.async.js} +1 -1
  108. package/static/dist/{6013.2d7bb12a.async.js → 6013.4e38de36.async.js} +1 -1
  109. package/static/dist/{6016.9c379049.async.js → 6016.1e6574b6.async.js} +1 -1
  110. package/static/dist/{6035.5889ddc7.async.js → 6035.32ebfad9.async.js} +1 -1
  111. package/static/dist/6339.ab305e96.async.js +1 -0
  112. package/static/dist/6569.55177f6a.async.js +1 -0
  113. package/static/dist/6610.c111af7e.async.js +1 -0
  114. package/static/dist/{3034.6413c38e.async.js → 6638.87a163d2.async.js} +1 -1
  115. package/static/dist/{6646.5fc37228.async.js → 6646.95e92533.async.js} +1 -1
  116. package/static/dist/6665.1d099ad1.async.js +1 -0
  117. package/static/dist/{7355.b7c0562f.async.js → 7355.f9f263d8.async.js} +1 -1
  118. package/static/dist/{7384.065ccae2.async.js → 7384.1bb449d5.async.js} +1 -1
  119. package/static/dist/7441.ee1bf2bb.async.js +1 -0
  120. package/static/dist/{7508.a31662a3.async.js → 7508.7dcee91c.async.js} +1 -1
  121. package/static/dist/{7802.6b73f16a.async.js → 7802.34255805.async.js} +1 -1
  122. package/static/dist/{7984.e6bb9378.async.js → 7984.594296a5.async.js} +1 -1
  123. package/static/dist/{5653.4fce7ce8.async.js → 8033.5cb31493.async.js} +1 -1
  124. package/static/dist/8147.2fb55202.async.js +1 -0
  125. package/static/dist/8187.48c130d6.async.js +1 -0
  126. package/static/dist/8495.d53e15ca.async.js +1 -0
  127. package/static/dist/8587.315b06c0.async.js +1 -0
  128. package/static/dist/8826.2447a104.async.js +1 -0
  129. package/static/dist/{2742.4852aac8.async.js → 8865.ed665d31.async.js} +1 -1
  130. package/static/dist/901.7ee5c6d3.async.js +1 -0
  131. package/static/dist/9271.231db2ce.async.js +1 -0
  132. package/static/dist/9323.a33f47da.async.js +1 -0
  133. package/static/dist/{9730.30083c91.async.js → 9730.801665a3.async.js} +1 -1
  134. package/static/dist/{9761.627ca3b5.async.js → 9761.360a19d8.async.js} +1 -1
  135. package/static/dist/index.html +2 -2
  136. package/static/dist/layouts__index.a7a2bfe0.async.js +1 -0
  137. package/static/dist/layouts__index.adf0692f.chunk.css +1 -0
  138. package/static/dist/preload_helper.0431c0f3.js +1 -0
  139. package/static/dist/{src__pages__config__index.622b6ee8.async.js → src__pages__config__index.a22ff7dc.async.js} +1 -1
  140. package/static/dist/{src__pages__crontab__const.323d5124.async.js → src__pages__crontab__const.aba07deb.async.js} +1 -1
  141. package/static/dist/src__pages__crontab__detail.b9c36808.async.js +1 -0
  142. package/static/dist/src__pages__crontab__index.fed99fd0.async.js +1 -0
  143. package/static/dist/{src__pages__crontab__logModal.5e6a4bf2.async.js → src__pages__crontab__logModal.0b8cce8c.async.js} +1 -1
  144. package/static/dist/src__pages__crontab__modal.f7041c7c.async.js +1 -0
  145. package/static/dist/src__pages__crontab__type.d7af36e5.async.js +1 -0
  146. package/static/dist/{src__pages__crontab__viewCreateModal.ffcf7a24.async.js → src__pages__crontab__viewCreateModal.4d589f66.async.js} +1 -1
  147. package/static/dist/{src__pages__crontab__viewManageModal.c2724575.async.js → src__pages__crontab__viewManageModal.0e317746.async.js} +1 -1
  148. package/static/dist/src__pages__dashboard__index.b30f2f47.async.js +1 -0
  149. package/static/dist/{src__pages__dependence__logModal.f123e2ac.async.js → src__pages__dependence__logModal.0681830b.async.js} +1 -1
  150. package/static/dist/src__pages__dependence__modal.11124896.async.js +1 -0
  151. package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +1 -0
  152. package/static/dist/src__pages__env__index.baa27d4e.async.js +1 -0
  153. package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +1 -0
  154. package/static/dist/src__pages__error__index.f156b45e.async.js +1 -0
  155. package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +1 -0
  156. package/static/dist/{src__pages__log__index.cf00c9af.async.js → src__pages__log__index.e0978bae.async.js} +1 -1
  157. package/static/dist/{src__pages__login__index.cd6e3152.async.js → src__pages__login__index.8c813eb1.async.js} +1 -1
  158. package/static/dist/{src__pages__script__components__UnsupportedFilePreview__index.39074c68.async.js → src__pages__script__components__UnsupportedFilePreview__index.c347a13a.async.js} +1 -1
  159. package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +1 -0
  160. package/static/dist/src__pages__script__index.2765d1b8.async.js +1 -0
  161. package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +1 -0
  162. package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +1 -0
  163. package/static/dist/src__pages__script__setting.a535793a.async.js +1 -0
  164. package/static/dist/src__pages__setting__appModal.251cd14f.async.js +1 -0
  165. package/static/dist/src__pages__setting__dependence.8a7a9529.async.js +1 -0
  166. package/static/dist/src__pages__setting__index.cc85fdfb.async.js +1 -0
  167. package/static/dist/src__pages__setting__notification.390fc905.async.js +1 -0
  168. package/static/dist/src__pages__setting__other.b22d2165.async.js +1 -0
  169. package/static/dist/src__pages__setting__security.598720a8.async.js +1 -0
  170. package/static/dist/src__pages__setting__systemLog.67406721.async.js +1 -0
  171. package/static/dist/{src__pages__subscription__logModal.0caa7283.async.js → src__pages__subscription__logModal.3864b37f.async.js} +1 -1
  172. package/static/dist/src__pages__subscription__modal.3562c670.async.js +1 -0
  173. package/static/dist/{umi.ef8199a4.js → umi.ca04a019.js} +1 -1
  174. package/version.yaml +33 -4
  175. package/sample/notify.py.save +0 -1010
  176. package/static/dist/105.85a5c47a.async.js +0 -1
  177. package/static/dist/1083.f86ce804.async.js +0 -1
  178. package/static/dist/1147.32f41a88.async.js +0 -1
  179. package/static/dist/1352.ab6da08e.async.js +0 -1
  180. package/static/dist/1690.f0290540.async.js +0 -1
  181. package/static/dist/1742.6cbe5aca.async.js +0 -1
  182. package/static/dist/2208.8e3a7325.async.js +0 -1
  183. package/static/dist/5312.74b95311.async.js +0 -1
  184. package/static/dist/5691.931f59c5.async.js +0 -1
  185. package/static/dist/6159.55cb068a.async.js +0 -1
  186. package/static/dist/7025.f4080d63.async.js +0 -1
  187. package/static/dist/739.6be5552a.async.js +0 -1
  188. package/static/dist/7571.4f6240b1.async.js +0 -1
  189. package/static/dist/786.59fc381c.async.js +0 -1
  190. package/static/dist/8317.c44c1ebd.async.js +0 -1
  191. package/static/dist/8826.0291edfd.async.js +0 -1
  192. package/static/dist/955.3c9481f7.async.js +0 -1
  193. package/static/dist/layouts__index.1fce90e0.chunk.css +0 -1
  194. package/static/dist/layouts__index.8dcf1576.async.js +0 -1
  195. package/static/dist/preload_helper.116a62f6.js +0 -1
  196. package/static/dist/src__pages__crontab__detail.b07f0c0a.async.js +0 -1
  197. package/static/dist/src__pages__crontab__index.2e2e1096.async.js +0 -1
  198. package/static/dist/src__pages__crontab__modal.4d8c2a22.async.js +0 -1
  199. package/static/dist/src__pages__crontab__type.db7c1858.async.js +0 -1
  200. package/static/dist/src__pages__dependence__modal.631ffb5b.async.js +0 -1
  201. package/static/dist/src__pages__env__editNameModal.ff85ef8c.async.js +0 -1
  202. package/static/dist/src__pages__env__index.a0a2fece.async.js +0 -1
  203. package/static/dist/src__pages__env__modal.d1004662.async.js +0 -1
  204. package/static/dist/src__pages__error__index.1bc3c90b.async.js +0 -1
  205. package/static/dist/src__pages__initialization__index.8b1cbaf9.async.js +0 -1
  206. package/static/dist/src__pages__script__editNameModal.53424d49.async.js +0 -1
  207. package/static/dist/src__pages__script__index.e65df827.async.js +0 -1
  208. package/static/dist/src__pages__script__renameModal.4bbe7fb1.async.js +0 -1
  209. package/static/dist/src__pages__script__saveModal.cf449f3c.async.js +0 -1
  210. package/static/dist/src__pages__script__setting.b345d59a.async.js +0 -1
  211. package/static/dist/src__pages__setting__appModal.03faec89.async.js +0 -1
  212. package/static/dist/src__pages__setting__dependence.4495c7b6.async.js +0 -1
  213. package/static/dist/src__pages__setting__index.6919c399.async.js +0 -1
  214. package/static/dist/src__pages__setting__notification.d6a3884f.async.js +0 -1
  215. package/static/dist/src__pages__setting__other.0d931d6f.async.js +0 -1
  216. package/static/dist/src__pages__setting__security.91cb545f.async.js +0 -1
  217. package/static/dist/src__pages__setting__systemLog.1d433a48.async.js +0 -1
  218. package/static/dist/src__pages__subscription__modal.0b84f6cf.async.js +0 -1
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RunningInstanceModel = exports.RunningInstance = exports.InstanceStatus = void 0;
4
+ const _1 = require(".");
5
+ const sequelize_1 = require("sequelize");
6
+ var InstanceStatus;
7
+ (function (InstanceStatus) {
8
+ InstanceStatus[InstanceStatus["running"] = 0] = "running";
9
+ InstanceStatus[InstanceStatus["finished"] = 1] = "finished";
10
+ InstanceStatus[InstanceStatus["stopped"] = 2] = "stopped";
11
+ InstanceStatus[InstanceStatus["error"] = 3] = "error";
12
+ })(InstanceStatus || (exports.InstanceStatus = InstanceStatus = {}));
13
+ class RunningInstance {
14
+ constructor(options) {
15
+ this.id = options.id;
16
+ this.cron_id = options.cron_id;
17
+ this.pid = options.pid;
18
+ this.log_path = options.log_path;
19
+ this.started_at = options.started_at;
20
+ this.finished_at = options.finished_at;
21
+ this.status = options.status;
22
+ this.exit_code = options.exit_code;
23
+ }
24
+ }
25
+ exports.RunningInstance = RunningInstance;
26
+ exports.RunningInstanceModel = _1.sequelize.define('RunningInstance', {
27
+ cron_id: {
28
+ type: sequelize_1.DataTypes.NUMBER,
29
+ allowNull: false,
30
+ },
31
+ pid: {
32
+ type: sequelize_1.DataTypes.NUMBER,
33
+ allowNull: true,
34
+ },
35
+ log_path: {
36
+ type: sequelize_1.DataTypes.STRING,
37
+ allowNull: true,
38
+ },
39
+ started_at: {
40
+ type: sequelize_1.DataTypes.NUMBER,
41
+ allowNull: false,
42
+ },
43
+ finished_at: {
44
+ type: sequelize_1.DataTypes.NUMBER,
45
+ allowNull: true,
46
+ },
47
+ status: {
48
+ type: sequelize_1.DataTypes.NUMBER,
49
+ allowNull: false,
50
+ defaultValue: InstanceStatus.running,
51
+ },
52
+ exit_code: {
53
+ type: sequelize_1.DataTypes.NUMBER,
54
+ allowNull: true,
55
+ },
56
+ });
57
+ //# sourceMappingURL=runningInstance.js.map
@@ -15,7 +15,7 @@ exports.default = async ({ app }) => {
15
15
  logger_1.default.info('✌️ Dependency loaded');
16
16
  await (0, deps_1.default)();
17
17
  logger_1.default.info('✌️ Link deps loaded');
18
- (0, initFile_1.default)();
18
+ await (0, initFile_1.default)();
19
19
  logger_1.default.info('✌️ Init file loaded');
20
20
  await (0, initData_1.default)();
21
21
  logger_1.default.info('✌️ Init data loaded');
@@ -11,6 +11,8 @@ const open_1 = require("../data/open");
11
11
  const system_1 = require("../data/system");
12
12
  const subscription_1 = require("../data/subscription");
13
13
  const cronView_1 = require("../data/cronView");
14
+ const cronStats_1 = require("../data/cronStats");
15
+ const runningInstance_1 = require("../data/runningInstance");
14
16
  const data_1 = require("../data");
15
17
  exports.default = async () => {
16
18
  try {
@@ -21,6 +23,8 @@ exports.default = async () => {
21
23
  await env_1.EnvModel.sync();
22
24
  await subscription_1.SubscriptionModel.sync();
23
25
  await cronView_1.CrontabViewModel.sync();
26
+ await cronStats_1.CrontabStatModel.sync();
27
+ await runningInstance_1.RunningInstanceModel.sync();
24
28
  // 初始化新增字段
25
29
  const migrations = [
26
30
  {
@@ -42,7 +46,9 @@ exports.default = async () => {
42
46
  column: 'allow_multiple_instances',
43
47
  type: 'NUMBER',
44
48
  },
49
+ { table: 'Crontabs', column: 'work_dir', type: 'VARCHAR(255)' },
45
50
  { table: 'Envs', column: 'isPinned', type: 'NUMBER' },
51
+ { table: 'Envs', column: 'labels', type: 'JSON' },
46
52
  ];
47
53
  for (const migration of migrations) {
48
54
  try {
@@ -16,6 +16,7 @@ const serverEnv_1 = require("../config/serverEnv");
16
16
  const store_1 = require("../shared/store");
17
17
  const auth_1 = require("../shared/auth");
18
18
  const path_1 = __importDefault(require("path"));
19
+ const i18n_1 = require("../shared/i18n");
19
20
  exports.default = ({ app }) => {
20
21
  // Security: Enable strict routing to prevent case-insensitive path bypass
21
22
  app.set('case sensitive routing', true);
@@ -119,7 +120,7 @@ exports.default = ({ app }) => {
119
120
  isInitialized = false;
120
121
  }
121
122
  if (isInitialized) {
122
- return res.send({ code: 450, message: '未知错误' });
123
+ return res.send({ code: 450, message: (0, i18n_1.t)('未知错误') });
123
124
  }
124
125
  else {
125
126
  return next();
@@ -46,8 +46,10 @@ const open_1 = __importDefault(require("../services/open"));
46
46
  const store_1 = require("../shared/store");
47
47
  const logger_1 = __importDefault(require("./logger"));
48
48
  const open_2 = require("../data/open");
49
+ const runningInstance_1 = require("../data/runningInstance");
50
+ const i18n_1 = require("../shared/i18n");
49
51
  exports.default = async () => {
50
- var _a, _b, _c, _d, _e, _f, _g;
52
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
51
53
  const cronService = typedi_1.Container.get(cron_2.default);
52
54
  const envService = typedi_1.Container.get(env_1.default);
53
55
  const dependenceService = typedi_1.Container.get(dependence_1.default);
@@ -61,11 +63,14 @@ exports.default = async () => {
61
63
  if (!systemApp) {
62
64
  systemApp = await open_2.AppModel.create({
63
65
  name: 'system',
64
- scopes: ['crons', 'system'],
66
+ scopes: ['crons', 'system', 'dashboard'],
65
67
  client_id: (0, util_1.createRandomString)(12, 12),
66
68
  client_secret: (0, util_1.createRandomString)(24, 24),
67
69
  });
68
70
  }
71
+ else if (!systemApp.scopes.includes('dashboard')) {
72
+ await open_2.AppModel.update({ scopes: [...systemApp.scopes, 'dashboard'] }, { where: { name: 'system' } });
73
+ }
69
74
  const [systemConfig] = await system_1.SystemModel.findOrCreate({
70
75
  where: { type: system_1.AuthDataType.systemConfig },
71
76
  });
@@ -146,6 +151,8 @@ exports.default = async () => {
146
151
  });
147
152
  // 初始化更新所有任务状态为空闲
148
153
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle }, { where: {} });
154
+ // 清空所有运行中的实例记录(服务重启后进程已不存在)
155
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped }, { where: { status: runningInstance_1.InstanceStatus.running } });
149
156
  // 初始化时执行一次所有的 ql repo 任务
150
157
  cron_1.CrontabModel.findAll({
151
158
  where: {
@@ -201,6 +208,16 @@ exports.default = async () => {
201
208
  });
202
209
  // 初始化保存一次ck和定时任务数据
203
210
  await cronService.autosave_crontab();
211
+ // 确保 lang_env.sh 存在,提供默认 QL_LANG
212
+ try {
213
+ const langEnvExist = await (0, util_1.fileExist)(config_1.default.langEnvFile);
214
+ if (!langEnvExist) {
215
+ const lang = ((_h = systemConfig.info) === null || _h === void 0 ? void 0 : _h.lang) || 'zh';
216
+ await (0, promises_1.writeFile)(config_1.default.langEnvFile, `export QL_LANG='${lang}'\n`);
217
+ }
218
+ }
219
+ catch (_k) { }
220
+ (0, i18n_1.setLang)(((_j = systemConfig.info) === null || _j === void 0 ? void 0 : _j.lang) || 'zh');
204
221
  await envService.set_envs();
205
222
  const authInfo = await userService.getAuthInfo();
206
223
  const apps = await openService.findApps();
@@ -22,6 +22,7 @@ const uploadPath = path_1.default.join(dataPath, 'upload/');
22
22
  const bakPath = path_1.default.join(dataPath, 'bak/');
23
23
  const samplePath = path_1.default.join(rootPath, 'sample/');
24
24
  const tmpPath = path_1.default.join(logPath, '.tmp/');
25
+ const rootTmpPath = path_1.default.join(rootPath, '.tmp/');
25
26
  const confFile = path_1.default.join(configPath, 'config.sh');
26
27
  const sampleConfigFile = path_1.default.join(samplePath, 'config.sample.sh');
27
28
  const sampleTaskShellFile = path_1.default.join(samplePath, 'task.sample.sh');
@@ -45,6 +46,7 @@ const directories = [
45
46
  preloadPath,
46
47
  logPath,
47
48
  tmpPath,
49
+ rootTmpPath,
48
50
  uploadPath,
49
51
  sshPath,
50
52
  bakPath,
@@ -203,7 +203,7 @@ const normalizeCronData = (data) => {
203
203
  var _a, _b, _c, _d, _e;
204
204
  if (!data)
205
205
  return undefined;
206
- return Object.assign(Object.assign({}, data), { sub_id: (_a = data.sub_id) !== null && _a !== void 0 ? _a : undefined, extra_schedules: (_b = data.extra_schedules) !== null && _b !== void 0 ? _b : undefined, pid: (_c = data.pid) !== null && _c !== void 0 ? _c : undefined, task_before: (_d = data.task_before) !== null && _d !== void 0 ? _d : undefined, task_after: (_e = data.task_after) !== null && _e !== void 0 ? _e : undefined });
206
+ return Object.assign(Object.assign({}, data), { sub_id: (_a = data.sub_id) !== null && _a !== void 0 ? _a : undefined, extra_schedules: (_b = data.extra_schedules) !== null && _b !== void 0 ? _b : [], pid: (_c = data.pid) !== null && _c !== void 0 ? _c : undefined, task_before: (_d = data.task_before) !== null && _d !== void 0 ? _d : undefined, task_after: (_e = data.task_after) !== null && _e !== void 0 ? _e : undefined });
207
207
  };
208
208
  const getCronDetail = async (call, callback) => {
209
209
  try {
@@ -6,9 +6,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const grpc_js_1 = require("@grpc/grpc-js");
7
7
  const cron_1 = require("../protos/cron");
8
8
  const config_1 = __importDefault(require("../config"));
9
+ const grpcCerts_1 = require("../config/grpcCerts");
9
10
  class Client {
10
11
  constructor() {
11
- this.client = new cron_1.CronClient(`0.0.0.0:${config_1.default.grpcPort}`, grpc_js_1.credentials.createInsecure(), { 'grpc.enable_http_proxy': 0 });
12
+ this._client = null;
13
+ }
14
+ get client() {
15
+ if (!this._client) {
16
+ const tlsConfig = (0, grpcCerts_1.getGrpcCerts)();
17
+ this._client = new cron_1.CronClient(`localhost:${config_1.default.grpcPort}`, grpc_js_1.credentials.createSsl(Buffer.from(tlsConfig.caCert), Buffer.from(tlsConfig.clientKey), Buffer.from(tlsConfig.clientCert)), { 'grpc.enable_http_proxy': 0 });
18
+ }
19
+ return this._client;
12
20
  }
13
21
  addCron(request) {
14
22
  return new Promise((resolve, reject) => {
@@ -9,7 +9,7 @@ const util_1 = require("../config/util");
9
9
  const check = async (call, callback) => {
10
10
  switch (call.request.service) {
11
11
  case 'cron':
12
- const res = await (0, util_1.promiseExec)(`curl -s --noproxy '*' http://0.0.0.0:${config_1.default.port}/api/system`);
12
+ const res = await (0, util_1.promiseExec)(`curl -s --noproxy '*' http://localhost:${config_1.default.port}/api/system`);
13
13
  if (res.includes('200')) {
14
14
  return callback(null, { status: 1 });
15
15
  }
@@ -39,16 +39,30 @@ const typedi_1 = require("typedi");
39
39
  const path_1 = __importStar(require("path"));
40
40
  const config_1 = __importDefault(require("../config"));
41
41
  const util_1 = require("../config/util");
42
+ const i18n_1 = require("../shared/i18n");
42
43
  const undici_1 = require("undici");
43
44
  let ConfigService = class ConfigService {
44
45
  constructor() { }
45
46
  async getFile(filePath, res) {
46
47
  let content = '';
47
- const avaliablePath = [config_1.default.rootPath, config_1.default.configPath].map((x) => path_1.default.resolve(x, filePath));
48
- if (config_1.default.blackFileList.includes(filePath) ||
49
- avaliablePath.every((x) => !x.startsWith(config_1.default.scriptPath) && !x.startsWith(config_1.default.configPath)) ||
50
- !filePath) {
51
- return res.send({ code: 403, message: '文件无法访问' });
48
+ if (!filePath) {
49
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
50
+ }
51
+ const normalized = path_1.default.normalize(filePath);
52
+ if (normalized.startsWith('..') || path_1.default.isAbsolute(normalized)) {
53
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
54
+ }
55
+ const resolvedRoot = path_1.default.resolve(config_1.default.rootPath, normalized);
56
+ const resolvedConfig = path_1.default.resolve(config_1.default.configPath, normalized);
57
+ const isValidPath = resolvedRoot.startsWith(config_1.default.scriptPath) ||
58
+ resolvedRoot.startsWith(config_1.default.configPath) ||
59
+ resolvedConfig.startsWith(config_1.default.scriptPath) ||
60
+ resolvedConfig.startsWith(config_1.default.configPath);
61
+ if (!isValidPath) {
62
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
63
+ }
64
+ if (config_1.default.blackFileList.includes(path_1.default.basename(normalized))) {
65
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
52
66
  }
53
67
  if (filePath.startsWith('sample/')) {
54
68
  const res = await (0, undici_1.request)(`https://gitlab.com/whyour/qinglong/-/raw/master/${filePath}`);
@@ -19,6 +19,7 @@ const typedi_1 = require("typedi");
19
19
  const winston_1 = __importDefault(require("winston"));
20
20
  const config_1 = __importDefault(require("../config"));
21
21
  const cron_1 = require("../data/cron");
22
+ const runningInstance_1 = require("../data/runningInstance");
22
23
  const child_process_1 = require("child_process");
23
24
  const promises_1 = __importDefault(require("fs/promises"));
24
25
  const cron_parser_1 = __importDefault(require("cron-parser"));
@@ -33,8 +34,10 @@ const dayjs_1 = __importDefault(require("dayjs"));
33
34
  const pickBy_1 = __importDefault(require("lodash/pickBy"));
34
35
  const omit_1 = __importDefault(require("lodash/omit"));
35
36
  const utils_1 = require("../shared/utils");
37
+ const i18n_1 = require("../shared/i18n");
36
38
  const schedule_1 = require("../interface/schedule");
37
39
  const logStreamManager_1 = require("../shared/logStreamManager");
40
+ const lodash_1 = require("lodash");
38
41
  let CronService = class CronService {
39
42
  constructor(logger) {
40
43
  this.logger = logger;
@@ -46,6 +49,26 @@ let CronService = class CronService {
46
49
  }
47
50
  return false;
48
51
  }
52
+ get schedulerMode() {
53
+ const env = process.env.QL_SCHEDULER;
54
+ if (env === 'system')
55
+ return 'system';
56
+ if (env === 'node')
57
+ return 'node';
58
+ try {
59
+ (0, child_process_1.execSync)('which crond', { stdio: 'ignore' });
60
+ return 'system';
61
+ }
62
+ catch (_a) {
63
+ return 'node';
64
+ }
65
+ }
66
+ shouldUseCronClient(cron) {
67
+ if (this.schedulerMode === 'node') {
68
+ return !this.isSpecialSchedule(cron.schedule);
69
+ }
70
+ return this.isNodeCron(cron) && !this.isSpecialSchedule(cron.schedule);
71
+ }
49
72
  isOnceSchedule(schedule) {
50
73
  return schedule === null || schedule === void 0 ? void 0 : schedule.startsWith(schedule_1.ScheduleType.ONCE);
51
74
  }
@@ -81,7 +104,7 @@ let CronService = class CronService {
81
104
  if ((0, util_1.isDemoEnv)()) {
82
105
  return doc;
83
106
  }
84
- if (!this.isSpecialSchedule(doc.schedule)) {
107
+ if (this.shouldUseCronClient(doc)) {
85
108
  await client_1.default.addCron([
86
109
  {
87
110
  name: doc.name || '',
@@ -108,7 +131,7 @@ let CronService = class CronService {
108
131
  return newDoc;
109
132
  }
110
133
  await client_1.default.delCron([String(newDoc.id)]);
111
- if (!this.isSpecialSchedule(newDoc.schedule)) {
134
+ if (this.shouldUseCronClient(newDoc)) {
112
135
  await client_1.default.addCron([
113
136
  {
114
137
  name: doc.name || '',
@@ -148,6 +171,31 @@ let CronService = class CronService {
148
171
  if (status === cron_1.CrontabStatus.idle && log_path !== cron.log_path) {
149
172
  options = (0, omit_1.default)(options, ['status', 'log_path', 'pid']);
150
173
  }
174
+ // Manage RunningInstance records for status transitions from shell scripts
175
+ if (status === cron_1.CrontabStatus.running) {
176
+ // Create a new running instance record
177
+ await runningInstance_1.RunningInstanceModel.create({
178
+ cron_id: id,
179
+ pid: pid || undefined,
180
+ log_path: log_path || undefined,
181
+ started_at: last_execution_time || (0, dayjs_1.default)().unix(),
182
+ status: runningInstance_1.InstanceStatus.running,
183
+ });
184
+ }
185
+ else if (status === cron_1.CrontabStatus.idle) {
186
+ // Mark the matching running instance as finished
187
+ const finishedAt = (0, dayjs_1.default)().unix();
188
+ await runningInstance_1.RunningInstanceModel.update({
189
+ finished_at: finishedAt,
190
+ status: runningInstance_1.InstanceStatus.finished,
191
+ }, {
192
+ where: {
193
+ cron_id: id,
194
+ pid: pid || undefined,
195
+ status: runningInstance_1.InstanceStatus.running,
196
+ },
197
+ });
198
+ }
151
199
  await cron_1.CrontabModel.update(Object.assign({}, (0, pickBy_1.default)(options, (v) => v === 0 || !!v)), { where: { id } });
152
200
  }
153
201
  }
@@ -308,7 +356,7 @@ let CronService = class CronService {
308
356
  }
309
357
  }
310
358
  formatFilterQuery(query, filterQuery) {
311
- if (filterQuery) {
359
+ if (!(0, lodash_1.isEmpty)(filterQuery)) {
312
360
  if (!query[sequelize_1.Op.and]) {
313
361
  query[sequelize_1.Op.and] = [];
314
362
  }
@@ -418,8 +466,36 @@ let CronService = class CronService {
418
466
  this.logger.error(`[panel][停止任务失败] 任务ID: ${doc.id}, 错误: ${error}`);
419
467
  }
420
468
  }
469
+ // Mark all running instances as stopped
470
+ const finishedAt = (0, dayjs_1.default)().unix();
471
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped, finished_at: finishedAt }, { where: { cron_id: ids, status: runningInstance_1.InstanceStatus.running } });
421
472
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id: ids } });
422
473
  }
474
+ async stopInstance(instanceId) {
475
+ const instance = await runningInstance_1.RunningInstanceModel.findOne({
476
+ where: { id: instanceId, status: runningInstance_1.InstanceStatus.running },
477
+ });
478
+ if (!instance) {
479
+ return { code: 400, message: (0, i18n_1.t)('实例不存在或已停止') };
480
+ }
481
+ if (instance.pid) {
482
+ try {
483
+ await (0, util_1.killTask)(instance.pid);
484
+ }
485
+ catch (error) {
486
+ this.logger.error(`[panel][停止实例失败] 实例ID: ${instanceId}, PID: ${instance.pid}, 错误: ${error}`);
487
+ }
488
+ }
489
+ await runningInstance_1.RunningInstanceModel.update({ status: runningInstance_1.InstanceStatus.stopped, finished_at: (0, dayjs_1.default)().unix() }, { where: { id: instanceId } });
490
+ // Check if there are still other running instances for this cron
491
+ const otherRunning = await runningInstance_1.RunningInstanceModel.count({
492
+ where: { cron_id: instance.cron_id, status: runningInstance_1.InstanceStatus.running },
493
+ });
494
+ if (otherRunning === 0) {
495
+ await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id: instance.cron_id } });
496
+ }
497
+ return { code: 200, message: (0, i18n_1.t)('实例已停止') };
498
+ }
423
499
  async runSingle(cronId) {
424
500
  return pLimit_1.default.manualRunWithCronLimit(() => {
425
501
  return new Promise(async (resolve) => {
@@ -445,6 +521,14 @@ let CronService = class CronService {
445
521
  const logPath = `${uniqPath}/${logTime}.log`;
446
522
  const absolutePath = path_1.default.resolve(config_1.default.logPath, `${logPath}`);
447
523
  const cp = (0, cross_spawn_1.spawn)(`real_log_path=${logPath} no_delay=true ${this.makeCommand(cron, true)}`, { shell: '/bin/bash' });
524
+ const startedAt = (0, dayjs_1.default)().unix();
525
+ const instance = await runningInstance_1.RunningInstanceModel.create({
526
+ cron_id: id,
527
+ pid: cp.pid,
528
+ log_path: logPath,
529
+ started_at: startedAt,
530
+ status: runningInstance_1.InstanceStatus.running,
531
+ });
448
532
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.running, pid: cp.pid, log_path: logPath }, { where: { id } });
449
533
  cp.stdout.on('data', async (data) => {
450
534
  await logStreamManager_1.logStreamManager.write(absolutePath, data.toString());
@@ -459,9 +543,20 @@ let CronService = class CronService {
459
543
  });
460
544
  cp.on('exit', async (code) => {
461
545
  this.logger.info('[panel][执行任务结束] 参数: %s, 退出码: %j', JSON.stringify(params), code);
462
- // Close the stream after task completion
463
546
  await logStreamManager_1.logStreamManager.closeStream(absolutePath);
464
- await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id } });
547
+ const finishedAt = (0, dayjs_1.default)().unix();
548
+ await runningInstance_1.RunningInstanceModel.update({
549
+ finished_at: finishedAt,
550
+ status: code === 0 ? runningInstance_1.InstanceStatus.finished : runningInstance_1.InstanceStatus.error,
551
+ exit_code: code !== null && code !== void 0 ? code : undefined,
552
+ }, { where: { id: instance.id } });
553
+ // Only set cron to idle if no other running instances exist
554
+ const otherRunning = await runningInstance_1.RunningInstanceModel.count({
555
+ where: { cron_id: id, status: runningInstance_1.InstanceStatus.running },
556
+ });
557
+ if (otherRunning === 0) {
558
+ await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.idle, pid: undefined }, { where: { id } });
559
+ }
465
560
  resolve(Object.assign(Object.assign({}, params), { pid: cp.pid, code }));
466
561
  });
467
562
  });
@@ -475,7 +570,9 @@ let CronService = class CronService {
475
570
  async enabled(ids) {
476
571
  await cron_1.CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
477
572
  const docs = await cron_1.CrontabModel.findAll({ where: { id: ids } });
478
- const crons = docs.map((doc) => ({
573
+ const crons = docs
574
+ .filter((x) => this.shouldUseCronClient(x))
575
+ .map((doc) => ({
479
576
  name: doc.name || '',
480
577
  id: String(doc.id),
481
578
  schedule: doc.schedule,
@@ -550,6 +647,9 @@ let CronService = class CronService {
550
647
  .replace(/;? *\n/g, ';')
551
648
  .trim()}' `;
552
649
  }
650
+ if (tab.work_dir) {
651
+ commandVariable += `work_dir='${tab.work_dir.replace(/'/g, "'\\''")}' `;
652
+ }
553
653
  const crontab_job_string = `${commandVariable}${command}`;
554
654
  return crontab_job_string;
555
655
  }
@@ -574,6 +674,15 @@ let CronService = class CronService {
574
674
  }
575
675
  });
576
676
  await (0, utils_1.writeFileWithLock)(config_1.default.crontabFile, crontab_string);
677
+ if (this.schedulerMode === 'system') {
678
+ try {
679
+ (0, child_process_1.execSync)(`crontab ${config_1.default.crontabFile}`);
680
+ }
681
+ catch (error) {
682
+ const errorMsg = error.message || String(error);
683
+ this.logger.error('[crontab] Failed to update system crontab:', errorMsg);
684
+ }
685
+ }
577
686
  await cron_1.CrontabModel.update({ saved: true }, { where: {} });
578
687
  }
579
688
  importCrontab() {
@@ -611,7 +720,8 @@ let CronService = class CronService {
611
720
  async autosave_crontab() {
612
721
  const tabs = await this.crontabs();
613
722
  const regularCrons = tabs.data
614
- .filter((x) => x.isDisabled !== 1 && !this.isSpecialSchedule(x.schedule))
723
+ .filter((x) => x.isDisabled !== 1 &&
724
+ this.shouldUseCronClient(x))
615
725
  .map((doc) => ({
616
726
  name: doc.name || '',
617
727
  id: String(doc.id),
@@ -632,7 +742,7 @@ let CronService = class CronService {
632
742
  if (bootTasks.length > 0) {
633
743
  await cron_1.CrontabModel.update({ status: cron_1.CrontabStatus.queued }, { where: { id: bootTasks.map((t) => t.id) } });
634
744
  for (const task of bootTasks) {
635
- await this.runSingle(task.id);
745
+ this.runSingle(task.id);
636
746
  }
637
747
  }
638
748
  }
@@ -33,7 +33,7 @@ let EnvService = class EnvService {
33
33
  if (envs &&
34
34
  envs.length > 0 &&
35
35
  typeof envs[envs.length - 1].position === 'number') {
36
- position = envs[envs.length - 1].position;
36
+ position = this.getPrecisionPosition(envs[envs.length - 1].position);
37
37
  }
38
38
  const tabs = payloads.map((x) => {
39
39
  position = position - env_1.stepPosition;
@@ -90,7 +90,7 @@ let EnvService = class EnvService {
90
90
  return newDoc;
91
91
  }
92
92
  async checkPosition(position, edge = 0) {
93
- const precisionPosition = parseFloat(position.toPrecision(16));
93
+ const precisionPosition = this.getPrecisionPosition(position);
94
94
  if (precisionPosition < env_1.minPosition ||
95
95
  precisionPosition > env_1.maxPosition ||
96
96
  Math.abs(precisionPosition - edge) < env_1.minPosition) {
@@ -103,7 +103,7 @@ let EnvService = class EnvService {
103
103
  }
104
104
  }
105
105
  getPrecisionPosition(position) {
106
- return parseFloat(position.toPrecision(16));
106
+ return Math.trunc(parseFloat(position.toPrecision(16)));
107
107
  }
108
108
  async envs(searchText = '', query = {}) {
109
109
  let condition = Object.assign({}, query);
@@ -171,6 +171,34 @@ let EnvService = class EnvService {
171
171
  async unPin(ids) {
172
172
  await env_1.EnvModel.update({ isPinned: 0 }, { where: { id: ids } });
173
173
  }
174
+ async addLabels(ids, labels) {
175
+ await data_1.sequelize.transaction(async (transaction) => {
176
+ const docs = await env_1.EnvModel.findAll({
177
+ where: { id: ids },
178
+ transaction,
179
+ });
180
+ for (const doc of docs) {
181
+ const env = doc.get({ plain: true });
182
+ await env_1.EnvModel.update({ labels: Array.from(new Set([...(env.labels || []), ...labels])) }, { where: { id: env.id }, transaction });
183
+ }
184
+ });
185
+ return await this.find({ id: ids });
186
+ }
187
+ async removeLabels(ids, labels) {
188
+ await data_1.sequelize.transaction(async (transaction) => {
189
+ const docs = await env_1.EnvModel.findAll({
190
+ where: { id: ids },
191
+ transaction,
192
+ });
193
+ for (const doc of docs) {
194
+ const env = doc.get({ plain: true });
195
+ await env_1.EnvModel.update({
196
+ labels: (env.labels || []).filter((label) => !labels.includes(label)),
197
+ }, { where: { id: env.id }, transaction });
198
+ }
199
+ });
200
+ return await this.find({ id: ids });
201
+ }
174
202
  async set_envs() {
175
203
  const envs = await this.envs('', {
176
204
  name: { [sequelize_1.Op.not]: null },
@@ -46,23 +46,49 @@ const util_1 = require("util");
46
46
  const config_1 = __importDefault(require("../config"));
47
47
  const metrics_1 = require("./metrics");
48
48
  const typedi_1 = require("typedi");
49
+ const grpcCerts_1 = require("../config/grpcCerts");
49
50
  let GrpcServerService = class GrpcServerService {
50
51
  constructor() {
51
52
  this.server = new grpc_js_1.Server({ 'grpc.enable_http_proxy': 0 });
52
53
  }
54
+ formatGrpcAddress(host, port) {
55
+ if (host === '::') {
56
+ return `[::]:${port}`;
57
+ }
58
+ return `${host}:${port}`;
59
+ }
53
60
  async initialize() {
54
61
  try {
55
62
  this.server.addService(health_1.HealthService, { check: health_2.check });
56
63
  this.server.addService(cron_1.CronService, { addCron: addCron_1.addCron, delCron: delCron_1.delCron });
57
64
  this.server.addService(api_1.ApiService, Api);
65
+ const tlsConfig = await (0, grpcCerts_1.initGrpcCerts)();
66
+ const credentials = grpc_js_1.ServerCredentials.createSsl(Buffer.from(tlsConfig.caCert), [{ cert_chain: Buffer.from(tlsConfig.serverCert), private_key: Buffer.from(tlsConfig.serverKey) }], true);
58
67
  const grpcPort = config_1.default.grpcPort;
68
+ const hostsToTry = [
69
+ config_1.default.bindHostGrpc,
70
+ ...(config_1.default.bindHostGrpc !== '0.0.0.0' ? ['0.0.0.0'] : [])
71
+ ];
59
72
  const bindAsync = (0, util_1.promisify)(this.server.bindAsync).bind(this.server);
60
- await bindAsync(`0.0.0.0:${grpcPort}`, grpc_js_1.ServerCredentials.createInsecure());
61
- logger_1.default.debug(`✌️ gRPC service started successfully`);
62
- metrics_1.metricsService.record('grpc_service_start', 1, {
63
- port: grpcPort.toString(),
64
- });
65
- return grpcPort;
73
+ let lastError = null;
74
+ for (const host of hostsToTry) {
75
+ try {
76
+ const address = this.formatGrpcAddress(host, grpcPort);
77
+ await bindAsync(address, credentials);
78
+ logger_1.default.debug(`✌️ gRPC service started successfully on ${address}`);
79
+ metrics_1.metricsService.record('grpc_service_start', 1, {
80
+ port: grpcPort.toString(),
81
+ host
82
+ });
83
+ return grpcPort;
84
+ }
85
+ catch (err) {
86
+ lastError = err;
87
+ logger_1.default.warn(`Failed to bind gRPC on ${host}:${grpcPort}, trying next...`, err);
88
+ }
89
+ }
90
+ logger_1.default.error('Failed to start gRPC service on all hosts');
91
+ throw lastError || new Error('Failed to start gRPC service');
66
92
  }
67
93
  catch (err) {
68
94
  logger_1.default.error('Failed to start gRPC service:', err);