@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,18 @@
1
+ const CORE_VERSION_TOKEN_CHARACTER = /^[A-Za-z0-9._-]$/;
2
+ function isCoreVersionTokenCharacter(value) {
3
+ return value !== undefined && CORE_VERSION_TOKEN_CHARACTER.test(value);
4
+ }
5
+ export function containsCoreVersionToken(observed, expected) {
6
+ if (expected.length === 0)
7
+ return false;
8
+ let index = observed.indexOf(expected);
9
+ while (index >= 0) {
10
+ const end = index + expected.length;
11
+ if (!isCoreVersionTokenCharacter(index === 0 ? undefined : observed[index - 1]) &&
12
+ !isCoreVersionTokenCharacter(end === observed.length ? undefined : observed[end])) {
13
+ return true;
14
+ }
15
+ index = observed.indexOf(expected, index + 1);
16
+ }
17
+ return false;
18
+ }
package/dist/core.js ADDED
@@ -0,0 +1,206 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Readable, Transform } from "node:stream";
5
+ import { pipeline } from "node:stream/promises";
6
+ import zlib from "node:zlib";
7
+ import AdmZip from "adm-zip";
8
+ import { currentCoreVersion, readInstallRecord, validateCoreReleaseTag, writeInstallRecord, } from "./core-install-record.js";
9
+ import { beginCoreInstallTransaction, clearCoreInstallTransaction, markCoreInstallTransactionCommitted, recoverCoreInstallTransaction, } from "./core-install-transaction.js";
10
+ import { containsCoreVersionToken } from "./core-version.js";
11
+ import { durableRenameSync, pathEntryExists } from "./fs-atomic.js";
12
+ import { downloadReleaseAsset, listReleaseAssets, MIHOMO_REPO, RELEASE_ASSET_SIZE_LIMIT, resolveLatestTag, } from "./github.js";
13
+ import { sashLayout } from "./paths.js";
14
+ import { buildSanitizedEnv, recoverBinaryUnlockProbe } from "./process.js";
15
+ export function goOsArch(platform = process.platform, arch = process.arch) {
16
+ const osMap = { win32: "windows", darwin: "darwin", linux: "linux" };
17
+ const archMap = { x64: "amd64", arm64: "arm64" };
18
+ const goOs = osMap[platform];
19
+ const goArch = archMap[arch];
20
+ if (!goOs || !goArch) {
21
+ throw new Error(`Unsupported platform: ${platform}/${arch} (supported: win32/darwin/linux × x64/arm64)`);
22
+ }
23
+ return { os: goOs, arch: goArch };
24
+ }
25
+ export function mihomoAssetCandidates(tag, platform = process.platform, arch = process.arch) {
26
+ const { os, arch: goArch } = goOsArch(platform, arch);
27
+ const ext = platform === "win32" ? "zip" : "gz";
28
+ if (goArch === "amd64") {
29
+ return [
30
+ `mihomo-${os}-amd64-compatible-${tag}.${ext}`,
31
+ `mihomo-${os}-amd64-v1-${tag}.${ext}`,
32
+ `mihomo-${os}-amd64-${tag}.${ext}`,
33
+ ];
34
+ }
35
+ return [`mihomo-${os}-arm64-${tag}.${ext}`];
36
+ }
37
+ const EXTRACT_SIZE_LIMIT = 512 * 1024 * 1024;
38
+ function createCoreExtractionLimiter() {
39
+ let bytes = 0;
40
+ return new Transform({
41
+ transform(chunk, _encoding, callback) {
42
+ bytes += chunk.length;
43
+ if (bytes > EXTRACT_SIZE_LIMIT) {
44
+ callback(new Error("Extracted binary exceeds 512MB safety limit"));
45
+ return;
46
+ }
47
+ callback(null, chunk);
48
+ },
49
+ });
50
+ }
51
+ export async function extractCoreArchive(archivePath, assetName, destExe) {
52
+ const extracted = `${destExe}.extracted`;
53
+ try {
54
+ if (assetName.endsWith(".zip")) {
55
+ if (fs.statSync(archivePath).size > RELEASE_ASSET_SIZE_LIMIT) {
56
+ throw new Error("Core archive exceeds the download safety limit");
57
+ }
58
+ const zip = new AdmZip(archivePath);
59
+ const entry = zip
60
+ .getEntries()
61
+ .find((candidate) => !candidate.isDirectory && /^mihomo.*\.exe$/i.test(path.basename(candidate.entryName)));
62
+ if (!entry)
63
+ throw new Error(`No mihomo*.exe found inside ${assetName}`);
64
+ if (entry.header.size > EXTRACT_SIZE_LIMIT) {
65
+ throw new Error("Extracted binary exceeds 512MB safety limit");
66
+ }
67
+ const header = entry.header;
68
+ if (header.encrypted) {
69
+ throw new Error("Encrypted Core archives are not supported");
70
+ }
71
+ if (header.method !== 0 && header.method !== 8) {
72
+ throw new Error(`Unsupported ZIP compression method: ${header.method}`);
73
+ }
74
+ const compressed = entry.getCompressedData();
75
+ const output = fs.createWriteStream(extracted, { mode: 0o755 });
76
+ if (header.method === 0) {
77
+ await pipeline(Readable.from([compressed]), createCoreExtractionLimiter(), output);
78
+ }
79
+ else {
80
+ await pipeline(Readable.from([compressed]), zlib.createInflateRaw(), createCoreExtractionLimiter(), output);
81
+ }
82
+ }
83
+ else if (assetName.endsWith(".gz")) {
84
+ await pipeline(fs.createReadStream(archivePath), zlib.createGunzip(), createCoreExtractionLimiter(), fs.createWriteStream(extracted, { mode: 0o755 }));
85
+ }
86
+ else {
87
+ throw new Error(`Unsupported archive type: ${assetName}`);
88
+ }
89
+ fs.renameSync(extracted, destExe);
90
+ }
91
+ catch (err) {
92
+ fs.rmSync(extracted, { force: true });
93
+ throw err;
94
+ }
95
+ }
96
+ export { currentCoreVersion, readInstallRecord, validateCoreReleaseTag, writeInstallRecord };
97
+ export function verifyCoreExecutable(exe, timeoutMs = 5000, expectedVersion) {
98
+ try {
99
+ const output = execFileSync(exe, ["-v"], {
100
+ encoding: "utf8",
101
+ env: buildSanitizedEnv(),
102
+ timeout: timeoutMs,
103
+ windowsHide: true,
104
+ });
105
+ if (expectedVersion && !containsCoreVersionToken(output, expectedVersion)) {
106
+ throw new Error(`version output does not contain expected release ${expectedVersion}: ${output.trim()}`);
107
+ }
108
+ }
109
+ catch (err) {
110
+ throw new Error(`Downloaded core binary failed validation: ${err.message}`);
111
+ }
112
+ }
113
+ export async function stageCore(opts = {}) {
114
+ const layout = opts.layout ?? sashLayout();
115
+ const tag = validateCoreReleaseTag(opts.tag ?? (await resolveLatestTag(MIHOMO_REPO)));
116
+ const assets = await listReleaseAssets(MIHOMO_REPO, tag);
117
+ const candidates = mihomoAssetCandidates(tag);
118
+ fs.mkdirSync(layout.tempDir, { recursive: true });
119
+ fs.mkdirSync(layout.binDir, { recursive: true });
120
+ const archivePath = path.join(layout.tempDir, `mihomo-${tag}-${process.pid}.download`);
121
+ const stagedExe = `${layout.coreExe}.${process.pid}.new`;
122
+ try {
123
+ const assetName = await downloadReleaseAsset({
124
+ repo: MIHOMO_REPO,
125
+ tag,
126
+ assets,
127
+ candidates,
128
+ dest: archivePath,
129
+ onProgress: opts.onProgress,
130
+ });
131
+ await extractCoreArchive(archivePath, assetName, stagedExe);
132
+ fs.chmodSync(stagedExe, 0o755);
133
+ verifyCoreExecutable(stagedExe, 5000, tag);
134
+ return { version: tag, exe: stagedExe };
135
+ }
136
+ catch (err) {
137
+ fs.rmSync(stagedExe, { force: true });
138
+ throw err;
139
+ }
140
+ finally {
141
+ fs.rmSync(archivePath, { force: true });
142
+ }
143
+ }
144
+ export async function installCore(opts = {}) {
145
+ const layout = opts.layout ?? sashLayout();
146
+ recoverCoreInstallTransaction(layout);
147
+ assertCoreInstallationConsistent(layout);
148
+ if (coreInstalled(layout)) {
149
+ throw new Error(`Core executable already exists at ${layout.coreExe}; use the update flow`);
150
+ }
151
+ const staged = await stageCore({ ...opts, layout });
152
+ let transactionStarted = false;
153
+ let committed = false;
154
+ try {
155
+ const transaction = beginCoreInstallTransaction(staged.version, layout);
156
+ transactionStarted = true;
157
+ durableRenameSync(staged.exe, layout.coreExe);
158
+ writeInstallRecord({ coreVersion: staged.version, installedAt: transaction.createdAt }, layout);
159
+ markCoreInstallTransactionCommitted(transaction, layout);
160
+ committed = true;
161
+ clearCoreInstallTransaction(layout);
162
+ return { version: staged.version, exe: layout.coreExe };
163
+ }
164
+ catch (err) {
165
+ if (transactionStarted && !committed) {
166
+ try {
167
+ recoverCoreInstallTransaction(layout);
168
+ }
169
+ catch (recoveryErr) {
170
+ throw new Error(`${err.message}; Core install recovery also failed: ${recoveryErr.message}`);
171
+ }
172
+ }
173
+ throw err;
174
+ }
175
+ finally {
176
+ fs.rmSync(staged.exe, { force: true });
177
+ }
178
+ }
179
+ function isRegularFile(file) {
180
+ try {
181
+ return fs.lstatSync(file).isFile();
182
+ }
183
+ catch {
184
+ return false;
185
+ }
186
+ }
187
+ export function coreInstalled(layout = sashLayout()) {
188
+ return isRegularFile(layout.coreExe) && readInstallRecord(layout) !== undefined;
189
+ }
190
+ export function assertCoreInstallationConsistent(layout = sashLayout()) {
191
+ recoverBinaryUnlockProbe(layout.coreExe);
192
+ const binaryExists = pathEntryExists(layout.coreExe);
193
+ const installRecordExists = pathEntryExists(layout.installFile);
194
+ const binaryValid = isRegularFile(layout.coreExe);
195
+ const record = readInstallRecord(layout);
196
+ if ((!binaryExists && !installRecordExists) || (binaryValid && record))
197
+ return;
198
+ const reason = binaryExists
199
+ ? record
200
+ ? "the Core executable is not a regular file"
201
+ : "the Core executable exists without valid install metadata"
202
+ : record
203
+ ? "Core install metadata exists but the executable is missing"
204
+ : "Core install metadata is malformed without an executable";
205
+ throw new Error(`Core installation is incomplete or invalid: ${reason}. Run \`sash update --force\` to repair it.`);
206
+ }
@@ -0,0 +1,157 @@
1
+ import crypto from "node:crypto";
2
+ import { MihomoApi } from "../api.js";
3
+ import { currentCoreVersion } from "../core.js";
4
+ import { validateCoreConfigText } from "../core-config-validation.js";
5
+ import { pendingCoreUpdateVersion, readCoreUpdateTransaction } from "../core-update.js";
6
+ import { completeCoordinatedCoreUpdateAfterStart, rollbackCoordinatedCoreUpdate, } from "../core-update-coordination.js";
7
+ import { ProfileConflictError, ProfileService, } from "../profile-service.js";
8
+ import { RuntimeLifecycle } from "../runtime-lifecycle.js";
9
+ import { saveSettings } from "../settings.js";
10
+ import { SettingsService } from "../settings-service.js";
11
+ import { StateMutationQueue } from "../state-lock.js";
12
+ import { CoreSupervisor } from "../supervisor.js";
13
+ import { SystemProxyManager } from "../system-proxy-manager.js";
14
+ import { DaemonGate } from "./context.js";
15
+ export function buildDaemonContext(deps) {
16
+ const layout = deps.layout;
17
+ let committedSettings = saveSettings({ ...deps.settings }, layout);
18
+ let runtimeSettings = committedSettings;
19
+ const token = deps.token ?? crypto.randomBytes(24).toString("hex");
20
+ const startedAt = new Date().toISOString();
21
+ const systemProxy = deps.systemProxy ?? new SystemProxyManager({ layout });
22
+ const mutations = new StateMutationQueue(layout.mutationLockFile);
23
+ let profileRevision = 0;
24
+ let lifecycle;
25
+ const supervisor = deps.supervisor ??
26
+ new CoreSupervisor({
27
+ layout,
28
+ settings: () => runtimeSettings,
29
+ expectedVersion: () => pendingCoreUpdateVersion(layout) || currentCoreVersion(layout) || undefined,
30
+ onExit: async () => {
31
+ try {
32
+ await lifecycle?.handleUnexpectedCoreExit();
33
+ }
34
+ catch (err) {
35
+ console.error(`[sashd] failed to restore system proxy after Core exit: ${err.message}`);
36
+ }
37
+ },
38
+ });
39
+ lifecycle = new RuntimeLifecycle({
40
+ supervisor,
41
+ systemProxy,
42
+ settings: () => runtimeSettings,
43
+ coreUpdate: {
44
+ pending: () => readCoreUpdateTransaction(layout) !== undefined,
45
+ completeAfterStart: () => {
46
+ completeCoordinatedCoreUpdateAfterStart(layout);
47
+ },
48
+ rollbackAfterStartFailure: () => rollbackCoordinatedCoreUpdate(layout),
49
+ },
50
+ });
51
+ const gate = new DaemonGate(mutations, async () => {
52
+ const coreWasRunning = supervisor.isRunning();
53
+ await lifecycle.close();
54
+ return { coreWasRunning };
55
+ });
56
+ const mutate = (purpose, action) => gate.mutate(purpose, action);
57
+ const profiles = new ProfileService({
58
+ layout,
59
+ settings: () => committedSettings,
60
+ ...(deps.fetchProfileFn ? { fetchProfile: deps.fetchProfileFn } : {}),
61
+ validateConfig: deps.validateConfigFn ?? ((generated) => validateCoreConfigText(generated.yaml, layout)),
62
+ reloadConfig: async (configPath) => {
63
+ if (!supervisor.isRunning())
64
+ return;
65
+ const api = new MihomoApi(runtimeSettings.controller, runtimeSettings.secret);
66
+ await api.reloadConfig(configPath);
67
+ },
68
+ commit: mutate,
69
+ onChange: () => {
70
+ profileRevision += 1;
71
+ },
72
+ });
73
+ const settingsService = new SettingsService({
74
+ layout,
75
+ getCommitted: () => committedSettings,
76
+ setCommitted: (next) => {
77
+ committedSettings = { ...next };
78
+ },
79
+ setRuntime: (next) => {
80
+ runtimeSettings = { ...next };
81
+ },
82
+ profiles,
83
+ supervisor,
84
+ lifecycle,
85
+ commit: mutate,
86
+ });
87
+ const commitPreparedReload = (prepared, reloadRuntime) => profiles.commitPreparedActiveReload(prepared, {
88
+ reloadRuntime,
89
+ boundary: "already-held",
90
+ });
91
+ const startCore = async () => {
92
+ const retryAfterPreparation = Symbol("retry Core start after preparation");
93
+ for (;;) {
94
+ let prepared;
95
+ if (!supervisor.isRunning()) {
96
+ try {
97
+ prepared = await profiles.prepareActiveReload();
98
+ }
99
+ catch (err) {
100
+ if (supervisor.isRunning())
101
+ continue;
102
+ throw err;
103
+ }
104
+ }
105
+ const preparedForStart = prepared;
106
+ const result = await mutate("start core", async () => {
107
+ if (!preparedForStart && !supervisor.isRunning())
108
+ return retryAfterPreparation;
109
+ return lifecycle.start(preparedForStart
110
+ ? async () => {
111
+ await commitPreparedReload(preparedForStart, false);
112
+ }
113
+ : undefined);
114
+ });
115
+ if (result !== retryAfterPreparation)
116
+ return result;
117
+ }
118
+ };
119
+ const withPreparedReloadRetry = async (purpose, action) => {
120
+ for (let attempt = 0;; attempt += 1) {
121
+ const prepared = await profiles.prepareActiveReload();
122
+ try {
123
+ return await mutate(purpose, () => action(prepared));
124
+ }
125
+ catch (err) {
126
+ if (!(err instanceof ProfileConflictError) || attempt >= 1)
127
+ throw err;
128
+ }
129
+ }
130
+ };
131
+ const context = {
132
+ layout,
133
+ token,
134
+ startedAt,
135
+ profiles,
136
+ settingsService,
137
+ lifecycle,
138
+ supervisor,
139
+ systemProxy,
140
+ gate,
141
+ settings: {
142
+ committed: () => committedSettings,
143
+ runtime: () => runtimeSettings,
144
+ },
145
+ mutate,
146
+ profileRevision: () => profileRevision,
147
+ startCore,
148
+ restartCore: () => withPreparedReloadRetry("restart core", (prepared) => lifecycle.restart(async () => {
149
+ await commitPreparedReload(prepared, false);
150
+ })),
151
+ reloadCoreConfig: () => withPreparedReloadRetry("reload core config", (prepared) => commitPreparedReload(prepared, true)),
152
+ shutdown: () => gate.shutdown(),
153
+ closeListener: () => Promise.reject(new Error("listener close is not wired yet")),
154
+ ...(deps.onShutdown ? { onShutdown: deps.onShutdown } : {}),
155
+ };
156
+ return { context, supervisor, lifecycle, token };
157
+ }
@@ -0,0 +1,40 @@
1
+ import { ShuttingDownError } from "./errors.js";
2
+ export class DaemonGate {
3
+ queue;
4
+ cleanup;
5
+ closing = false;
6
+ cleanupPromise;
7
+ constructor(queue, cleanup) {
8
+ this.queue = queue;
9
+ this.cleanup = cleanup;
10
+ }
11
+ get isClosing() {
12
+ return this.closing;
13
+ }
14
+ async mutate(purpose, action) {
15
+ if (this.closing)
16
+ throw new ShuttingDownError();
17
+ return this.queue.run(purpose, () => {
18
+ if (this.closing)
19
+ throw new ShuttingDownError();
20
+ return action();
21
+ });
22
+ }
23
+ shutdown() {
24
+ if (this.cleanupPromise)
25
+ return this.cleanupPromise;
26
+ this.closing = true;
27
+ const attempt = this.queue.run("close daemon", this.cleanup);
28
+ this.cleanupPromise = attempt;
29
+ void attempt.catch(() => {
30
+ if (this.cleanupPromise === attempt) {
31
+ this.cleanupPromise = undefined;
32
+ this.closing = false;
33
+ }
34
+ });
35
+ return attempt;
36
+ }
37
+ reopen() {
38
+ this.closing = false;
39
+ }
40
+ }
@@ -0,0 +1,67 @@
1
+ import { assertCoreInstallationConsistent } from "../core.js";
2
+ import { recoverCoreInstallTransaction } from "../core-install-transaction.js";
3
+ import { atomicWriteFileSync } from "../fs-atomic.js";
4
+ import { readManagedStateTransactionStatus, recoverManagedStateTransaction, } from "../managed-state-transaction.js";
5
+ import { sashLayout } from "../paths.js";
6
+ import { clearPidRecord } from "../process.js";
7
+ import { migrateProfileState } from "../profile-migration.js";
8
+ import { reconcileOrphanedRuntime } from "../runtime-recovery.js";
9
+ import { loadSettings } from "../settings.js";
10
+ import { acquireStateLock, StateMutationQueue } from "../state-lock.js";
11
+ import { createDaemonServer } from "./server.js";
12
+ export async function runDaemon(opts = {}) {
13
+ const layout = opts.layout ?? sashLayout();
14
+ const daemonLease = await acquireStateLock(layout.daemonLeaseFile, {
15
+ purpose: "sashd singleton",
16
+ timeoutMs: 0,
17
+ });
18
+ let onSignal;
19
+ try {
20
+ const initialization = new StateMutationQueue(layout.mutationLockFile);
21
+ const settings = await initialization.run("initialize daemon state", async () => {
22
+ recoverCoreInstallTransaction(layout);
23
+ const managed = readManagedStateTransactionStatus(layout);
24
+ if (managed?.coordination !== "core-update") {
25
+ recoverManagedStateTransaction(layout);
26
+ }
27
+ let loaded = loadSettings(layout);
28
+ const pendingUpdate = await reconcileOrphanedRuntime({ layout, settings: loaded });
29
+ assertCoreInstallationConsistent(layout);
30
+ loaded = loadSettings(layout);
31
+ if (!pendingUpdate) {
32
+ await migrateProfileState(loaded, layout);
33
+ }
34
+ return loaded;
35
+ });
36
+ const instance = createDaemonServer({ layout, settings });
37
+ const serverClosed = new Promise((resolve) => instance.server.once("close", resolve));
38
+ const port = settings.daemonPort;
39
+ await new Promise((resolve, reject) => {
40
+ instance.server.listen(port, "127.0.0.1", () => resolve());
41
+ instance.server.once("error", reject);
42
+ });
43
+ const pidRecord = {
44
+ pid: process.pid,
45
+ token: instance.token,
46
+ port,
47
+ startedAt: new Date().toISOString(),
48
+ };
49
+ atomicWriteFileSync(layout.daemonPidFile, `${JSON.stringify(pidRecord, null, 2)}\n`);
50
+ onSignal = () => {
51
+ void instance.close().catch((err) => {
52
+ console.error(`[sashd] shutdown blocked: ${err.message}`);
53
+ });
54
+ };
55
+ process.on("SIGTERM", onSignal);
56
+ process.on("SIGINT", onSignal);
57
+ await serverClosed;
58
+ }
59
+ finally {
60
+ if (onSignal) {
61
+ process.removeListener("SIGTERM", onSignal);
62
+ process.removeListener("SIGINT", onSignal);
63
+ }
64
+ clearPidRecord(layout.daemonPidFile);
65
+ daemonLease.release();
66
+ }
67
+ }
@@ -0,0 +1,47 @@
1
+ import { HttpError } from "../daemon-http.js";
2
+ import { ProfileConflictError, ProfileInputError, ProfileNotFoundError, } from "../profile-service.js";
3
+ import { CoreUnhealthyError, SettingsInputError } from "../settings-service.js";
4
+ export class ShuttingDownError extends Error {
5
+ constructor(message = "sashd is shutting down") {
6
+ super(message);
7
+ this.name = "ShuttingDownError";
8
+ }
9
+ }
10
+ function defaultCodeForStatus(status) {
11
+ if (status === 400 || status === 413)
12
+ return "invalid_input";
13
+ if (status === 401 || status === 403)
14
+ return "unauthorized";
15
+ if (status === 404)
16
+ return "not_found";
17
+ if (status === 409)
18
+ return "conflict";
19
+ if (status === 503)
20
+ return "shutting_down";
21
+ if (status >= 500)
22
+ return "internal";
23
+ return "http";
24
+ }
25
+ export function errorToHttp(err) {
26
+ if (err instanceof HttpError) {
27
+ return {
28
+ status: err.statusCode,
29
+ code: err.code ?? defaultCodeForStatus(err.statusCode),
30
+ message: err.message,
31
+ };
32
+ }
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ if (err instanceof ProfileNotFoundError)
35
+ return { status: 404, code: "not_found", message };
36
+ if (err instanceof ProfileInputError)
37
+ return { status: 400, code: "invalid_input", message };
38
+ if (err instanceof SettingsInputError)
39
+ return { status: 400, code: "invalid_input", message };
40
+ if (err instanceof ProfileConflictError)
41
+ return { status: 409, code: "conflict", message };
42
+ if (err instanceof CoreUnhealthyError)
43
+ return { status: 409, code: "core_unhealthy", message };
44
+ if (err instanceof ShuttingDownError)
45
+ return { status: 503, code: "shutting_down", message };
46
+ return { status: 500, code: "internal", message };
47
+ }
@@ -0,0 +1,17 @@
1
+ export async function startCore(ctx) {
2
+ const result = await ctx.startCore();
3
+ return { status: 200, json: result };
4
+ }
5
+ export async function stopCore(ctx) {
6
+ await ctx.mutate("stop core", () => ctx.lifecycle.stop());
7
+ return { status: 204 };
8
+ }
9
+ export async function restartCore(ctx) {
10
+ const result = await ctx.restartCore();
11
+ return { status: 200, json: result };
12
+ }
13
+ export async function reloadCoreConfig(ctx) {
14
+ const result = await ctx.reloadCoreConfig();
15
+ const body = { proxyCount: result.proxyCount, source: result.source };
16
+ return { status: 200, json: body };
17
+ }
@@ -0,0 +1,66 @@
1
+ import { currentCoreVersion } from "../../core.js";
2
+ import { publicSettings } from "../../settings.js";
3
+ export function health(ctx) {
4
+ const body = { token: ctx.token, pid: process.pid, startedAt: ctx.startedAt };
5
+ return { status: 200, json: body };
6
+ }
7
+ export async function daemonStatus(ctx, req) {
8
+ const settings = ctx.settings.committed();
9
+ const runtimeCore = await ctx.supervisor.status();
10
+ const installedVersion = currentCoreVersion(ctx.layout);
11
+ const core = runtimeCore.version || !installedVersion
12
+ ? runtimeCore
13
+ : { ...runtimeCore, version: installedVersion };
14
+ let actualProxy;
15
+ let proxyApplied = false;
16
+ let proxyAppliedKnown = false;
17
+ let proxyStateKnown = false;
18
+ let proxyQueryError;
19
+ try {
20
+ const inspection = await ctx.systemProxy.inspect(req.searchParams.get("fresh") === "1");
21
+ proxyApplied = inspection.applied;
22
+ proxyAppliedKnown = inspection.appliedKnown;
23
+ proxyStateKnown = inspection.stateKnown;
24
+ if (proxyStateKnown)
25
+ actualProxy = inspection.state;
26
+ proxyQueryError = inspection.queryError;
27
+ }
28
+ catch (err) {
29
+ proxyQueryError = err instanceof Error ? err.message : String(err);
30
+ }
31
+ const active = ctx.profiles.active();
32
+ const status = {
33
+ daemon: {
34
+ pid: process.pid,
35
+ startedAt: ctx.startedAt,
36
+ port: settings.daemonPort,
37
+ },
38
+ revisions: {
39
+ profiles: ctx.profileRevision(),
40
+ },
41
+ core,
42
+ systemProxy: {
43
+ desired: settings.systemProxy,
44
+ applied: proxyApplied,
45
+ actual: actualProxy,
46
+ appliedKnown: proxyAppliedKnown,
47
+ stateKnown: proxyStateKnown,
48
+ ...(proxyQueryError ? { queryError: proxyQueryError } : {}),
49
+ },
50
+ settings: publicSettings(settings),
51
+ activeProfile: active ? { id: active.id, name: active.name, url: active.url } : null,
52
+ };
53
+ return { status: 200, json: status };
54
+ }
55
+ export function shutdownDaemon(ctx) {
56
+ return ctx.shutdown().then((snapshot) => ({
57
+ status: 200,
58
+ json: { coreWasRunning: snapshot.coreWasRunning },
59
+ after: () => {
60
+ void ctx
61
+ .closeListener()
62
+ .then(() => ctx.onShutdown?.())
63
+ .catch(() => undefined);
64
+ },
65
+ }));
66
+ }
@@ -0,0 +1,57 @@
1
+ import { ProfileInputError } from "../../profile-service.js";
2
+ import { requiredParam } from "../router.js";
3
+ export function listProfiles(ctx) {
4
+ return { status: 200, json: ctx.profiles.list() };
5
+ }
6
+ export async function addProfile(ctx, req) {
7
+ const body = await req.readJson();
8
+ const url = typeof body.url === "string" ? body.url.trim() : "";
9
+ const name = typeof body.name === "string" ? body.name.trim() : undefined;
10
+ const result = await ctx.profiles.addRemote(url, {
11
+ ...(name ? { name } : {}),
12
+ activate: body.activate === true,
13
+ });
14
+ return { status: 200, json: result };
15
+ }
16
+ export async function importProfile(ctx, req) {
17
+ const body = await req.readJson(8 * 1024 * 1024);
18
+ const name = typeof body.name === "string" ? body.name : "imported";
19
+ const content = typeof body.content === "string" ? body.content : "";
20
+ const result = await ctx.profiles.importLocal(name, content);
21
+ return { status: 200, json: result };
22
+ }
23
+ export async function activateProfile(ctx, req) {
24
+ const body = await req.readJson();
25
+ const id = body.id === null ? null : typeof body.id === "string" ? body.id : undefined;
26
+ if (id === undefined)
27
+ throw new ProfileInputError("Missing profile id string or null");
28
+ const result = await ctx.profiles.activate(id);
29
+ return { status: 200, json: result };
30
+ }
31
+ export async function updateAllProfiles(ctx) {
32
+ const result = await ctx.profiles.updateAll();
33
+ return { status: 200, json: result };
34
+ }
35
+ export async function updateProfile(ctx, req) {
36
+ const result = await ctx.profiles.update(requiredParam(req, "id"));
37
+ return { status: 200, json: result };
38
+ }
39
+ export function readProfileContent(ctx, req) {
40
+ return { status: 200, json: ctx.profiles.readContent(requiredParam(req, "id")) };
41
+ }
42
+ export async function writeProfileContent(ctx, req) {
43
+ const body = await req.readJson(8 * 1024 * 1024);
44
+ const content = typeof body.content === "string" ? body.content : "";
45
+ const result = await ctx.profiles.writeContent(requiredParam(req, "id"), content);
46
+ return { status: 200, json: result };
47
+ }
48
+ export async function renameProfile(ctx, req) {
49
+ const body = await req.readJson();
50
+ const name = typeof body.name === "string" ? body.name : "";
51
+ const result = await ctx.profiles.rename(requiredParam(req, "id"), name);
52
+ return { status: 200, json: result };
53
+ }
54
+ export async function removeProfile(ctx, req) {
55
+ const result = await ctx.profiles.remove(requiredParam(req, "id"));
56
+ return { status: 200, json: result };
57
+ }