@whyour/qinglong 2.20.2-3 → 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,41 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const builtinModules = [
5
+ 'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
6
+ 'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'process', 'querystring',
7
+ 'readline', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util',
8
+ 'v8', 'zlib',
9
+ ];
10
+
11
+ function isBareSpecifier(specifier) {
12
+ return !specifier.startsWith('.') &&
13
+ !specifier.startsWith('/') &&
14
+ !specifier.startsWith('file:') &&
15
+ !specifier.startsWith('node:') &&
16
+ !builtinModules.includes(specifier) &&
17
+ !builtinModules.includes(specifier.split('/')[0]);
18
+ }
19
+
20
+ export function resolve(specifier, context, nextResolve) {
21
+ if (!isBareSpecifier(specifier)) {
22
+ return nextResolve(specifier, context);
23
+ }
24
+
25
+ // 解析优先级:全局 pnpm > 系统全局
26
+ const bases = [
27
+ process.env.QL_NODE_GLOBAL_PATH,
28
+ '/usr/local/lib/node_modules',
29
+ ].filter(Boolean);
30
+
31
+ for (const base of bases) {
32
+ if (existsSync(join(base, specifier))) {
33
+ return nextResolve(specifier, {
34
+ ...context,
35
+ parentURL: new URL(`${join(base, specifier)}/`, 'file://').href,
36
+ });
37
+ }
38
+ }
39
+
40
+ return nextResolve(specifier, context);
41
+ }
@@ -1,7 +1,41 @@
1
1
  const { execSync } = require('child_process');
2
+ const Module = require('module');
3
+ const path = require('path');
2
4
  const client = require('./client.js');
3
5
  require(`./env.js`);
4
6
 
