@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.
- package/CHANGELOG.md +184 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/THIRD_PARTY_NOTICES.md +54 -0
- package/dist/api.js +103 -0
- package/dist/browser.js +25 -0
- package/dist/cli-errors.js +15 -0
- package/dist/cli.js +122 -0
- package/dist/commands/lifecycle.js +50 -0
- package/dist/commands/logs.js +54 -0
- package/dist/commands/shared.js +8 -0
- package/dist/commands/status.js +39 -0
- package/dist/commands/update.js +5 -0
- package/dist/commands/upgrade.js +43 -0
- package/dist/commands/web.js +33 -0
- package/dist/contracts.js +412 -0
- package/dist/core-config-validation.js +72 -0
- package/dist/core-install-record.js +55 -0
- package/dist/core-install-transaction.js +114 -0
- package/dist/core-update-coordination.js +105 -0
- package/dist/core-update-service.js +245 -0
- package/dist/core-update.js +804 -0
- package/dist/core-version.js +18 -0
- package/dist/core.js +206 -0
- package/dist/daemon/app.js +157 -0
- package/dist/daemon/context.js +40 -0
- package/dist/daemon/entry.js +67 -0
- package/dist/daemon/errors.js +47 -0
- package/dist/daemon/handlers/core.js +17 -0
- package/dist/daemon/handlers/daemon.js +66 -0
- package/dist/daemon/handlers/profiles.js +57 -0
- package/dist/daemon/handlers/proxy.js +12 -0
- package/dist/daemon/handlers/settings.js +48 -0
- package/dist/daemon/router.js +312 -0
- package/dist/daemon/scheduler.js +33 -0
- package/dist/daemon/server.js +121 -0
- package/dist/daemon-auth.js +71 -0
- package/dist/daemon-client.js +55 -0
- package/dist/daemon-entry.js +13 -0
- package/dist/daemon-http.js +93 -0
- package/dist/daemon-lifecycle.js +378 -0
- package/dist/daemon-proxy.js +176 -0
- package/dist/daemon-static.js +106 -0
- package/dist/daemon.js +3 -0
- package/dist/error-utils.js +14 -0
- package/dist/fs-atomic.js +123 -0
- package/dist/github.js +187 -0
- package/dist/http.js +282 -0
- package/dist/json-shape.js +17 -0
- package/dist/log-follow.js +280 -0
- package/dist/log.js +17 -0
- package/dist/managed-state-transaction.js +377 -0
- package/dist/mihomo-config.js +334 -0
- package/dist/node-version-guard.js +8 -0
- package/dist/offline-mutation.js +58 -0
- package/dist/paths.js +57 -0
- package/dist/process.js +572 -0
- package/dist/profile-migration.js +101 -0
- package/dist/profile-service.js +667 -0
- package/dist/profiles.js +182 -0
- package/dist/runtime-lifecycle.js +205 -0
- package/dist/runtime-owner.js +87 -0
- package/dist/runtime-recovery.js +31 -0
- package/dist/sash-client.js +163 -0
- package/dist/settings-service.js +242 -0
- package/dist/settings.js +314 -0
- package/dist/state-lock.js +331 -0
- package/dist/status.js +279 -0
- package/dist/supervisor.js +301 -0
- package/dist/sysproxy/common.js +99 -0
- package/dist/sysproxy/darwin.js +287 -0
- package/dist/sysproxy/factory.js +71 -0
- package/dist/sysproxy/gnome.js +181 -0
- package/dist/sysproxy/legacy.js +58 -0
- package/dist/sysproxy/snapshot.js +284 -0
- package/dist/sysproxy/types.js +24 -0
- package/dist/sysproxy/windows.js +216 -0
- package/dist/sysproxy.js +8 -0
- package/dist/system-proxy-manager.js +460 -0
- package/dist/tun-guidance.js +11 -0
- package/dist/ui/assets/05129d5ad91a6181d272cc3d031363d6-DJkDXMrK.woff2 +0 -0
- package/dist/ui/assets/0526ebf94418638dfd5b6cd9b06c07d4-BzWe4Lji.woff2 +0 -0
- package/dist/ui/assets/053f2647a78f659f6f8a3ae8436bd265-BocXYDl5.woff2 +0 -0
- package/dist/ui/assets/078f666863424d3fd2d98e3ea528704c-DQ_QCvOt.woff2 +0 -0
- package/dist/ui/assets/07f3ff379663ffa0222c12f241421dd9-Cma7oDlr.woff2 +0 -0
- package/dist/ui/assets/08427b8d865853bbd7c439321b26495f-CJmC8fWx.woff2 +0 -0
- package/dist/ui/assets/095224e4db29d40c54098da02b266b0a-DjsP0nQn.woff2 +0 -0
- package/dist/ui/assets/0ada18ca45940b08accde5c61ee871e3-E1BZD3RX.woff2 +0 -0
- package/dist/ui/assets/0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 +0 -0
- package/dist/ui/assets/0bf18c938d0c27cce06c39f855405fdb-CruLZZZJ.woff2 +0 -0
- package/dist/ui/assets/0cfb3e9f4edf60839c37dc83e6f75ea3-DzAtRHvi.woff2 +0 -0
- package/dist/ui/assets/0fbbd340d0467dfe3101d58846f30cb0-gtFcO250.woff2 +0 -0
- package/dist/ui/assets/1063fa2dc7334d1f383e30824e016eae-B7U-kD-0.woff2 +0 -0
- package/dist/ui/assets/109e26e62ddc85d946e699464afbece7-BLnUjAEV.woff2 +0 -0
- package/dist/ui/assets/12a443b0ae9748a993b5395f8fad412b-Btjff2Lz.woff2 +0 -0
- package/dist/ui/assets/135c4621de7a4d85d852e03d416f13ee-DdSuW36e.woff2 +0 -0
- package/dist/ui/assets/13aa415469ee761c968749b1c0c56ec6-Dwe-Wa_w.woff2 +0 -0
- package/dist/ui/assets/1435b1ad447bd9c0e8bc52035c9dcfa8-C05ldXtw.woff2 +0 -0
- package/dist/ui/assets/175e0915aa874e7446b79b6bc9b00721-Dp0-l77L.woff2 +0 -0
- package/dist/ui/assets/1876e7d02187f006e2b7f769391c3089-CNjpRdap.woff2 +0 -0
- package/dist/ui/assets/1886dc284b3b845e3b5fe04aa2107276-Bh_wHwL1.woff2 +0 -0
- package/dist/ui/assets/19a3dda6611aaf5d787403bd1159fe15-zoEBvdpA.woff2 +0 -0
- package/dist/ui/assets/1afa74e14da193125e45d4783d358a4f-BgjSTENX.woff2 +0 -0
- package/dist/ui/assets/1cd26a6a6d784720b4a43053e9058bcf-TAP3dvNi.woff2 +0 -0
- package/dist/ui/assets/1ce19bddc3beab385a863b1e05685678-BCdXitZw.woff2 +0 -0
- package/dist/ui/assets/1d3459ac6578c80502bdb876a4824a93-CSwdnEGP.woff2 +0 -0
- package/dist/ui/assets/1d479e364dab4af01c539f0a6a7ac036-ZFI-djGt.woff2 +0 -0
- package/dist/ui/assets/1dcea2408d17fcec4197adce50f8b32d-CnK2xXR-.woff2 +0 -0
- package/dist/ui/assets/1e855385699999e24a33f7df8aed0489-DGFHkDVQ.woff2 +0 -0
- package/dist/ui/assets/1f55944b8f7972580e3029694462a6b0-CB7ogVJ9.woff2 +0 -0
- package/dist/ui/assets/1f96eda70929089762dc3c4feb33f9c8-DtSQ6YOf.woff2 +0 -0
- package/dist/ui/assets/1fb275532caadc746455881b31da27c4-BHFTsg74.woff2 +0 -0
- package/dist/ui/assets/20e679bdc00d06f802ea71fd9154b284-D9gRjq_v.woff2 +0 -0
- package/dist/ui/assets/25aff66c4a9f8cb801cbd525f4a923e4-Dgi87hP7.woff2 +0 -0
- package/dist/ui/assets/28e6700380b023b23e77f2d75aee624f-b19D855Q.woff2 +0 -0
- package/dist/ui/assets/2a1c50aa9cd99ce3a0197f5d49948e63-DrztdXCB.woff2 +0 -0
- package/dist/ui/assets/2b4e0ae0bf3af610b97717dfd66a012d-CBoapvha.woff2 +0 -0
- package/dist/ui/assets/2be2b7a672b616bc2f07059a67bfaad8-BUR0aNML.woff2 +0 -0
- package/dist/ui/assets/2e0a358ada78ea98ec8ba86002a27eac-DvgbSr9v.woff2 +0 -0
- package/dist/ui/assets/2e1cf63a2a22a50516c49b1dc521ea8a-CmltIxig.woff2 +0 -0
- package/dist/ui/assets/2e4481801925fcb6573c4337c73f15d7-DYOoCk-m.woff2 +0 -0
- package/dist/ui/assets/2fe5e634c568cd874f94d24b8f02bfd1-DcEHY35G.woff2 +0 -0
- package/dist/ui/assets/309c33f63f29887fb557115aa24d4bb5-CUgqrXIj.woff2 +0 -0
- package/dist/ui/assets/3158f45c959e1b346b0bbd6f3193dbf0-kimcClqp.woff2 +0 -0
- package/dist/ui/assets/316adbc8037a36f8d804637b053bbe5e-CjbZmJf9.woff2 +0 -0
- package/dist/ui/assets/319e9631bc29fc7235b70fc9aac43cd9-IV6yZuHZ.woff2 +0 -0
- package/dist/ui/assets/32054f9bb88d9431b1cdf023f770e614-CYrpCgqK.woff2 +0 -0
- package/dist/ui/assets/3245fa1aac6338c1d9d04befb7c346f8-CzvPhVPA.woff2 +0 -0
- package/dist/ui/assets/326c730f98afa476057957f2743c81e2-GPHJ3Fpu.woff2 +0 -0
- package/dist/ui/assets/33885558dc783bf9bae6c5d4668fef07-D33us0yR.woff2 +0 -0
- package/dist/ui/assets/33cd11f5e61556e4618801c8231f2744-CoR_Tv3X.woff2 +0 -0
- package/dist/ui/assets/34a170749cb7e6c5851acc510882af6b-BCZwsihk.woff2 +0 -0
- package/dist/ui/assets/3538de2d387591258d7e9908aad803b9-CewOkERZ.woff2 +0 -0
- package/dist/ui/assets/38d7b029c036080067a2e94211098171-CettR5Qv.woff2 +0 -0
- package/dist/ui/assets/393f47efeefc9f9e9cca83eb10a82c69-Brhz4z1y.woff2 +0 -0
- package/dist/ui/assets/396a51f2b274d01f6ba606a94f3e5fe2-DgRk-jtI.woff2 +0 -0
- package/dist/ui/assets/3991bf955d4c925e84b4a6542760d4d5-II4HtRkQ.woff2 +0 -0
- package/dist/ui/assets/39b6cb6ae8d1ea0e9832b88d2c21ef99-CbJm-q6b.woff2 +0 -0
- package/dist/ui/assets/3a1133157fbd259f1a51aaa9f8418a26-3FmUjjvi.woff2 +0 -0
- package/dist/ui/assets/3a34cff07dca7a65ed2486be19229290-B7ml2yBN.woff2 +0 -0
- package/dist/ui/assets/3c339dd7c27e6b8a06eea431733e076f-dGQqXaEj.woff2 +0 -0
- package/dist/ui/assets/3d074ea67f60953b816ce7cf5d87a980-B5VkPOmg.woff2 +0 -0
- package/dist/ui/assets/3d38f54dcf4a9f4a50165d5849becf71-CVDtFSZn.woff2 +0 -0
- package/dist/ui/assets/3e3fc9a19a27f94e4ff1b815b55b2671-Ci736GLi.woff2 +0 -0
- package/dist/ui/assets/3e8abb1f606f29e2f0a8a8a6b702d263-C5sOSf5L.woff2 +0 -0
- package/dist/ui/assets/3f0ab4e1cd99bdbfdf9acbc8f4bcb9ce-0DcdTLxV.woff2 +0 -0
- package/dist/ui/assets/3f30c0482650345c981d66920f56ab61-CTD2CsZI.woff2 +0 -0
- package/dist/ui/assets/41a9aa12ad9a60758b304c1a998fb781-BTQUDLf1.woff2 +0 -0
- package/dist/ui/assets/424010b2bc5cee540b92b28da49b61bf-DT8upW2M.woff2 +0 -0
- package/dist/ui/assets/42fbdafff2d44b76c6665d51bb2cee9d-_UrojhXD.woff2 +0 -0
- package/dist/ui/assets/4341db13c311b3cb0eaaf6b4c71b89e5-CzjA7MzG.woff2 +0 -0
- package/dist/ui/assets/434db5a796128131fde78d4389d14b71-AeNdWKua.woff2 +0 -0
- package/dist/ui/assets/43cb51fc70a15274d49e2d956c5ec3b4-BQbufd96.woff2 +0 -0
- package/dist/ui/assets/43e79de1c7815f21efbc966ff96fcbd6-DlCQvOsQ.woff2 +0 -0
- package/dist/ui/assets/43faf6a8a3cde1088bd5f7c830890a89-SvDzF3nA.woff2 +0 -0
- package/dist/ui/assets/44731ae21d7690ce538d0f8242755833-CEmu9Z88.woff2 +0 -0
- package/dist/ui/assets/45324b2a64cc0c0db797d5c09e239d34-BuFudtvM.woff2 +0 -0
- package/dist/ui/assets/4584df05d00aa75ae6e1f850fdc9df1d-GNHt6xwM.woff2 +0 -0
- package/dist/ui/assets/48d539539e98481fb511a761031c59fd-Cd9gdi8B.woff2 +0 -0
- package/dist/ui/assets/49a1fb9fc6a35796b2e0d3539088ede8-CD1cRDOy.woff2 +0 -0
- package/dist/ui/assets/4bec02eb0c2371e59bfb7d41a141ea1e-CmBnNPo0.woff2 +0 -0
- package/dist/ui/assets/4c5a81ef5683f6fbf9935e640de804e7-B4wQxzVs.woff2 +0 -0
- package/dist/ui/assets/4f11c9c75e86214b7f69ddee3a9f8076-DHsQ2xpE.woff2 +0 -0
- package/dist/ui/assets/50434b8ff88e27101d1c67ba953ee873-PH_hIqxu.woff2 +0 -0
- package/dist/ui/assets/5107fd5153d52c6a3f8d43ff214dd6c2-ls3eo5Qd.woff2 +0 -0
- package/dist/ui/assets/520cb7d3683dee99e767650efc0aa5b1-Lkd4z-9S.woff2 +0 -0
- package/dist/ui/assets/5455087df2f091b7bf3d38c5a102a2e7-QNkiOxwe.woff2 +0 -0
- package/dist/ui/assets/556450fdf0978d329cf8277e3b71dcdb-CtypGAPw.woff2 +0 -0
- package/dist/ui/assets/5a1b5c1b71cad50c51a79a7f4825e6e6-DQA407Ec.woff2 +0 -0
- package/dist/ui/assets/5ba02e809d2ef4f7639178e7a7d654c0-Dty0omtl.woff2 +0 -0
- package/dist/ui/assets/5bd654afe48e094f95e5142e4eaadc75-D9pUq71R.woff2 +0 -0
- package/dist/ui/assets/5c4c3ca0a3f4115879a61a96d02e3efc-RcVLBj31.woff2 +0 -0
- package/dist/ui/assets/5c737d740638e8d764f12c1e5c1cd144-D2Npjq-N.woff2 +0 -0
- package/dist/ui/assets/5e78019102102aa5741aa591313fd5b5-V7Azrw-2.woff2 +0 -0
- package/dist/ui/assets/6287cacac64e1849941f7b7d7e6316c5-DzkbPOID.woff2 +0 -0
- package/dist/ui/assets/643df7194160d54edc48d16d76e5ba74-BKmmIRjw.woff2 +0 -0
- package/dist/ui/assets/65d3b214c69ff6786fd1cab06f8394b7-B0cRUJAr.woff2 +0 -0
- package/dist/ui/assets/6698103c11bfe260d44fcf00c6d8b623-DVWYOeyb.woff2 +0 -0
- package/dist/ui/assets/692e07b3a609a8f1a21c15c45631f348-DJW5LcZH.woff2 +0 -0
- package/dist/ui/assets/694e87456af181279c2bfd551aa3c6e3-Cu7kRuJB.woff2 +0 -0
- package/dist/ui/assets/6b88066bdc7a6e8bc823128dbd0f6f3b-BG38UZSq.woff2 +0 -0
- package/dist/ui/assets/6d6f36f15381826d113832bfdd41b021-Bgh7YPiq.woff2 +0 -0
- package/dist/ui/assets/6e22361162bd0a5cd189ca3c98d23d52-DqnptHqF.woff2 +0 -0
- package/dist/ui/assets/6e884dd501b8da24ecd37425a9b3d51d-BVNqra8s.woff2 +0 -0
- package/dist/ui/assets/71200dbc7497fbafa28db5cfeae74acb-B6xMqoC6.woff2 +0 -0
- package/dist/ui/assets/72a85441a9757146440e3985f3b02075-CzTSlCPZ.woff2 +0 -0
- package/dist/ui/assets/7472d7e02045dea3ff288eac850e38eb-CboJEy4X.woff2 +0 -0
- package/dist/ui/assets/795ffa953bea666763ae79ac88aa7e5a-C6UsoDmi.woff2 +0 -0
- package/dist/ui/assets/7aa8647d6c52d249169b73417aa8e444-kJejhQ0-.woff2 +0 -0
- package/dist/ui/assets/7be6b794fc233ccd8b5769496301313a-Ba2iZC0a.woff2 +0 -0
- package/dist/ui/assets/7c0748f04d52086809c9f551c5aaded7-DSzO_ybF.woff2 +0 -0
- package/dist/ui/assets/7c858bb53a7652227fab8cf9753d3e1f-Bi9fbvIF.woff2 +0 -0
- package/dist/ui/assets/7e7026091b4e6061b05963bdb3a7f944-CrmRDJpc.woff2 +0 -0
- package/dist/ui/assets/7f80369a7479d568ab8bf492000282bd-CqcI5ZOp.woff2 +0 -0
- package/dist/ui/assets/834ce2f2571aaa26e47a618c4ad55e58-DZfWJrxo.woff2 +0 -0
- package/dist/ui/assets/834faab602d9c795341bc7dfd6578fe5-B1VeBVWh.woff2 +0 -0
- package/dist/ui/assets/8513a8eea35b460a047a16a87c36f8bb-H259w_6b.woff2 +0 -0
- package/dist/ui/assets/8699e8a36465bf46671dc28ce24e188c-YlkPpQ6h.woff2 +0 -0
- package/dist/ui/assets/86ae8881eae9eac7e9c0182c8ce6c627-Ch2r3LNs.woff2 +0 -0
- package/dist/ui/assets/86b3ae1be58d76f7147793f09264451b-CpgTfcn-.woff2 +0 -0
- package/dist/ui/assets/8769f8d7d45adcfcbd3f80460d22720d-DV_yqcTo.woff2 +0 -0
- package/dist/ui/assets/87bb0ceea9f5515490c832bdf6ebf641-CJpxd8e6.woff2 +0 -0
- package/dist/ui/assets/8928b9a50b5efbaaafa039674a3c7301-C-42WRD6.woff2 +0 -0
- package/dist/ui/assets/899f2c2a36cbeac473a7caeeaabc46b1-xrIcXJXU.woff2 +0 -0
- package/dist/ui/assets/8a075d4043e6bf66740808b492186438-Dq0346Qm.woff2 +0 -0
- package/dist/ui/assets/8b86676936792154c53205ab5d90b797-BGH_CGsg.woff2 +0 -0
- package/dist/ui/assets/8c63ca479653200a0e12f428242e1171-CsBTcPwY.woff2 +0 -0
- package/dist/ui/assets/8c9c7ff8342f5fed60e73c4a7ad67325-CGSYpt52.woff2 +0 -0
- package/dist/ui/assets/8ca655fcc5d9cf33d49815e231f3f356-b8PyEeZg.woff2 +0 -0
- package/dist/ui/assets/8e96f0f9385ef826f900587d0fc18b3d-D7GIG6Uq.woff2 +0 -0
- package/dist/ui/assets/8ea917252cf2c1e664f06ee5badffeea-CDZ1AFr0.woff2 +0 -0
- package/dist/ui/assets/8f42f890fc37e292017446af85af2745-Qx6oGI0z.woff2 +0 -0
- package/dist/ui/assets/8f9589e71c53bb753749a9c412f15c01-BVR5mPJl.woff2 +0 -0
- package/dist/ui/assets/90e70b52a3cc2491bbb2b4c0caf0ae7e-BdwPu67j.woff2 +0 -0
- package/dist/ui/assets/92ac4ca42fe050c1e680c585551abd9c-CXLyuj7l.woff2 +0 -0
- package/dist/ui/assets/92c12bb794c491930fd06f3f97299807-u_0ognJ0.woff2 +0 -0
- package/dist/ui/assets/92f0b4d2bb274a801aac10fc34c1e0cc-DFHB6dyu.woff2 +0 -0
- package/dist/ui/assets/93247aebd628367f5518eafdb2770ce9-o1N4zvTO.woff2 +0 -0
- package/dist/ui/assets/940446ea160995af66a9ee223cc3d4d1-C-_7aPYO.woff2 +0 -0
- package/dist/ui/assets/94be430de618453b6ae27bd366827ba2-1YYf1n5N.woff2 +0 -0
- package/dist/ui/assets/98cd7d62095e466e55885b34f63c86f1-z33YCk0X.woff2 +0 -0
- package/dist/ui/assets/99049fd5cec5894cafe2a1e5e13d1e03-DNrg8nIk.woff2 +0 -0
- package/dist/ui/assets/99e2752b7351aaa7681b4d91db80e89a-b3hi7miY.woff2 +0 -0
- package/dist/ui/assets/9d1b670cbfd64716ac685dea26237d95-DAZ9U6es.woff2 +0 -0
- package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +1 -0
- package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +14 -0
- package/dist/ui/assets/ConnectionsView-DHo6yvU6.css +1 -0
- package/dist/ui/assets/ConnectionsView-DNGmZBSU.js +1 -0
- package/dist/ui/assets/LogsView-BoeE92VF.css +1 -0
- package/dist/ui/assets/LogsView-fSiaxQ13.js +1 -0
- package/dist/ui/assets/PaginationFooter-B3kHzRfB.js +1 -0
- package/dist/ui/assets/PaginationFooter-BNkpcNoB.css +1 -0
- package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +1 -0
- package/dist/ui/assets/ProfilesView-Btc1DOxE.js +2 -0
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +1 -0
- package/dist/ui/assets/RulesView-CHyynVqA.css +1 -0
- package/dist/ui/assets/RulesView-D9vZBiJ1.js +1 -0
- package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +1 -0
- package/dist/ui/assets/SettingsView-BlDhZkXQ.js +2 -0
- package/dist/ui/assets/SettingsView-CngS3vBM.css +1 -0
- package/dist/ui/assets/a0f2d41645a113d821e7ff0891daad1e-XK0rDAci.woff2 +0 -0
- package/dist/ui/assets/a1d1345d8407dd6169f39c28ff6b1708-Bn7hMmv8.woff2 +0 -0
- package/dist/ui/assets/a29f51b4ebb6776e43e7492a036be28e-CrAKKz8u.woff2 +0 -0
- package/dist/ui/assets/a3252ac86b2c4e88bfb9baecabcad817-qnA_3LNE.woff2 +0 -0
- package/dist/ui/assets/a3d63a61e7eb2bb497dbc2da65bd0873-BRMJAm0m.woff2 +0 -0
- package/dist/ui/assets/a45eea933252dbe3529f30a0323c2661-D4kBOrXR.woff2 +0 -0
- package/dist/ui/assets/a4886c4ebed8c8b1f8a87b5b491a39aa-CBQdRqpF.woff2 +0 -0
- package/dist/ui/assets/a50b4ab745c7676d9d3ceb3c0a023a3d-CW990pgI.woff2 +0 -0
- package/dist/ui/assets/a5851ae4d7e58996d8bd681f80d4727c-BBytdb0t.woff2 +0 -0
- package/dist/ui/assets/a5cdaa96bf7d1db6c671432f9ab7bccb-slKJjgfX.woff2 +0 -0
- package/dist/ui/assets/a640dd2e962cf72c7fcf76234c973303-CUCWUDBY.woff2 +0 -0
- package/dist/ui/assets/a67e457bbd366aef5104ce743a2cf6d4-DkE8fLlE.woff2 +0 -0
- package/dist/ui/assets/a6c3f2257315d45aa212bd2e86a02852-CxDbXnZV.woff2 +0 -0
- package/dist/ui/assets/a73399a40a5f81fb7314b8b9a10cd221-DRAvJPKI.woff2 +0 -0
- package/dist/ui/assets/a73a64b0941803d9d807b9ec082df641-BbPpPyy5.woff2 +0 -0
- package/dist/ui/assets/a73d1a0a1dfd4f4234f7a0649ece4da4-BHeqktY9.woff2 +0 -0
- package/dist/ui/assets/a740044e77a400a450e71fcdea55523f-Co5hpBNK.woff2 +0 -0
- package/dist/ui/assets/a7669b29f404662c179c71b87e67c8d2-CVnuBgHy.woff2 +0 -0
- package/dist/ui/assets/a964a4def15811e91b05a91740a1cf37-CzcDfCRn.woff2 +0 -0
- package/dist/ui/assets/aac3f69132da164921fda75d2d90bfb7-B6rUZQ4_.woff2 +0 -0
- package/dist/ui/assets/aacd263c3bb4ba163a4d5079c159680a-DtrDLFlk.woff2 +0 -0
- package/dist/ui/assets/ac62ec881ded2cdb450e542b396e24d0-CRlVJvPa.woff2 +0 -0
- package/dist/ui/assets/aebcb9b7d4b174ab5a4b911206f24036-BuleSpgP.woff2 +0 -0
- package/dist/ui/assets/af70712dadc05c7b18d248996a5ab0a9-gCZSZGzb.woff2 +0 -0
- package/dist/ui/assets/afbd3a8a4cfaa4561b7848c28bc6c4f8-DgnqceEw.woff2 +0 -0
- package/dist/ui/assets/b02d3867e6e2ea921d2845b44c4a5a31-C9v4FrqD.woff2 +0 -0
- package/dist/ui/assets/b1b22825b930688403337d4fd7bbba62-vmMs2pJf.woff2 +0 -0
- package/dist/ui/assets/b3206c979bfb28d12d7ad6df58e74b9f-X1PqLaCR.woff2 +0 -0
- package/dist/ui/assets/b6280e42d4f11dcab70594f9d6774936-CtezImLG.woff2 +0 -0
- package/dist/ui/assets/b71b8cd1a70405c3dbc0e7a44055ca9a-Cd6cIOEr.woff2 +0 -0
- package/dist/ui/assets/b9198c4f220a4ca075f8daa4db529e50-Bgc7zlEt.woff2 +0 -0
- package/dist/ui/assets/baad304a58d7d42d0af29235c5e9490b-BQZYwARB.woff2 +0 -0
- package/dist/ui/assets/bb99e34f8784cc44f0eeadbe735dd238-UHFYI827.woff2 +0 -0
- package/dist/ui/assets/bc1cb0f523c8a2ff566a047aeddf90fd-BSlXQn97.woff2 +0 -0
- package/dist/ui/assets/bd3171e975d62612e50148bf38b2158f-CtZKzCzQ.woff2 +0 -0
- package/dist/ui/assets/be10640c403263dd5917ad65c4d453a6-B-rbuuja.woff2 +0 -0
- package/dist/ui/assets/be764e101e78b50b749aa1414d4810a7-yIPaQQih.woff2 +0 -0
- package/dist/ui/assets/bfa682a7f5f12945b41230c35eb859f6-CzagcI5f.woff2 +0 -0
- package/dist/ui/assets/bfa9753ed26f2e1f81ab7020167bd1a0-C6ZNQQ2p.woff2 +0 -0
- package/dist/ui/assets/branding/sash-cat.png +0 -0
- package/dist/ui/assets/c0e78c6ad709b19ce437677c91033489-BtwtlApg.woff2 +0 -0
- package/dist/ui/assets/c5a59409c610cc8c265d1691eb3f71eb-DmI_ky2X.woff2 +0 -0
- package/dist/ui/assets/c5ff66a1a57f2a29424e8de5d4ef3756-C9pOsaV1.woff2 +0 -0
- package/dist/ui/assets/c607bdee84a0e5b69721367ccc0c9c31-eoHbdXeg.woff2 +0 -0
- package/dist/ui/assets/c8095033941d18db730496eb4c863757-DoexTYks.woff2 +0 -0
- package/dist/ui/assets/c8295519e16f4cf3a350e9729f07653a-C8bAtIO_.woff2 +0 -0
- package/dist/ui/assets/c91f9c9f4db81afeba37c8e73afb0bfd-BGIl2iKW.woff2 +0 -0
- package/dist/ui/assets/c9ad9caa4afdd40766add91d321b0350-HfuiRq1k.woff2 +0 -0
- package/dist/ui/assets/cc4f271c90f9ea5157db9c0a4edc976b-MAjuDvHE.woff2 +0 -0
- package/dist/ui/assets/cdf7c3d96ba172214d7e1e9fe5e6ba8b-CUx4fWQE.woff2 +0 -0
- package/dist/ui/assets/cf70d5821ea7c3ca34653169ac763606-CE0T0Svc.woff2 +0 -0
- package/dist/ui/assets/d0d5efe92496b1ceea38e180b0ac97d3-7G71Z6v3.woff2 +0 -0
- package/dist/ui/assets/d1977142eceb878b06440375e64c4372-mr-7LWm-.woff2 +0 -0
- package/dist/ui/assets/d42feb13392b4dd9214c61b61a6e5ae8-DjnKADf_.woff2 +0 -0
- package/dist/ui/assets/d46a696ec6a077c6873fc39043c3c082-C62E-13k.woff2 +0 -0
- package/dist/ui/assets/d4c0bbf1605a8a9c36fff5a2ead81bb5-C2F6tzOj.woff2 +0 -0
- package/dist/ui/assets/d918d4e47697b7c752ea223e0d0d4c81-twovU2ty.woff2 +0 -0
- package/dist/ui/assets/d988c05da0b5624267dd825fedd133d1-Bgbc9SVH.woff2 +0 -0
- package/dist/ui/assets/da3a071df79531f4f033b7c8499651d0-0tgZJL_N.woff2 +0 -0
- package/dist/ui/assets/da8eba4acbf93048645043006de1c5f0-Ce0dmHn_.woff2 +0 -0
- package/dist/ui/assets/dc3f16892e20a85a8fcad2d3097e622b-8PRrFPJt.woff2 +0 -0
- package/dist/ui/assets/dd5f808ec691f911483a2eb60b5049bd-BhVupSbT.woff2 +0 -0
- package/dist/ui/assets/e2b04534cc697a76afd70e885119cb8c-DO303iCb.woff2 +0 -0
- package/dist/ui/assets/e307280eb2fb7809adee14ff0d247d32-DHMLXvSs.woff2 +0 -0
- package/dist/ui/assets/e3b484bec850c669094f713570870b3a-BI_9kRw8.woff2 +0 -0
- package/dist/ui/assets/e7b1d196949b7530cbda0ad956fbb2cc-i2m2Id0q.woff2 +0 -0
- package/dist/ui/assets/e7c2e5fb387880508811cadc2926e13a-BfROXX4U.woff2 +0 -0
- package/dist/ui/assets/e8a5fe283c8b9e173097bee0b5757ab6-BvfjDjYP.woff2 +0 -0
- package/dist/ui/assets/e8e3465d48435612e9c3509be3984188-BeUPU5-9.woff2 +0 -0
- package/dist/ui/assets/e93903f4604b007fc1b7134a74f5c534-B1M4h46T.woff2 +0 -0
- package/dist/ui/assets/eb0994906efd969102d522b6d8b3aff2-i9SHU5CJ.woff2 +0 -0
- package/dist/ui/assets/eb32eee23ceadd3fc4eb5763d3573f57-B3h9kgf3.woff2 +0 -0
- package/dist/ui/assets/ebadbc005027fb3e2b1c2d15be38c66b-qVQSBwBu.woff2 +0 -0
- package/dist/ui/assets/ecbbb464ff6934b5012cf33c80c7a907-CBEPqZfS.woff2 +0 -0
- package/dist/ui/assets/ed79ac25c014b6434f22fe1e2549bc26-mWhz7hkU.woff2 +0 -0
- package/dist/ui/assets/eed64f40443554cb9f65edaca176abb0-Cqw29XTm.woff2 +0 -0
- package/dist/ui/assets/efc749471476cce09e12e7038c757950-BhSWPf1Q.woff2 +0 -0
- package/dist/ui/assets/f2a7c2cb375662aa93d8f31116d39c20-D1zsMYCW.woff2 +0 -0
- package/dist/ui/assets/f5ba0fb4332ccceadc481e47134023d3-CAKMPiu5.woff2 +0 -0
- package/dist/ui/assets/f6326eb5031e18478aa4588598f96965-Dj3uu55n.woff2 +0 -0
- package/dist/ui/assets/f8678cbaab9caef95e1986731dcbd92f-CnIuR3EN.woff2 +0 -0
- package/dist/ui/assets/f8d9d55e2a811e9839c9af5f8f2d62ac-DIUQRBsH.woff2 +0 -0
- package/dist/ui/assets/f98b117e363eb51f4d7c7d1056da7688-O9MtqjIW.woff2 +0 -0
- package/dist/ui/assets/f9ab5d9163148ceb211e5b4f15a3d8e0-Dtqse-AT.woff2 +0 -0
- package/dist/ui/assets/fa69ce224ff0a6c0ff97b10a829779c2-pX45dCNY.woff2 +0 -0
- package/dist/ui/assets/fc38d1da3930d39dd8e286f708105765-DmbOHwTI.woff2 +0 -0
- package/dist/ui/assets/fe03c1dfaffc1cad3521fe55f1541d6b-BW8aEqCj.woff2 +0 -0
- package/dist/ui/assets/fe0b2421eb4d1f4f8949c602871a317c-BsBLV6jH.woff2 +0 -0
- package/dist/ui/assets/fe9c75d0cb3d808023412d26dfc9d388-eXjcgUPI.woff2 +0 -0
- package/dist/ui/assets/index-B61V60w_.js +19 -0
- package/dist/ui/assets/index-bkyxJG8J.css +1 -0
- package/dist/ui/assets/theme-BNq4FkXS.js +1 -0
- package/dist/ui/index.html +52 -0
- package/dist/webui.js +21 -0
- package/docs/backend.md +260 -0
- package/docs/frontend.md +125 -0
- package/docs/remix-icon-license.txt +186 -0
- package/docs/usage.md +227 -0
- 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();
|