termdock 1.4.261 → 1.4.262
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/dist/client/assets/App-Cb_XllaU.js +156 -0
- package/dist/client/assets/App-Cb_XllaU.js.br +0 -0
- package/dist/client/assets/App-Cb_XllaU.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-DCDXuo9g.js → ChangeWalkthroughPanel-jX6d1ENn.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-jX6d1ENn.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-jX6d1ENn.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DQ-f4yDq.js → DagPlayground-TGM2t4Ye.js} +1 -1
- package/dist/client/assets/DagPlayground-TGM2t4Ye.js.br +0 -0
- package/dist/client/assets/DagPlayground-TGM2t4Ye.js.gz +0 -0
- package/dist/client/assets/{DiffLab-2IOw8MAu.js → DiffLab-Cw8cAI4p.js} +2 -2
- package/dist/client/assets/DiffLab-Cw8cAI4p.js.br +0 -0
- package/dist/client/assets/DiffLab-Cw8cAI4p.js.gz +0 -0
- package/dist/client/assets/{DiffReview-CVVPwpq8.js → DiffReview-BpxhFZLY.js} +1 -1
- package/dist/client/assets/DiffReview-BpxhFZLY.js.br +0 -0
- package/dist/client/assets/DiffReview-BpxhFZLY.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-D_ApcQkp.js → DiffReviewLab-CV_QO6UR.js} +1 -1
- package/dist/client/assets/DiffReviewLab-CV_QO6UR.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CV_QO6UR.js.gz +0 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js +36 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js.br +0 -0
- package/dist/client/assets/DiffViewer-680KNh4c.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-B7LjjdM1.js → ModelPreview-CUlJCDwX.js} +1 -1
- package/dist/client/assets/ModelPreview-CUlJCDwX.js.br +0 -0
- package/dist/client/assets/ModelPreview-CUlJCDwX.js.gz +0 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js +1678 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js.br +0 -0
- package/dist/client/assets/RightSidebar-ClSRXoi2.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-mrLkUgFf.js → abnfDiagram-VCTEODGH-BOd7FTr2.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BOd7FTr2.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BOd7FTr2.js.gz +0 -0
- package/dist/client/assets/app-Bl11JYak.js +35 -0
- package/dist/client/assets/app-Bl11JYak.js.br +0 -0
- package/dist/client/assets/app-Bl11JYak.js.gz +0 -0
- package/dist/client/assets/{arc-DmupYkTe.js → arc-BPAf1nZK.js} +1 -1
- package/dist/client/assets/arc-BPAf1nZK.js.br +0 -0
- package/dist/client/assets/arc-BPAf1nZK.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-BsHtkmDE.js → architectureDiagram-5GKGNRK7-fQvzZnB9.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-fQvzZnB9.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-fQvzZnB9.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-ncLrg2Xh.js → blockDiagram-I7D4REHJ-BwLMUrdi.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BwLMUrdi.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BwLMUrdi.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-EPHQSBvq.js → c4Diagram-7LVT6UL2-EoyhDhQx.js} +1 -1
- package/dist/client/assets/{c4Diagram-7LVT6UL2-EPHQSBvq.js.br → c4Diagram-7LVT6UL2-EoyhDhQx.js.br} +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-EoyhDhQx.js.gz +0 -0
- package/dist/client/assets/channel-BUQABAmH.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-xUusOn_a.js → chunk-2Q5K7J3B-BV2VpCfF.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-cYhK2MoD.js → chunk-5VM5RSS4-BFiQW7Ta.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-BgMxwAfS.js → chunk-F27PBJKO-DqlURJFH.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DqlURJFH.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DqlURJFH.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-B6CRxrMW.js → chunk-IMKFNOWR-BcM_EtAP.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BcM_EtAP.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BcM_EtAP.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-DZn1jA44.js → chunk-JWPE2WC7-B8NvVdil.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-DQ3eNn4I.js → chunk-POPQ4Y6H-B5Vz70dF.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-BmxWA3QV.js → chunk-SVP7TREG-CVv3_3sI.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-CVv3_3sI.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CVv3_3sI.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-CspXvKlS.js → chunk-TICWLB2K-Wpnx00aD.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Wpnx00aD.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Wpnx00aD.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-BqApWArB.js → chunk-XXDRQBXY-aBQvQ4Ji.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-ZOMwbuDI.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-ZOMwbuDI.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CoFCEHUR.js → cose-bilkent-JH36ORCC-DWySu0Hm.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DWySu0Hm.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DWySu0Hm.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-DR69rOWi.js → cynefin-OW5HDTMX-BEUAB_wN.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-BEUAB_wN.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BEUAB_wN.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js → cynefinDiagram-5FMLGOSQ-CCfHefWz.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CCfHefWz.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CCfHefWz.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-D3fm-UnU.js → dagre-GXQ25YYZ-CRPX2oo7.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CRPX2oo7.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CRPX2oo7.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-BaDXNCkJ.js → diagram-S7CK7UJ4-Pg--K2pK.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-Pg--K2pK.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-Pg--K2pK.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-CRm0G3Vr.js → diagram-UQ7AKVKN-CzU1IUDD.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-CzU1IUDD.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CzU1IUDD.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-w6yyxMEN.js → diagram-VSXAHHWV-D3ns7ZFW.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-D3ns7ZFW.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-D3ns7ZFW.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-C7AvCpG2.js → diagram-VX7I27RA-0PzViCov.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-0PzViCov.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-0PzViCov.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-Do48vc7u.js → diagram-Z3DM3KII-Cm6dBe6-.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-Cm6dBe6-.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Cm6dBe6-.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-CtqbLVix.js → ebnfDiagram-PWID7BFC-BxSW6eBA.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BxSW6eBA.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BxSW6eBA.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-GREU7AG0.js → erDiagram-RLTQ6QDP-DpWE-TjX.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DpWE-TjX.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DpWE-TjX.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-AF48X1A6.js → flowDiagram-HODETNUW-DYO0LUmp.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DYO0LUmp.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DYO0LUmp.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DXN1ogyz.js → ganttDiagram-EL5Y4UJY-CIq6FHXs.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CIq6FHXs.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CIq6FHXs.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-CGvVFAKz.js → gestureArbiter-DPglmaJZ.js} +1 -1
- package/dist/client/assets/gestureArbiter-DPglmaJZ.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DPglmaJZ.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CvYbDGx_.js → gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Ck5V0n5a.js.gz +0 -0
- package/dist/client/assets/{index-mZxreyvP.css → index-ddQQwU7M.css} +1 -1
- package/dist/client/assets/index-ddQQwU7M.css.br +0 -0
- package/dist/client/assets/index-ddQQwU7M.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DjL3Ebik.js → infoDiagram-27XIBGKW-DCWx_lXp.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CPM34-nt.js → ishikawaDiagram-5VMMS53U-Dri_MMmE.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Dri_MMmE.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Dri_MMmE.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-lQ_yAhk1.js → journeyDiagram-3NMN7TZE-BIBa8ZPU.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIBa8ZPU.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIBa8ZPU.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-C1uwQkzK.js → kanban-definition-UXKFOSKX-CVvCyGTR.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVvCyGTR.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CVvCyGTR.js.gz +0 -0
- package/dist/client/assets/{layout-CoSxduiU.js → layout-CePKkZZM.js} +1 -1
- package/dist/client/assets/layout-CePKkZZM.js.br +0 -0
- package/dist/client/assets/layout-CePKkZZM.js.gz +0 -0
- package/dist/client/assets/{linear-BL_xULdD.js → linear-C2Jk6X94.js} +1 -1
- package/dist/client/assets/linear-C2Jk6X94.js.br +0 -0
- package/dist/client/assets/linear-C2Jk6X94.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-JMckmwXW.js → mermaid.core-B0TLkbaQ.js} +5 -5
- package/dist/client/assets/mermaid.core-B0TLkbaQ.js.br +0 -0
- package/dist/client/assets/mermaid.core-B0TLkbaQ.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-cvRobstd.js → mindmap-definition-YA3MSWOX-BiqaDyAI.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BiqaDyAI.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BiqaDyAI.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-pL3w9QFh.js → passwordBootstrap-D8QzMobL.js} +2 -2
- package/dist/client/assets/passwordBootstrap-D8QzMobL.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-D8QzMobL.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BBw52-pE.js → pegDiagram-XKGWAZYB-mBmk-WA4.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-mBmk-WA4.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-mBmk-WA4.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-DkpRcJ2F.js → pieDiagram-E7YTZNPT-CNLgv2X-.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CNLgv2X-.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CNLgv2X-.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DXniqIK0.js → quadrantDiagram-AXDQQJYC-B6R_6sAT.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B6R_6sAT.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B6R_6sAT.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-CeTDIXQh.js → railroadDiagram-O6MQD6OU-CMHZvyFR.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CMHZvyFR.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CMHZvyFR.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BD3NoqKH.js → referenceLongPress-BclZaogy.js} +1 -1
- package/dist/client/assets/referenceLongPress-BclZaogy.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BclZaogy.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CIU0yJaX.js → requirementDiagram-BXWQKSXE-Chcr9JIV.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Chcr9JIV.js.br +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CIU0yJaX.js.gz → requirementDiagram-BXWQKSXE-Chcr9JIV.js.gz} +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-dDnMB9nm.js → sankeyDiagram-P5KCCOFB-DgOdSJOh.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DgOdSJOh.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DgOdSJOh.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DzaYOfu7.js → sequenceDiagram-WJ2MYXX4-CLgh_PNU.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLgh_PNU.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLgh_PNU.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CueSAYic.js → sizeCapture-INFHLROL-C7Ai3zIt.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-C7Ai3zIt.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-C7Ai3zIt.js.gz +0 -0
- package/dist/client/assets/{sparkles-56xYHAnJ.js → sparkles-DikPE3OY.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-im-7B3pn.js → stateDiagram-D77RDMKH-BjzRCePZ.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-BjzRCePZ.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BjzRCePZ.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-C9yeiSIs.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-bNyXNqi0.js → swimlanes-42K2YHIH-DfyuNxsB.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DfyuNxsB.js.br +0 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-bNyXNqi0.js.gz → swimlanes-42K2YHIH-DfyuNxsB.js.gz} +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BFAodsZz.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BB9HIAin.js → timeline-definition-24CTP7MA-BvSf8YQs.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-BvSf8YQs.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BvSf8YQs.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-_k_kGfRA.js → triangle-alert-nWNzdjS5.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CjRz0A2w.js → vennDiagram-4TSXK5OY-BPs48uzW.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-BPs48uzW.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BPs48uzW.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DUp5G-FF.js → wardleyDiagram-VM6X3IG4-DSa8jkGg.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DSa8jkGg.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DSa8jkGg.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DJj6fGG6.js → xychartDiagram-S5SC5T6Z-C4Gb4X25.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C4Gb4X25.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C4Gb4X25.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/cli.js +336 -99
- package/dist/server/entry.js +11 -0
- package/dist/server/routes/terminal.js +45 -0
- package/dist/server/supervisor.js +525 -0
- package/dist/server/utils/backoff.js +15 -0
- package/dist/server/utils/crashForensics.js +227 -0
- package/dist/server/utils/healthProbe.js +92 -0
- package/dist/server/utils/ptySpawnBackoff.js +2 -1
- package/dist/server/utils/serverHealth.js +126 -0
- package/dist/server/utils/serverLogger.js +27 -4
- package/dist/server/utils/supervisorClient.js +151 -0
- package/dist/server/utils/supervisorProtocol.js +159 -0
- package/dist/server/utils/termdockState.js +62 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-DvHlDcFI.js +0 -156
- package/dist/client/assets/App-DvHlDcFI.js.br +0 -0
- package/dist/client/assets/App-DvHlDcFI.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DCDXuo9g.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DCDXuo9g.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DQ-f4yDq.js.br +0 -0
- package/dist/client/assets/DagPlayground-DQ-f4yDq.js.gz +0 -0
- package/dist/client/assets/DiffLab-2IOw8MAu.js.br +0 -0
- package/dist/client/assets/DiffLab-2IOw8MAu.js.gz +0 -0
- package/dist/client/assets/DiffReview-CVVPwpq8.js.br +0 -0
- package/dist/client/assets/DiffReview-CVVPwpq8.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-D_ApcQkp.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-D_ApcQkp.js.gz +0 -0
- package/dist/client/assets/DiffViewer-JbnGZ1in.js +0 -35
- package/dist/client/assets/DiffViewer-JbnGZ1in.js.br +0 -0
- package/dist/client/assets/DiffViewer-JbnGZ1in.js.gz +0 -0
- package/dist/client/assets/ModelPreview-B7LjjdM1.js.br +0 -0
- package/dist/client/assets/ModelPreview-B7LjjdM1.js.gz +0 -0
- package/dist/client/assets/RightSidebar-5-LgWsUf.js +0 -1678
- package/dist/client/assets/RightSidebar-5-LgWsUf.js.br +0 -0
- package/dist/client/assets/RightSidebar-5-LgWsUf.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-mrLkUgFf.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-mrLkUgFf.js.gz +0 -0
- package/dist/client/assets/app-Cvxoycn7.js +0 -35
- package/dist/client/assets/app-Cvxoycn7.js.br +0 -0
- package/dist/client/assets/app-Cvxoycn7.js.gz +0 -0
- package/dist/client/assets/arc-DmupYkTe.js.br +0 -0
- package/dist/client/assets/arc-DmupYkTe.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BsHtkmDE.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BsHtkmDE.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-ncLrg2Xh.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-ncLrg2Xh.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-EPHQSBvq.js.gz +0 -0
- package/dist/client/assets/channel-D1PE0_Ej.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-BgMxwAfS.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BgMxwAfS.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-B6CRxrMW.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-B6CRxrMW.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BmxWA3QV.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BmxWA3QV.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CspXvKlS.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CspXvKlS.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-C6cf2s8I.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-C6cf2s8I.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CoFCEHUR.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CoFCEHUR.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DR69rOWi.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DR69rOWi.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D3fm-UnU.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D3fm-UnU.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaDXNCkJ.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaDXNCkJ.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CRm0G3Vr.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CRm0G3Vr.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-w6yyxMEN.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-w6yyxMEN.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C7AvCpG2.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C7AvCpG2.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Do48vc7u.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Do48vc7u.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CtqbLVix.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CtqbLVix.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-GREU7AG0.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-GREU7AG0.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-AF48X1A6.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-AF48X1A6.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DXN1ogyz.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DXN1ogyz.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-CGvVFAKz.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CGvVFAKz.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CvYbDGx_.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CvYbDGx_.js.gz +0 -0
- package/dist/client/assets/index-mZxreyvP.css.br +0 -0
- package/dist/client/assets/index-mZxreyvP.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CPM34-nt.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CPM34-nt.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-lQ_yAhk1.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-lQ_yAhk1.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C1uwQkzK.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C1uwQkzK.js.gz +0 -0
- package/dist/client/assets/layout-CoSxduiU.js.br +0 -0
- package/dist/client/assets/layout-CoSxduiU.js.gz +0 -0
- package/dist/client/assets/linear-BL_xULdD.js.br +0 -0
- package/dist/client/assets/linear-BL_xULdD.js.gz +0 -0
- package/dist/client/assets/mermaid.core-JMckmwXW.js.br +0 -0
- package/dist/client/assets/mermaid.core-JMckmwXW.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-cvRobstd.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-cvRobstd.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-pL3w9QFh.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-pL3w9QFh.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BBw52-pE.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BBw52-pE.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DkpRcJ2F.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DkpRcJ2F.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DXniqIK0.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DXniqIK0.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CeTDIXQh.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CeTDIXQh.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BD3NoqKH.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BD3NoqKH.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CIU0yJaX.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-dDnMB9nm.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-dDnMB9nm.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DzaYOfu7.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DzaYOfu7.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CueSAYic.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CueSAYic.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-im-7B3pn.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-im-7B3pn.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-f2rjjFm2.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-bNyXNqi0.js.br +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BFOOCrnj.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BB9HIAin.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BB9HIAin.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CjRz0A2w.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CjRz0A2w.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DUp5G-FF.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DUp5G-FF.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DJj6fGG6.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DJj6fGG6.js.gz +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -37,6 +37,11 @@ import { isFirstRunCompleted, markFirstRunCompleted, normalizeLocalAccessName, s
|
|
|
37
37
|
import { runBootChecks, formatBootCheckReport } from './utils/bootCheck.js';
|
|
38
38
|
import { localAccessManager, getLanIPv4Addresses } from './utils/localAccess.js';
|
|
39
39
|
import { OFFICIAL_NPM_REGISTRY, updateTermdockFromOfficialRegistry } from './utils/npmUpdate.js';
|
|
40
|
+
import { DAEMON_START_TIMEOUT_MS, waitForHealth } from './utils/healthProbe.js';
|
|
41
|
+
import { isProcessRunning, STOP_GRACE_MS } from './utils/termdockState.js';
|
|
42
|
+
import { recordSupervisorLost } from './utils/crashForensics.js';
|
|
43
|
+
import { getTermdockVersion } from './utils/version.js';
|
|
44
|
+
import { getSupervisorStatus, signalSupervisorRestart, spawnSupervisor, stopSupervisor, } from './utils/supervisorClient.js';
|
|
40
45
|
import { clearAuthFile, destroyAllSessions, hashPassword, isAuthEnabled, isEnvPasswordSet, writeAuthFile, } from './utils/authProtection.js';
|
|
41
46
|
const execFileAsync = promisify(execFile);
|
|
42
47
|
const TERMDOCK_VERSION = (() => {
|
|
@@ -61,6 +66,7 @@ const TERMDOCK_PID = String(process.pid);
|
|
|
61
66
|
const TERMDOCK_TMUX_HISTORY_LIMIT = TMUX.historyLimit;
|
|
62
67
|
const stateDir = path.join(os.homedir(), '.termdock');
|
|
63
68
|
const stateFilePath = path.join(stateDir, 'server.json');
|
|
69
|
+
const supervisorStatePath = path.join(stateDir, 'supervisor.json');
|
|
64
70
|
const logFilePath = path.join(stateDir, 'server.log');
|
|
65
71
|
const globalSessionStateFilePath = path.join(stateDir, 'global-session-state.json');
|
|
66
72
|
const localApiTokenPath = path.join(stateDir, 'local-api-token');
|
|
@@ -129,6 +135,11 @@ Options:
|
|
|
129
135
|
--setup-local-https
|
|
130
136
|
Generate and trust local HTTPS certs with mkcert
|
|
131
137
|
--foreground Run in the foreground
|
|
138
|
+
--supervise Like --foreground, but under the process supervisor
|
|
139
|
+
(auto-restarts on crash, records why). Use this for
|
|
140
|
+
setsid/systemd/docker deployments.
|
|
141
|
+
--no-supervisor Background start without the supervisor
|
|
142
|
+
--restart Restart the supervised server without stopping it
|
|
132
143
|
--status Show background server status
|
|
133
144
|
--stop Stop the background server
|
|
134
145
|
--update Update the global CLI from the official npm registry
|
|
@@ -286,13 +297,13 @@ function removeStateFile() {
|
|
|
286
297
|
// ignore cleanup errors
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
|
-
|
|
300
|
+
/** 只在用户明确要求停止时清 supervisor 记录:它是"为什么停了"的唯一证据。 */
|
|
301
|
+
function removeSupervisorStateFile() {
|
|
290
302
|
try {
|
|
291
|
-
|
|
292
|
-
return true;
|
|
303
|
+
fs.rmSync(supervisorStatePath, { force: true });
|
|
293
304
|
}
|
|
294
305
|
catch {
|
|
295
|
-
|
|
306
|
+
// ignore cleanup errors
|
|
296
307
|
}
|
|
297
308
|
}
|
|
298
309
|
function getRunningState() {
|
|
@@ -346,6 +357,48 @@ function resolveHttpsOptions(options) {
|
|
|
346
357
|
}
|
|
347
358
|
return { source: 'none' };
|
|
348
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* 自己这个 entry 的绝对路径,解析掉符号链接。
|
|
362
|
+
*
|
|
363
|
+
* 必须 realpath:全局安装时 `bin/termdock` 是指向 `dist/server/cli.js` 的软链,
|
|
364
|
+
* 而 `npm i -g` 会先 unlink 再重建它——那个窗口里按软链路径 spawn 会 ENOENT。
|
|
365
|
+
* 解析成真实路径之后,重启落到的永远是同一个安装。
|
|
366
|
+
*/
|
|
367
|
+
function resolveSelfEntry() {
|
|
368
|
+
const entry = path.resolve(process.argv[1]);
|
|
369
|
+
try {
|
|
370
|
+
return fs.realpathSync(entry);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return entry;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** supervisor 之外的子进程参数。`--foreground` 由 supervisor 自己补。 */
|
|
377
|
+
function buildForegroundChildArgs(options, https) {
|
|
378
|
+
const childArgs = [];
|
|
379
|
+
if (options.host)
|
|
380
|
+
childArgs.push('--host', options.host);
|
|
381
|
+
if (options.port)
|
|
382
|
+
childArgs.push('--port', String(options.port));
|
|
383
|
+
if (https.cert)
|
|
384
|
+
childArgs.push('--https-cert', https.cert);
|
|
385
|
+
if (https.key)
|
|
386
|
+
childArgs.push('--https-key', https.key);
|
|
387
|
+
if (https.ca)
|
|
388
|
+
childArgs.push('--https-ca', https.ca);
|
|
389
|
+
return childArgs;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 给 supervisor 用的健康探测目标。host 为 0.0.0.0 时探 localhost——
|
|
393
|
+
* 0.0.0.0 只是"监听所有网卡"的写法,不是可连接的地址。
|
|
394
|
+
*/
|
|
395
|
+
function resolveHealthTarget(options, https) {
|
|
396
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
397
|
+
const port = options.port ?? PORT.backend;
|
|
398
|
+
const scheme = https.cert && https.key ? 'https' : 'http';
|
|
399
|
+
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
400
|
+
return { url: `${scheme}://${displayHost}:${port}/health`, scheme, host: displayHost, port };
|
|
401
|
+
}
|
|
349
402
|
const EXTRA_EXECUTABLE_DIRS = ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
350
403
|
function executableCandidates(command) {
|
|
351
404
|
if (command.includes(path.sep))
|
|
@@ -479,6 +532,88 @@ function printRunningState(state) {
|
|
|
479
532
|
console.log(` ${c.dim('Log:')} ${state.logFile}`);
|
|
480
533
|
console.log(` ${c.dim('Auth:')} ${authLine}`);
|
|
481
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* 退出事件 → 人话。机器可读的 event 保留在 crash.log / supervisor.json 里。
|
|
537
|
+
*
|
|
538
|
+
* 这里只翻译"发生了什么",**不重复技术细节**——退出码/信号就在同一行的括号里
|
|
539
|
+
* (`incident.detail` 与 signal/exitCode 字段),两边各说一遍会读成
|
|
540
|
+
* "was killed (SIGKILL) (signal SIGKILL)"。
|
|
541
|
+
*/
|
|
542
|
+
const SUPERVISOR_EVENT_LABELS = {
|
|
543
|
+
'stop': 'stopped on request',
|
|
544
|
+
'update-restart': 'restarted to apply an update',
|
|
545
|
+
'manual-restart': 'restarted on request',
|
|
546
|
+
'startup-failure': 'failed to start',
|
|
547
|
+
'exit-zero-unexpected': 'exited with code 0 without being asked to',
|
|
548
|
+
'crash': 'crashed',
|
|
549
|
+
'crash-native': 'crashed in native code',
|
|
550
|
+
'killed': 'was killed outright',
|
|
551
|
+
'terminated-externally': 'was terminated by another process',
|
|
552
|
+
'port-conflict': 'could not start: the port is already in use',
|
|
553
|
+
'wedge': 'stopped responding and had to be killed',
|
|
554
|
+
'gave-up': 'crashed repeatedly; automatic restart stopped',
|
|
555
|
+
'supervisor-lost': 'lost its supervisor (service kept running)',
|
|
556
|
+
};
|
|
557
|
+
function describeIncident(incident) {
|
|
558
|
+
return SUPERVISOR_EVENT_LABELS[incident.event] ?? incident.event;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* 我们自己发起的事件:label 已经把话说完了,`detail` 只是换个说法再说一遍
|
|
562
|
+
* ("restarted on request" / "restart requested by termdock --restart")。
|
|
563
|
+
*/
|
|
564
|
+
const SELF_EXPLANATORY_EVENTS = new Set(['stop', 'update-restart', 'manual-restart']);
|
|
565
|
+
/** 括号里的技术细节:`detail` 是主句,信号/退出码只在它没说的时候补上。 */
|
|
566
|
+
function describeIncidentFacts(incident) {
|
|
567
|
+
const uptime = incident.uptimeMs >= 1_000 ? `${Math.round(incident.uptimeMs / 1_000)}s` : `${incident.uptimeMs}ms`;
|
|
568
|
+
const facts = [];
|
|
569
|
+
if (!SELF_EXPLANATORY_EVENTS.has(incident.event) && incident.detail)
|
|
570
|
+
facts.push(incident.detail);
|
|
571
|
+
if (incident.signal && !incident.detail.includes(incident.signal))
|
|
572
|
+
facts.push(`signal ${incident.signal}`);
|
|
573
|
+
if (incident.exitCode !== null && !incident.detail.includes(String(incident.exitCode)))
|
|
574
|
+
facts.push(`exit ${incident.exitCode}`);
|
|
575
|
+
facts.push(`up ${uptime}`);
|
|
576
|
+
return facts.filter(Boolean).join(', ');
|
|
577
|
+
}
|
|
578
|
+
function printSupervisorState(status, serverRunning) {
|
|
579
|
+
if (!status) {
|
|
580
|
+
console.log(` ${c.dim('Supervisor:')} ${c.yellow('none — a crash will not be restarted')}`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const { state, alive } = status;
|
|
584
|
+
if (alive) {
|
|
585
|
+
const label = state.phase === 'gave-up' ? c.red(state.phase) : c.green(state.phase);
|
|
586
|
+
console.log(` ${c.dim('Supervisor:')} pid ${state.pid} ${label}${state.restarts > 0 ? c.dim(` (restarted ${state.restarts}×)`) : ''}`);
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
// 进程没了,但状态文件是一份结论而非垃圾——give-up 尤其如此。
|
|
590
|
+
console.log(` ${c.dim('Supervisor:')} pid ${state.pid} ${c.red(`not running (last phase: ${state.phase})`)}`);
|
|
591
|
+
}
|
|
592
|
+
if (state.lastIncident) {
|
|
593
|
+
const incident = state.lastIncident;
|
|
594
|
+
const when = new Date(incident.at).toISOString();
|
|
595
|
+
console.log(` ${c.dim('Last incident:')} ${when} — ${describeIncident(incident)} ${c.dim(`(${describeIncidentFacts(incident)})`)}`);
|
|
596
|
+
if (incident.oomSuspect)
|
|
597
|
+
console.log(` ${c.dim('Note:')} ${c.yellow('memory was near the limit before it died')}`);
|
|
598
|
+
}
|
|
599
|
+
if (state.phase === 'gave-up') {
|
|
600
|
+
console.log(` ${ICON.err} ${c.red('Automatic restart stopped after repeated crashes. The service is NOT running.')}`);
|
|
601
|
+
console.log(` ${c.dim('Reset with:')} ${c.cyan('td --stop && td')}`);
|
|
602
|
+
if (state.lastIncident?.version)
|
|
603
|
+
console.log(` ${c.dim('Roll back with:')} ${c.cyan(`npm i -g termdock@${state.lastIncident.version}`)}`);
|
|
604
|
+
}
|
|
605
|
+
else if (!alive) {
|
|
606
|
+
console.log(` ${ICON.err} ${c.red(serverRunning
|
|
607
|
+
? 'The supervisor is gone — the service is running unsupervised.'
|
|
608
|
+
: 'The supervisor is gone and the service is not running.')}`);
|
|
609
|
+
if (serverRunning) {
|
|
610
|
+
console.log(` ${c.dim('Restore supervision with:')} ${c.cyan('td --stop && td')} ${c.dim('(this restarts the service)')}`);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
console.log(` ${c.dim('Start again with:')} ${c.cyan('td')}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
482
617
|
function parseArgs(argv) {
|
|
483
618
|
let host;
|
|
484
619
|
let port;
|
|
@@ -487,6 +622,9 @@ function parseArgs(argv) {
|
|
|
487
622
|
let httpsCa;
|
|
488
623
|
let setupLocalHttps = false;
|
|
489
624
|
let foreground = false;
|
|
625
|
+
let supervise = false;
|
|
626
|
+
let noSupervisor = false;
|
|
627
|
+
let restart = false;
|
|
490
628
|
let status = false;
|
|
491
629
|
let stop = false;
|
|
492
630
|
let update = false;
|
|
@@ -770,6 +908,18 @@ function parseArgs(argv) {
|
|
|
770
908
|
foreground = true;
|
|
771
909
|
continue;
|
|
772
910
|
}
|
|
911
|
+
if (arg === '--supervise') {
|
|
912
|
+
supervise = true;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (arg === '--no-supervisor') {
|
|
916
|
+
noSupervisor = true;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (arg === '--restart') {
|
|
920
|
+
restart = true;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
773
923
|
if (arg === '--status') {
|
|
774
924
|
status = true;
|
|
775
925
|
continue;
|
|
@@ -997,6 +1147,9 @@ function parseArgs(argv) {
|
|
|
997
1147
|
httpsCa,
|
|
998
1148
|
setupLocalHttps,
|
|
999
1149
|
foreground,
|
|
1150
|
+
supervise,
|
|
1151
|
+
noSupervisor,
|
|
1152
|
+
restart,
|
|
1000
1153
|
status,
|
|
1001
1154
|
stop,
|
|
1002
1155
|
update,
|
|
@@ -1385,46 +1538,6 @@ async function runInjectBranchAudit(source) {
|
|
|
1385
1538
|
}
|
|
1386
1539
|
}
|
|
1387
1540
|
const CHANGE_AUDIT_SNAPSHOT_MAX_AGE_MS = 10 * 60 * 1000;
|
|
1388
|
-
/** Grace period for --stop: wait up to this long for the daemon to exit
|
|
1389
|
-
* after SIGTERM before falling back to SIGKILL. */
|
|
1390
|
-
const STOP_GRACE_MS = 5000;
|
|
1391
|
-
/** Max time to wait for the daemon child to respond to /health after spawn. */
|
|
1392
|
-
const DAEMON_START_TIMEOUT_MS = 10000;
|
|
1393
|
-
/** Poll the health endpoint until it responds or timeout expires. */
|
|
1394
|
-
async function waitForHealth(healthUrl, caPath, timeoutMs, isReady, isAlive) {
|
|
1395
|
-
const deadline = Date.now() + timeoutMs;
|
|
1396
|
-
while (Date.now() < deadline) {
|
|
1397
|
-
if (!isAlive())
|
|
1398
|
-
return false;
|
|
1399
|
-
if (!isReady()) {
|
|
1400
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1401
|
-
continue;
|
|
1402
|
-
}
|
|
1403
|
-
try {
|
|
1404
|
-
const ok = await new Promise((resolve) => {
|
|
1405
|
-
const transport = healthUrl.startsWith('https:') ? https : http;
|
|
1406
|
-
const req = transport.get(healthUrl, {
|
|
1407
|
-
ca: caPath ? fs.readFileSync(caPath) : undefined,
|
|
1408
|
-
rejectUnauthorized: Boolean(caPath),
|
|
1409
|
-
timeout: 2000,
|
|
1410
|
-
}, (res) => {
|
|
1411
|
-
let data = '';
|
|
1412
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
1413
|
-
res.on('end', () => resolve(data.includes('"status":"ok"')));
|
|
1414
|
-
});
|
|
1415
|
-
req.on('error', () => resolve(false));
|
|
1416
|
-
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
1417
|
-
});
|
|
1418
|
-
if (ok && isAlive())
|
|
1419
|
-
return true;
|
|
1420
|
-
}
|
|
1421
|
-
catch {
|
|
1422
|
-
// Retry on any error.
|
|
1423
|
-
}
|
|
1424
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
1425
|
-
}
|
|
1426
|
-
return false;
|
|
1427
|
-
}
|
|
1428
1541
|
function fnv1a32(text) {
|
|
1429
1542
|
let hash = 0x811c9dc5;
|
|
1430
1543
|
for (let i = 0; i < text.length; i += 1) {
|
|
@@ -3429,21 +3542,89 @@ async function main() {
|
|
|
3429
3542
|
process.exit(0);
|
|
3430
3543
|
}
|
|
3431
3544
|
if (options.status) {
|
|
3545
|
+
const supervisorStatus = getSupervisorStatus();
|
|
3432
3546
|
const runningState = getRunningState();
|
|
3433
|
-
if (!runningState) {
|
|
3547
|
+
if (!runningState && !supervisorStatus) {
|
|
3434
3548
|
console.log(`${ICON.info} ${c.dim('Termdock is not running.')}`);
|
|
3435
3549
|
process.exit(0);
|
|
3436
3550
|
}
|
|
3437
|
-
|
|
3551
|
+
// supervisor 还活着但服务没起来(重启中 / 已放弃)也必须报出来——
|
|
3552
|
+
// 只说"未运行"就把这个功能的意义抹掉了。
|
|
3553
|
+
if (runningState)
|
|
3554
|
+
printRunningState(runningState);
|
|
3555
|
+
else
|
|
3556
|
+
console.log(`${ICON.err} ${c.red('Termdock is not running.')}`);
|
|
3557
|
+
printSupervisorState(supervisorStatus, Boolean(runningState));
|
|
3558
|
+
process.exit(0);
|
|
3559
|
+
}
|
|
3560
|
+
if (options.restart) {
|
|
3561
|
+
const supervisorStatus = getSupervisorStatus();
|
|
3562
|
+
if (!supervisorStatus?.alive) {
|
|
3563
|
+
console.error(`${ICON.err} ${c.red('Termdock is not supervised, so there is nothing to restart.')}`);
|
|
3564
|
+
console.error(` ${c.dim('Start it with:')} ${c.cyan('td')} ${c.dim('(or restart it by hand with: td --stop && td)')}`);
|
|
3565
|
+
process.exit(1);
|
|
3566
|
+
}
|
|
3567
|
+
if (!signalSupervisorRestart(supervisorStatus.state.pid)) {
|
|
3568
|
+
console.error(`${ICON.err} ${c.red(`Could not signal supervisor ${supervisorStatus.state.pid}.`)}`);
|
|
3569
|
+
process.exit(1);
|
|
3570
|
+
}
|
|
3571
|
+
console.log(`${ICON.ok} ${c.green('Restart requested.')} ${c.dim('Follow along with: tail -f ' + shellQuote(logFilePath))}`);
|
|
3438
3572
|
process.exit(0);
|
|
3439
3573
|
}
|
|
3440
3574
|
if (options.stop) {
|
|
3575
|
+
const supervisorStatus = getSupervisorStatus();
|
|
3441
3576
|
const runningState = getRunningState();
|
|
3442
|
-
if (!runningState) {
|
|
3577
|
+
if (!runningState && !supervisorStatus) {
|
|
3443
3578
|
console.log(`${ICON.info} ${c.dim('Termdock is not running.')}`);
|
|
3444
3579
|
process.exit(0);
|
|
3445
3580
|
}
|
|
3446
3581
|
const bridgeCaffeinateStarted = startRestartBridgeCaffeinate();
|
|
3582
|
+
if (supervisorStatus) {
|
|
3583
|
+
const { state: supervisorState, alive } = supervisorStatus;
|
|
3584
|
+
// 只停 supervisor:子进程必须由它自己杀,否则会变成"意外死亡"被它自己拉起来。
|
|
3585
|
+
// (supervisor 已经死了的情况也走这里——兜底段会把它的孤儿服务子进程收掉。)
|
|
3586
|
+
if (alive)
|
|
3587
|
+
await stopSupervisor(supervisorState.pid);
|
|
3588
|
+
// 兜底:supervisor 没来得及收尾就走了的话,服务子进程可能还在。
|
|
3589
|
+
const serverPid = supervisorState.serverPid;
|
|
3590
|
+
const orphanWasRunning = Boolean(serverPid && isProcessRunning(serverPid));
|
|
3591
|
+
if (orphanWasRunning && serverPid) {
|
|
3592
|
+
try {
|
|
3593
|
+
process.kill(serverPid, 'SIGTERM');
|
|
3594
|
+
}
|
|
3595
|
+
catch { /* already gone */ }
|
|
3596
|
+
const deadline = Date.now() + STOP_GRACE_MS;
|
|
3597
|
+
while (isProcessRunning(serverPid) && Date.now() < deadline) {
|
|
3598
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
3599
|
+
}
|
|
3600
|
+
if (isProcessRunning(serverPid)) {
|
|
3601
|
+
try {
|
|
3602
|
+
process.kill(serverPid, 'SIGKILL');
|
|
3603
|
+
}
|
|
3604
|
+
catch { /* already gone */ }
|
|
3605
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
removeStateFile();
|
|
3609
|
+
// supervisor 已经死掉时(例如崩溃循环后放弃),状态文件是唯一还留着那段结论的地方;
|
|
3610
|
+
// `--stop` 是用户明确说"到此为止"的动作,此时才该把它清掉。
|
|
3611
|
+
removeSupervisorStateFile();
|
|
3612
|
+
// 说实话:到底是"停了服务"还是"只是清了一份记录",取决于刚才谁还活着。
|
|
3613
|
+
const summary = alive
|
|
3614
|
+
? `Stopped Termdock (supervisor ${supervisorState.pid}${serverPid ? `, server ${serverPid}` : ''}).`
|
|
3615
|
+
: orphanWasRunning
|
|
3616
|
+
? `Stopped Termdock (server ${serverPid}). The supervisor (pid ${supervisorState.pid}) had already stopped.`
|
|
3617
|
+
: `Cleared the record of the stopped supervisor (pid ${supervisorState.pid}).`;
|
|
3618
|
+
console.log(`${ICON.ok} ${c.green(summary)}`);
|
|
3619
|
+
if (bridgeCaffeinateStarted) {
|
|
3620
|
+
console.log(`${ICON.info} ${c.dim(`Keeping macOS awake for up to ${restartBridgeCaffeinateSeconds}s while Termdock restarts.`)}`);
|
|
3621
|
+
}
|
|
3622
|
+
process.exit(0);
|
|
3623
|
+
}
|
|
3624
|
+
if (!runningState) {
|
|
3625
|
+
console.log(`${ICON.info} ${c.dim('Termdock is not running.')}`);
|
|
3626
|
+
process.exit(0);
|
|
3627
|
+
}
|
|
3447
3628
|
process.kill(runningState.pid, 'SIGTERM');
|
|
3448
3629
|
removeStateFile();
|
|
3449
3630
|
// Wait for the process to actually exit — graceful shutdown (session
|
|
@@ -3468,6 +3649,24 @@ async function main() {
|
|
|
3468
3649
|
await refreshDefaultHttpsCertificateSafely();
|
|
3469
3650
|
const https = resolveHttpsOptions(options);
|
|
3470
3651
|
const isManagedDefaultHttps = Boolean(https.cert === defaultHttpsCertPath && https.key === defaultHttpsKeyPath);
|
|
3652
|
+
if (options.supervise && !options.foreground) {
|
|
3653
|
+
// 前台运行 + 受监督:setsid/systemd/docker 部署走这条。这是**正式服务**的路径——
|
|
3654
|
+
// `--foreground` 按设计不受管,而出问题的恰恰是正式服务。
|
|
3655
|
+
const { runSupervisor } = await import('./supervisor.js');
|
|
3656
|
+
const health = resolveHealthTarget(options, https);
|
|
3657
|
+
runSupervisor({
|
|
3658
|
+
childEntry: resolveSelfEntry(),
|
|
3659
|
+
childArgs: buildForegroundChildArgs(options, https),
|
|
3660
|
+
healthUrl: health.url,
|
|
3661
|
+
healthCaPath: https.ca,
|
|
3662
|
+
version: getTermdockVersion(),
|
|
3663
|
+
});
|
|
3664
|
+
console.log(`${ICON.ok} ${c.green('Termdock is under supervision.')}`);
|
|
3665
|
+
console.log(` ${c.dim('Health:')} ${health.url}`);
|
|
3666
|
+
console.log(` ${c.dim('State:')} ${supervisorStatePath}`);
|
|
3667
|
+
console.log(` ${c.dim('Stop:')} ${c.cyan('td --stop')} ${c.dim('(supervisor and server)')}`);
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3471
3670
|
if (options.foreground) {
|
|
3472
3671
|
// Boot check (marker prevents re-run if parent already completed)
|
|
3473
3672
|
runBootChecks();
|
|
@@ -3539,10 +3738,15 @@ async function main() {
|
|
|
3539
3738
|
});
|
|
3540
3739
|
// Only this child can acknowledge startup; an existing listener on the
|
|
3541
3740
|
// same port must not make the parent report a successful launch.
|
|
3741
|
+
//
|
|
3742
|
+
// 受监督时**不能**在这之后 disconnect:那条 IPC 通道同时是 supervisor 的
|
|
3743
|
+
// 存活信号和重启意图通道,而它恰好在就绪这一刻断掉——最糟的时机。
|
|
3744
|
+
// supervisor 通过 env 里的 TERMDOCK_SUPERVISED 告知自己存在。
|
|
3745
|
+
const supervised = process.env.TERMDOCK_SUPERVISED === '1';
|
|
3542
3746
|
const acknowledgeStartup = () => {
|
|
3543
3747
|
if (process.connected) {
|
|
3544
3748
|
process.send?.({ type: 'termdock-ready' }, () => {
|
|
3545
|
-
if (process.connected)
|
|
3749
|
+
if (process.connected && !supervised)
|
|
3546
3750
|
process.disconnect();
|
|
3547
3751
|
});
|
|
3548
3752
|
}
|
|
@@ -3551,6 +3755,12 @@ async function main() {
|
|
|
3551
3755
|
acknowledgeStartup();
|
|
3552
3756
|
else
|
|
3553
3757
|
result.server.once('listening', acknowledgeStartup);
|
|
3758
|
+
if (supervised) {
|
|
3759
|
+
// supervisor 死了服务还能活(丢监督好过丢服务),但必须留下痕迹。
|
|
3760
|
+
process.on('disconnect', () => {
|
|
3761
|
+
recordSupervisorLost(`supervisor pid ${process.env.TERMDOCK_SUPERVISOR_PID ?? 'unknown'} disconnected`);
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3554
3764
|
return;
|
|
3555
3765
|
}
|
|
3556
3766
|
const runningState = getRunningState();
|
|
@@ -3558,6 +3768,14 @@ async function main() {
|
|
|
3558
3768
|
printRunningState(runningState);
|
|
3559
3769
|
process.exit(0);
|
|
3560
3770
|
}
|
|
3771
|
+
// 服务这一瞬间没在跑,不等于没人管它:supervisor 可能正卡在重启退避里。
|
|
3772
|
+
// 此时再起一个就会有两个 supervisor 抢同一个端口和同一份状态文件。
|
|
3773
|
+
const existingSupervisor = getSupervisorStatus();
|
|
3774
|
+
if (existingSupervisor?.alive) {
|
|
3775
|
+
console.log(`${ICON.info} ${c.green('Termdock is already supervised.')} ${c.dim(`The service is not up this instant, but supervisor ${existingSupervisor.state.pid} is still working on it.`)}`);
|
|
3776
|
+
printSupervisorState(existingSupervisor, false);
|
|
3777
|
+
process.exit(0);
|
|
3778
|
+
}
|
|
3561
3779
|
// —— Boot check: 确保原生模块和系统依赖可用 ——
|
|
3562
3780
|
const bootChecks = runBootChecks();
|
|
3563
3781
|
if (!bootChecks.nodePty.ok || !bootChecks.tmux.ok) {
|
|
@@ -3572,68 +3790,87 @@ async function main() {
|
|
|
3572
3790
|
const refreshedHttps = resolveHttpsOptions(options);
|
|
3573
3791
|
const activeHttps = refreshedHttps;
|
|
3574
3792
|
ensureStateDir();
|
|
3575
|
-
const
|
|
3793
|
+
const childEntry = resolveSelfEntry();
|
|
3794
|
+
const childArgs = buildForegroundChildArgs(options, activeHttps);
|
|
3576
3795
|
const childHost = options.host ?? DEFAULT_HOST;
|
|
3577
3796
|
const childPort = options.port ?? PORT.backend;
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
if (activeHttps.cert) {
|
|
3585
|
-
childArgs.push('--https-cert', activeHttps.cert);
|
|
3586
|
-
}
|
|
3587
|
-
if (activeHttps.key) {
|
|
3588
|
-
childArgs.push('--https-key', activeHttps.key);
|
|
3589
|
-
}
|
|
3590
|
-
if (activeHttps.ca) {
|
|
3591
|
-
childArgs.push('--https-ca', activeHttps.ca);
|
|
3592
|
-
}
|
|
3593
|
-
const scheme = activeHttps.cert && activeHttps.key ? 'https' : 'http';
|
|
3797
|
+
const health = resolveHealthTarget(options, activeHttps);
|
|
3798
|
+
const scheme = health.scheme;
|
|
3799
|
+
const displayHost = health.host;
|
|
3800
|
+
// 默认受管:受 supervisor 盯着的服务才会在崩溃/更新失败后自己回来。
|
|
3801
|
+
// 退路留给"我就是要一个裸进程"的场景(调试、被外部进程管理器接管)。
|
|
3802
|
+
const supervised = !options.noSupervisor && process.env.TERMDOCK_SUPERVISOR !== '0';
|
|
3594
3803
|
const logFileFd = fs.openSync(logFilePath, 'a');
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3804
|
+
let startedPid;
|
|
3805
|
+
if (supervised) {
|
|
3806
|
+
const launch = await spawnSupervisor({
|
|
3807
|
+
childEntry,
|
|
3808
|
+
childArgs,
|
|
3809
|
+
healthUrl: health.url,
|
|
3810
|
+
healthCaPath: activeHttps.ca,
|
|
3811
|
+
version: getTermdockVersion(),
|
|
3812
|
+
logFileFd,
|
|
3813
|
+
// 比 CLI 自己盲等的 10s 宽裕:supervisor 会在子进程一失败就回报,
|
|
3814
|
+
// 这里只是兜底上限,不该成为慢机器上的假阴性。
|
|
3815
|
+
readyTimeoutMs: DAEMON_START_TIMEOUT_MS * 3,
|
|
3816
|
+
});
|
|
3817
|
+
fs.closeSync(logFileFd);
|
|
3818
|
+
startedPid = launch.pid;
|
|
3819
|
+
if (!launch.ok) {
|
|
3820
|
+
const detail = launch.detail ? `${launch.reason}: ${launch.detail}` : String(launch.reason);
|
|
3821
|
+
console.error(`${ICON.err} ${c.red('Termdock startup could not be confirmed.')} ${detail}`);
|
|
3822
|
+
console.error(` ${c.dim('Note:')} the supervisor keeps retrying in the background — check with: td --status`);
|
|
3823
|
+
console.error(` ${c.dim('Log:')} ${logFilePath}`);
|
|
3824
|
+
console.error(` Read the startup error with: tail -n 80 ${shellQuote(logFilePath)}`);
|
|
3825
|
+
process.exitCode = 1;
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
else {
|
|
3830
|
+
const child = spawn(process.execPath, [childEntry, '--foreground', ...childArgs], {
|
|
3831
|
+
detached: true,
|
|
3832
|
+
stdio: ['ignore', logFileFd, logFileFd, 'ipc'],
|
|
3833
|
+
});
|
|
3834
|
+
fs.closeSync(logFileFd);
|
|
3835
|
+
startedPid = child.pid;
|
|
3836
|
+
let ready = false;
|
|
3837
|
+
let startupError;
|
|
3838
|
+
child.on('error', (error) => { startupError = error; });
|
|
3839
|
+
child.on('message', (message) => {
|
|
3840
|
+
if (message && typeof message === 'object' && 'type' in message
|
|
3841
|
+
&& message.type === 'termdock-ready')
|
|
3842
|
+
ready = true;
|
|
3843
|
+
});
|
|
3844
|
+
const isAlive = () => !startupError && child.pid !== undefined
|
|
3845
|
+
&& child.exitCode === null && child.signalCode === null && isProcessRunning(child.pid);
|
|
3846
|
+
const healthOk = await waitForHealth(health.url, activeHttps.ca, DAEMON_START_TIMEOUT_MS, () => ready, isAlive);
|
|
3847
|
+
if (child.connected)
|
|
3848
|
+
child.disconnect();
|
|
3849
|
+
child.unref();
|
|
3850
|
+
if (!healthOk) {
|
|
3851
|
+
const alive = isAlive();
|
|
3852
|
+
if (!alive && child.pid !== undefined && readState()?.pid === child.pid)
|
|
3853
|
+
removeStateFile();
|
|
3854
|
+
const detail = startupError?.message ?? (alive
|
|
3855
|
+
? 'Startup timed out; the process is still running but readiness could not be confirmed.'
|
|
3856
|
+
: `Background process exited (code: ${child.exitCode ?? 'unknown'}, signal: ${child.signalCode ?? 'none'}).`);
|
|
3857
|
+
console.error(`${ICON.err} ${c.red('Termdock startup could not be confirmed.')} ${detail}`);
|
|
3858
|
+
console.error(` ${c.dim('Log:')} ${logFilePath}`);
|
|
3859
|
+
console.error(` Read the startup error with: tail -n 80 ${shellQuote(logFilePath)}`);
|
|
3860
|
+
process.exitCode = 1;
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3627
3863
|
}
|
|
3628
3864
|
console.log(`${ICON.ok} ${c.green('Termdock started in background.')}`);
|
|
3629
|
-
console.log(` ${c.dim('URL:')} ${c.cyan(`${scheme}://${
|
|
3865
|
+
console.log(` ${c.dim('URL:')} ${c.cyan(`${scheme}://${displayHost}:${childPort}`)}`);
|
|
3630
3866
|
if (scheme === 'https') {
|
|
3631
3867
|
console.log(` ${c.dim('HTTPS:')} ${activeHttps.source === 'default' ? c.green('auto') : c.green('enabled')}`);
|
|
3632
3868
|
}
|
|
3633
3869
|
else {
|
|
3634
3870
|
console.log(` ${c.dim('HTTPS:')} ${c.dim('not configured — run td --setup-local-https')}`);
|
|
3635
3871
|
}
|
|
3636
|
-
console.log(` ${c.dim('PID:')} ${
|
|
3872
|
+
console.log(` ${c.dim('PID:')} ${startedPid}`);
|
|
3873
|
+
console.log(` ${c.dim('Supervised:')} ${supervised ? c.green('yes') : c.yellow('no — a crash will not be restarted')}`);
|
|
3637
3874
|
console.log(` ${c.dim('Log:')} ${logFilePath}`);
|
|
3638
3875
|
warnIfAuthDisabled(childHost);
|
|
3639
3876
|
process.exit(0);
|
package/dist/server/entry.js
CHANGED
|
@@ -49,6 +49,7 @@ import { startOnboardingServer, stopOnboardingServer, getOnboardingServerUrl } f
|
|
|
49
49
|
import { CertificateWatcher } from './certificateWatcher.js';
|
|
50
50
|
import { startTermdockLogMaintenance, writeDiffTraceLog, writeErrorLog, writeJsonLog, writeTextLog, } from './utils/serverLogger.js';
|
|
51
51
|
import { pinBundledRuntimeClientDist, resolveRuntimeClientDist } from './utils/runtimeClient.js';
|
|
52
|
+
import { installCrashForensics } from './utils/crashForensics.js';
|
|
52
53
|
import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, } from './utils/version.js';
|
|
53
54
|
import { PORT, DEFAULT_HOST } from './config.js';
|
|
54
55
|
const CLIENT_STATE_COOKIE = 'termdock-client';
|
|
@@ -398,6 +399,12 @@ function reloadHttpsCertificate(server, options) {
|
|
|
398
399
|
export function startServer(options = {}) {
|
|
399
400
|
assertPublicSecurity();
|
|
400
401
|
const stopLogMaintenance = startTermdockLogMaintenance();
|
|
402
|
+
// 装上之后,"运行中挂了"至少留得下一条能说清原因的记录(谁重启它是 supervisor 的事)。
|
|
403
|
+
const crashForensics = installCrashForensics({
|
|
404
|
+
role: 'server',
|
|
405
|
+
version: getTermdockVersion(),
|
|
406
|
+
startedAt: Date.now(),
|
|
407
|
+
});
|
|
401
408
|
const port = options.port ?? Number(process.env.PORT || DEFAULT_PORT);
|
|
402
409
|
const host = options.host ?? (process.env.HOST || DEFAULT_HOST);
|
|
403
410
|
if (process.env.TERMDOCK_PUBLIC_ORIGIN && !(options.httpsCertPath && options.httpsKeyPath) && !['127.0.0.1', '::1', 'localhost'].includes(host)) {
|
|
@@ -761,6 +768,9 @@ export function startServer(options = {}) {
|
|
|
761
768
|
});
|
|
762
769
|
server.on('error', (error) => {
|
|
763
770
|
if (error.code === 'EADDRINUSE') {
|
|
771
|
+
// 记一条再退:不然 supervisor 只看到一个 exit code 1,分不清"端口被占"
|
|
772
|
+
// 和"代码崩了",而这两者的处置方式完全不同(前者重启多少次都没用)。
|
|
773
|
+
crashForensics.recordIncident('port-conflict', { extra: { port, host, code: error.code } });
|
|
764
774
|
console.error(`Port ${port} is already in use.`);
|
|
765
775
|
console.error(`To free it, find and stop the process: lsof -tiTCP:${port} -sTCP:LISTEN | xargs kill`);
|
|
766
776
|
process.exit(1);
|
|
@@ -770,6 +780,7 @@ export function startServer(options = {}) {
|
|
|
770
780
|
});
|
|
771
781
|
server.on('close', () => {
|
|
772
782
|
stopLogMaintenance();
|
|
783
|
+
crashForensics.dispose();
|
|
773
784
|
certWatcher.stop();
|
|
774
785
|
stopOnboardingServer();
|
|
775
786
|
void localAccessManager.stop();
|