@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
@@ -15,9 +15,68 @@ log_with_style() {
15
15
  printf "\n[%s] [%7s] %s\n" "${timestamp}" "${level}" "${message}"
16
16
  }
17
17
 
18
+ # ============================================
19
+ # 确保当前用户对 /ql 和 /ql/data 目录有写入权限
20
+ # /ql/data 是 Docker Volume 挂载点,权限可能与 /ql 不同,需单独检测
21
+ # ============================================
22
+ ensure_ql_permissions() {
23
+ local current_uid
24
+ local current_gid
25
+ current_uid=$(id -u)
26
+ current_gid=$(id -g)
27
+
28
+ if [ "$current_uid" -eq 0 ]; then
29
+ return 0
30
+ fi
31
+
32
+ # ---- 检查 /ql 目录 ----
33
+ if ! mkdir -p "$QL_DIR/.tmp" 2>/dev/null; then
34
+ if chown -R "$current_uid:$current_gid" "$QL_DIR" 2>/dev/null; then
35
+ log_with_style "INFO" "已修正 /ql 目录权限: UID=$current_uid GID=$current_gid"
36
+ else
37
+ local ql_owner
38
+ ql_owner=$(stat -c '%u' "$QL_DIR" 2>/dev/null || stat -f '%u' "$QL_DIR" 2>/dev/null)
39
+ log_with_style "ERROR" "============================================="
40
+ log_with_style "ERROR" " 权限错误:无法写入 /ql 目录"
41
+ log_with_style "ERROR" " 当前用户 UID: $current_uid"
42
+ log_with_style "ERROR" " /ql 目录所有者 UID: ${ql_owner:-未知}"
43
+ log_with_style "ERROR" ""
44
+ log_with_style "ERROR" " 解决方案:"
45
+ log_with_style "ERROR" " 1. 使用镜像内置用户: docker run --user ${ql_owner:-5432}:${ql_owner:-5432} ..."
46
+ log_with_style "ERROR" " 2. 使用 root 运行: 移除 --user 参数"
47
+ log_with_style "ERROR" " 3. 修正宿主机数据目录: chown -R $current_uid:$current_gid /path/to/ql/data"
48
+ log_with_style "ERROR" "============================================="
49
+ exit 1
50
+ fi
51
+ fi
52
+ rmdir "$QL_DIR/.tmp" 2>/dev/null || true
53
+
54
+ # ---- 检查 /ql/data 目录(Volume 挂载点,不在用户数据卷内创建临时文件) ----
55
+ if [ ! -w "$QL_DIR/data" ] || [ ! -x "$QL_DIR/data" ]; then
56
+ if chown "$current_uid:$current_gid" "$QL_DIR/data" 2>/dev/null; then
57
+ log_with_style "INFO" "已修正 /ql/data 目录权限: UID=$current_uid GID=$current_gid"
58
+ if [ ! -w "$QL_DIR/data" ] || [ ! -x "$QL_DIR/data" ]; then
59
+ log_with_style "ERROR" "修正后仍无法写入 /ql/data,请检查挂载的数据卷权限"
60
+ log_with_style "ERROR" "确保宿主机目录: chown -R $current_uid:$current_gid /your/data"
61
+ exit 1
62
+ fi
63
+ else
64
+ local data_owner
65
+ data_owner=$(stat -c '%u' "$QL_DIR/data" 2>/dev/null || stat -f '%u' "$QL_DIR/data" 2>/dev/null)
66
+ log_with_style "ERROR" "============================================="
67
+ log_with_style "ERROR" " 权限错误:无法写入 /ql/data (Volume 挂载点)"
68
+ log_with_style "ERROR" " 当前用户 UID: $current_uid"
69
+ log_with_style "ERROR" " /ql/data 所有者 UID: ${data_owner:-未知}"
70
+ log_with_style "ERROR" ""
71
+ log_with_style "ERROR" " 请修正宿主机数据目录权限:"
72
+ log_with_style "ERROR" " chown -R $current_uid:$current_gid /your/ql/data"
73
+ log_with_style "ERROR" "============================================="
74
+ exit 1
75
+ fi
76
+ fi
77
+ }
78
+
18
79
  # Fix DNS resolution issues in Alpine Linux
