@astralyn/sash 0.1.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 (339) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/LICENSE +21 -0
  3. package/README.md +71 -0
  4. package/THIRD_PARTY_NOTICES.md +54 -0
  5. package/dist/api.js +103 -0
  6. package/dist/browser.js +25 -0
  7. package/dist/cli-errors.js +15 -0
  8. package/dist/cli.js +122 -0
  9. package/dist/commands/lifecycle.js +50 -0
  10. package/dist/commands/logs.js +54 -0
  11. package/dist/commands/shared.js +8 -0
  12. package/dist/commands/status.js +39 -0
  13. package/dist/commands/update.js +5 -0
  14. package/dist/commands/upgrade.js +43 -0
  15. package/dist/commands/web.js +33 -0
  16. package/dist/contracts.js +412 -0
  17. package/dist/core-config-validation.js +72 -0
  18. package/dist/core-install-record.js +55 -0
  19. package/dist/core-install-transaction.js +114 -0
  20. package/dist/core-update-coordination.js +105 -0
  21. package/dist/core-update-service.js +245 -0
  22. package/dist/core-update.js +804 -0
  23. package/dist/core-version.js +18 -0
  24. package/dist/core.js +206 -0
  25. package/dist/daemon/app.js +157 -0
  26. package/dist/daemon/context.js +40 -0
  27. package/dist/daemon/entry.js +67 -0
  28. package/dist/daemon/errors.js +47 -0
  29. package/dist/daemon/handlers/core.js +17 -0
  30. package/dist/daemon/handlers/daemon.js +66 -0
  31. package/dist/daemon/handlers/profiles.js +57 -0
  32. package/dist/daemon/handlers/proxy.js +12 -0
  33. package/dist/daemon/handlers/settings.js +48 -0
  34. package/dist/daemon/router.js +312 -0
  35. package/dist/daemon/scheduler.js +33 -0
  36. package/dist/daemon/server.js +121 -0
  37. package/dist/daemon-auth.js +71 -0
  38. package/dist/daemon-client.js +55 -0
  39. package/dist/daemon-entry.js +13 -0
  40. package/dist/daemon-http.js +93 -0
  41. package/dist/daemon-lifecycle.js +378 -0
  42. package/dist/daemon-proxy.js +176 -0
  43. package/dist/daemon-static.js +106 -0
  44. package/dist/daemon.js +3 -0
  45. package/dist/error-utils.js +14 -0
  46. package/dist/fs-atomic.js +123 -0
  47. package/dist/github.js +187 -0
  48. package/dist/http.js +282 -0
  49. package/dist/json-shape.js +17 -0
  50. package/dist/log-follow.js +280 -0
  51. package/dist/log.js +17 -0
  52. package/dist/managed-state-transaction.js +377 -0
  53. package/dist/mihomo-config.js +334 -0
  54. package/dist/node-version-guard.js +8 -0
  55. package/dist/offline-mutation.js +58 -0
  56. package/dist/paths.js +57 -0
  57. package/dist/process.js +572 -0
  58. package/dist/profile-migration.js +101 -0
  59. package/dist/profile-service.js +667 -0
  60. package/dist/profiles.js +182 -0
  61. package/dist/runtime-lifecycle.js +205 -0
  62. package/dist/runtime-owner.js +87 -0
  63. package/dist/runtime-recovery.js +31 -0
  64. package/dist/sash-client.js +163 -0
  65. package/dist/settings-service.js +242 -0
  66. package/dist/settings.js +314 -0
  67. package/dist/state-lock.js +331 -0
  68. package/dist/status.js +279 -0
  69. package/dist/supervisor.js +301 -0
  70. package/dist/sysproxy/common.js +99 -0
  71. package/dist/sysproxy/darwin.js +287 -0
  72. package/dist/sysproxy/factory.js +71 -0
  73. package/dist/sysproxy/gnome.js +181 -0
  74. package/dist/sysproxy/legacy.js +58 -0
  75. package/dist/sysproxy/snapshot.js +284 -0
  76. package/dist/sysproxy/types.js +24 -0
  77. package/dist/sysproxy/windows.js +216 -0
  78. package/dist/sysproxy.js +8 -0
  79. package/dist/system-proxy-manager.js +460 -0
  80. package/dist/tun-guidance.js +11 -0
  81. package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
  82. package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
  83. package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
  84. package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
  85. package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
  86. package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
  87. package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
  88. package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
  89. package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
  90. package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
  91. package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
  92. package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
  93. package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
  94. package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
  95. package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
  96. package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
  97. package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
  98. package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
  99. package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
  100. package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
  101. package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
  102. package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
  103. package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
  104. package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
  105. package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
  106. package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
  107. package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
  108. package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
  109. package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
  110. package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
  111. package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
  112. package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
  113. package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
  114. package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
  115. package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
  116. package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
  117. package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
  118. package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
  119. package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
  120. package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
  121. package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
  122. package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
  123. package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
  124. package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
  125. package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
  126. package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
  127. package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
  128. package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
  129. package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
  130. package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
  131. package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
  132. package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
  133. package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
  134. package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
  135. package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
  136. package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
  137. package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
  138. package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
  139. package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
  140. package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
  141. package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
  142. package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
  143. package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
  144. package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
  145. package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
  146. package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
  147. package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
  148. package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
  149. package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
  150. package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
  151. package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
  152. package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
  153. package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
  154. package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
  155. package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
  156. package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
  157. package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
  158. package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
  159. package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
  160. package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
  161. package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
  162. package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
  163. package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
  164. package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
  165. package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
  166. package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
  167. package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
  168. package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
  169. package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
  170. package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
  171. package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
  172. package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
  173. package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
  174. package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
  175. package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
  176. package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
  177. package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
  178. package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
  179. package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
  180. package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
  181. package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
  182. package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
  183. package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
  184. package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
  185. package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
  186. package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
  187. package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
  188. package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
  189. package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
  190. package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
  191. package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
  192. package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
  193. package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
  194. package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
  195. package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
  196. package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
  197. package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
  198. package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
  199. package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
  200. package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
  201. package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
  202. package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
  203. package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
  204. package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
  205. package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
  206. package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
  207. package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
  208. package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
  209. package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
  210. package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
  211. package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
  212. package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
  213. package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
  214. package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
  215. package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
  216. package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
  217. package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
  218. package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
  219. package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
  220. package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
  221. package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
  222. package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
  223. package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
  224. package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
  225. package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
  226. package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
  227. package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
  228. package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
  229. package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
  230. package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
  231. package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
  232. package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
  233. package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
  234. package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
  235. package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
  236. package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
  237. package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
  238. package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
  239. package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
  240. package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
  241. package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
  242. package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
  243. package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
  244. package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
  245. package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
  246. package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
  247. package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
  248. package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
  249. package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
  250. package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
  251. package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
  252. package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
  253. package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
  254. package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
  255. package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
  256. package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
  257. package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
  258. package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
  259. package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
  260. package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
  261. package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
  262. package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
  263. package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
  264. package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
  265. package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
  266. package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
  267. package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
  268. package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
  269. package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
  270. package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
  271. package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
  272. package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
  273. package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
  274. package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
  275. package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
  276. package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
  277. package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
  278. package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
  279. package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
  280. package/dist/ui/assets/branding/sash-cat.png +0 -0
  281. package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
  282. package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
  283. package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
  284. package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
  285. package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
  286. package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
  287. package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
  288. package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
  289. package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
  290. package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
  291. package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
  292. package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
  293. package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
  294. package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
  295. package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
  296. package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
  297. package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
  298. package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
  299. package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
  300. package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
  301. package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
  302. package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
  303. package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
  304. package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
  305. package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
  306. package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
  307. package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
  308. package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
  309. package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
  310. package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
  311. package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
  312. package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
  313. package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
  314. package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
  315. package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
  316. package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
  317. package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
  318. package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
  319. package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
  320. package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
  321. package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
  322. package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
  323. package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
  324. package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
  325. package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
  326. package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
  327. package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
  328. package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
  329. package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
  330. package/dist/ui/assets/index-B61V60w_.js +19 -0
  331. package/dist/ui/assets/index-bkyxJG8J.css +1 -0
  332. package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
  333. package/dist/ui/index.html +52 -0
  334. package/dist/webui.js +21 -0
  335. package/docs/backend.md +260 -0
  336. package/docs/frontend.md +125 -0
  337. package/docs/remix-icon-license.txt +186 -0
  338. package/docs/usage.md +227 -0
  339. package/package.json +82 -0
