@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,242 @@
|
|
|
1
|
+
import { commitManagedStateTransaction } from "./managed-state-transaction.js";
|
|
2
|
+
import { ProfileConflictError, } from "./profile-service.js";
|
|
3
|
+
import { sameSettings, validateSettingsCandidate } from "./settings.js";
|
|
4
|
+
import { tunPrivilegeGuidance } from "./tun-guidance.js";
|
|
5
|
+
export class SettingsInputError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export class CoreUnhealthyError extends Error {
|
|
8
|
+
}
|
|
9
|
+
const CORE_SETTING_KEYS = ["mixedPort", "controller", "secret", "tun", "allowLan"];
|
|
10
|
+
export class SettingsService {
|
|
11
|
+
options;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
async apply(patch) {
|
|
16
|
+
const previous = { ...this.options.getCommitted() };
|
|
17
|
+
let candidate;
|
|
18
|
+
try {
|
|
19
|
+
candidate = validateSettingsCandidate(mergePatch(previous, patch));
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
throw new SettingsInputError(err.message);
|
|
23
|
+
}
|
|
24
|
+
const restartRequired = candidate.daemonPort !== previous.daemonPort;
|
|
25
|
+
const proxyChanged = candidate.systemProxy !== previous.systemProxy;
|
|
26
|
+
const coreChanged = CORE_SETTING_KEYS.some((key) => candidate[key] !== previous[key]);
|
|
27
|
+
const daemonChanged = restartRequired || candidate.daemonSecret !== previous.daemonSecret;
|
|
28
|
+
let committed = previous;
|
|
29
|
+
if (coreChanged || daemonChanged) {
|
|
30
|
+
const staged = { ...candidate, systemProxy: previous.systemProxy };
|
|
31
|
+
committed = coreChanged
|
|
32
|
+
? await this.commitCoreChange(previous, staged, {
|
|
33
|
+
verifyTun: candidate.tun && !previous.tun,
|
|
34
|
+
})
|
|
35
|
+
: await this.commitSettingsOnly(previous, staged);
|
|
36
|
+
}
|
|
37
|
+
if (proxyChanged) {
|
|
38
|
+
committed = await this.commitSystemProxy(committed, candidate);
|
|
39
|
+
}
|
|
40
|
+
return { settings: committed, restartRequired };
|
|
41
|
+
}
|
|
42
|
+
async applyFileSettings(next) {
|
|
43
|
+
const current = this.options.getCommitted();
|
|
44
|
+
const patch = {};
|
|
45
|
+
if (next.mixedPort !== current.mixedPort)
|
|
46
|
+
patch.mixedPort = next.mixedPort;
|
|
47
|
+
if (next.controller !== current.controller)
|
|
48
|
+
patch.controller = next.controller;
|
|
49
|
+
if (next.secret !== current.secret)
|
|
50
|
+
patch.secret = next.secret;
|
|
51
|
+
if (next.tun !== current.tun)
|
|
52
|
+
patch.tun = next.tun;
|
|
53
|
+
if (next.allowLan !== current.allowLan)
|
|
54
|
+
patch.allowLan = next.allowLan;
|
|
55
|
+
if (next.systemProxy !== current.systemProxy)
|
|
56
|
+
patch.systemProxy = next.systemProxy;
|
|
57
|
+
if (next.daemonPort !== current.daemonPort)
|
|
58
|
+
patch.daemonPort = next.daemonPort;
|
|
59
|
+
if (next.daemonSecret !== current.daemonSecret)
|
|
60
|
+
patch.daemonSecret = next.daemonSecret;
|
|
61
|
+
return this.apply(patch);
|
|
62
|
+
}
|
|
63
|
+
async commitSettingsOnly(previous, candidate) {
|
|
64
|
+
return this.options.commit("update daemon settings", async () => {
|
|
65
|
+
this.assertCurrent(previous);
|
|
66
|
+
this.options.setRuntime(candidate);
|
|
67
|
+
try {
|
|
68
|
+
await commitManagedStateTransaction(this.options.layout, { settings: candidate }, undefined);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
this.options.setRuntime(previous);
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
this.options.setCommitted(candidate);
|
|
75
|
+
return candidate;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async commitCoreChange(previous, candidate, opts) {
|
|
79
|
+
for (let attempt = 0;; attempt += 1) {
|
|
80
|
+
const prepared = await this.options.profiles.prepareActiveConfig(candidate, previous);
|
|
81
|
+
let retryableProfileConflict;
|
|
82
|
+
let callbackEntered = false;
|
|
83
|
+
try {
|
|
84
|
+
return await this.options.commit("update settings", async () => {
|
|
85
|
+
this.assertCurrent(previous);
|
|
86
|
+
try {
|
|
87
|
+
return await this.options.profiles.withPreparedActivePublication(prepared, async (publication) => {
|
|
88
|
+
callbackEntered = true;
|
|
89
|
+
return this.commitCoreSettings(previous, candidate, opts, publication);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err instanceof ProfileConflictError && !callbackEntered) {
|
|
94
|
+
retryableProfileConflict = err;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err !== retryableProfileConflict || attempt >= 1)
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async commitSystemProxy(previous, candidate) {
|
|
107
|
+
if (!candidate.systemProxy) {
|
|
108
|
+
return this.options.commit("disable system proxy", async () => {
|
|
109
|
+
this.assertCurrent(previous);
|
|
110
|
+
this.options.setRuntime(candidate);
|
|
111
|
+
try {
|
|
112
|
+
await commitManagedStateTransaction(this.options.layout, { settings: candidate }, undefined);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
this.options.setRuntime(previous);
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
this.options.setCommitted(candidate);
|
|
119
|
+
if (this.options.lifecycle)
|
|
120
|
+
await this.options.lifecycle.reconcileSystemProxy();
|
|
121
|
+
else
|
|
122
|
+
await this.options.releaseSystemProxy?.();
|
|
123
|
+
return candidate;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const core = await this.options.supervisor?.status();
|
|
127
|
+
if (!core?.running || !core.healthy) {
|
|
128
|
+
throw new CoreUnhealthyError("Cannot enable system proxy: core is not healthy");
|
|
129
|
+
}
|
|
130
|
+
return this.options.commit("enable system proxy", async () => {
|
|
131
|
+
this.assertCurrent(previous);
|
|
132
|
+
this.options.setRuntime(candidate);
|
|
133
|
+
try {
|
|
134
|
+
await commitManagedStateTransaction(this.options.layout, {
|
|
135
|
+
settings: candidate,
|
|
136
|
+
applyRuntime: () => this.requireLifecycle().reconcileSystemProxy(),
|
|
137
|
+
}, undefined);
|
|
138
|
+
this.options.setCommitted(candidate);
|
|
139
|
+
return candidate;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
this.options.setRuntime(previous);
|
|
143
|
+
try {
|
|
144
|
+
await this.requireLifecycle().reconcileSystemProxy();
|
|
145
|
+
}
|
|
146
|
+
catch (rollback) {
|
|
147
|
+
throw new Error(`${err.message}; system proxy rollback failed: ${rollback.message}`);
|
|
148
|
+
}
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async commitCoreSettings(previous, candidate, opts, publication) {
|
|
154
|
+
const wasRunning = this.options.supervisor?.isRunning() ?? false;
|
|
155
|
+
this.options.setRuntime(candidate);
|
|
156
|
+
try {
|
|
157
|
+
await commitManagedStateTransaction(this.options.layout, {
|
|
158
|
+
...(publication.index ? { index: publication.index } : {}),
|
|
159
|
+
...(publication.profile
|
|
160
|
+
? {
|
|
161
|
+
profile: {
|
|
162
|
+
id: publication.profile.id,
|
|
163
|
+
yamlText: publication.profile.yamlText,
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
: {}),
|
|
167
|
+
config: publication.config,
|
|
168
|
+
settings: candidate,
|
|
169
|
+
reloadRuntime: false,
|
|
170
|
+
applyRuntime: async () => {
|
|
171
|
+
if (!wasRunning)
|
|
172
|
+
return;
|
|
173
|
+
const result = await this.requireLifecycle().restart();
|
|
174
|
+
if (opts.verifyTun && result.tunActive !== true) {
|
|
175
|
+
throw new Error(result.tunActive === false
|
|
176
|
+
? `TUN did not become active. ${tunPrivilegeGuidance("activation-rolled-back", { root: this.options.layout.root })}`
|
|
177
|
+
: `TUN activation could not be verified through the Core controller. ${tunPrivilegeGuidance("activation-rolled-back", { root: this.options.layout.root })}`);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
}, undefined);
|
|
181
|
+
this.options.setCommitted(candidate);
|
|
182
|
+
return candidate;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
this.options.setRuntime(previous);
|
|
186
|
+
const rollbackErrors = [];
|
|
187
|
+
try {
|
|
188
|
+
await this.options.profiles.commitPreparedActiveReload(publication.rollback, {
|
|
189
|
+
reloadRuntime: false,
|
|
190
|
+
boundary: "already-held",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (rollback) {
|
|
194
|
+
rollbackErrors.push(`config rollback failed: ${rollback.message}`);
|
|
195
|
+
}
|
|
196
|
+
if (wasRunning) {
|
|
197
|
+
try {
|
|
198
|
+
if (this.options.supervisor?.isRunning())
|
|
199
|
+
await this.requireLifecycle().restart();
|
|
200
|
+
else
|
|
201
|
+
await this.requireLifecycle().start();
|
|
202
|
+
}
|
|
203
|
+
catch (rollback) {
|
|
204
|
+
rollbackErrors.push(`runtime rollback failed: ${rollback.message}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (rollbackErrors.length)
|
|
208
|
+
throw new Error(`${err.message}; ${rollbackErrors.join("; ")}`);
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
requireLifecycle() {
|
|
213
|
+
if (!this.options.lifecycle)
|
|
214
|
+
throw new Error("No runtime lifecycle is available for this settings mutation");
|
|
215
|
+
return this.options.lifecycle;
|
|
216
|
+
}
|
|
217
|
+
assertCurrent(previous) {
|
|
218
|
+
if (!sameSettings(this.options.getCommitted(), previous)) {
|
|
219
|
+
throw new Error("Settings changed while preparing configuration");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function mergePatch(previous, patch) {
|
|
224
|
+
const candidate = { ...previous };
|
|
225
|
+
if (patch.mixedPort !== undefined)
|
|
226
|
+
candidate.mixedPort = patch.mixedPort;
|
|
227
|
+
if (patch.controller !== undefined)
|
|
228
|
+
candidate.controller = patch.controller;
|
|
229
|
+
if (patch.secret !== undefined)
|
|
230
|
+
candidate.secret = patch.secret;
|
|
231
|
+
if (patch.tun !== undefined)
|
|
232
|
+
candidate.tun = patch.tun;
|
|
233
|
+
if (patch.allowLan !== undefined)
|
|
234
|
+
candidate.allowLan = patch.allowLan;
|
|
235
|
+
if (patch.systemProxy !== undefined)
|
|
236
|
+
candidate.systemProxy = patch.systemProxy;
|
|
237
|
+
if (patch.daemonPort !== undefined)
|
|
238
|
+
candidate.daemonPort = patch.daemonPort;
|
|
239
|
+
if (patch.daemonSecret !== undefined)
|
|
240
|
+
candidate.daemonSecret = patch.daemonSecret;
|
|
241
|
+
return candidate;
|
|
242
|
+
}
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { atomicWriteFileSync } from "./fs-atomic.js";
|
|
4
|
+
import { isPlainObject } from "./json-shape.js";
|
|
5
|
+
import { sashLayout } from "./paths.js";
|
|
6
|
+
import { acquireStateLockSync } from "./state-lock.js";
|
|
7
|
+
const PUBLIC_SETTINGS_KEYS = [
|
|
8
|
+
"mixedPort",
|
|
9
|
+
"controller",
|
|
10
|
+
"tun",
|
|
11
|
+
"allowLan",
|
|
12
|
+
"daemonPort",
|
|
13
|
+
"systemProxy",
|
|
14
|
+
];
|
|
15
|
+
function exhaustiveKeys() {
|
|
16
|
+
return (keys) => keys;
|
|
17
|
+
}
|
|
18
|
+
const CANONICAL_SETTINGS_KEYS = exhaustiveKeys()([
|
|
19
|
+
"schemaVersion",
|
|
20
|
+
"subscriptionUrl",
|
|
21
|
+
"mixedPort",
|
|
22
|
+
"controller",
|
|
23
|
+
"secret",
|
|
24
|
+
"tun",
|
|
25
|
+
"allowLan",
|
|
26
|
+
"daemonPort",
|
|
27
|
+
"daemonSecret",
|
|
28
|
+
"systemProxy",
|
|
29
|
+
]);
|
|
30
|
+
const LEGACY_SETTINGS_KEYS = ["coreVersion", "uiVersion"];
|
|
31
|
+
const ACCEPTED_SETTINGS_KEYS = new Set([
|
|
32
|
+
...CANONICAL_SETTINGS_KEYS,
|
|
33
|
+
...LEGACY_SETTINGS_KEYS,
|
|
34
|
+
]);
|
|
35
|
+
export function publicSettings(settings) {
|
|
36
|
+
return {
|
|
37
|
+
mixedPort: settings.mixedPort,
|
|
38
|
+
controller: settings.controller,
|
|
39
|
+
tun: settings.tun,
|
|
40
|
+
allowLan: settings.allowLan,
|
|
41
|
+
daemonPort: settings.daemonPort,
|
|
42
|
+
systemProxy: settings.systemProxy,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function sameSettings(a, b) {
|
|
46
|
+
return CANONICAL_SETTINGS_KEYS.every((key) => a[key] === b[key]);
|
|
47
|
+
}
|
|
48
|
+
export const DEFAULT_SETTINGS = {
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
mixedPort: 7890,
|
|
51
|
+
controller: "127.0.0.1:9090",
|
|
52
|
+
secret: "",
|
|
53
|
+
tun: false,
|
|
54
|
+
allowLan: false,
|
|
55
|
+
daemonPort: 19090,
|
|
56
|
+
daemonSecret: "",
|
|
57
|
+
systemProxy: false,
|
|
58
|
+
};
|
|
59
|
+
function hasOwn(object, key) {
|
|
60
|
+
return Object.hasOwn(object, key);
|
|
61
|
+
}
|
|
62
|
+
function invalidSettings(file, message) {
|
|
63
|
+
return new Error(`Settings are invalid: ${file}: ${message}`);
|
|
64
|
+
}
|
|
65
|
+
function readRequiredField(source, key, defaultValue, allowMissing, file, parse) {
|
|
66
|
+
if (!hasOwn(source, key)) {
|
|
67
|
+
if (!allowMissing)
|
|
68
|
+
throw invalidSettings(file, `${key} is required`);
|
|
69
|
+
return { value: defaultValue, missing: true };
|
|
70
|
+
}
|
|
71
|
+
const value = source[key];
|
|
72
|
+
if (value === null)
|
|
73
|
+
throw invalidSettings(file, `${key} must not be null`);
|
|
74
|
+
return { value: parse(value, key, file), missing: false };
|
|
75
|
+
}
|
|
76
|
+
function readOptionalString(source, key, file) {
|
|
77
|
+
if (!hasOwn(source, key))
|
|
78
|
+
return undefined;
|
|
79
|
+
const value = source[key];
|
|
80
|
+
if (value === null)
|
|
81
|
+
throw invalidSettings(file, `${key} must not be null`);
|
|
82
|
+
return parseString(value, key, file);
|
|
83
|
+
}
|
|
84
|
+
function parseString(value, key, file) {
|
|
85
|
+
if (typeof value !== "string")
|
|
86
|
+
throw invalidSettings(file, `${key} must be a string`);
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
function hasSecretControlCharacter(secret) {
|
|
90
|
+
for (const char of secret) {
|
|
91
|
+
const code = char.charCodeAt(0);
|
|
92
|
+
if (code <= 31 || code === 127)
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function parseSecret(value, key, file) {
|
|
98
|
+
const secret = parseString(value, key, file);
|
|
99
|
+
if (!secret.trim())
|
|
100
|
+
throw invalidSettings(file, `${key} must not be blank`);
|
|
101
|
+
if (hasSecretControlCharacter(secret)) {
|
|
102
|
+
throw invalidSettings(file, `${key} must not contain control characters`);
|
|
103
|
+
}
|
|
104
|
+
return secret;
|
|
105
|
+
}
|
|
106
|
+
function parseBoolean(value, key, file) {
|
|
107
|
+
if (typeof value !== "boolean")
|
|
108
|
+
throw invalidSettings(file, `${key} must be a boolean`);
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
function parsePort(value, key, file) {
|
|
112
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 65_535) {
|
|
113
|
+
throw invalidSettings(file, `${key} must be an integer from 1 to 65535`);
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function parseController(value, key, file) {
|
|
118
|
+
const controller = parseString(value, key, file);
|
|
119
|
+
const address = parseControllerAddress(controller);
|
|
120
|
+
if (!address) {
|
|
121
|
+
throw invalidSettings(file, `${key} must be a loopback host:port controller address`);
|
|
122
|
+
}
|
|
123
|
+
return address.canonical;
|
|
124
|
+
}
|
|
125
|
+
function validateSettingsRelations(settings, file) {
|
|
126
|
+
const controller = parseControllerAddress(settings.controller);
|
|
127
|
+
if (!controller) {
|
|
128
|
+
throw invalidSettings(file, "controller must be a loopback host:port controller address");
|
|
129
|
+
}
|
|
130
|
+
const ports = [
|
|
131
|
+
["mixedPort", settings.mixedPort],
|
|
132
|
+
["daemonPort", settings.daemonPort],
|
|
133
|
+
["controller", controller.port],
|
|
134
|
+
];
|
|
135
|
+
for (let i = 0; i < ports.length; i++) {
|
|
136
|
+
for (let j = i + 1; j < ports.length; j++) {
|
|
137
|
+
if (ports[i]?.[1] === ports[j]?.[1]) {
|
|
138
|
+
throw invalidSettings(file, `${ports[i]?.[0]} and ${ports[j]?.[0]} must use different ports`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function parseSchemaVersion(source, allowMissing, file) {
|
|
144
|
+
if (!hasOwn(source, "schemaVersion")) {
|
|
145
|
+
if (!allowMissing)
|
|
146
|
+
throw invalidSettings(file, "schemaVersion is required");
|
|
147
|
+
return { value: 0, missing: true };
|
|
148
|
+
}
|
|
149
|
+
const value = source.schemaVersion;
|
|
150
|
+
if (value === null)
|
|
151
|
+
throw invalidSettings(file, "schemaVersion must not be null");
|
|
152
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
153
|
+
throw invalidSettings(file, "schemaVersion must be an integer");
|
|
154
|
+
}
|
|
155
|
+
if (value === 0 || value === 1)
|
|
156
|
+
return { value, missing: false };
|
|
157
|
+
if (value > 1) {
|
|
158
|
+
throw invalidSettings(file, `schemaVersion ${value} is from a future version`);
|
|
159
|
+
}
|
|
160
|
+
throw invalidSettings(file, "schemaVersion must be 0 or 1");
|
|
161
|
+
}
|
|
162
|
+
function parseSettings(document, file, allowMissing) {
|
|
163
|
+
if (!isPlainObject(document)) {
|
|
164
|
+
throw invalidSettings(file, "JSON root must be a plain object");
|
|
165
|
+
}
|
|
166
|
+
for (const key of Object.keys(document)) {
|
|
167
|
+
if (!ACCEPTED_SETTINGS_KEYS.has(key)) {
|
|
168
|
+
throw invalidSettings(file, `unknown field ${JSON.stringify(key)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const schemaVersion = parseSchemaVersion(document, allowMissing, file);
|
|
172
|
+
const subscriptionUrl = readOptionalString(document, "subscriptionUrl", file);
|
|
173
|
+
const mixedPort = readRequiredField(document, "mixedPort", DEFAULT_SETTINGS.mixedPort, allowMissing, file, parsePort);
|
|
174
|
+
const controller = readRequiredField(document, "controller", DEFAULT_SETTINGS.controller, allowMissing, file, parseController);
|
|
175
|
+
const secret = readRequiredField(document, "secret", DEFAULT_SETTINGS.secret, allowMissing, file, parseSecret);
|
|
176
|
+
const tun = readRequiredField(document, "tun", DEFAULT_SETTINGS.tun, allowMissing, file, parseBoolean);
|
|
177
|
+
const allowLan = readRequiredField(document, "allowLan", DEFAULT_SETTINGS.allowLan, allowMissing, file, parseBoolean);
|
|
178
|
+
const daemonPort = readRequiredField(document, "daemonPort", DEFAULT_SETTINGS.daemonPort, allowMissing, file, parsePort);
|
|
179
|
+
const daemonSecret = readRequiredField(document, "daemonSecret", DEFAULT_SETTINGS.daemonSecret, allowMissing, file, parseSecret);
|
|
180
|
+
const systemProxy = readRequiredField(document, "systemProxy", DEFAULT_SETTINGS.systemProxy, allowMissing, file, parseBoolean);
|
|
181
|
+
let needsRewrite = schemaVersion.missing ||
|
|
182
|
+
schemaVersion.value === 0 ||
|
|
183
|
+
mixedPort.missing ||
|
|
184
|
+
controller.missing ||
|
|
185
|
+
controller.value !== document.controller ||
|
|
186
|
+
secret.missing ||
|
|
187
|
+
tun.missing ||
|
|
188
|
+
allowLan.missing ||
|
|
189
|
+
daemonPort.missing ||
|
|
190
|
+
daemonSecret.missing ||
|
|
191
|
+
systemProxy.missing;
|
|
192
|
+
for (const key of LEGACY_SETTINGS_KEYS) {
|
|
193
|
+
if (!hasOwn(document, key))
|
|
194
|
+
continue;
|
|
195
|
+
const value = document[key];
|
|
196
|
+
if (value === null)
|
|
197
|
+
throw invalidSettings(file, `${key} must not be null`);
|
|
198
|
+
parseString(value, key, file);
|
|
199
|
+
needsRewrite = true;
|
|
200
|
+
}
|
|
201
|
+
const normalizedSecret = secret.missing ? generateSecret() : secret.value;
|
|
202
|
+
const normalizedDaemonSecret = daemonSecret.missing ? generateSecret() : daemonSecret.value;
|
|
203
|
+
const settings = {
|
|
204
|
+
schemaVersion: 1,
|
|
205
|
+
mixedPort: mixedPort.value,
|
|
206
|
+
controller: controller.value,
|
|
207
|
+
secret: normalizedSecret,
|
|
208
|
+
tun: tun.value,
|
|
209
|
+
allowLan: allowLan.value,
|
|
210
|
+
daemonPort: daemonPort.value,
|
|
211
|
+
daemonSecret: normalizedDaemonSecret,
|
|
212
|
+
systemProxy: systemProxy.value,
|
|
213
|
+
};
|
|
214
|
+
if (subscriptionUrl !== undefined)
|
|
215
|
+
settings.subscriptionUrl = subscriptionUrl;
|
|
216
|
+
validateSettingsRelations(settings, file);
|
|
217
|
+
return { settings, needsRewrite };
|
|
218
|
+
}
|
|
219
|
+
function readSettingsFile(file) {
|
|
220
|
+
let text;
|
|
221
|
+
try {
|
|
222
|
+
text = fs.readFileSync(file, "utf8");
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
if (err.code === "ENOENT") {
|
|
226
|
+
return { exists: false, document: {} };
|
|
227
|
+
}
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return { exists: true, document: JSON.parse(text) };
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
throw new Error(`Settings file is invalid JSON: ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function canonicalSettings(settings) {
|
|
238
|
+
const canonical = {
|
|
239
|
+
schemaVersion: 1,
|
|
240
|
+
mixedPort: settings.mixedPort,
|
|
241
|
+
controller: settings.controller,
|
|
242
|
+
secret: settings.secret,
|
|
243
|
+
tun: settings.tun,
|
|
244
|
+
allowLan: settings.allowLan,
|
|
245
|
+
daemonPort: settings.daemonPort,
|
|
246
|
+
daemonSecret: settings.daemonSecret,
|
|
247
|
+
systemProxy: settings.systemProxy,
|
|
248
|
+
};
|
|
249
|
+
if (settings.subscriptionUrl !== undefined)
|
|
250
|
+
canonical.subscriptionUrl = settings.subscriptionUrl;
|
|
251
|
+
return canonical;
|
|
252
|
+
}
|
|
253
|
+
function writeCanonicalSettings(settings, layout) {
|
|
254
|
+
atomicWriteFileSync(layout.settingsFile, `${JSON.stringify(canonicalSettings(settings), null, 2)}\n`);
|
|
255
|
+
}
|
|
256
|
+
function loadSettingsUnlocked(layout) {
|
|
257
|
+
const source = readSettingsFile(layout.settingsFile);
|
|
258
|
+
const parsed = parseSettings(source.document, layout.settingsFile, true);
|
|
259
|
+
if (!source.exists || parsed.needsRewrite) {
|
|
260
|
+
writeCanonicalSettings(parsed.settings, layout);
|
|
261
|
+
}
|
|
262
|
+
return parsed.settings;
|
|
263
|
+
}
|
|
264
|
+
export function generateSecret() {
|
|
265
|
+
return crypto.randomBytes(24).toString("hex");
|
|
266
|
+
}
|
|
267
|
+
export function loadSettings(layout = sashLayout()) {
|
|
268
|
+
const lease = acquireStateLockSync(layout.settingsLockFile, { purpose: "settings" });
|
|
269
|
+
try {
|
|
270
|
+
return loadSettingsUnlocked(layout);
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
lease.release();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
export function saveSettings(settings, layout = sashLayout()) {
|
|
277
|
+
const lease = acquireStateLockSync(layout.settingsLockFile, { purpose: "settings" });
|
|
278
|
+
try {
|
|
279
|
+
const parsed = parseSettings(settings, layout.settingsFile, false);
|
|
280
|
+
writeCanonicalSettings(parsed.settings, layout);
|
|
281
|
+
return parsed.settings;
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
lease.release();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export function parseSettingsText(text, file = "sash.json") {
|
|
288
|
+
let document;
|
|
289
|
+
try {
|
|
290
|
+
document = JSON.parse(text);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
throw new Error(`Settings file is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
294
|
+
}
|
|
295
|
+
return parseSettings(document, file, false).settings;
|
|
296
|
+
}
|
|
297
|
+
export function validateSettingsCandidate(candidate) {
|
|
298
|
+
return parseSettings(candidate, "candidate settings", false).settings;
|
|
299
|
+
}
|
|
300
|
+
export function parseControllerAddress(value) {
|
|
301
|
+
const match = value.trim().match(/^(127\.0\.0\.1|localhost|\[::1\]):(\d+)$/i);
|
|
302
|
+
const rawHost = match?.[1]?.toLowerCase();
|
|
303
|
+
const rawPort = match?.[2];
|
|
304
|
+
if (!rawHost || !rawPort)
|
|
305
|
+
return undefined;
|
|
306
|
+
const port = Number.parseInt(rawPort, 10);
|
|
307
|
+
if (port < 1 || port > 65_535 || String(port) !== rawPort)
|
|
308
|
+
return undefined;
|
|
309
|
+
if (rawHost === "[::1]") {
|
|
310
|
+
return { host: "::1", port, canonical: `[::1]:${port}` };
|
|
311
|
+
}
|
|
312
|
+
const host = rawHost === "localhost" ? "localhost" : "127.0.0.1";
|
|
313
|
+
return { host, port, canonical: `${host}:${port}` };
|
|
314
|
+
}
|