19
- # Alpine uses musl libc which has known DNS resolver issues with certain domains
20
- # Adding ndots:0 prevents unnecessary search domain appending
21
80
  if [ -f /etc/alpine-release ]; then
22
81
  if ! grep -q "^options ndots:0" /etc/resolv.conf 2>/dev/null; then
23
82
  echo "options ndots:0" >> /etc/resolv.conf
@@ -25,6 +84,26 @@ if [ -f /etc/alpine-release ]; then
25
84
  fi
26
85
  fi
27
86
 
87
+ # 确保 /etc/hosts 包含 localhost 解析(应对精简镜像或仅 IPv4/IPv6 环境)
88
+ if ! grep -qE '^127\.0\.0\.1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then
89
+ echo "127.0.0.1 localhost" >> /etc/hosts
90
+ log_with_style "INFO" "🔧 0. 已添加 IPv4 localhost 解析"
91
+ fi
92
+ if ! grep -qE '^::1[[:space:]]+.*localhost' /etc/hosts 2>/dev/null; then
93
+ echo "::1 localhost ip6-localhost ip6-loopback" >> /etc/hosts
94
+ log_with_style "INFO" "🔧 0. 已添加 IPv6 localhost 解析"
95
+ fi
96
+
97
+ # 自定义用户(非 qinglong/root)可能 HOME 为空或不可写
98
+ # 修正 HOME 确保 npm/pip/pm2 等工具有可用的缓存目录
99
+ if [ ! -w "$HOME" ]; then
100
+ mkdir -p "$QL_DIR/.tmp"
101
+ export HOME="$QL_DIR/.tmp"
102
+ fi
103
+
104
+ # 在一切操作之前检查目录权限
105
+ ensure_ql_permissions
106
+
28
107
  log_with_style "INFO" "🚀 1. 检测配置文件..."
29
108
  load_ql_envs
30
109
  export_ql_envs
@@ -50,6 +129,19 @@ fi
50
129
 
51
130
  log_with_style "SUCCESS" "🎉 容器启动成功!"
52
131
 
53
- tail -f /dev/null
132
+ # 自动检测调度模式:有 crond 二进制 → system 模式,否则 node 模式
133
+ if [ -z "$QL_SCHEDULER" ]; then
134
+ if command -v crond &>/dev/null; then
135
+ export QL_SCHEDULER="system"
136
+ else
137
+ export QL_SCHEDULER="node"
138
+ fi
139
+ fi
140
+
141
+ if [ "$QL_SCHEDULER" = "system" ]; then
142
+ crond -f > /dev/null
143
+ else
144
+ tail -f /dev/null
145
+ fi
54
146
 
55
147
  exec "$@"