7
+ // 注册 ESM loader,使全局安装的包也可通过 import 导入
8
+ try {
9
+ Module.register(new URL('esm-loader.mjs', `file://${__dirname}/`).href);
10
+ } catch (_) {}
11
+
12
+ function preferGlobalNodeModules() {
13
+ const { QL_NODE_GLOBAL_PATH } = process.env;
14
+ if (!QL_NODE_GLOBAL_PATH || Module._qlGlobalPathPatched) {
15
+ return;
16
+ }
17
+
18
+ const originalResolveFilename = Module._resolveFilename;
19
+ Module._resolveFilename = function (request, parent, isMain, options) {
20
+ if (
21
+ !Module.builtinModules.includes(request) &&
22
+ !request.startsWith('node:') &&
23
+ !request.startsWith('.') &&
24
+ !path.isAbsolute(request)
25
+ ) {
26
+ try {
27
+ return originalResolveFilename.call(this, request, parent, isMain, {
28
+ ...options,
29
+ paths: [QL_NODE_GLOBAL_PATH],
30
+ });
31
+ } catch (error) {}
32
+ }
33
+
34
+ return originalResolveFilename.call(this, request, parent, isMain, options);
35
+ };
36
+ Module._qlGlobalPathPatched = true;
37
+ }
38
+
5
39
  function expandRange(rangeStr, max) {
6
40
  const tempRangeStr = rangeStr
7
41
  .trim()
@@ -113,6 +147,8 @@ try {
113
147
  return;
114
148
  }
115
149
 
150
+ preferGlobalNodeModules();
151
+
116
152
  process.on('SIGTERM', (code) => {
117
153
  process.exit(15);
118
154
  });
package/shell/pub.sh CHANGED
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env bash
2
2
  echo -e "开始发布"
3
3
 
4
- echo -e "切换 debian 分支"
5
- git branch -D debian
6
- git checkout -b debian
7
- git push --set-upstream origin debian -f
4
+ echo -e "切换master分支"
5
+ git branch -D master
6
+ git checkout -b master
7
+ git push --set-upstream origin master -f
8
8
 
9
9
  echo -e "更新cdn文件"
10
10
  ts-node-transpile-only sample/tool.ts
11
11
 
12
12
  string=$(cat version.yaml | grep "version" | egrep "[^ ]*" -o | egrep "\d\.*")
13
13
  version="v$string"
14
- echo -e "当前版本$version-debian"
14
+ echo -e "当前版本$version"
15
15
 
16
16
  echo -e "删除已经存在的本地tag"
17
- git tag -d "$version-debian" &>/dev/null
17
+ git tag -d "$version" &>/dev/null
18
18
 
19
19
  echo -e "删除已经存在的远程tag"
20
- git push origin :refs/tags/$version-debian &>/dev/null
20
+ git push origin :refs/tags/$version &>/dev/null
21
21
 
22
22
  echo -e "创建新tag"
23
- git tag -a "$version-debian" -m "release $version-debian"
23
+ git tag -a "$version" -m "release $version"
24
24
 
25
25
  echo -e "提交tag"
26
26
  git push --tags
package/shell/rmlog.sh CHANGED
@@ -22,12 +22,12 @@ remove_js_log() {
22
22
  if [[ $diff_time -gt $((${days} * 86400)) ]]; then
23
23
  local log_path=$(echo "$log" | sed "s,${dir_log}/,,g")
24
24
  local result=$(find_cron_api "log_path=$log_path")
25
- echo -e "查询文件 $log_path"
25
+ t '查询文件 %s' "$log_path"
26
26
  if [[ -z $result ]]; then
27
- echo -e "删除中~"
27
+ t '删除中~'
28
28
  rm -vf $log
29
29
  else
30
- echo -e "正在被 $result 使用,跳过~"
30
+ t '正在被 %s 使用,跳过~' "$result"
31
31
  fi
32
32
  fi
33
33
  done
@@ -43,8 +43,8 @@ remove_empty_dir() {
43
43
  }
44
44
 
45
45
  if [[ ${days} ]]; then
46
- echo -e "查找旧日志文件中...\n"
46
+ t '查找旧日志文件中...\n'
47
47
  remove_js_log
48
48
  remove_empty_dir
49
- echo -e "删除旧日志执行完毕\n"
49
+ t '删除旧日志执行完毕\n'
50
50
  fi
package/shell/share.sh CHANGED
@@ -65,17 +65,7 @@ link_name=(
65
65
  )
66
66
 
67
67
  init_env() {
68
- local pnpm_global_path=$(pnpm root -g 2>/dev/null)
69
- export NODE_PATH="/usr/local/bin:/usr/local/lib/node_modules${pnpm_global_path:+:${pnpm_global_path}}"
70
-
71
- # 如果存在 pnpm 全局路径,创建软链接
72
- if [[ -n "$pnpm_global_path" ]]; then
73
- # 确保目标目录存在
74
- mkdir -p "${dir_root}/node_modules"
75
- # 链接全局模块到项目的 node_modules
76
- ln -sf "${pnpm_global_path}/"* "${dir_root}/node_modules/" 2>/dev/null || true
77
- fi
78
-
68
+ export NODE_PATH="/usr/local/bin:/usr/local/lib/node_modules"
79
69
  export PYTHONUNBUFFERED=1
80
70
  }
81
71
 
@@ -88,6 +78,16 @@ load_ql_envs() {
88
78
 
89
79
  import_config() {
90
80
  [[ -f $file_config_user ]] && . $file_config_user
81
+ [[ -f $dir_preload/lang_env.sh ]] && . $dir_preload/lang_env.sh
82
+
83
+ # 加载语言包(bash 4+ 支持 declare -A,不兼容时回退输出中文 key)
84
+ local lang=${QL_LANG:-zh}
85
+ local lang_file="$dir_shell/lang/${lang}.sh"
86
+ if [[ ${BASH_VERSINFO[0]} -ge 4 ]] && [[ -f $lang_file ]]; then
87
+ . $lang_file
88
+ elif [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then
89
+ . "$dir_shell/lang/zh.sh"
90
+ fi
91
91
 
92
92
  load_ql_envs
93
93
  command_timeout_time=${CommandTimeoutTime:-""}
@@ -101,6 +101,18 @@ import_config() {
101
101
  fi
102
102
  }
103
103
 
104
+ t() {
105
+ local key="$1"
106
+ shift
107
+ local msg
108
+ if declare -p LANG_MESSAGES &>/dev/null; then
109
+ msg="${LANG_MESSAGES["$key"]}"
110
+ fi
111
+ [[ -z $msg ]] && msg="$key"
112
+ # shellcheck disable=SC2059
113
+ printf "$msg\n" "$@"
114
+ }
115
+
104
116
  set_proxy() {
105
117
  local proxy="$1"
106
118
  if [[ $proxy ]]; then
@@ -235,7 +247,7 @@ npm_install_2() {
235
247
  local dir_work=$1
236
248
 
237
249
  cd $dir_work
238
- echo -e "安装 $dir_work 依赖包...\n"
250
+ t '安装 %s 依赖包...\n' "$dir_work"
239
251
  npm_install_sub
240
252
  cd $dir_current
241
253
  }
@@ -254,7 +266,7 @@ git_clone_scripts() {
254
266
  local branch="$3"
255
267
  local proxy="$4"
256
268
  [[ $branch ]] && local part_cmd="-b $branch "
257
- echo -e "开始拉取仓库 ${uniq_path} $dir\n"
269
+ t '开始拉取仓库 %s 到 %s\n' "${uniq_path}" "$dir"
258
270
 
259
271
  set_proxy "$proxy"
260
272
 
@@ -287,18 +299,18 @@ reload_pm2() {
287
299
  return 0
288
300
  else
289
301
  local exit_code=$?
290
- echo "警告: PM2 启动失败 (退出码: $exit_code),可能是由于硬件不兼容"
291
- echo "正在尝试直接使用 Node.js 启动服务..."
292
-
302
+ t '警告: PM2 启动失败 (退出码: %s),可能是由于硬件不兼容' "$exit_code"
303
+ t '正在尝试直接使用 Node.js 启动服务...'
304
+
293
305
  # Kill any existing node processes for qinglong
294
306
  pkill -f "node.*static/build/app.js" 2>/dev/null || true
295
-
307
+
296
308
  # Start node directly in the background
297
309
  nohup node static/build/app.js > $dir_log/qinglong.log 2>&1 &
298
310
  local node_pid=$!
299
-
300
- echo "已使用 Node.js 直接启动服务 (PID: $node_pid)"
301
- echo "注意: 使用此模式时,部分 PM2 管理功能将不可用"
311
+
312
+ t '已使用 Node.js 直接启动服务 (PID: %s)' "$node_pid"
313
+ t '注意: 使用此模式时,部分 PM2 管理功能将不可用'
302
314
  return 0
303
315
  fi
304
316
  }
@@ -370,16 +382,16 @@ handle_task_start() {
370
382
  error_message=", 任务状态更新失败(${error})"
371
383
  fi
372
384
  fi
373
- echo -e "## 开始执行... ${begin_time}${error_message}\n"
385
+ t '## 开始执行... %s\n' "${begin_time}${error_message}"
374
386
  }
375
387
 
376
388
  run_task_before() {
377
389
  . $file_task_before "$@"
378
390
 
379
391
  if [[ ${task_before:=} ]]; then
380
- echo -e "执行前置命令\n"
392
+ t '执行前置命令\n'
381
393
  eval "${task_before%;}"
382
- echo -e "\n执行前置命令结束\n"
394
+ t '\n执行前置命令结束\n'
383
395
  fi
384
396
  }
385
397
 
@@ -387,9 +399,9 @@ run_task_after() {
387
399
  . $file_task_after "$@"
388
400
 
389
401
  if [[ ${task_after:=} ]]; then
390
- echo -e "\n执行后置命令\n"
402
+ t '\n执行后置命令\n'
391
403
  eval "${task_after%;}"
392
- echo -e "\n执行后置命令结束"
404
+ t '\n执行后置命令结束'
393
405
  fi
394
406
  }
395
407
 
@@ -398,18 +410,25 @@ handle_task_end() {
398
410
  local end_time=$(format_time "$time_format" "$etime")
399
411
  local end_timestamp=$(format_timestamp "$time_format" "$etime")
400
412
  local diff_time=$(($end_timestamp - $begin_timestamp))
401
- local suffix=""
402
- [[ "${MANUAL:=}" == "true" ]] && suffix="(手动停止)"
403
-
413
+ local exit_code="${_task_exit_code:-0}"
404
414
  [[ "$diff_time" == 0 ]] && diff_time=1
405
415
 
406
416
  if [[ $ID ]]; then
407
417
  local error=$(update_cron "\"$ID\"" "1" "$$" "$log_path" "$begin_timestamp" "$diff_time")
408
418
  if [[ $error ]]; then
409
- error_message=", 任务状态更新失败(${error})"
419
+ error_message=", 状态更新失败(${error})"
410
420
  fi
411
421
  fi
412
- echo -e "\n## 执行结束$suffix... $end_time 耗时 $diff_time 秒${error_message:=}     "
422
+
423
+ record_cron_stat "$ID" "${exit_code:-0}" "$diff_time"
424
+
425
+ if [[ "${MANUAL:=}" == "true" ]]; then
426
+ t '\n## 已停止 🛑... %s 耗时 %s 秒%s' "$end_time" "$diff_time" "${error_message:=}     "
427
+ elif [[ $exit_code -eq 0 ]]; then
428
+ t '\n## 完成 ✅... %s 耗时 %s 秒%s' "$end_time" "$diff_time" "${error_message:=}     "
429
+ else
430
+ t '\n## 失败 ❌(退出码 %s)... %s 耗时 %s 秒%s' "$exit_code" "$end_time" "$diff_time" "${error_message:=}     "
431
+ fi
413
432
  }
414
433
 
415
434
  init_env
package/shell/start.sh CHANGED
@@ -12,23 +12,23 @@ if [[ ! $QL_DIR ]]; then
12
12
  elif [[ -d "$pnpm_dir/@whyour/qinglong" ]]; then
13
13
  QL_DIR="$pnpm_dir/@whyour/qinglong"
14
14
  else
15
- echo -e "未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装"
15
+ t '未找到 qinglong 模块,请先执行 npm i -g @whyour/qinglong 安装'
16
16
  fi
17
17
 
18
18
  if [[ $QL_DIR ]]; then
19
- echo -e "请先手动设置 export QL_DIR=$QL_DIR,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务"
19
+ t '请先手动设置 export QL_DIR=%s,环境变量,并手动添加到系统环境变量,然后再次执行命令 qinglong 启动服务' "$QL_DIR"
20
20
  fi
21
21
 
22
22
  exit 1
23
23
  fi
24
24
 
25
25
  if [[ ! $QL_DATA_DIR ]]; then
26
- echo -e "请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量"
26
+ t '请先手动设置数据存储目录 export QL_DATA_DIR 环境变量,目录必须以斜杠开头的绝对路径,并且以 /data 结尾,例如 /ql/data 并手动添加到系统环境变量'
27
27
  exit 1
28
28
  fi
29
29
 
30
30
  if [[ $QL_DATA_DIR != */data ]]; then
31
- echo -e "QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中"
31
+ t 'QL_DATA_DIR 必须以 /data 结尾,例如 /ql/data,如果有历史数据,请新建 data 目录,把历史数据放到 data 目录中'
32
32
  exit 1
33
33
  fi
34
34
 
@@ -36,31 +36,44 @@ command="$1"
36
36
 
37
37
  if [[ $command != "reload" ]]; then
38
38
  # 安装依赖
39
- os_name=$(source /etc/os-release && echo "$ID")
40
-
41
- if [[ $os_name == 'alpine' ]]; then
42
- apk update
43
- apk add -f bash \
44
- coreutils \
45
- git \
46
- curl \
47
- wget \
48
- tzdata \
49
- perl \
50
- openssl \
51
- jq \
52
- nginx \
53
- openssh \
54
- procps \
55
- netcat-openbsd
56
- elif [[ $os_name == 'debian' ]] || [[ $os_name == 'ubuntu' ]]; then
57
- apt-get update
58
- apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client
59
- else
60
- echo -e "暂不支持此系统部署 $os_name"
61
- exit 1
39
+ os_name="${QL_OS_TYPE:-}"
40
+ if [ -z "$os_name" ]; then
41
+ os_name=$(source /etc/os-release && echo "$ID")
42
+ fi
43
+
44
+ # 非 root 用户使用 sudo
45
+ SUDO=""
46
+ if [ "$(id -u)" -ne 0 ]; then
47
+ SUDO="sudo"
62
48
  fi
63
49
 
50
+ case "$os_name" in
51
+ alpine)
52
+ $SUDO apk update
53
+ $SUDO apk add -f bash \
54
+ coreutils \
55
+ git \
56
+ curl \
57
+ wget \
58
+ tzdata \
59
+ perl \
60
+ openssl \
61
+ jq \
62
+ nginx \
63
+ openssh \
64
+ procps \
65
+ netcat-openbsd
66
+ ;;
67
+ debian|ubuntu)
68
+ $SUDO apt-get update
69
+ $SUDO apt-get install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client
70
+ ;;
71
+ *)
72
+ t '暂不支持此系统部署 %s' "$os_name"
73
+ exit 1
74
+ ;;
75
+ esac
76
+
64
77
  npm install -g pnpm@8.3.1 pm2 ts-node
65
78
  fi
66
79
 
package/shell/task.sh CHANGED
@@ -4,9 +4,17 @@ dir_shell=$QL_DIR/shell
4
4
  . $dir_shell/share.sh
5
5
  . $dir_shell/api.sh
6
6
 
7
- trap "single_hanle" 2 3 20 15 14 19 1
7
+ trap 'single_hanle SIGINT' INT
8
+ trap 'single_hanle SIGTERM' TERM
9
+ trap 'single_hanle SIGHUP' HUP
10
+ trap 'single_hanle SIGALRM' ALRM
11
+ trap 'single_hanle SIGTSTP' TSTP
12
+ trap 'single_hanle SIGQUIT' QUIT
13
+
8
14
  single_hanle() {
9
- eval MANUAL=true handle_task_end "$@" "$cmd"
15
+ _task_exit_code="${_task_exit_code:-$?}"
16
+ [[ "$_task_exit_code" == "0" ]] && _task_exit_code="1"
17
+ eval MANUAL=true handle_task_end "$@"
10
18
  exit 1
11
19
  }
12
20
 
package/shell/update.sh CHANGED
@@ -33,7 +33,7 @@ output_list_add_drop() {
33
33
  local list=$1
34
34
  local type=$2
35
35
  if [[ -s $list ]]; then
36
- echo -e "检测到有${type}的定时任务:"
36
+ t '检测到有%s的定时任务:' "$type"
37
37
  cat $list
38
38
  fi
39
39
  }
@@ -45,7 +45,7 @@ del_cron() {
45
45
  local path=$2
46
46
  local detail=""
47
47
  local ids=""
48
- echo -e "\n开始尝试自动删除失效的定时任务..."
48
+ t '\n开始尝试自动删除失效的定时任务...'
49
49
  for cron in $(cat $list_drop); do
50
50
  local id=$(cat $list_crontab_user | grep -E "$cmd_task.* $cron" | perl -pe "s|.*ID=(.*) $cmd_task.* $cron\.*|\1|" | head -1 | awk -F " " '{print $1}')
51
51
  if [[ $ids ]]; then
@@ -76,7 +76,7 @@ del_cron() {
76
76
  add_cron() {
77
77
  local list_add=$1
78
78
  local path=$2
79
- echo -e "\n开始尝试自动添加定时任务..."
79
+ t '\n开始尝试自动添加定时任务...'
80
80
  local detail=""
81
81
  cd $dir_scripts
82
82
  for file in $(cat $list_add); do
@@ -136,10 +136,10 @@ update_repo() {
136
136
  git_clone_scripts "${formatUrl}" ${repo_path} "${branch}" "${proxy}"
137
137
 
138
138
  if [[ $exit_status -eq 0 ]]; then
139
- echo -e "拉取 ${uniq_path} 成功...\n"
139
+ t '拉取 %s 成功...\n' "${uniq_path}"
140
140
  diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
141
141
  else
142
- echo -e "拉取 ${uniq_path} 失败,请检查日志...\n"
142
+ t '拉取 %s 失败,请检查日志...\n' "${uniq_path}"
143
143
  fi
144
144
  }
145
145
 
@@ -160,7 +160,7 @@ update_raw() {
160
160
  local raw_url="$url"
161
161
  local suffix="${raw_url##*.}"
162
162
  local raw_file_name="${uniq_path}.${suffix}"
163
- echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
163
+ t '开始下载:%s 保存路径:%s\n' "${raw_url}" "$dir_raw/${raw_file_name}"
164
164
 
165
165
  set_proxy "$proxy"
166
166
  wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
@@ -169,7 +169,7 @@ update_raw() {
169
169
 
170
170
  if [[ $? -eq 0 ]]; then
171
171
  mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
172
- echo -e "下载 ${raw_file_name} 成功...\n"
172
+ t '下载 %s 成功...\n' "${raw_file_name}"
173
173
  cd $dir_raw
174
174
  local filename="raw_${raw_file_name}"
175
175
  local cron_id=$(cat $list_crontab_user | grep -E "$cmd_task.* $filename" | perl -pe "s|.*ID=(.*) $cmd_task.* $filename\.*|\1|" | head -1 | awk -F " " '{print $1}')
@@ -196,7 +196,7 @@ update_raw() {
196
196
  # update_cron_api "$cron_line:$cmd_task $filename:$cron_name:$cron_id"
197
197
  fi
198
198
  else
199
- echo -e "下载 ${raw_file_name} 失败,保留之前正常下载的版本...\n"
199
+ t '下载 %s 失败,保留之前正常下载的版本...\n' "${raw_file_name}"
200
200
  [[ -f "$dir_raw/${raw_file_name}.new" ]] && rm -f "$dir_raw/${raw_file_name}.new"
201
201
  fi
202
202
 
@@ -207,13 +207,13 @@ run_extra_shell() {
207
207
  if [[ -f $file_extra_shell ]]; then
208
208
  . $file_extra_shell
209
209
  else
210
- echo -e "$file_extra_shell文件不存在,跳过执行...\n"
210
+ t '%s文件不存在,跳过执行...\n' "$file_extra_shell"
211
211
  fi
212
212
  }
213
213
 
214
214
  ## 脚本用法
215
215
  usage() {
216
- echo -e "$cmd_update 命令使用方法:"
216
+ t "$cmd_update 命令使用方法:"
217
217
  echo -e "1. $cmd_update update # 更新并重启青龙"
218
218
  echo -e "2. $cmd_update extra # 运行自定义脚本"
219
219
  echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
@@ -268,7 +268,7 @@ update_qinglong() {
268
268
  downloadQLUrl="https://github.com/whyour/qinglong/archive/refs/heads"
269
269
  downloadStaticUrl="https://github.com/whyour/qinglong-static/archive/refs/heads"
270
270
  fi
271
- echo -e "使用 ${mirror} 源更新...\n"
271
+ t '使用 %s 源更新...\n' "${mirror}"
272
272
 
273
273
  local primary_branch="master"
274
274
  if [[ "${QL_BRANCH}" == "develop" ]] || [[ "${QL_BRANCH}" == "debian" ]] || [[ "${QL_BRANCH}" == "debian-dev" ]]; then
@@ -279,13 +279,13 @@ update_qinglong() {
279
279
  exit_status=$?
280
280
 
281
281
  if [[ $exit_status -eq 0 ]]; then
282
- echo -e "更新青龙源文件成功...\n"
282
+ t '更新青龙源文件成功...\n'
283
283
 
284
284
  unzip -oq ${dir_tmp}/ql.zip -d ${dir_tmp}
285
285
 
286
286
  update_qinglong_static
287
287
  else
288
- echo -e "更新青龙源文件失败,请检查网络...\n"
288
+ t '更新青龙源文件失败,请检查网络...\n'
289
289
  fi
290
290
  }
291
291
 
@@ -294,30 +294,30 @@ update_qinglong_static() {
294
294
  exit_status=$?
295
295
 
296
296
  if [[ $exit_status -eq 0 ]]; then
297
- echo -e "更新青龙静态资源成功...\n"
297
+ t '更新青龙静态资源成功...\n'
298
298
  unzip -oq ${dir_tmp}/static.zip -d ${dir_tmp}
299
299
 
300
300
  check_update_dep
301
301
  else
302
- echo -e "更新青龙静态资源失败,请检查网络...\n"
302
+ t '更新青龙静态资源失败,请检查网络...\n'
303
303
  fi
304
304
  }
305
305
 
306
306
  check_update_dep() {
307
- echo -e "\n开始检测依赖...\n"
307
+ t '\n开始检测依赖...\n'
308
308
  if [[ $(diff $dir_root/package.json ${dir_tmp}/qinglong-${primary_branch}/package.json) ]]; then
309
309
  npm_install_2 "${dir_tmp}/qinglong-${primary_branch}"
310
310
  fi
311
311
 
312
312
  if [[ $exit_status -eq 0 ]]; then
313
- echo -e "\n依赖检测安装成功...\n"
314
- echo -e "更新包下载成功..."
313
+ t '\n依赖检测安装成功...\n'
314
+ t '更新包下载成功...\n'
315
315
 
316
316
  if [[ "$needRestart" == 'true' ]]; then
317
317
  reload_qinglong "system"
318
318
  fi
319
319
  else
320
- echo -e "\n依赖检测安装失败,请检查网络...\n"
320
+ t '\n依赖检测安装失败,请检查网络...\n'
321
321
  fi
322
322
  }
323
323
 
@@ -514,7 +514,7 @@ main() {
514
514
  if [[ -n $p2 ]]; then
515
515
  update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7" "$p8" "$p9" "$p10"
516
516
  else
517
- eval echo -e "命令输入错误...\\\n" $cmd
517
+ t '命令输入错误...\n'
518
518
  eval usage $cmd
519
519
  fi
520
520
  ;;
@@ -523,7 +523,7 @@ main() {
523
523
  if [[ -n $p2 ]]; then
524
524
  update_raw "$p2" "$p3" "$p4" "$p5"
525
525
  else
526
- eval echo -e "命令输入错误...\\\n" $cmd
526
+ t '命令输入错误...\n'
527
527
  eval usage $cmd
528
528
  fi
529
529
  ;;
@@ -549,7 +549,7 @@ main() {
549
549
  eval update_auth_config "\\\"username\\\":\\\"$p2\\\"" "重置用户名" $cmd
550
550
  ;;
551
551
  *)
552
- eval echo -e "命令输入错误...\\\n" $cmd
552
+ t '命令输入错误...\n'
553
553
  eval usage $cmd
554
554
  ;;
555
555
  esac
@@ -33,6 +33,7 @@ const fs = __importStar(require("fs/promises"));
33
33
  const celebrate_1 = require("celebrate");
34
34
  const path_1 = require("path");
35
35
  const const_1 = require("../config/const");
36
+ const i18n_1 = require("../shared/i18n");
36
37
  const config_2 = __importDefault(require("../services/config"));
37
38
  const utils_1 = require("../shared/utils");
38
39
  const route = (0, express_1.Router)();
@@ -84,15 +85,24 @@ exports.default = (app) => {
84
85
  const logger = typedi_1.Container.get('logger');
85
86
  try {
86
87
  const { name, content } = req.body;
87
- if (config_1.default.blackFileList.includes(name)) {
88
- res.send({ code: 403, message: '文件无法访问' });
89
- }
90
- let path = (0, path_1.join)(config_1.default.configPath, name);
88
+ // Resolve path first to prevent traversal attacks
89
+ let basePath = config_1.default.configPath;
91
90
  if (name.startsWith('data/scripts/')) {
92
- path = (0, path_1.join)(config_1.default.rootPath, name);
91
+ basePath = (0, path_1.join)(config_1.default.rootPath, 'data/scripts');
92
+ }
93
+ const cleanName = name.replace(/^data\/scripts\//, '');
94
+ const resolvedPath = (0, path_1.join)(basePath, cleanName);
95
+ const normalized = (0, path_1.join)(resolvedPath);
96
+ // Verify the resolved path stays within allowed directory
97
+ if (!normalized.startsWith(basePath)) {
98
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件路径无效') });
99
+ }
100
+ // Check blacklist on actual filename (not user input)
101
+ if (config_1.default.blackFileList.includes((0, path_1.basename)(normalized))) {
102
+ return res.send({ code: 403, message: (0, i18n_1.t)('文件无法访问') });
93
103
  }
94
- await (0, utils_1.writeFileWithLock)(path, content);
95
- res.send({ code: 200, message: '保存成功' });
104
+ await (0, utils_1.writeFileWithLock)(normalized, content);
105
+ res.send({ code: 200, message: (0, i18n_1.t)('保存成功') });
96
106
  }
97
107
  catch (e) {
98
108
  return next(e);