@@ -0,0 +1,182 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import YAML from "yaml";
5
+ import { atomicWriteFileSync } from "./fs-atomic.js";
6
+ import { isPlainObject } from "./json-shape.js";
7
+ import { isValidMihomoConfig, PROFILE_DOWNLOAD_SIZE_LIMIT, } from "./mihomo-config.js";
8
+ import { sashLayout } from "./paths.js";
9
+ export const DEFAULT_PROFILE_INTERVAL_HOURS = 24;
10
+ export const MAX_PROFILE_INTERVAL_HOURS = 24 * 365;
11
+ export const PROFILE_INDEX_SIZE_LIMIT = 2 * 1024 * 1024;
12
+ export const NEVER_UPDATED = new Date(0).toISOString();
13
+ export function profileFilePath(layout, id) {
14
+ if (!/^[0-9]+$/.test(id))
15
+ throw new Error(`invalid profile id: ${id}`);
16
+ return path.join(layout.profilesDir, `${id}.yaml`);
17
+ }
18
+ function isSubscriptionUserinfo(value) {
19
+ if (typeof value !== "object" || value === null || Array.isArray(value))
20
+ return false;
21
+ const info = value;
22
+ return (Number.isFinite(info.upload) &&
23
+ Number.isFinite(info.download) &&
24
+ Number.isFinite(info.total) &&
25
+ (info.expire === undefined || Number.isFinite(info.expire)));
26
+ }
27
+ function isProfileMeta(value) {
28
+ if (typeof value !== "object" || value === null || Array.isArray(value))
29
+ return false;
30
+ const p = value;
31
+ return (typeof p.id === "string" &&
32
+ /^[0-9]+$/.test(p.id) &&
33
+ typeof p.name === "string" &&
34
+ typeof p.url === "string" &&
35
+ typeof p.intervalHours === "number" &&
36
+ Number.isSafeInteger(p.intervalHours) &&
37
+ p.intervalHours >= 0 &&
38
+ p.intervalHours <= MAX_PROFILE_INTERVAL_HOURS &&
39
+ typeof p.createdAt === "string" &&
40
+ Number.isFinite(new Date(p.createdAt).getTime()) &&
41
+ typeof p.updatedAt === "string" &&
42
+ Number.isFinite(new Date(p.updatedAt).getTime()) &&
43
+ (p.subInfo === undefined || isSubscriptionUserinfo(p.subInfo)) &&
44
+ (p.homePage === undefined || typeof p.homePage === "string") &&
45
+ (p.lastError === undefined || typeof p.lastError === "string"));
46
+ }
47
+ export function loadProfiles(layout = sashLayout()) {
48
+ let stat;
49
+ try {
50
+ stat = fs.lstatSync(layout.profilesIndexFile);
51
+ }
52
+ catch (err) {
53
+ if (err.code === "ENOENT") {
54
+ return { activeId: null, profiles: [] };
55
+ }
56
+ throw err;
57
+ }
58
+ if (!stat.isFile() || stat.size > PROFILE_INDEX_SIZE_LIMIT) {
59
+ throw new Error(`Profiles index must be a regular file no larger than ${PROFILE_INDEX_SIZE_LIMIT} bytes: ${layout.profilesIndexFile}`);
60
+ }
61
+ const content = fs.readFileSync(layout.profilesIndexFile);
62
+ if (content.length > PROFILE_INDEX_SIZE_LIMIT) {
63
+ throw new Error(`Profiles index must be a regular file no larger than ${PROFILE_INDEX_SIZE_LIMIT} bytes: ${layout.profilesIndexFile}`);
64
+ }
65
+ const text = content.toString("utf8");
66
+ let raw;
67
+ try {
68
+ raw = JSON.parse(text);
69
+ }
70
+ catch (err) {
71
+ throw new Error(`Profiles index is invalid JSON: ${layout.profilesIndexFile}: ${err.message}`);
72
+ }
73
+ if (!isPlainObject(raw)) {
74
+ throw new Error(`Profiles index root must be a plain object: ${layout.profilesIndexFile}`);
75
+ }
76
+ if (Object.keys(raw).some((key) => key !== "activeId" && key !== "profiles")) {
77
+ throw new Error(`Profiles index has unexpected root fields: ${layout.profilesIndexFile}`);
78
+ }
79
+ if (!Array.isArray(raw.profiles) || !raw.profiles.every(isProfileMeta)) {
80
+ throw new Error(`Profiles index has invalid profile metadata: ${layout.profilesIndexFile}`);
81
+ }
82
+ if (raw.activeId !== null && raw.activeId !== undefined && typeof raw.activeId !== "string") {
83
+ throw new Error(`Profiles index has an invalid activeId: ${layout.profilesIndexFile}`);
84
+ }
85
+ const profiles = raw.profiles;
86
+ if (new Set(profiles.map((profile) => profile.id)).size !== profiles.length) {
87
+ throw new Error(`Profiles index has duplicate profile ids: ${layout.profilesIndexFile}`);
88
+ }
89
+ const activeId = typeof raw.activeId === "string" && profiles.some((p) => p.id === raw.activeId)
90
+ ? raw.activeId
91
+ : null;
92
+ return { activeId, profiles };
93
+ }
94
+ export function serializeProfiles(index) {
95
+ const activeId = index.profiles.some((p) => p.id === index.activeId) ? index.activeId : null;
96
+ return `${JSON.stringify({ ...index, activeId }, null, 2)}\n`;
97
+ }
98
+ export function saveProfiles(index, layout = sashLayout()) {
99
+ atomicWriteFileSync(layout.profilesIndexFile, serializeProfiles(index));
100
+ }
101
+ export function getActiveProfile(index) {
102
+ return index.profiles.find((p) => p.id === index.activeId) ?? null;
103
+ }
104
+ export function findProfileByUrl(index, url) {
105
+ return index.profiles.find((p) => p.url !== "" && p.url === url) ?? null;
106
+ }
107
+ function readProfileBytes(layout, id) {
108
+ const file = profileFilePath(layout, id);
109
+ let stat;
110
+ try {
111
+ stat = fs.lstatSync(file);
112
+ }
113
+ catch (err) {
114
+ if (err.code === "ENOENT")
115
+ return undefined;
116
+ throw err;
117
+ }
118
+ if (!stat.isFile() || stat.size > PROFILE_DOWNLOAD_SIZE_LIMIT) {
119
+ throw new Error(`Profile ${id} must be a regular file no larger than ${PROFILE_DOWNLOAD_SIZE_LIMIT} bytes: ${file}`);
120
+ }
121
+ const content = fs.readFileSync(file);
122
+ if (content.length > PROFILE_DOWNLOAD_SIZE_LIMIT) {
123
+ throw new Error(`Profile ${id} must be a regular file no larger than ${PROFILE_DOWNLOAD_SIZE_LIMIT} bytes: ${file}`);
124
+ }
125
+ return content;
126
+ }
127
+ export function profileContentDigest(content) {
128
+ return crypto.createHash("sha256").update(content).digest("hex");
129
+ }
130
+ export function readProfileSource(layout, id) {
131
+ const content = readProfileBytes(layout, id);
132
+ if (!content)
133
+ return undefined;
134
+ const yamlText = content.toString("utf8");
135
+ let doc;
136
+ try {
137
+ doc = YAML.parse(yamlText);
138
+ }
139
+ catch (err) {
140
+ throw new Error(`Profile ${id} contains invalid YAML: ${err.message}`);
141
+ }
142
+ if (!isValidMihomoConfig(doc)) {
143
+ throw new Error(`Profile ${id} is not a valid core configuration`);
144
+ }
145
+ return { doc, yamlText, digest: profileContentDigest(content) };
146
+ }
147
+ export function readProfileDigest(layout, id) {
148
+ const content = readProfileBytes(layout, id);
149
+ return content ? profileContentDigest(content) : null;
150
+ }
151
+ export function readProfileDoc(layout, id) {
152
+ return readProfileSource(layout, id)?.doc;
153
+ }
154
+ export function allocateProfileId(index, layout, nowMs = Date.now()) {
155
+ let candidate = BigInt(nowMs);
156
+ for (;;) {
157
+ const id = candidate.toString();
158
+ if (!index.profiles.some((profile) => profile.id === id) &&
159
+ !fs.existsSync(profileFilePath(layout, id))) {
160
+ return id;
161
+ }
162
+ candidate += 1n;
163
+ }
164
+ }
165
+ export function profileNameFromUrl(url) {
166
+ try {
167
+ return new URL(url).hostname || "profile";
168
+ }
169
+ catch {
170
+ return "profile";
171
+ }
172
+ }
173
+ export function profileDueForUpdate(profile, fileExists, nowMs = Date.now()) {
174
+ if (!profile.url || profile.intervalHours <= 0)
175
+ return false;
176
+ if (!fileExists)
177
+ return true;
178
+ const updatedMs = new Date(profile.updatedAt).getTime();
179
+ if (!Number.isFinite(updatedMs))
180
+ return true;
181
+ return nowMs - updatedMs >= profile.intervalHours * 3_600_000;
182
+ }
@@ -0,0 +1,205 @@
1
+ function sleep(ms) {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
4
+ export class RuntimeLifecycle {
5
+ supervisor;
6
+ systemProxy;
7
+ getSettings;
8
+ coreUpdate;
9
+ operationQueue = Promise.resolve();
10
+ phase;
11
+ generation = 0;
12
+ constructor(options) {
13
+ this.supervisor = options.supervisor;
14
+ this.systemProxy = options.systemProxy;
15
+ this.getSettings = options.settings;
16
+ this.coreUpdate = options.coreUpdate;
17
+ this.phase = this.supervisor.isRunning() ? "running" : "stopped";
18
+ }
19
+ state() {
20
+ return { phase: this.phase, generation: this.generation };
21
+ }
22
+ start(prepare) {
23
+ return this.enqueue(() => this.startUnlocked(prepare));
24
+ }
25
+ stop() {
26
+ return this.enqueue(() => this.stopUnlocked());
27
+ }
28
+ restart(prepare) {
29
+ return this.enqueue(() => this.restartUnlocked(prepare));
30
+ }
31
+ reconcileSystemProxy() {
32
+ return this.enqueue(() => this.reconcileSystemProxyUnlocked());
33
+ }
34
+ recoverStartup() {
35
+ return this.enqueue(async () => {
36
+ await this.systemProxy.release();
37
+ this.phase = this.supervisor.isRunning() ? "running" : "stopped";
38
+ });
39
+ }
40
+ handleUnexpectedCoreExit() {
41
+ return this.enqueue(async () => {
42
+ if (this.supervisor.isRunning())
43
+ return;
44
+ this.generation++;
45
+ this.phase = "failed";
46
+ let lastError;
47
+ for (let attempt = 0; attempt < 3; attempt++) {
48
+ try {
49
+ await this.systemProxy.release();
50
+ this.phase = "stopped";
51
+ return;
52
+ }
53
+ catch (err) {
54
+ lastError = err;
55
+ if (attempt < 2)
56
+ await sleep(250 * (attempt + 1));
57
+ }
58
+ }
59
+ throw lastError;
60
+ });
61
+ }
62
+ close() {
63
+ return this.enqueue(() => this.stopUnlocked());
64
+ }
65
+ enqueue(operation) {
66
+ const next = this.operationQueue.then(operation, operation);
67
+ this.operationQueue = next.then(() => undefined, () => undefined);
68
+ return next;
69
+ }
70
+ async startUnlocked(prepare) {
71
+ if (this.supervisor.isRunning()) {
72
+ const status = await this.supervisor.status();
73
+ if (!status.pid)
74
+ throw new Error("Core reports running without a process identifier");
75
+ if (!status.healthy) {
76
+ throw new Error(`Core process is running but unhealthy (PID=${status.pid})`);
77
+ }
78
+ this.coreUpdate?.completeAfterStart();
79
+ await this.reconcileSystemProxyUnlocked();
80
+ return {
81
+ pid: status.pid,
82
+ ...(status.version ? { version: status.version } : {}),
83
+ ...(status.tunActive !== undefined ? { tunActive: status.tunActive } : {}),
84
+ };
85
+ }
86
+ this.generation++;
87
+ this.phase = "starting";
88
+ let startAttempted = false;
89
+ try {
90
+ await this.systemProxy.release();
91
+ await prepare?.();
92
+ startAttempted = true;
93
+ const result = await this.supervisor.start();
94
+ this.coreUpdate?.completeAfterStart();
95
+ this.phase = "running";
96
+ await this.applyDesiredProxyUnlocked();
97
+ return result;
98
+ }
99
+ catch (err) {
100
+ if (startAttempted && !this.supervisor.isRunning() && this.coreUpdate) {
101
+ let pending;
102
+ try {
103
+ pending = this.coreUpdate.pending();
104
+ }
105
+ catch (inspectError) {
106
+ this.phase = "failed";
107
+ throw new Error(`${err.message}; pending Core update inspection failed: ${inspectError.message}`);
108
+ }
109
+ if (pending) {
110
+ let previous;
111
+ try {
112
+ previous = this.coreUpdate.rollbackAfterStartFailure();
113
+ if (previous) {
114
+ await this.supervisor.start();
115
+ this.phase = "running";
116
+ await this.applyDesiredProxyUnlocked();
117
+ }
118
+ else {
119
+ this.phase = "stopped";
120
+ }
121
+ }
122
+ catch (rollbackError) {
123
+ this.phase = this.supervisor.isRunning() ? "running" : "failed";
124
+ throw new Error(`${err.message}; Core update rollback failed: ${rollbackError.message}`);
125
+ }
126
+ const destination = previous ? ` to ${previous.coreVersion}` : "";
127
+ throw new Error(`${err.message}; Core update rolled back${destination}`);
128
+ }
129
+ }
130
+ this.phase = this.supervisor.isRunning() ? "running" : "failed";
131
+ throw err;
132
+ }
133
+ }
134
+ async stopUnlocked() {
135
+ this.generation++;
136
+ this.phase = "stopping";
137
+ try {
138
+ await this.systemProxy.release();
139
+ await this.supervisor.stop();
140
+ this.phase = "stopped";
141
+ }
142
+ catch (err) {
143
+ this.phase = this.supervisor.isRunning() ? "running" : "failed";
144
+ throw err;
145
+ }
146
+ }
147
+ async restartUnlocked(prepare) {
148
+ this.generation++;
149
+ this.phase = "restarting";
150
+ try {
151
+ await prepare?.();
152
+ await this.systemProxy.release();
153
+ const result = await this.supervisor.restart();
154
+ this.phase = "running";
155
+ await this.applyDesiredProxyUnlocked();
156
+ return result;
157
+ }
158
+ catch (err) {
159
+ this.phase = this.supervisor.isRunning() ? "running" : "failed";
160
+ throw err;
161
+ }
162
+ }
163
+ async reconcileSystemProxyUnlocked() {
164
+ const settings = this.getSettings();
165
+ if (!settings.systemProxy) {
166
+ await this.systemProxy.release();
167
+ return;
168
+ }
169
+ await this.applyProxyToHealthyOwnedCoreUnlocked(settings.mixedPort);
170
+ }
171
+ async applyDesiredProxyUnlocked() {
172
+ const settings = this.getSettings();
173
+ if (!settings.systemProxy)
174
+ return;
175
+ await this.applyProxyToHealthyOwnedCoreUnlocked(settings.mixedPort);
176
+ }
177
+ async applyProxyToHealthyOwnedCoreUnlocked(port) {
178
+ const ownership = await this.requireHealthyOwnedCoreUnlocked();
179
+ await this.systemProxy.apply({ port });
180
+ const coreAfterApply = await this.supervisor.status();
181
+ if (coreAfterApply.running && coreAfterApply.healthy && this.supervisor.ownsCore(ownership)) {
182
+ return;
183
+ }
184
+ try {
185
+ await this.systemProxy.release();
186
+ }
187
+ catch (err) {
188
+ throw new Error(`Core ownership was lost while applying the system proxy; proxy release also failed: ${err.message}`);
189
+ }
190
+ throw new Error("Core ownership was lost while applying the system proxy");
191
+ }
192
+ async requireHealthyOwnedCoreUnlocked() {
193
+ const ownership = this.supervisor.ownedCoreSnapshot();
194
+ if (!ownership)
195
+ throw new Error("Cannot enable system proxy: core is not running");
196
+ const core = await this.supervisor.status();
197
+ if (!core.running || !core.healthy) {
198
+ throw new Error("Cannot enable system proxy: core is not healthy");
199
+ }
200
+ if (!this.supervisor.ownsCore(ownership)) {
201
+ throw new Error("Cannot enable system proxy: core ownership changed during health check");
202
+ }
203
+ return ownership;
204
+ }
205
+ }
@@ -0,0 +1,87 @@
1
+ import { assertCoreInstallationConsistent, coreInstalled, installCore } from "./core.js";
2
+ import { recoverCoreInstallTransaction } from "./core-install-transaction.js";
3
+ import { recoverCoordinatedCoreUpdate } from "./core-update-coordination.js";
4
+ import { SashDaemonClient } from "./daemon-client.js";
5
+ import { ensureDaemon, evaluateDaemon, prepareDaemonMaintenance, spawnDaemon, stopDaemonFromCli, } from "./daemon-lifecycle.js";
6
+ import { log } from "./log.js";
7
+ import { runOfflineMutation } from "./offline-mutation.js";
8
+ import { withStateLock } from "./state-lock.js";
9
+ export async function resolveRuntimeOwner(ctx, dependencies = {}) {
10
+ const daemon = await (dependencies.evaluateDaemon ?? evaluateDaemon)(ctx.layout, ctx.settings);
11
+ if (daemon.kind === "healthy") {
12
+ const client = (dependencies.clientFactory ?? ((port, secret) => new SashDaemonClient(port, secret)))(daemon.port, ctx.settings.daemonSecret);
13
+ return { kind: "daemon", daemon, client };
14
+ }
15
+ return daemon.kind === "stopped" ? { kind: "offline", daemon } : { kind: "unhealthy", daemon };
16
+ }
17
+ export async function ensureCore(ctx) {
18
+ recoverCoreInstallTransaction(ctx.layout);
19
+ recoverCoordinatedCoreUpdate(ctx.layout);
20
+ assertCoreInstallationConsistent(ctx.layout);
21
+ if (coreInstalled(ctx.layout))
22
+ return;
23
+ log.info("mihomo core not installed; downloading latest release...");
24
+ const { version } = await installCore({ layout: ctx.layout });
25
+ log.ok(`mihomo core ${version} installed`);
26
+ }
27
+ function withRuntimeOperation(ctx, purpose, action) {
28
+ return withStateLock(ctx.layout.runtimeOperationLockFile, { purpose, timeoutMs: 120_000 }, () => action());
29
+ }
30
+ async function healthyDaemonOwner(ctx) {
31
+ const owner = await resolveRuntimeOwner(ctx);
32
+ if (owner.kind !== "daemon") {
33
+ throw new Error("sashd did not become healthy after startup");
34
+ }
35
+ return owner;
36
+ }
37
+ export async function ensureRunning(ctx) {
38
+ return withRuntimeOperation(ctx, "start runtime", async () => {
39
+ const initialOwner = await resolveRuntimeOwner(ctx);
40
+ if (initialOwner.kind === "unhealthy") {
41
+ throw new Error("sashd is already starting or unresponsive; refusing a competing start");
42
+ }
43
+ if (initialOwner.kind === "offline") {
44
+ await runOfflineMutation(ctx, "install Core before start", () => ensureCore(ctx));
45
+ }
46
+ await ensureDaemon({ layout: ctx.layout, settings: ctx.settings });
47
+ const owner = await healthyDaemonOwner(ctx);
48
+ const result = await owner.client.startCore();
49
+ return { owner, result };
50
+ });
51
+ }
52
+ export async function stopRuntime(ctx) {
53
+ return withRuntimeOperation(ctx, "stop runtime", async () => {
54
+ const owner = await resolveRuntimeOwner(ctx);
55
+ if (owner.kind !== "offline") {
56
+ const stopped = await stopDaemonFromCli({ layout: ctx.layout, settings: ctx.settings });
57
+ if (!stopped)
58
+ throw new Error("sashd could not be stopped safely");
59
+ }
60
+ await runOfflineMutation(ctx, "finalize stopped runtime", () => undefined, {
61
+ reconcileRuntime: {
62
+ ...(owner.kind !== "offline" && owner.daemon.legacyOwnership ? { legacyDaemon: true } : {}),
63
+ },
64
+ });
65
+ return {
66
+ wasRunning: owner.kind !== "offline",
67
+ legacyDaemon: owner.kind !== "offline" && owner.daemon.legacyOwnership === true,
68
+ };
69
+ });
70
+ }
71
+ export async function restartRuntime(ctx, deps = {}) {
72
+ return withRuntimeOperation(ctx, "restart runtime", async () => {
73
+ const maintenance = await prepareDaemonMaintenance(ctx.layout, ctx.settings, "runtime restart", deps);
74
+ if (maintenance.legacyDaemon) {
75
+ await runOfflineMutation(ctx, "clean up legacy runtime", () => undefined, {
76
+ reconcileRuntime: { legacyDaemon: true },
77
+ });
78
+ }
79
+ if (!maintenance.daemonWasRunning) {
80
+ await runOfflineMutation(ctx, "install Core before restart", () => ensureCore(ctx));
81
+ }
82
+ const { pid: daemonPid } = await spawnDaemon({ layout: ctx.layout, settings: ctx.settings });
83
+ const owner = await healthyDaemonOwner(ctx);
84
+ const result = await owner.client.startCore();
85
+ return { owner, result, daemonPid, daemonWasRunning: maintenance.daemonWasRunning };
86
+ });
87
+ }
@@ -0,0 +1,31 @@
1
+ import { MihomoApi } from "./api.js";
2
+ import { recoverCoordinatedCoreUpdate } from "./core-update-coordination.js";
3
+ import { CoreSupervisor } from "./supervisor.js";
4
+ import { disableLegacySystemProxyIfOwned } from "./sysproxy.js";
5
+ import { SystemProxyManager } from "./system-proxy-manager.js";
6
+ export async function assertCoreControllerVacant(settings, probe = (current) => new MihomoApi(current.controller, current.secret).isReachable()) {
7
+ if (await probe(settings)) {
8
+ throw new Error("A Core controller is active without a live Sash PID owner; refusing to replace the executable");
9
+ }
10
+ }
11
+ export async function reconcileOrphanedRuntime(options, deps = {}) {
12
+ const { layout, settings } = options;
13
+ if (options.legacyDaemon && settings.systemProxy) {
14
+ await (deps.disableLegacyProxy ?? disableLegacySystemProxyIfOwned)({
15
+ port: settings.mixedPort,
16
+ });
17
+ }
18
+ const systemProxy = deps.systemProxy ?? new SystemProxyManager({ layout });
19
+ await systemProxy.release();
20
+ const supervisor = deps.supervisor ??
21
+ new CoreSupervisor({
22
+ layout,
23
+ settings: () => settings,
24
+ });
25
+ await supervisor.cleanStaleCore();
26
+ const pending = (deps.recoverCoreUpdate ?? recoverCoordinatedCoreUpdate)(layout);
27
+ if (options.verifyControllerVacant) {
28
+ await assertCoreControllerVacant(settings, deps.controllerReachable);
29
+ }
30
+ return pending;
31
+ }
@@ -0,0 +1,163 @@
1
+ import { parseApiErrorBody, parseCoreReloadResult, parseCoreStartResult, parseDaemonStatus, parseHealthInfo, parseProfileActionResponse, parseProfileActivateResponse, parseProfileContentResponse, parseProfileRemoveResponse, parseProfileRenameResponse, parseProfilesIndex, parseProfilesUpdateAllResponse, parseProfileUpdateResponse, parsePublicSettings, parseSettingsFileContent, parseSettingsWriteResult, parseShutdownResult, parseSystemProxyStatusResponse, } from "./contracts.js";
2
+ const defaultFetch = async (url, init) => {
3
+ const response = await fetch(url, {
4
+ method: init.method,
5
+ headers: init.headers,
6
+ ...(init.body !== undefined ? { body: init.body } : {}),
7
+ ...(init.timeoutMs !== undefined ? { signal: AbortSignal.timeout(init.timeoutMs) } : {}),
8
+ });
9
+ return { status: response.status, text: () => response.text() };
10
+ };
11
+ export class SashApiError extends Error {
12
+ status;
13
+ code;
14
+ constructor(status, code, message) {
15
+ super(message);
16
+ this.status = status;
17
+ this.code = code;
18
+ this.name = "SashApiError";
19
+ }
20
+ }
21
+ export class SashClient {
22
+ baseUrl;
23
+ token;
24
+ tokenHeader;
25
+ fetchFn;
26
+ timeoutMs;
27
+ constructor(options) {
28
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
29
+ if (options.token)
30
+ this.token = options.token;
31
+ this.tokenHeader = options.tokenHeader ?? "authorization";
32
+ this.fetchFn = options.fetchFn ?? defaultFetch;
33
+ this.timeoutMs = options.timeoutMs ?? 5_000;
34
+ }
35
+ async request(endpoint, options = {}) {
36
+ const headers = {};
37
+ const token = this.token?.() ?? "";
38
+ if (token) {
39
+ if (this.tokenHeader === "x-sash-token")
40
+ headers["X-Sash-Token"] = token;
41
+ else
42
+ headers.Authorization = `Bearer ${token}`;
43
+ }
44
+ let body;
45
+ if (options.body !== undefined) {
46
+ headers["Content-Type"] = "application/json";
47
+ body = JSON.stringify(options.body);
48
+ }
49
+ const response = await this.fetchFn(`${this.baseUrl}${endpoint}`, {
50
+ method: options.method ?? "GET",
51
+ headers,
52
+ ...(body !== undefined ? { body } : {}),
53
+ timeoutMs: options.timeoutMs ?? this.timeoutMs,
54
+ ...(options.attempts !== undefined ? { attempts: options.attempts } : {}),
55
+ });
56
+ const text = await response.text();
57
+ let data;
58
+ try {
59
+ data = text ? JSON.parse(text) : undefined;
60
+ }
61
+ catch (err) {
62
+ if (response.status >= 200 && response.status < 300) {
63
+ throw new Error(`Invalid JSON response from ${endpoint}: ${err.message}`);
64
+ }
65
+ data = text;
66
+ }
67
+ if (response.status < 200 || response.status >= 300) {
68
+ const parsedError = parseApiErrorBody(data);
69
+ const message = parsedError?.message ?? (typeof data === "string" ? data.slice(0, 300).trim() : "");
70
+ throw new SashApiError(response.status, parsedError?.code, message || `HTTP ${response.status}`);
71
+ }
72
+ return data;
73
+ }
74
+ async health() {
75
+ return parseHealthInfo(await this.request("/sash/daemon/health", { timeoutMs: 2_000, attempts: 1 }));
76
+ }
77
+ async status(fresh = false) {
78
+ return parseDaemonStatus(await this.request(fresh ? "/sash/daemon/status?fresh=1" : "/sash/daemon/status"));
79
+ }
80
+ async shutdown() {
81
+ return parseShutdownResult(await this.request("/sash/daemon/shutdown", { method: "POST", timeoutMs: 45_000 }));
82
+ }
83
+ async startCore() {
84
+ return parseCoreStartResult(await this.request("/sash/core/start", { method: "POST", timeoutMs: 15_000 }));
85
+ }
86
+ async stopCore() {
87
+ await this.request("/sash/core/stop", { method: "POST", timeoutMs: 30_000 });
88
+ }
89
+ async restartCore() {
90
+ return parseCoreStartResult(await this.request("/sash/core/restart", { method: "POST", timeoutMs: 30_000 }));
91
+ }
92
+ async reloadCoreConfig() {
93
+ return parseCoreReloadResult(await this.request("/sash/core/reload", { method: "POST", timeoutMs: 30_000 }));
94
+ }
95
+ async proxyStatus(fresh = false) {
96
+ return parseSystemProxyStatusResponse(await this.request(fresh ? "/sash/proxy?fresh=1" : "/sash/proxy"));
97
+ }
98
+ async getSettings() {
99
+ return parsePublicSettings(await this.request("/sash/settings"));
100
+ }
101
+ async patchSettings(patch) {
102
+ return parseSettingsWriteResult(await this.request("/sash/settings", { method: "PATCH", body: patch, timeoutMs: 45_000 }));
103
+ }
104
+ async getSettingsFile() {
105
+ return parseSettingsFileContent(await this.request("/sash/settings/file"));
106
+ }
107
+ async writeSettingsFile(content) {
108
+ return parseSettingsWriteResult(await this.request("/sash/settings/file", {
109
+ method: "PUT",
110
+ body: { content },
111
+ timeoutMs: 45_000,
112
+ }));
113
+ }
114
+ async listProfiles() {
115
+ return parseProfilesIndex(await this.request("/sash/profiles"));
116
+ }
117
+ async addProfile(url, opts = {}) {
118
+ return parseProfileActionResponse(await this.request("/sash/profiles", {
119
+ method: "POST",
120
+ body: { url, ...opts },
121
+ timeoutMs: 60_000,
122
+ }));
123
+ }
124
+ async importProfile(name, content) {
125
+ return parseProfileActionResponse(await this.request("/sash/profiles/import", {
126
+ method: "POST",
127
+ body: { name, content },
128
+ timeoutMs: 30_000,
129
+ }));
130
+ }
131
+ async activateProfile(id) {
132
+ return parseProfileActivateResponse(await this.request("/sash/profiles/active", {
133
+ method: "PUT",
134
+ body: { id },
135
+ timeoutMs: 30_000,
136
+ }));
137
+ }
138
+ async updateProfile(id) {
139
+ return parseProfileUpdateResponse(await this.request(`/sash/profiles/${id}/update`, {
140
+ method: "POST",
141
+ timeoutMs: 60_000,
142
+ }));
143
+ }
144
+ async updateAllProfiles() {
145
+ return parseProfilesUpdateAllResponse(await this.request("/sash/profiles/update-all", { method: "POST", timeoutMs: 120_000 }));
146
+ }
147
+ async getProfileContent(id) {
148
+ return parseProfileContentResponse(await this.request(`/sash/profiles/${id}/content`));
149
+ }
150
+ async writeProfileContent(id, content) {
151
+ return parseProfileUpdateResponse(await this.request(`/sash/profiles/${id}/content`, {
152
+ method: "PUT",
153
+ body: { content },
154
+ timeoutMs: 30_000,
155
+ }));
156
+ }
157
+ async renameProfile(id, name) {
158
+ return parseProfileRenameResponse(await this.request(`/sash/profiles/${id}`, { method: "PATCH", body: { name } }));
159
+ }
160
+ async removeProfile(id) {
161
+ return parseProfileRemoveResponse(await this.request(`/sash/profiles/${id}`, { method: "DELETE", timeoutMs: 30_000 }));
162
+ }
163
+ }