@@ -0,0 +1,550 @@
1
+ # Project Architecture Guide
2
+
3
+ This document is written for AI coding agents and maintainers who need to understand and modify this project safely. It focuses on where behavior lives, how the application starts, and which files are usually involved for common changes.
4
+
5
+ ## Project Summary
6
+
7
+ Qinglong is a timed task management platform. It provides a web admin panel for managing cron jobs, scripts, environment variables, subscriptions, dependencies, logs, configuration files, and system settings.
8
+
9
+ The repository is organized as a full-stack TypeScript application:
10
+
11
+ - `src/`: frontend admin panel, built with Umi Max, React, Ant Design, and Ant Design Pro Layout.
12
+ - `back/`: backend application, built with Express, TypeScript, typedi, Sequelize, SQLite, gRPC, and worker processes.
13
+ - `shell/`: runtime shell scripts used to execute tasks and preload task environments.
14
+ - `data/`: local runtime data, including scripts, logs, configs, SQLite database, uploaded files, and cloned repositories.
15
+ - `static/`: built frontend and backend artifacts.
16
+ - `docker/`: Docker images, compose file, and entrypoint.
17
+ - `sample/`: sample scripts and default config templates.
18
+
19
+ ## High-Level Runtime Flow
20
+
21
+ ```text
22
+ Browser
23
+ -> src/pages/*
24
+ -> src/utils/http.tsx
25
+ -> /api/*
26
+ -> back/api/*
27
+ -> back/services/*
28
+ -> back/data/* Sequelize models
29
+ -> data/db/database.sqlite
30
+
31
+ Cron/task execution
32
+ -> back/services/cron.ts
33
+ -> shell/task.sh or shell/otask.sh
34
+ -> data/scripts/*
35
+ -> data/log/*
36
+
37
+ Frontend assets in production
38
+ -> static/dist/*
39
+ -> served by back/loaders/express.ts
40
+ ```
41
+
42
+ ## Main Startup Path
43
+
44
+ Development starts from `package.json`:
45
+
46
+ ```bash
47
+ pnpm start
48
+ ```
49
+
50
+ This runs:
51
+
52
+ - `start:back`: `nodemon ./back/app.ts`
53
+ - `start:front`: `max dev`
54
+
55
+ Backend startup begins in `back/app.ts`.
56
+
57
+ Important details:
58
+
59
+ - The backend uses Node `cluster`.
60
+ - The primary process initializes the database first.
61
+ - A gRPC worker starts before the HTTP worker.
62
+ - The HTTP worker starts Express and serves API routes plus frontend static files.
63
+ - If the gRPC worker restarts, the HTTP worker is asked to re-register cron jobs.
64
+
65
+ Production-style backend output is generated by:
66
+
67
+ ```bash
68
+ pnpm run build:back
69
+ ```
70
+
71
+ The compiled backend is placed under `static/build`.
72
+
73
+ Frontend output is generated by:
74
+
75
+ ```bash
76
+ pnpm run build:front
77
+ ```
78
+
79
+ The compiled frontend is placed under `static/dist`.
80
+
81
+ ## Backend Architecture
82
+
83
+ ### Entry Point
84
+
85
+ - `back/app.ts`
86
+
87
+ Responsibilities:
88
+
89
+ - Creates the Express application.
90
+ - Starts primary/worker process logic.
91
+ - Initializes database in the primary process.
92
+ - Starts gRPC and HTTP workers.
93
+ - Handles graceful shutdown.
94
+ - Re-registers cron jobs after gRPC worker recovery.
95
+
96
+ ### Loaders
97
+
98
+ - `back/loaders/app.ts`
99
+ - `back/loaders/express.ts`
100
+ - `back/loaders/db.ts`
101
+ - `back/loaders/depInjector.ts`
102
+ - `back/loaders/initData.ts`
103
+ - `back/loaders/initFile.ts`
104
+ - `back/loaders/initTask.ts`
105
+ - `back/loaders/server.ts`
106
+ - `back/loaders/sock.ts`
107
+
108
+ Loader responsibilities:
109
+
110
+ - Register dependency injection bindings.
111
+ - Sync Sequelize models.
112
+ - Initialize files and default data.
113
+ - Initialize scheduled tasks.
114
+ - Configure Express middleware.
115
+ - Register routes.
116
+ - Attach socket/server behavior.
117
+
118
+ `back/loaders/express.ts` is the main HTTP middleware and routing setup. It handles:
119
+
120
+ - CORS.
121
+ - Helmet.
122
+ - body parser.
123
+ - static frontend serving.
124
+ - JWT validation.
125
+ - token validation against shared auth state.
126
+ - `/open/*` rewrite to `/api/*`.
127
+ - route mounting through `back/api/index.ts`.
128
+ - frontend fallback to `static/dist/index.html`.
129
+ - API error handling.
130
+
131
+ ### API Routes
132
+
133
+ - `back/api/index.ts`
134
+
135
+ This file registers all API modules:
136
+
137
+ - `user.ts`: login, initialization, authentication-related user endpoints.
138
+ - `env.ts`: environment variable endpoints.
139
+ - `config.ts`: config file endpoints.
140
+ - `log.ts`: log endpoints.
141
+ - `cron.ts`: cron/task endpoints.
142
+ - `script.ts`: script file endpoints.
143
+ - `open.ts`: open API/app token endpoints.
144
+ - `dependence.ts`: dependency management endpoints.
145
+ - `system.ts`: system information/settings endpoints.
146
+ - `subscription.ts`: subscription endpoints.
147
+ - `update.ts`: update/check endpoints.
148
+ - `health.ts`: health check endpoints.
149
+
150
+ Route files should stay thin. They should validate input, get a service from `typedi`'s `Container`, call the service, and return `{ code, data, message }` style responses.
151
+
152
+ ### Services
153
+
154
+ - `back/services/*`
155
+
156
+ Services contain most business logic. Common examples:
157
+
158
+ - `cron.ts`: create/update/delete/run cron jobs, generate crontab data, manage logs, call scheduler client.
159
+ - `env.ts`: manage environment variables.
160
+ - `config.ts`: read/write config files.
161
+ - `script.ts`: manage script files.
162
+ - `subscription.ts`: manage script subscriptions and repository pulls.
163
+ - `dependence.ts`: install/manage runtime dependencies.
164
+ - `system.ts`: system info and settings.
165
+ - `notify.ts`: notification behavior.
166
+ - `sock.ts`: socket/log stream behavior.
167
+ - `grpc.ts`: gRPC server lifecycle.
168
+ - `http.ts`: HTTP server lifecycle.
169
+
170
+ When changing backend behavior, first find the API route, then follow it into the matching service. In most cases, the service is the right place for behavioral changes.
171
+
172
+ ### Data Models
173
+
174
+ - `back/data/index.ts`
175
+ - `back/data/*.ts`
176
+
177
+ The backend uses Sequelize with SQLite. Database storage is configured in `back/data/index.ts`:
178
+
179
+ ```text
180
+ data/db/database.sqlite
181
+ ```
182
+
183
+ Common model files:
184
+
185
+ - `cron.ts`: cron job model.
186
+ - `cronView.ts`: saved cron table views.
187
+ - `env.ts`: environment variable model.
188
+ - `dependence.ts`: dependency model.
189
+ - `open.ts`: open API app/token model.
190
+ - `subscription.ts`: subscription model.
191
+ - `system.ts`: system settings model.
192
+ - `notify.ts`: notification-related data.
193
+
194
+ Model sync and simple column migrations are currently handled in `back/loaders/db.ts`.
195
+
196
+ ### Configuration
197
+
198
+ - `back/config/index.ts`
199
+
200
+ This is the central runtime config file. It reads `.env`, establishes `QL_DIR`, and defines important paths:
201
+
202
+ - `dataPath`: runtime data root.
203
+ - `configPath`: config files.
204
+ - `scriptPath`: user scripts.
205
+ - `repoPath`: subscription repositories.
206
+ - `logPath`: task logs.
207
+ - `dbPath`: SQLite database location.
208
+ - `uploadPath`: uploaded files.
209
+ - `shellPath`: shell runtime scripts.
210
+ - `preloadPath`: JS/Python/Shell preload files.
211
+
212
+ Before hardcoding paths, check `back/config/index.ts`.
213
+
214
+ ### Scheduling And gRPC
215
+
216
+ - `back/schedule/*`
217
+ - `back/protos/*`
218
+ - `back/services/grpc.ts`
219
+
220
+ The project has two scheduling paths:
221
+
222
+ - Standard crontab-style tasks are persisted and written through backend cron logic.
223
+ - Node/gRPC scheduler logic handles cases such as second-level cron expressions or additional schedules.
224
+
225
+ `back/services/cron.ts` decides whether a task needs the Node scheduler using schedule shape and `extra_schedules`.
226
+
227
+ ### Shared Backend Utilities
228
+
229
+ - `back/shared/*`
230
+ - `back/config/util.ts`
231
+ - `back/config/share.ts`
232
+ - `back/config/http.ts`
233
+
234
+ Use these before adding new global helpers. Existing shared code includes:
235
+
236
+ - auth helpers.
237
+ - shared store.
238
+ - log stream manager.
239
+ - task runner helpers.
240
+ - concurrency limits.
241
+ - file locking utilities.
242
+ - HTTP/proxy helpers.
243
+
244
+ ## Frontend Architecture
245
+
246
+ ### Umi Config
247
+
248
+ - `.umirc.ts`
249
+
250
+ Important behavior:
251
+
252
+ - Dev server proxies API requests to `http://127.0.0.1:5700/`.
253
+ - Frontend build output is `static/dist`.
254
+ - Runtime env script is loaded from `./api/env.js`.
255
+ - `QlBaseUrl` affects frontend public path and routing base.
256
+
257
+ ### App Initialization
258
+
259
+ - `src/app.ts`
260
+
261
+ Responsibilities:
262
+
263
+ - Load Chinese and English locale JSON.
264
+ - Determine locale from URL/cookie/localStorage.
265
+ - Set Umi locale.
266
+ - Apply `QlBaseUrl` as public path and router basename.
267
+
268
+ ### Layout And Routes
269
+
270
+ - `src/layouts/defaultProps.tsx`
271
+ - `src/layouts/index.tsx`
272
+
273
+ `defaultProps.tsx` defines the main route/menu list. If adding a new page visible in the sidebar, update this file.
274
+
275
+ Current major pages:
276
+
277
+ - `src/pages/crontab`: timed task management.
278
+ - `src/pages/subscription`: subscription management.
279
+ - `src/pages/env`: environment variables.
280
+ - `src/pages/config`: config files.
281
+ - `src/pages/script`: script management.
282
+ - `src/pages/dependence`: dependency management.
283
+ - `src/pages/log`: log management.
284
+ - `src/pages/diff`: diff tool.
285
+ - `src/pages/setting`: system settings.
286
+ - `src/pages/login`: login.
287
+ - `src/pages/initialization`: first-run initialization.
288
+ - `src/pages/error`: error page.
289
+
290
+ ### Frontend Utilities
291
+
292
+ - `src/utils/http.tsx`: API request helper.
293
+ - `src/utils/websocket.ts`: socket connection behavior.
294
+ - `src/utils/config.ts`: frontend config helpers.
295
+ - `src/utils/const.ts`: constants.
296
+ - `src/utils/date.ts`: date formatting helpers.
297
+ - `src/utils/init.ts`: initialization helpers.
298
+ - `src/utils/codemirror/*`: CodeMirror integration.
299
+ - `src/utils/monaco/*`: Monaco integration.
300
+
301
+ When changing a page's API behavior, inspect both the page file and `src/utils/http.tsx`.
302
+
303
+ ### Components And Styling
304
+
305
+ - `src/components/*`: reusable UI components.
306
+ - `src/pages/**/index.less`: page-level styles.
307
+ - `src/pages/script/index.module.less` and `src/pages/log/index.module.less`: CSS module styles.
308
+ - `src/assets/fonts/*`: bundled fonts.
309
+ - `src/locales/*.json`: i18n text.
310
+
311
+ Follow the existing Ant Design and Ant Design Pro patterns when modifying UI.
312
+
313
+ ## Shell Runtime
314
+
315
+ - `shell/task.sh`: task execution path.
316
+ - `shell/otask.sh`: alternate/manual task execution path.
317
+ - `shell/api.sh`: shell-side API helpers.
318
+ - `shell/env.sh`: environment setup.
319
+ - `shell/check.sh`: runtime check helpers.
320
+ - `shell/update.sh`: update helpers.
321
+ - `shell/rmlog.sh`: log cleanup.
322
+ - `shell/share.sh`: shared shell helpers.
323
+ - `shell/preload/*`: preload files injected into JS/Python/Shell task environments.
324
+
325
+ The backend often coordinates task execution, but the actual user script process environment is shaped by files in `shell/`.
326
+
327
+ ## Runtime Data Directory
328
+
329
+ - `data/`
330
+
331
+ This directory is runtime state, not just source code. Be careful when modifying or deleting files here.
332
+
333
+ Important subdirectories:
334
+
335
+ - `data/db`: SQLite database.
336
+ - `data/config`: generated and user-edited config files.
337
+ - `data/scripts`: user scripts.
338
+ - `data/repo`: cloned subscription repositories.
339
+ - `data/log`: task logs.
340
+ - `data/upload`: uploaded files.
341
+ - `data/syslog`: system logs.
342
+ - `data/ssh.d`: SSH-related runtime data.
343
+ - `data/dep_cache`: dependency cache, when present.
344
+
345
+ Many bugs that appear as "backend logic" may involve state stored under `data/`.
346
+
347
+ ## Docker And Release Files
348
+
349
+ - `docker/Dockerfile`
350
+ - `docker/310.Dockerfile`
351
+ - `docker/docker-compose.yml`
352
+ - `docker/docker-entrypoint.sh`
353
+ - `ecosystem.config.js`
354
+ - `version.yaml`
355
+
356
+ Use these when changing deployment, container startup, PM2 behavior, or release metadata.
357
+
358
+ ## Common Modification Map
359
+
360
+ ### Add Or Modify A Backend API
361
+
362
+ Typical files:
363
+
364
+ 1. Add or update route in `back/api/<module>.ts`.
365
+ 2. Add or update service logic in `back/services/<module>.ts`.
366
+ 3. Add or update model in `back/data/<module>.ts` if persistence changes.
367
+ 4. Add validation with `celebrate`/`Joi` near the route.
368
+ 5. Update frontend caller in `src/pages/**` or `src/utils/**`.
369
+
370
+ ### Add A New Frontend Page
371
+
372
+ Typical files:
373
+
374
+ 1. Create `src/pages/<page>/index.tsx`.
375
+ 2. Add styles in `src/pages/<page>/index.less` if needed.
376
+ 3. Register route/menu in `src/layouts/defaultProps.tsx`.
377
+ 4. Add locale strings in `src/locales/zh-CN.json` and `src/locales/en-US.json`.
378
+ 5. Add API calls through the existing request helper.
379
+
380
+ ### Change Cron/Task Behavior
381
+
382
+ Start with:
383
+
384
+ - `back/api/cron.ts`
385
+ - `back/services/cron.ts`
386
+ - `back/schedule/*`
387
+ - `shell/task.sh`
388
+ - `shell/otask.sh`
389
+ - `shell/preload/*`
390
+
391
+ Also inspect:
392
+
393
+ - `back/data/cron.ts`
394
+ - `back/validation/schedule.ts`
395
+ - `data/config/crontab.list`
396
+ - `data/log/*`
397
+
398
+ ### Change Environment Variable Behavior
399
+
400
+ Start with:
401
+
402
+ - `back/api/env.ts`
403
+ - `back/services/env.ts`
404
+ - `back/data/env.ts`
405
+ - `src/pages/env/index.tsx`
406
+
407
+ Also inspect:
408
+
409
+ - `shell/preload/env.sh`
410
+ - `shell/preload/env.js`
411
+ - `shell/preload/env.py`
412
+
413
+ ### Change Script Management
414
+
415
+ Start with:
416
+
417
+ - `back/api/script.ts`
418
+ - `back/services/script.ts`
419
+ - `src/pages/script/index.tsx`
420
+ - `data/scripts/*`
421
+
422
+ ### Change Login/Auth/Security
423
+
424
+ Start with:
425
+
426
+ - `back/api/user.ts`
427
+ - `back/services/user.ts`
428
+ - `back/shared/auth.ts`
429
+ - `back/shared/store.ts`
430
+ - `back/loaders/express.ts`
431
+ - `back/token.ts`
432
+ - `src/pages/login/index.tsx`
433
+ - `src/pages/initialization/index.tsx`
434
+
435
+ Be careful with:
436
+
437
+ - JWT behavior.
438
+ - open API token behavior.
439
+ - first-run initialization.
440
+ - platform-specific session limits.
441
+
442
+ ### Change Subscription Behavior
443
+
444
+ Start with:
445
+
446
+ - `back/api/subscription.ts`
447
+ - `back/services/subscription.ts`
448
+ - `back/data/subscription.ts`
449
+ - `src/pages/subscription/index.tsx`
450
+ - `data/repo/*`
451
+
452
+ ### Change Dependency Management
453
+
454
+ Start with:
455
+
456
+ - `back/api/dependence.ts`
457
+ - `back/services/dependence.ts`
458
+ - `back/data/dependence.ts`
459
+ - `src/pages/dependence/index.tsx`
460
+ - `data/deps`
461
+ - `data/dep_cache`
462
+
463
+ ### Change Logs Or Live Log Streaming
464
+
465
+ Start with:
466
+
467
+ - `back/api/log.ts`
468
+ - `back/services/log.ts`
469
+ - `back/services/sock.ts`
470
+ - `back/shared/logStreamManager.ts`
471
+ - `src/pages/log/index.tsx`
472
+ - `src/components/terminal.tsx`
473
+ - `data/log/*`
474
+
475
+ ## Coding Conventions
476
+
477
+ Backend:
478
+
479
+ - Prefer adding business logic to services, not route files.
480
+ - Use `typedi` services consistently.
481
+ - Use existing config paths from `back/config/index.ts`.
482
+ - Return API responses in the existing `{ code, data, message }` shape.
483
+ - Use existing utilities before adding new helpers.
484
+ - Preserve current SQLite/Sequelize style unless doing a larger data-layer refactor.
485
+
486
+ Frontend:
487
+
488
+ - Follow existing Umi/React/Ant Design patterns.
489
+ - Keep route/menu changes in `src/layouts/defaultProps.tsx`.
490
+ - Use existing request/WebSocket helpers.
491
+ - Add or update locale strings for visible UI text.
492
+ - Keep page-specific styles near the page.
493
+
494
+ Shell/runtime:
495
+
496
+ - Treat `shell/` as part of production behavior.
497
+ - Test task execution changes with realistic scripts when possible.
498
+ - Be careful with path quoting and environment variable propagation.
499
+
500
+ Data:
501
+
502
+ - Treat `data/` as mutable runtime state.
503
+ - Do not delete runtime state unless explicitly requested.
504
+ - Schema changes should account for existing SQLite databases.
505
+
506
+ ## Suggested First Steps For AI Agents
507
+
508
+ When asked to modify behavior:
509
+
510
+ 1. Identify whether the change is frontend, backend, shell runtime, data model, or deployment.
511
+ 2. Search by feature name in `src/pages`, `back/api`, and `back/services`.
512
+ 3. Read the route file and matching service before editing.
513
+ 4. If persistence is involved, read the matching `back/data` model and `back/loaders/db.ts`.
514
+ 5. If task execution is involved, inspect `shell/` and `back/services/cron.ts`.
515
+ 6. Make the smallest scoped change that matches existing patterns.
516
+ 7. Run the most relevant check:
517
+ - `pnpm run build:back` for backend TypeScript changes.
518
+ - `pnpm run build:front` for frontend build changes.
519
+ - targeted manual task/API checks for shell and scheduler changes.
520
+
521
+ ## Quick Directory Reference
522
+
523
+ ```text
524
+ .
525
+ ├── back/ Backend TypeScript application
526
+ │ ├── api/ Express route modules
527
+ │ ├── config/ Runtime config, paths, constants, helpers
528
+ │ ├── data/ Sequelize models and SQLite connection
529
+ │ ├── loaders/ Startup initialization and Express setup
530
+ │ ├── middlewares/ Express middlewares
531
+ │ ├── protos/ gRPC proto files and generated TS
532
+ │ ├── schedule/ Scheduler/gRPC client helpers
533
+ │ ├── services/ Business logic services
534
+ │ ├── shared/ Shared backend utilities
535
+ │ └── validation/ Joi validation schemas
536
+ ├── src/ Frontend Umi/React application
537
+ │ ├── assets/ Fonts and static frontend assets
538
+ │ ├── components/ Shared UI components
539
+ │ ├── hooks/ Frontend hooks
540
+ │ ├── layouts/ Main layout and menu route config
541
+ │ ├── locales/ i18n JSON
542
+ │ ├── pages/ Feature pages
543
+ │ └── utils/ HTTP, WebSocket, editor, date, and config utilities
544
+ ├── shell/ Task runtime shell scripts and preload files
545
+ ├── data/ Runtime state: db, logs, scripts, repos, configs
546
+ ├── docker/ Docker build and compose files
547
+ ├── sample/ Sample scripts and default config templates
548
+ ├── static/ Built frontend/backend artifacts
549
+ └── docs/ Project documentation
550
+ ```