@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,12 @@
1
+ export async function proxyStatus(ctx, req) {
2
+ const inspection = await ctx.systemProxy.inspect(req.searchParams.get("fresh") === "1");
3
+ const body = {
4
+ desired: ctx.settings.committed().systemProxy,
5
+ applied: inspection.applied,
6
+ ...inspection.state,
7
+ appliedKnown: inspection.appliedKnown,
8
+ stateKnown: inspection.stateKnown,
9
+ ...(inspection.queryError ? { queryError: inspection.queryError } : {}),
10
+ };
11
+ return { status: 200, json: body };
12
+ }
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs";
2
+ import { parseSettingsPatch, } from "../../contracts.js";
3
+ import { HttpError } from "../../daemon-http.js";
4
+ import { parseSettingsText, publicSettings } from "../../settings.js";
5
+ function writeResultBody(result) {
6
+ return {
7
+ restartRequired: result.restartRequired,
8
+ settings: publicSettings(result.settings),
9
+ };
10
+ }
11
+ export function readSettings(ctx) {
12
+ return { status: 200, json: publicSettings(ctx.settings.committed()) };
13
+ }
14
+ export async function patchSettings(ctx, req) {
15
+ const body = await req.readJson();
16
+ let patch;
17
+ try {
18
+ patch = parseSettingsPatch(body, "request");
19
+ }
20
+ catch (err) {
21
+ throw new HttpError(400, err.message);
22
+ }
23
+ const result = await ctx.settingsService.apply(patch);
24
+ return { status: 200, json: writeResultBody(result) };
25
+ }
26
+ export function readSettingsFile(ctx) {
27
+ let content;
28
+ try {
29
+ content = fs.readFileSync(ctx.layout.settingsFile, "utf8");
30
+ }
31
+ catch {
32
+ throw new HttpError(404, "Settings file does not exist yet");
33
+ }
34
+ return { status: 200, json: { content } };
35
+ }
36
+ export async function writeSettingsFile(ctx, req) {
37
+ const body = await req.readJson(256 * 1024);
38
+ const content = typeof body.content === "string" ? body.content : "";
39
+ let parsed;
40
+ try {
41
+ parsed = parseSettingsText(content, ctx.layout.settingsFile);
42
+ }
43
+ catch (err) {
44
+ throw new HttpError(400, err.message);
45
+ }
46
+ const result = await ctx.settingsService.applyFileSettings(parsed);
47
+ return { status: 200, json: writeResultBody(result) };
48
+ }
@@ -0,0 +1,312 @@
1
+ import { isControlMutation, isControlRequestAuthorized, isLoopbackHostHeader, isLoopbackOriginHeader, } from "../daemon-auth.js";
2
+ import { parseJsonObjectBody, sendError, sendJson } from "../daemon-http.js";
3
+ import { forwardHttpToCore } from "../daemon-proxy.js";
4
+ import { serveStaticUi } from "../daemon-static.js";
5
+ import { errorToHttp } from "./errors.js";
6
+ import { reloadCoreConfig, restartCore, startCore, stopCore } from "./handlers/core.js";
7
+ import { daemonStatus, health, shutdownDaemon } from "./handlers/daemon.js";
8
+ import { activateProfile, addProfile, importProfile, listProfiles, readProfileContent, removeProfile, renameProfile, updateAllProfiles, updateProfile, writeProfileContent, } from "./handlers/profiles.js";
9
+ import { proxyStatus } from "./handlers/proxy.js";
10
+ import { patchSettings, readSettings, readSettingsFile, writeSettingsFile, } from "./handlers/settings.js";
11
+ export function parseDaemonRequestTarget(rawTarget, hostHeader) {
12
+ if (!rawTarget.startsWith("/") || rawTarget.startsWith("//")) {
13
+ throw new Error("Unsupported HTTP request-target form");
14
+ }
15
+ const base = new URL(`http://${hostHeader}`);
16
+ const url = new URL(rawTarget, base);
17
+ if (url.origin !== base.origin || url.hash) {
18
+ throw new Error("Invalid HTTP origin-form request target");
19
+ }
20
+ return {
21
+ pathname: url.pathname,
22
+ routePathname: url.pathname.replace(/\/+$/, "") || "/",
23
+ search: url.search,
24
+ searchParams: url.searchParams,
25
+ };
26
+ }
27
+ export function coreApiTarget(target) {
28
+ const prefix = "/core/api";
29
+ const suffix = target.pathname.slice(prefix.length);
30
+ const upstreamPath = suffix ? `/${suffix.replace(/^\/+/, "")}` : "/";
31
+ return `${upstreamPath}${target.search}`;
32
+ }
33
+ function path(pathname) {
34
+ return new URLPattern({ pathname });
35
+ }
36
+ const CORE_API_PREFIX = "/core/api";
37
+ export function buildRoutes() {
38
+ return [
39
+ { methods: ["GET"], pattern: path("/sash/daemon/health"), auth: "public", handler: health },
40
+ {
41
+ methods: ["GET"],
42
+ pattern: path("/sash/daemon/status"),
43
+ auth: "public",
44
+ handler: daemonStatus,
45
+ },
46
+ {
47
+ methods: ["POST"],
48
+ pattern: path("/sash/daemon/shutdown"),
49
+ auth: "control",
50
+ handler: shutdownDaemon,
51
+ },
52
+ { methods: ["POST"], pattern: path("/sash/core/start"), auth: "control", handler: startCore },
53
+ { methods: ["POST"], pattern: path("/sash/core/stop"), auth: "control", handler: stopCore },
54
+ {
55
+ methods: ["POST"],
56
+ pattern: path("/sash/core/restart"),
57
+ auth: "control",
58
+ handler: restartCore,
59
+ },
60
+ {
61
+ methods: ["POST"],
62
+ pattern: path("/sash/core/reload"),
63
+ auth: "control",
64
+ handler: reloadCoreConfig,
65
+ },
66
+ { methods: ["GET"], pattern: path("/sash/proxy"), auth: "public", handler: proxyStatus },
67
+ { methods: ["GET"], pattern: path("/sash/settings"), auth: "public", handler: readSettings },
68
+ {
69
+ methods: ["PATCH"],
70
+ pattern: path("/sash/settings"),
71
+ auth: "control",
72
+ handler: patchSettings,
73
+ },
74
+ {
75
+ methods: ["GET"],
76
+ pattern: path("/sash/settings/file"),
77
+ auth: "control",
78
+ handler: readSettingsFile,
79
+ },
80
+ {
81
+ methods: ["PUT"],
82
+ pattern: path("/sash/settings/file"),
83
+ auth: "control",
84
+ handler: writeSettingsFile,
85
+ },
86
+ {
87
+ methods: ["GET", "POST"],
88
+ pattern: path("/sash/profiles"),
89
+ auth: "public",
90
+ handler: (ctx, req) => (req.method === "POST" ? addProfile(ctx, req) : listProfiles(ctx)),
91
+ },
92
+ {
93
+ methods: ["POST"],
94
+ pattern: path("/sash/profiles/import"),
95
+ auth: "control",
96
+ handler: importProfile,
97
+ },
98
+ {
99
+ methods: ["POST"],
100
+ pattern: path("/sash/profiles/update-all"),
101
+ auth: "control",
102
+ handler: updateAllProfiles,
103
+ },
104
+ {
105
+ methods: ["PUT"],
106
+ pattern: path("/sash/profiles/active"),
107
+ auth: "control",
108
+ handler: activateProfile,
109
+ },
110
+ {
111
+ methods: ["POST"],
112
+ pattern: path("/sash/profiles/:id([0-9]+)/update"),
113
+ auth: "control",
114
+ handler: updateProfile,
115
+ },
116
+ {
117
+ methods: ["GET", "PUT"],
118
+ pattern: path("/sash/profiles/:id([0-9]+)/content"),
119
+ auth: "control",
120
+ handler: (ctx, req) => req.method === "PUT" ? writeProfileContent(ctx, req) : readProfileContent(ctx, req),
121
+ },
122
+ {
123
+ methods: ["PATCH", "DELETE"],
124
+ pattern: path("/sash/profiles/:id([0-9]+)"),
125
+ auth: "control",
126
+ handler: (ctx, req) => req.method === "DELETE" ? removeProfile(ctx, req) : renameProfile(ctx, req),
127
+ },
128
+ {
129
+ methods: "*",
130
+ pattern: path(CORE_API_PREFIX),
131
+ auth: "gateway",
132
+ raw: forwardToCore,
133
+ },
134
+ {
135
+ methods: "*",
136
+ pattern: path(`${CORE_API_PREFIX}/*`),
137
+ auth: "gateway",
138
+ raw: forwardToCore,
139
+ },
140
+ {
141
+ methods: ["GET", "HEAD"],
142
+ pattern: path("/"),
143
+ auth: "public",
144
+ handler: (_ctx, req) => ({ status: 302, location: `/ui/${req.search}` }),
145
+ },
146
+ {
147
+ methods: ["GET", "HEAD"],
148
+ pattern: path("/ui"),
149
+ auth: "public",
150
+ raw: serveUiIndexOrRedirect,
151
+ },
152
+ {
153
+ methods: ["GET", "HEAD"],
154
+ pattern: path("/ui/*"),
155
+ auth: "public",
156
+ raw: serveUiAsset,
157
+ },
158
+ ];
159
+ }
160
+ function forwardToCore(ctx, req, res, target) {
161
+ const runtime = ctx.settings.runtime();
162
+ forwardHttpToCore(req, res, coreApiTarget(target), runtime.controller, runtime.secret);
163
+ }
164
+ function serveUiIndexOrRedirect(ctx, req, res, target) {
165
+ if (target.pathname === "/ui") {
166
+ res.writeHead(302, { Location: `/ui/${target.search}` });
167
+ res.end();
168
+ return;
169
+ }
170
+ serveUiAsset(ctx, req, res, target);
171
+ }
172
+ function serveUiAsset(ctx, req, res, target) {
173
+ if (!serveStaticUi(req, res, target.pathname, ctx.layout)) {
174
+ sendError(res, 404, "not_found", `Not found: ${req.method} ${target.routePathname}`);
175
+ }
176
+ }
177
+ const METHOD_ORDER = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
178
+ function methodOrder(method) {
179
+ const index = METHOD_ORDER.indexOf(method);
180
+ return index < 0 ? METHOD_ORDER.length : index;
181
+ }
182
+ export function requiredParam(req, name) {
183
+ const value = req.params[name];
184
+ if (value === undefined)
185
+ throw new Error(`Missing route parameter: ${name}`);
186
+ return value;
187
+ }
188
+ function writeResponse(res, response) {
189
+ if (response.after) {
190
+ const after = response.after;
191
+ let ran = false;
192
+ const run = () => {
193
+ if (ran)
194
+ return;
195
+ ran = true;
196
+ after();
197
+ };
198
+ res.once("finish", run);
199
+ res.once("close", run);
200
+ }
201
+ if (response.json !== undefined) {
202
+ sendJson(res, response.status, response.json);
203
+ return;
204
+ }
205
+ if (response.location !== undefined) {
206
+ res.writeHead(response.status, { Location: response.location });
207
+ res.end();
208
+ return;
209
+ }
210
+ res.writeHead(response.status);
211
+ res.end();
212
+ }
213
+ export function matchRoute(routes, method, pathname) {
214
+ const matches = [];
215
+ for (const route of routes) {
216
+ const result = route.pattern.exec({ pathname });
217
+ if (result)
218
+ matches.push({ route, params: result.pathname.groups });
219
+ }
220
+ const matched = matches.find((candidate) => candidate.route.methods === "*" || candidate.route.methods.includes(method));
221
+ if (matched)
222
+ return { kind: "matched", ...matched };
223
+ if (matches.length === 0)
224
+ return { kind: "notFound" };
225
+ const allow = [
226
+ ...new Set(matches.flatMap((candidate) => candidate.route.methods === "*" ? METHOD_ORDER : candidate.route.methods)),
227
+ ].sort((left, right) => methodOrder(left) - methodOrder(right));
228
+ return { kind: "methodNotAllowed", allow };
229
+ }
230
+ export async function dispatch(ctx, routes, req, res) {
231
+ if (!isLoopbackHostHeader(req.headers.host)) {
232
+ sendError(res, 421, "http", "Invalid Host header");
233
+ return;
234
+ }
235
+ let target;
236
+ try {
237
+ target = parseDaemonRequestTarget(req.url ?? "/", req.headers.host ?? "");
238
+ }
239
+ catch {
240
+ sendError(res, 400, "http", "Invalid request target");
241
+ return;
242
+ }
243
+ const method = req.method?.toUpperCase() ?? "GET";
244
+ const pathname = target.routePathname;
245
+ const match = matchRoute(routes, method, pathname);
246
+ const route = match.kind === "matched" ? match.route : undefined;
247
+ if (!isLoopbackOriginHeader(req.headers.origin)) {
248
+ sendError(res, 403, "unauthorized", "Invalid Origin header");
249
+ return;
250
+ }
251
+ const requiresAuth = route ? route.auth !== "public" : isControlMutation(method);
252
+ if (requiresAuth &&
253
+ !isControlRequestAuthorized(req, {
254
+ daemonSecret: ctx.settings.committed().daemonSecret,
255
+ bootToken: ctx.token,
256
+ })) {
257
+ sendError(res, 401, "unauthorized", "Unauthorized control request");
258
+ return;
259
+ }
260
+ if (ctx.gate.isClosing && isControlMutation(method)) {
261
+ sendError(res, 503, "shutting_down", "sashd is shutting down");
262
+ return;
263
+ }
264
+ if (!route || match.kind !== "matched") {
265
+ if (match.kind === "methodNotAllowed") {
266
+ res.setHeader("Allow", match.allow.join(", "));
267
+ sendError(res, 405, "http", "Method Not Allowed");
268
+ return;
269
+ }
270
+ sendError(res, 404, "not_found", `Not found: ${method} ${pathname}`);
271
+ return;
272
+ }
273
+ try {
274
+ if (route.raw) {
275
+ await route.raw(ctx, req, res, target);
276
+ return;
277
+ }
278
+ if (!route.handler)
279
+ throw new Error(`Route has no handler: ${method} ${pathname}`);
280
+ const request = {
281
+ method,
282
+ pathname,
283
+ params: match.params,
284
+ search: target.search,
285
+ searchParams: target.searchParams,
286
+ raw: req,
287
+ readJson: (maxBytes) => parseJsonObjectBody(req, maxBytes),
288
+ };
289
+ writeResponse(res, await route.handler(ctx, request));
290
+ }
291
+ catch (err) {
292
+ const mapping = errorToHttp(err);
293
+ if (mapping.status >= 500) {
294
+ console.error(`[sashd] unhandled error in ${method} ${pathname}:`, err);
295
+ }
296
+ if (res.writableEnded || res.destroyed)
297
+ return;
298
+ if (res.headersSent) {
299
+ res.destroy();
300
+ return;
301
+ }
302
+ sendError(res, mapping.status, mapping.code, mapping.message);
303
+ }
304
+ }
305
+ export function matchWebSocketUpgrade(routes, method, target) {
306
+ const gateways = routes.filter((route) => route.auth === "gateway" && route.pattern.test({ pathname: target.routePathname }));
307
+ if (gateways.length === 0)
308
+ return { kind: "notFound" };
309
+ if (method.toUpperCase() !== "GET")
310
+ return { kind: "methodNotAllowed", allow: ["GET"] };
311
+ return { kind: "gateway", target: coreApiTarget(target) };
312
+ }
@@ -0,0 +1,33 @@
1
+ export function startProfileUpdateScheduler(profiles, scheduler = {}) {
2
+ const intervalMs = scheduler.intervalMs ?? 15 * 60 * 1000;
3
+ const kickoffMs = scheduler.kickoffMs ?? 10_000;
4
+ const scheduleInterval = scheduler.setInterval ?? setInterval;
5
+ const clearScheduledInterval = scheduler.clearInterval ?? clearInterval;
6
+ const scheduleTimeout = scheduler.setTimeout ?? setTimeout;
7
+ const clearScheduledTimeout = scheduler.clearTimeout ?? clearTimeout;
8
+ const autoUpdateProfiles = async () => {
9
+ try {
10
+ await profiles.updateDue();
11
+ }
12
+ catch {
13
+ }
14
+ };
15
+ const intervalTimer = scheduleInterval(() => {
16
+ void autoUpdateProfiles();
17
+ }, intervalMs);
18
+ intervalTimer.unref();
19
+ const kickoffTimer = scheduleTimeout(() => {
20
+ void autoUpdateProfiles();
21
+ }, kickoffMs);
22
+ kickoffTimer.unref();
23
+ let stopped = false;
24
+ return {
25
+ stop: () => {
26
+ if (stopped)
27
+ return;
28
+ clearScheduledInterval(intervalTimer);
29
+ clearScheduledTimeout(kickoffTimer);
30
+ stopped = true;
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,121 @@
1
+ import http from "node:http";
2
+ import { isLoopbackHostHeader, isLoopbackOriginHeader, isWebSocketRequestAuthorized, } from "../daemon-auth.js";
3
+ import { forwardWsToCore } from "../daemon-proxy.js";
4
+ import { buildDaemonContext } from "./app.js";
5
+ import { buildRoutes, dispatch, matchWebSocketUpgrade, parseDaemonRequestTarget, } from "./router.js";
6
+ import { startProfileUpdateScheduler } from "./scheduler.js";
7
+ function rejectUpgrade(socket, status, message, allow) {
8
+ const body = `${message}\n`;
9
+ const allowHeader = allow ? `Allow: ${allow.join(", ")}\r\n` : "";
10
+ socket.end(`HTTP/1.1 ${status} ${message}\r\nConnection: close\r\n${allowHeader}Content-Type: text/plain; charset=utf-8\r\nContent-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
11
+ }
12
+ export function createDaemonServer(deps) {
13
+ const app = buildDaemonContext(deps);
14
+ const { context } = app;
15
+ const routes = buildRoutes();
16
+ const server = http.createServer((req, res) => {
17
+ void dispatch(context, routes, req, res).catch((err) => {
18
+ console.error("[sashd] unhandled HTTP request error:", err);
19
+ if (res.writableEnded || res.destroyed)
20
+ return;
21
+ if (res.headersSent) {
22
+ res.destroy();
23
+ return;
24
+ }
25
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
26
+ res.end(JSON.stringify({ error: { code: "internal", message: "Internal server error" } }));
27
+ });
28
+ });
29
+ const upgradedSockets = new Set();
30
+ const handleUpgrade = (req, socket, head) => {
31
+ if (!isLoopbackHostHeader(req.headers.host)) {
32
+ rejectUpgrade(socket, 421, "Invalid Host header");
33
+ return;
34
+ }
35
+ if (!isLoopbackOriginHeader(req.headers.origin)) {
36
+ rejectUpgrade(socket, 403, "Invalid Origin header");
37
+ return;
38
+ }
39
+ if (!isWebSocketRequestAuthorized(req, {
40
+ daemonSecret: context.settings.committed().daemonSecret,
41
+ bootToken: context.token,
42
+ })) {
43
+ rejectUpgrade(socket, 401, "Unauthorized WebSocket request");
44
+ return;
45
+ }
46
+ let target;
47
+ try {
48
+ target = parseDaemonRequestTarget(req.url ?? "/", req.headers.host ?? "");
49
+ }
50
+ catch {
51
+ rejectUpgrade(socket, 400, "Invalid request target");
52
+ return;
53
+ }
54
+ const route = matchWebSocketUpgrade(routes, req.method?.toUpperCase() ?? "GET", target);
55
+ if (route.kind === "methodNotAllowed") {
56
+ rejectUpgrade(socket, 405, "Method Not Allowed", route.allow);
57
+ return;
58
+ }
59
+ if (route.kind === "notFound") {
60
+ rejectUpgrade(socket, 404, "WebSocket endpoint not found");
61
+ return;
62
+ }
63
+ if (context.gate.isClosing) {
64
+ rejectUpgrade(socket, 503, "sashd is shutting down");
65
+ return;
66
+ }
67
+ const runtime = context.settings.runtime();
68
+ forwardWsToCore(req, socket, head, route.target, runtime.controller, runtime.secret);
69
+ };
70
+ server.on("upgrade", (req, socket, head) => {
71
+ upgradedSockets.add(socket);
72
+ socket.once("close", () => upgradedSockets.delete(socket));
73
+ try {
74
+ handleUpgrade(req, socket, head);
75
+ }
76
+ catch (err) {
77
+ console.error("[sashd] unhandled WebSocket upgrade error:", err);
78
+ if (!socket.destroyed)
79
+ rejectUpgrade(socket, 500, "WebSocket proxy failed");
80
+ }
81
+ });
82
+ const scheduler = startProfileUpdateScheduler(context.profiles, deps.scheduler ?? {});
83
+ let listenerClosePromise;
84
+ const closeListener = () => {
85
+ if (listenerClosePromise)
86
+ return listenerClosePromise;
87
+ const attempt = (async () => {
88
+ if (server.listening) {
89
+ const closed = new Promise((resolve, reject) => {
90
+ server.close((err) => (err ? reject(err) : resolve()));
91
+ });
92
+ server.closeAllConnections();
93
+ for (const socket of upgradedSockets)
94
+ socket.destroy();
95
+ await closed;
96
+ }
97
+ scheduler.stop();
98
+ })();
99
+ listenerClosePromise = attempt;
100
+ void attempt.catch(() => {
101
+ if (listenerClosePromise === attempt) {
102
+ listenerClosePromise = undefined;
103
+ context.gate.reopen();
104
+ }
105
+ });
106
+ return attempt;
107
+ };
108
+ context.closeListener = closeListener;
109
+ const closeDaemon = async () => {
110
+ await context.shutdown();
111
+ await closeListener();
112
+ };
113
+ return {
114
+ server,
115
+ supervisor: app.supervisor,
116
+ lifecycle: app.lifecycle,
117
+ token: app.token,
118
+ port: context.settings.committed().daemonPort,
119
+ close: closeDaemon,
120
+ };
121
+ }
@@ -0,0 +1,71 @@
1
+ import crypto from "node:crypto";
2
+ import { WEB_SOCKET_AUTH_PROTOCOL, WEB_SOCKET_TOKEN_PROTOCOL_PREFIX } from "./contracts.js";
3
+ function firstHeader(value) {
4
+ return Array.isArray(value) ? (value[0] ?? "") : (value ?? "");
5
+ }
6
+ function secretsEqual(actual, expected) {
7
+ const a = Buffer.from(actual);
8
+ const b = Buffer.from(expected);
9
+ return a.length === b.length && crypto.timingSafeEqual(a, b);
10
+ }
11
+ function isLoopbackHostname(hostname) {
12
+ const normalized = hostname.toLowerCase();
13
+ return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "[::1]";
14
+ }
15
+ export function isLoopbackHostHeader(value) {
16
+ if (!value)
17
+ return false;
18
+ try {
19
+ return isLoopbackHostname(new URL(`http://${value}`).hostname);
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export function isLoopbackOriginHeader(value) {
26
+ if (!value)
27
+ return true;
28
+ try {
29
+ return isLoopbackHostname(new URL(value).hostname);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export function isControlMutation(method) {
36
+ return method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
37
+ }
38
+ export function isControlRequestAuthorized(req, opts) {
39
+ const authorization = firstHeader(req.headers.authorization);
40
+ const bearer = authorization.match(/^Bearer\s+(.+)$/i)?.[1]?.trim() ?? "";
41
+ if (bearer && opts.daemonSecret && secretsEqual(bearer, opts.daemonSecret))
42
+ return true;
43
+ const webToken = firstHeader(req.headers["x-sash-token"]).trim();
44
+ return Boolean(webToken && secretsEqual(webToken, opts.bootToken));
45
+ }
46
+ function webSocketProtocols(value) {
47
+ return (Array.isArray(value) ? value.join(",") : (value ?? ""))
48
+ .split(",")
49
+ .map((protocol) => protocol.trim())
50
+ .filter(Boolean);
51
+ }
52
+ export function isWebSocketRequestAuthorized(req, opts) {
53
+ if (isControlRequestAuthorized(req, opts))
54
+ return true;
55
+ return webSocketProtocols(req.headers["sec-websocket-protocol"]).some((protocol) => {
56
+ if (!protocol.startsWith(WEB_SOCKET_TOKEN_PROTOCOL_PREFIX))
57
+ return false;
58
+ const token = protocol.slice(WEB_SOCKET_TOKEN_PROTOCOL_PREFIX.length);
59
+ return Boolean(token && secretsEqual(token, opts.bootToken));
60
+ });
61
+ }
62
+ export function webSocketAuthResponseProtocol(value) {
63
+ const protocols = webSocketProtocols(value);
64
+ return (protocols.find((protocol) => protocol === WEB_SOCKET_AUTH_PROTOCOL) ??
65
+ protocols.find((protocol) => protocol.startsWith(WEB_SOCKET_TOKEN_PROTOCOL_PREFIX)));
66
+ }
67
+ export function coreWebSocketProtocols(value) {
68
+ const protocols = webSocketProtocols(value).filter((protocol) => protocol !== WEB_SOCKET_AUTH_PROTOCOL &&
69
+ !protocol.startsWith(WEB_SOCKET_TOKEN_PROTOCOL_PREFIX));
70
+ return protocols.length > 0 ? protocols.join(", ") : undefined;
71
+ }
@@ -0,0 +1,55 @@
1
+ import { ERROR_BODY_LIMIT, fetchWithRetry } from "./http.js";
2
+ import { SashClient } from "./sash-client.js";
3
+ const DAEMON_SUCCESS_BODY_LIMIT = 1024 * 1024;
4
+ const daemonFetch = async (url, init) => {
5
+ const res = await fetchWithRetry(url, {
6
+ method: init.method,
7
+ headers: init.headers,
8
+ ...(init.body !== undefined ? { body: init.body } : {}),
9
+ direct: true,
10
+ deadlineMs: init.timeoutMs,
11
+ ...(init.attempts !== undefined ? { attempts: init.attempts } : {}),
12
+ });
13
+ return {
14
+ status: res.statusCode,
15
+ text: () => res.text(res.statusCode >= 200 && res.statusCode < 300
16
+ ? DAEMON_SUCCESS_BODY_LIMIT
17
+ : ERROR_BODY_LIMIT),
18
+ };
19
+ };
20
+ export class SashDaemonClient {
21
+ baseUrl;
22
+ client;
23
+ constructor(port, secret) {
24
+ this.baseUrl = `http://127.0.0.1:${port}`;
25
+ const trimmed = (secret || "").trim();
26
+ this.client = new SashClient({
27
+ baseUrl: this.baseUrl,
28
+ token: () => trimmed,
29
+ fetchFn: daemonFetch,
30
+ });
31
+ }
32
+ health() {
33
+ return this.client.health();
34
+ }
35
+ async isReachable() {
36
+ try {
37
+ return Boolean((await this.health()).token);
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ status(fresh = false) {
44
+ return this.client.status(fresh);
45
+ }
46
+ startCore() {
47
+ return this.client.startCore();
48
+ }
49
+ maintenanceShutdown() {
50
+ return this.client.shutdown();
51
+ }
52
+ async shutdown() {
53
+ await this.client.shutdown();
54
+ }
55
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import "./node-version-guard.js";
3
+ import { runDaemon } from "./daemon.js";
4
+ async function main() {
5
+ try {
6
+ await runDaemon();
7
+ }
8
+ catch (err) {
9
+ console.error(`[sashd] fatal: ${err.message}`);
10
+ process.exit(1);
11
+ }
12
+ }
13
+ await main();