@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,93 @@
1
+ import { apiErrorBody } from "./contracts.js";
2
+ export class HttpError extends Error {
3
+ statusCode;
4
+ code;
5
+ constructor(statusCode, message, code) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.code = code;
9
+ this.name = "HttpError";
10
+ }
11
+ }
12
+ export function parseJsonBody(req, maxBytes = 1024 * 1024) {
13
+ return new Promise((resolve, reject) => {
14
+ const chunks = [];
15
+ let bytes = 0;
16
+ let settled = false;
17
+ function cleanup() {
18
+ req.removeListener("data", onData);
19
+ req.removeListener("end", onEnd);
20
+ req.removeListener("aborted", onAborted);
21
+ req.removeListener("error", onError);
22
+ req.removeListener("close", onClose);
23
+ }
24
+ function fail(error, drain = false) {
25
+ if (settled)
26
+ return;
27
+ settled = true;
28
+ cleanup();
29
+ if (drain && !req.destroyed)
30
+ req.resume();
31
+ reject(error);
32
+ }
33
+ function onData(chunk) {
34
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
35
+ bytes += buffer.length;
36
+ if (bytes > maxBytes) {
37
+ fail(new HttpError(413, `Request body too large (exceeds ${Math.ceil(maxBytes / 1024 / 1024)}MB)`), true);
38
+ return;
39
+ }
40
+ chunks.push(buffer);
41
+ }
42
+ function onEnd() {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ cleanup();
47
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
48
+ if (!raw) {
49
+ resolve({});
50
+ return;
51
+ }
52
+ try {
53
+ resolve(JSON.parse(raw));
54
+ }
55
+ catch {
56
+ reject(new HttpError(400, "Invalid JSON request body"));
57
+ }
58
+ }
59
+ function onAborted() {
60
+ fail(new HttpError(400, "Request body was aborted"));
61
+ }
62
+ function onError() {
63
+ fail(new HttpError(400, "Request body stream failed"));
64
+ }
65
+ function onClose() {
66
+ if (!req.complete)
67
+ onAborted();
68
+ }
69
+ req.on("data", onData);
70
+ req.once("end", onEnd);
71
+ req.once("aborted", onAborted);
72
+ req.once("error", onError);
73
+ req.once("close", onClose);
74
+ });
75
+ }
76
+ export async function parseJsonObjectBody(req, maxBytes = 1024 * 1024) {
77
+ const body = await parseJsonBody(req, maxBytes);
78
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
79
+ throw new HttpError(400, "JSON request body must be an object");
80
+ }
81
+ return body;
82
+ }
83
+ export function sendJson(res, statusCode, data) {
84
+ const body = JSON.stringify(data);
85
+ res.writeHead(statusCode, {
86
+ "Content-Type": "application/json; charset=utf-8",
87
+ "Content-Length": Buffer.byteLength(body),
88
+ });
89
+ res.end(body);
90
+ }
91
+ export function sendError(res, statusCode, code, message) {
92
+ sendJson(res, statusCode, apiErrorBody(code, message));
93
+ }
@@ -0,0 +1,378 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { SashDaemonClient } from "./daemon-client.js";
7
+ import { isCanonicalIsoTimestamp, isPlainObject } from "./json-shape.js";
8
+ import { log } from "./log.js";
9
+ import { boundedLogTailSince, logTailCursor } from "./log-follow.js";
10
+ import { sashLayout } from "./paths.js";
11
+ import { buildSanitizedEnv, commandLineContains, isProcessAlive, killProcessGracefully, withPrivateAppendLogFds, } from "./process.js";
12
+ import { loadSettings } from "./settings.js";
13
+ import { readStateLockRecord, withStateLock } from "./state-lock.js";
14
+ export function readDaemonPidRecord(layout = sashLayout()) {
15
+ try {
16
+ if (!fs.existsSync(layout.daemonPidFile))
17
+ return undefined;
18
+ const parsed = JSON.parse(fs.readFileSync(layout.daemonPidFile, "utf8"));
19
+ if (!isPlainObject(parsed) ||
20
+ typeof parsed.pid !== "number" ||
21
+ !Number.isSafeInteger(parsed.pid) ||
22
+ parsed.pid <= 0 ||
23
+ typeof parsed.token !== "string" ||
24
+ !parsed.token.trim() ||
25
+ typeof parsed.port !== "number" ||
26
+ !Number.isInteger(parsed.port) ||
27
+ parsed.port < 1 ||
28
+ parsed.port > 65_535 ||
29
+ !isCanonicalIsoTimestamp(parsed.startedAt)) {
30
+ return undefined;
31
+ }
32
+ return {
33
+ pid: parsed.pid,
34
+ token: parsed.token,
35
+ port: parsed.port,
36
+ startedAt: parsed.startedAt,
37
+ };
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ function sleep(ms) {
44
+ return new Promise((resolve) => setTimeout(resolve, ms));
45
+ }
46
+ export async function evaluateDaemon(layout = sashLayout(), settings) {
47
+ const record = readDaemonPidRecord(layout);
48
+ let lease;
49
+ try {
50
+ lease = readStateLockRecord(layout.daemonLeaseFile);
51
+ }
52
+ catch {
53
+ return {
54
+ kind: "unhealthy",
55
+ running: true,
56
+ healthy: false,
57
+ ...(record ? { stalePidFile: true } : {}),
58
+ };
59
+ }
60
+ if (!lease || !isProcessAlive(lease.pid)) {
61
+ if (record && isProcessAlive(record.pid)) {
62
+ const s = settings ?? loadSettings(layout);
63
+ const legacyClient = new SashDaemonClient(record.port || s.daemonPort, s.daemonSecret);
64
+ try {
65
+ const health = await legacyClient.health();
66
+ if (health.token === record.token && health.pid === record.pid) {
67
+ return {
68
+ kind: "healthy",
69
+ running: true,
70
+ healthy: true,
71
+ legacyOwnership: true,
72
+ pid: record.pid,
73
+ port: record.port,
74
+ };
75
+ }
76
+ }
77
+ catch {
78
+ }
79
+ return {
80
+ kind: "unhealthy",
81
+ running: true,
82
+ healthy: false,
83
+ legacyOwnership: true,
84
+ pid: record.pid,
85
+ ...(lease ? { staleLeaseFile: true } : {}),
86
+ };
87
+ }
88
+ return {
89
+ kind: "stopped",
90
+ running: false,
91
+ healthy: false,
92
+ ...(record || lease ? { pid: record?.pid ?? lease?.pid } : {}),
93
+ ...(record ? { stalePidFile: true } : {}),
94
+ ...(lease ? { staleLeaseFile: true } : {}),
95
+ };
96
+ }
97
+ if (!record || record.pid !== lease.pid || !isProcessAlive(record.pid)) {
98
+ return {
99
+ kind: "unhealthy",
100
+ running: true,
101
+ healthy: false,
102
+ pid: lease.pid,
103
+ ...(record ? { stalePidFile: true } : {}),
104
+ };
105
+ }
106
+ const s = settings ?? loadSettings(layout);
107
+ const client = new SashDaemonClient(record.port || s.daemonPort, s.daemonSecret);
108
+ try {
109
+ const health = await client.health();
110
+ if (health.token === record.token && health.pid === record.pid) {
111
+ return {
112
+ kind: "healthy",
113
+ running: true,
114
+ pid: record.pid,
115
+ healthy: true,
116
+ port: record.port,
117
+ };
118
+ }
119
+ return {
120
+ kind: "unhealthy",
121
+ running: true,
122
+ healthy: false,
123
+ stalePidFile: true,
124
+ pid: record.pid,
125
+ };
126
+ }
127
+ catch {
128
+ return {
129
+ kind: "unhealthy",
130
+ running: true,
131
+ pid: record.pid,
132
+ healthy: false,
133
+ port: record.port,
134
+ };
135
+ }
136
+ }
137
+ function resolveDaemonEntryPath() {
138
+ const here = path.dirname(fileURLToPath(import.meta.url));
139
+ const candidate = path.join(here, "daemon-entry.js");
140
+ if (fs.existsSync(candidate))
141
+ return candidate;
142
+ const tsCandidate = path.join(here, "daemon-entry.ts");
143
+ if (fs.existsSync(tsCandidate))
144
+ return tsCandidate;
145
+ return candidate;
146
+ }
147
+ function daemonStartupDiagnostics(layout, cursor) {
148
+ const details = boundedLogTailSince(layout.daemonErrLogFile, cursor, { maxLines: 20 });
149
+ return `${details ? `\nRecent errors:\n${details}` : ""}\nCheck logs at: ${layout.daemonErrLogFile}`;
150
+ }
151
+ async function spawnDaemonUnlocked(opts = {}) {
152
+ const layout = opts.layout ?? sashLayout();
153
+ const settings = opts.settings ?? loadSettings(layout);
154
+ const timeoutMs = opts.timeoutMs ?? 10_000;
155
+ const state = await evaluateDaemon(layout, settings);
156
+ if (state.kind === "healthy") {
157
+ return { pid: state.pid };
158
+ }
159
+ if (state.kind !== "stopped") {
160
+ const deadline = Date.now() + timeoutMs;
161
+ while (Date.now() < deadline) {
162
+ await sleep(200);
163
+ const current = await evaluateDaemon(layout, settings);
164
+ if (current.kind === "healthy")
165
+ return { pid: current.pid };
166
+ if (current.kind === "stopped")
167
+ break;
168
+ }
169
+ const owner = state.pid ? ` (PID=${state.pid})` : "";
170
+ throw new Error(`sashd is already starting or unresponsive${owner}; refusing to start a competing daemon`);
171
+ }
172
+ fs.mkdirSync(layout.logsDir, { recursive: true });
173
+ fs.mkdirSync(layout.stateDir, { recursive: true });
174
+ const entryPath = resolveDaemonEntryPath();
175
+ const sanitizedEnv = buildSanitizedEnv();
176
+ const nodeArgs = entryPath.endsWith(".ts")
177
+ ? ["--import", pathToFileURL(createRequire(import.meta.url).resolve("tsx")).href, entryPath]
178
+ : [entryPath];
179
+ const errLogCursor = logTailCursor(layout.daemonErrLogFile);
180
+ const child = withPrivateAppendLogFds(layout.daemonLogFile, layout.daemonErrLogFile, ({ stdoutFd, stderrFd }) => spawn(process.execPath, nodeArgs, {
181
+ cwd: layout.root,
182
+ detached: true,
183
+ stdio: ["ignore", stdoutFd, stderrFd],
184
+ windowsHide: true,
185
+ env: { ...sanitizedEnv, SASH_HOME: layout.root },
186
+ }));
187
+ let spawnError;
188
+ child.once("error", (err) => {
189
+ spawnError = err;
190
+ });
191
+ child.unref();
192
+ const pid = child.pid;
193
+ if (!pid) {
194
+ throw new Error("Failed to start sashd process (no PID returned)");
195
+ }
196
+ const client = new SashDaemonClient(settings.daemonPort, settings.daemonSecret);
197
+ const deadline = Date.now() + timeoutMs;
198
+ while (Date.now() < deadline) {
199
+ if (spawnError) {
200
+ throw new Error(`Failed to start sashd: ${spawnError.message}${daemonStartupDiagnostics(layout, errLogCursor)}`);
201
+ }
202
+ if (!isProcessAlive(pid)) {
203
+ throw new Error(`sashd (PID=${pid}) exited unexpectedly during startup.${daemonStartupDiagnostics(layout, errLogCursor)}`);
204
+ }
205
+ try {
206
+ const record = readDaemonPidRecord(layout);
207
+ if (record) {
208
+ const health = await client.health();
209
+ if (record.pid === pid && health.token === record.token && health.pid === pid) {
210
+ return { pid };
211
+ }
212
+ }
213
+ }
214
+ catch {
215
+ }
216
+ await sleep(200);
217
+ }
218
+ const terminated = await killProcessGracefully(pid, {
219
+ timeoutMs: 3000,
220
+ verify: () => child.pid === pid &&
221
+ (child.exitCode === null || child.exitCode === undefined) &&
222
+ (child.signalCode === null || child.signalCode === undefined)
223
+ ? "match"
224
+ : "mismatch",
225
+ });
226
+ const cleanup = terminated
227
+ ? ""
228
+ : " The daemon could not be confirmed stopped; ownership state was preserved.";
229
+ throw new Error(`sashd started (PID=${pid}) but control API did not respond within ${timeoutMs}ms.${cleanup}${daemonStartupDiagnostics(layout, errLogCursor)}`);
230
+ }
231
+ export async function spawnDaemon(opts = {}) {
232
+ const layout = opts.layout ?? sashLayout();
233
+ const settings = opts.settings ?? loadSettings(layout);
234
+ const timeoutMs = opts.timeoutMs ?? 10_000;
235
+ return withStateLock(layout.daemonStartLockFile, { purpose: "start sashd", timeoutMs: timeoutMs + 5000 }, () => spawnDaemonUnlocked({ layout, settings, timeoutMs }));
236
+ }
237
+ export async function ensureDaemon(opts = {}) {
238
+ const layout = opts.layout ?? sashLayout();
239
+ const settings = opts.settings ?? loadSettings(layout);
240
+ const state = await evaluateDaemon(layout, settings);
241
+ if (state.kind === "healthy")
242
+ return;
243
+ await spawnDaemon({ layout, settings });
244
+ }
245
+ async function waitForDaemonExit(pid, timeoutMs) {
246
+ const deadline = Date.now() + timeoutMs;
247
+ while (Date.now() < deadline && isProcessAlive(pid))
248
+ await sleep(100);
249
+ if (isProcessAlive(pid)) {
250
+ throw new Error(`sashd PID ${pid} did not exit after maintenance shutdown`);
251
+ }
252
+ }
253
+ export async function prepareDaemonMaintenance(layout, settings, purpose, deps = {}) {
254
+ const daemonState = await (deps.evaluateDaemon ?? evaluateDaemon)(layout, settings);
255
+ if (daemonState.kind === "unhealthy") {
256
+ throw new Error(`sashd is running but unresponsive; refusing a competing ${purpose}`);
257
+ }
258
+ if (daemonState.kind === "stopped") {
259
+ return { daemonWasRunning: false, legacyDaemon: false, coreWasRunning: false };
260
+ }
261
+ const client = (deps.clientFactory ?? ((port, secret) => new SashDaemonClient(port, secret)))(daemonState.port, settings.daemonSecret);
262
+ if (daemonState.legacyOwnership) {
263
+ if (!client.status || !client.shutdown) {
264
+ throw new Error("Legacy sashd maintenance requires status and shutdown support");
265
+ }
266
+ log.warn("legacy sashd detected; using the pre-maintenance compatibility path");
267
+ const coreWasRunning = (await client.status()).core.running;
268
+ await client.shutdown();
269
+ await (deps.waitForDaemonExit ?? waitForDaemonExit)(daemonState.pid, 20_000);
270
+ return { daemonWasRunning: true, legacyDaemon: true, coreWasRunning };
271
+ }
272
+ const snapshot = await client.maintenanceShutdown();
273
+ await (deps.waitForDaemonExit ?? waitForDaemonExit)(daemonState.pid, 20_000);
274
+ return { daemonWasRunning: true, legacyDaemon: false, coreWasRunning: snapshot.coreWasRunning };
275
+ }
276
+ export async function stopDaemonFromCli(opts = {}) {
277
+ const layout = opts.layout ?? sashLayout();
278
+ const settings = opts.settings ?? loadSettings(layout);
279
+ const record = readDaemonPidRecord(layout);
280
+ if (!record) {
281
+ try {
282
+ const lease = readStateLockRecord(layout.daemonLeaseFile);
283
+ if (!lease || !isProcessAlive(lease.pid))
284
+ return true;
285
+ log.warn(`sashd is still starting or unresponsive (PID=${lease.pid}); try stopping again.`);
286
+ return false;
287
+ }
288
+ catch {
289
+ log.warn("Refusing to stop sashd: singleton ownership record is corrupt.");
290
+ return false;
291
+ }
292
+ }
293
+ const pid = record.pid;
294
+ if (!isProcessAlive(pid))
295
+ return true;
296
+ let lease;
297
+ try {
298
+ lease = readStateLockRecord(layout.daemonLeaseFile);
299
+ }
300
+ catch {
301
+ log.warn("Refusing to stop sashd: singleton ownership record is corrupt.");
302
+ return false;
303
+ }
304
+ if (lease && (!isProcessAlive(lease.pid) || lease.pid !== pid)) {
305
+ log.warn(`Refusing to stop PID ${pid}: daemon PID metadata does not match singleton ownership.`);
306
+ return false;
307
+ }
308
+ const client = new SashDaemonClient(record.port || settings.daemonPort, settings.daemonSecret);
309
+ const legacyOwnership = !lease;
310
+ const healthMatchesRecord = async () => {
311
+ try {
312
+ const health = await client.health();
313
+ return health.token === record.token && health.pid === pid;
314
+ }
315
+ catch {
316
+ return false;
317
+ }
318
+ };
319
+ if (!(await healthMatchesRecord())) {
320
+ log.warn(`Refusing to stop unverified sashd PID ${pid}.`);
321
+ return false;
322
+ }
323
+ try {
324
+ await client.shutdown();
325
+ }
326
+ catch {
327
+ }
328
+ const deadline = Date.now() + (opts.timeoutMs ?? 20_000);
329
+ while (Date.now() < deadline && isProcessAlive(pid)) {
330
+ await sleep(200);
331
+ }
332
+ if (!isProcessAlive(pid))
333
+ return true;
334
+ if (legacyOwnership) {
335
+ log.warn(`Legacy sashd PID ${pid} did not stop gracefully; refusing force termination without a singleton lease.`);
336
+ return false;
337
+ }
338
+ return withStateLock(layout.mutationLockFile, { purpose: "verify forced sashd stop", timeoutMs: 10_000 }, async () => {
339
+ if (!isProcessAlive(pid))
340
+ return true;
341
+ try {
342
+ const status = await client.status(true);
343
+ const actual = status.systemProxy.actual;
344
+ if (status.daemon.pid !== pid ||
345
+ status.core.running ||
346
+ status.systemProxy.applied ||
347
+ !actual ||
348
+ actual.enabled ||
349
+ (actual.supported && Boolean(actual.details)) ||
350
+ fs.existsSync(layout.systemProxyStateFile) ||
351
+ fs.existsSync(`${layout.systemProxyStateFile}.lock`)) {
352
+ log.warn(`Refusing to force-terminate sashd PID ${pid}: managed runtime cleanup is incomplete or unknown.`);
353
+ return false;
354
+ }
355
+ }
356
+ catch {
357
+ log.warn(`Refusing to force-terminate unresponsive sashd PID ${pid}: runtime cleanup cannot be verified.`);
358
+ return false;
359
+ }
360
+ const verifyDaemonIdentity = async () => {
361
+ if (!(await healthMatchesRecord()))
362
+ return "mismatch";
363
+ return commandLineContains(pid, "daemon-entry") ? "match" : "unknown";
364
+ };
365
+ if ((await verifyDaemonIdentity()) !== "match") {
366
+ log.warn(`Refusing to terminate PID ${pid}: process identity no longer matches sashd. Verify manually.`);
367
+ return false;
368
+ }
369
+ const killed = await killProcessGracefully(pid, {
370
+ timeoutMs: 4000,
371
+ verify: verifyDaemonIdentity,
372
+ });
373
+ if (killed)
374
+ return true;
375
+ log.error(`sashd process is still running after termination attempt (PID=${pid}).`);
376
+ return false;
377
+ });
378
+ }
@@ -0,0 +1,176 @@
1
+ import http from "node:http";
2
+ import { coreWebSocketProtocols, webSocketAuthResponseProtocol } from "./daemon-auth.js";
3
+ import { parseControllerAddress } from "./settings.js";
4
+ export function parseHostPort(address) {
5
+ const parsed = parseControllerAddress(address);
6
+ if (!parsed) {
7
+ throw new Error(`Invalid controller address: ${address} (expected loopback host:port)`);
8
+ }
9
+ return { host: parsed.host, port: parsed.port };
10
+ }
11
+ export function forwardHttpToCore(req, res, targetPath, controller, secret) {
12
+ const { host, port } = parseHostPort(controller);
13
+ const upstreamHeaders = { ...req.headers };
14
+ delete upstreamHeaders.host;
15
+ delete upstreamHeaders.authorization;
16
+ delete upstreamHeaders["x-sash-token"];
17
+ if (secret) {
18
+ upstreamHeaders.authorization = `Bearer ${secret}`;
19
+ }
20
+ let completed = false;
21
+ const proxyReq = http.request({
22
+ hostname: host,
23
+ port,
24
+ path: targetPath,
25
+ method: req.method,
26
+ headers: upstreamHeaders,
27
+ timeout: 30_000,
28
+ }, (proxyRes) => {
29
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
30
+ proxyRes.pipe(res);
31
+ proxyRes.on("end", () => {
32
+ completed = true;
33
+ });
34
+ proxyRes.on("error", () => {
35
+ res.destroy();
36
+ });
37
+ });
38
+ proxyReq.on("timeout", () => {
39
+ proxyReq.destroy(new Error("Core controller request timed out after 30000ms"));
40
+ });
41
+ proxyReq.on("error", (err) => {
42
+ if (!res.headersSent) {
43
+ const msg = JSON.stringify({
44
+ error: `Core controller unavailable: ${err.message}`,
45
+ });
46
+ res.writeHead(502, {
47
+ "Content-Type": "application/json; charset=utf-8",
48
+ "Content-Length": Buffer.byteLength(msg),
49
+ });
50
+ res.end(msg);
51
+ }
52
+ });
53
+ res.on("close", () => {
54
+ if (!completed) {
55
+ proxyReq.destroy();
56
+ }
57
+ });
58
+ req.pipe(proxyReq);
59
+ }
60
+ export function forwardWsToCore(req, socket, head, targetPath, controller, secret) {
61
+ const { host, port } = parseHostPort(controller);
62
+ const upstreamHeaders = { ...req.headers };
63
+ delete upstreamHeaders.host;
64
+ delete upstreamHeaders.authorization;
65
+ delete upstreamHeaders["x-sash-token"];
66
+ const coreProtocols = coreWebSocketProtocols(upstreamHeaders["sec-websocket-protocol"]);
67
+ if (coreProtocols) {
68
+ upstreamHeaders["sec-websocket-protocol"] = coreProtocols;
69
+ }
70
+ else {
71
+ delete upstreamHeaders["sec-websocket-protocol"];
72
+ }
73
+ if (secret) {
74
+ upstreamHeaders.authorization = `Bearer ${secret}`;
75
+ }
76
+ const proxyReq = http.request({
77
+ hostname: host,
78
+ port,
79
+ path: targetPath,
80
+ method: "GET",
81
+ headers: upstreamHeaders,
82
+ timeout: 10_000,
83
+ });
84
+ let upstreamTransportSocket;
85
+ let upstreamSocket;
86
+ let cleaned = false;
87
+ const cleanup = (closeDownstream) => {
88
+ if (cleaned)
89
+ return;
90
+ cleaned = true;
91
+ proxyReq.destroy();
92
+ upstreamTransportSocket?.destroy();
93
+ upstreamSocket?.destroy();
94
+ if (closeDownstream && !socket.destroyed)
95
+ socket.destroy();
96
+ };
97
+ proxyReq.once("socket", (transportSocket) => {
98
+ upstreamTransportSocket = transportSocket;
99
+ if (cleaned)
100
+ transportSocket.destroy();
101
+ });
102
+ socket.once("error", () => cleanup(true));
103
+ socket.once("end", () => cleanup(true));
104
+ socket.once("close", () => cleanup(false));
105
+ const pendingClientChunks = [];
106
+ let pendingClientBytes = 0;
107
+ const readPendingClientData = () => {
108
+ while (true) {
109
+ const chunk = socket.read();
110
+ if (chunk === null)
111
+ return;
112
+ pendingClientBytes += chunk.length;
113
+ if (pendingClientBytes > 64 * 1024) {
114
+ cleanup(true);
115
+ return;
116
+ }
117
+ pendingClientChunks.push(chunk);
118
+ }
119
+ };
120
+ socket.on("readable", readPendingClientData);
121
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
122
+ readPendingClientData();
123
+ socket.removeListener("readable", readPendingClientData);
124
+ upstreamSocket = proxySocket;
125
+ proxySocket.once("error", () => cleanup(true));
126
+ proxySocket.once("close", () => cleanup(true));
127
+ if (cleaned || socket.destroyed || !socket.writable) {
128
+ proxySocket.destroy();
129
+ return;
130
+ }
131
+ try {
132
+ const statusLine = `HTTP/1.1 ${proxyRes.statusCode ?? 101} ${proxyRes.statusMessage ?? "Switching Protocols"}\r\n`;
133
+ const responseHeaders = { ...proxyRes.headers };
134
+ if (!responseHeaders["sec-websocket-protocol"]) {
135
+ const authProtocol = webSocketAuthResponseProtocol(req.headers["sec-websocket-protocol"]);
136
+ if (authProtocol)
137
+ responseHeaders["sec-websocket-protocol"] = authProtocol;
138
+ }
139
+ const headerLines = Object.entries(responseHeaders)
140
+ .filter((entry) => entry[1] !== undefined)
141
+ .map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(", ") : value}`)
142
+ .join("\r\n");
143
+ socket.write(`${statusLine}${headerLines}\r\n\r\n`);
144
+ if (proxyHead.length > 0)
145
+ socket.write(proxyHead);
146
+ if (head.length > 0)
147
+ proxySocket.write(head);
148
+ for (const chunk of pendingClientChunks)
149
+ proxySocket.write(chunk);
150
+ proxySocket.pipe(socket);
151
+ socket.pipe(proxySocket);
152
+ }
153
+ catch {
154
+ cleanup(true);
155
+ }
156
+ });
157
+ proxyReq.on("response", (proxyRes) => {
158
+ proxyRes.once("error", () => cleanup(true));
159
+ if (cleaned || socket.destroyed || !socket.writable) {
160
+ proxyRes.destroy();
161
+ cleanup(false);
162
+ return;
163
+ }
164
+ const statusLine = `HTTP/1.1 ${proxyRes.statusCode ?? 502} ${proxyRes.statusMessage ?? "Bad Gateway"}\r\n\r\n`;
165
+ socket.end(statusLine);
166
+ proxyRes.resume();
167
+ });
168
+ proxyReq.on("timeout", () => {
169
+ proxyReq.destroy(new Error("Core WebSocket upgrade timed out after 10000ms"));
170
+ });
171
+ proxyReq.on("error", () => cleanup(true));
172
+ if (socket.destroyed || !socket.writable)
173
+ cleanup(false);
174
+ else
175
+ proxyReq.end();
176
+ }