@askexenow/exe-os 0.9.372 → 0.9.374
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/deploy/compose/.env.customer.example +45 -10
- package/deploy/compose/.env.example +46 -9
- package/deploy/compose/docker-compose.yml +136 -18
- package/deploy/compose/generate-env.ts +142 -34
- package/deploy/compose/sso-edge/default.conf.template +78 -7
- package/deploy/compose/sso-edge/sso-redirect.conf +109 -13
- package/deploy/stack-manifests/v0.9.30-apps.json +1 -1
- package/deploy/stack-manifests/v0.9.json +4 -4
- package/dist/.dist-verified.json +3 -3
- package/dist/{active-agent-GRT5MKD5.js → active-agent-ONPPEWQV.js} +7 -6
- package/dist/{active-agent-L2ZSSXDK.js → active-agent-UJKMG7LJ.js} +7 -6
- package/dist/{agent-defs-generator-M4ZNYIVX.js → agent-defs-generator-ZWI6DXOE.js} +6 -5
- package/dist/{agent-loop-FOK2IRZQ.js → agent-loop-OYHJKNYH.js} +1 -1
- package/dist/{agentic-ontology-DMPRDEUX.js → agentic-ontology-WHFJ3QFS.js} +1 -1
- package/dist/{artifact-backup-767STHSW.js → artifact-backup-WXZNB7KE.js} +2 -1
- package/dist/{backfill-gate-VX2RZTQV.js → backfill-gate-KWJQ35YX.js} +3 -2
- package/dist/{backfill-metadata-ZMEHJQ5M.js → backfill-metadata-3OUSQD5V.js} +8 -7
- package/dist/{background-jobs-LTPWCMA3.js → background-jobs-6VMD55PK.js} +3 -2
- package/dist/{backup-verify-EX2K732J.js → backup-verify-ZKTUYLTA.js} +4 -3
- package/dist/{behavior-injection-22AGMS25.js → behavior-injection-M6UYZCMW.js} +17 -15
- package/dist/{behaviors-DVFXAWF5.js → behaviors-ZJ45TFBJ.js} +21 -13
- package/dist/bin/age-ontology-load.js +3 -2
- package/dist/bin/agentic-ontology-backfill.js +10 -9
- package/dist/bin/agentic-reflection-backfill.js +11 -10
- package/dist/bin/agentic-semantic-label.js +10 -9
- package/dist/bin/backfill-conversations.js +13 -11
- package/dist/bin/backfill-responses.js +13 -11
- package/dist/bin/backfill-vectors.js +17 -15
- package/dist/bin/bulk-sync-postgres.js +22 -20
- package/dist/bin/cc-doctor.js +10 -9
- package/dist/bin/cleanup-stale-review-tasks.js +29 -26
- package/dist/bin/cli.js +36 -26
- package/dist/bin/deferred-daemon-restart.js +114 -110
- package/dist/bin/exe-agent-config.js +6 -5
- package/dist/bin/exe-agent.js +127 -33
- package/dist/bin/exe-assign.js +15 -13
- package/dist/bin/exe-boot.js +57 -54
- package/dist/bin/exe-call.js +8 -7
- package/dist/bin/exe-cloud.js +24 -22
- package/dist/bin/exe-config-dump.js +6 -5
- package/dist/bin/exe-dispatch.js +30 -27
- package/dist/bin/exe-doctor.js +14 -10
- package/dist/bin/exe-export-behaviors.js +19 -17
- package/dist/bin/exe-forget.js +10 -9
- package/dist/bin/exe-gateway.js +199 -19
- package/dist/bin/exe-grant.js +5 -5
- package/dist/bin/exe-healthcheck.js +10 -9
- package/dist/bin/exe-heartbeat.js +35 -32
- package/dist/bin/exe-kill.js +34 -31
- package/dist/bin/exe-launch-agent.js +126 -54
- package/dist/bin/exe-mcp.js +6 -5
- package/dist/bin/exe-memory-reads.js +8 -7
- package/dist/bin/exe-new-employee.js +12 -11
- package/dist/bin/exe-os-mcp-proxy.js +6 -5
- package/dist/bin/exe-pending-messages.js +30 -27
- package/dist/bin/exe-pending-notifications.js +29 -26
- package/dist/bin/exe-pending-reviews.js +30 -27
- package/dist/bin/exe-rename.js +8 -7
- package/dist/bin/exe-repo-truth.js +1 -1
- package/dist/bin/exe-review.js +33 -30
- package/dist/bin/exe-search.js +9 -8
- package/dist/bin/exe-session-cleanup.js +35 -32
- package/dist/bin/exe-settings.js +24 -22
- package/dist/bin/exe-start-codex.js +137 -55
- package/dist/bin/exe-start-opencode.js +121 -23
- package/dist/bin/exe-status.js +31 -28
- package/dist/bin/exe-support.js +5 -4
- package/dist/bin/exe-team.js +7 -6
- package/dist/bin/exe-watchdog.js +31 -11
- package/dist/bin/generate-hook-manifest.js +44 -7
- package/dist/bin/git-sweep.js +31 -28
- package/dist/bin/graph-backfill.js +89 -35
- package/dist/bin/graph-export.js +9 -8
- package/dist/bin/import-history.js +14 -13
- package/dist/bin/install.js +137 -113
- package/dist/bin/intercom-check.js +5 -5
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +7 -6
- package/dist/bin/postgres-agentic-reflection-backfill.js +5 -4
- package/dist/bin/postgres-agentic-semantic-backfill.js +4 -3
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/reconcile-backlog.js +2 -2
- package/dist/bin/registry-proxy.js +1 -1
- package/dist/bin/scan-tasks.js +29 -26
- package/dist/bin/setup.js +5 -4
- package/dist/bin/shard-migrate.js +9 -8
- package/dist/bin/stack-update.js +40 -9
- package/dist/bin/verify-stack.js +2 -2
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{boot-generation-OHXH4YC5.js → boot-generation-JKVZ4ZAT.js} +6 -5
- package/dist/{boot-review-sections-N5IXIGSS.js → boot-review-sections-2M6BTG73.js} +28 -25
- package/dist/{branch-hygiene-KCGXRAWP.js → branch-hygiene-U5GFRSTP.js} +31 -7
- package/dist/{branding-GH43W3PU.js → branding-I6KIMB7F.js} +3 -2
- package/dist/budget-fallback-XJRPMP2D.js +11 -0
- package/dist/build-manifest.json +1318 -1233
- package/dist/{capability-cards-4PXVLIVS.js → capability-cards-5IMJT6J7.js} +5 -4
- package/dist/{capacity-monitor-FKMQXORX.js → capacity-monitor-SAXCQK7O.js} +30 -27
- package/dist/{catchup-brief-SN5EKO4N.js → catchup-brief-U2TXBLHD.js} +34 -31
- package/dist/{chunk-CNC57JCS.js → chunk-23MVDXLY.js} +4 -4
- package/dist/{chunk-V4XAKXXF.js → chunk-2AJGV5UL.js} +1 -1
- package/dist/{chunk-FBYAXTDM.js → chunk-2ZOEC4KC.js} +10 -2
- package/dist/{chunk-ELXFSIIM.js → chunk-32YV32VA.js} +2 -2
- package/dist/{chunk-44C7QNZS.js → chunk-3KDCORDO.js} +1 -1
- package/dist/{chunk-JTB5V6BV.js → chunk-3SXHEQ7X.js} +1 -1
- package/dist/{chunk-HDW4XPDV.js → chunk-3UUX5OY4.js} +1 -1
- package/dist/{chunk-7PSLK36P.js → chunk-44TLJZJ5.js} +1 -1
- package/dist/{chunk-JTWTBX2M.js → chunk-4EN3IZ3Q.js} +2 -1
- package/dist/{chunk-KPMC7PY7.js → chunk-4HKTO2SV.js} +1 -1
- package/dist/{chunk-CDUB5Q6J.js → chunk-4IK2K3B3.js} +1 -1
- package/dist/{chunk-GH6YJOOC.js → chunk-4SJ4LK4V.js} +6 -6
- package/dist/{chunk-PVEM537A.js → chunk-4YLBKEL5.js} +2 -2
- package/dist/chunk-4YNA3CM6.js +872 -0
- package/dist/{chunk-KYFFVTGO.js → chunk-55BEFF4V.js} +3 -3
- package/dist/{chunk-SEP5ITFQ.js → chunk-636LKCKE.js} +2 -2
- package/dist/{chunk-QTI4QAWA.js → chunk-6EZQUNE6.js} +338 -5
- package/dist/{chunk-WAEKYKTK.js → chunk-6RKKVPYF.js} +1 -1
- package/dist/{chunk-VX2F4ZUY.js → chunk-6SCGLHJ7.js} +3 -3
- package/dist/{chunk-MEGRQ53A.js → chunk-6VHOQ3IM.js} +51 -6
- package/dist/{chunk-HIIXCAEE.js → chunk-6YVUR3OU.js} +3 -3
- package/dist/{chunk-L6ZNURWN.js → chunk-7GE2EQ23.js} +1 -1
- package/dist/{chunk-2CWS374F.js → chunk-7RI2CNXJ.js} +1 -1
- package/dist/{chunk-OL3QP3AU.js → chunk-7TSBV3SD.js} +1 -1
- package/dist/{chunk-DSOQUQTV.js → chunk-A27Q27CT.js} +27 -0
- package/dist/{chunk-5AHRZI3R.js → chunk-AETLDRZB.js} +1 -1
- package/dist/{chunk-RNLF3CBB.js → chunk-AKLJ4JH3.js} +1 -1
- package/dist/{chunk-XHYHX6F7.js → chunk-AKLQSC6W.js} +329 -164
- package/dist/chunk-ANLFH23R.js +261 -0
- package/dist/{chunk-QLA3RTH4.js → chunk-AWWKRZMB.js} +5 -5
- package/dist/{chunk-ERVQTR7P.js → chunk-BBSIEYF5.js} +17 -3
- package/dist/{chunk-IPMUWYRQ.js → chunk-BJ7AVSY6.js} +2 -2
- package/dist/{chunk-HV6Y6F4X.js → chunk-C2L622QJ.js} +89 -3
- package/dist/{chunk-TX3Q2GIL.js → chunk-C5HUMAA7.js} +24 -1
- package/dist/{chunk-QTYJPWJN.js → chunk-CBCGDEJA.js} +7 -7
- package/dist/{chunk-XXSNLM6R.js → chunk-DB55JZHU.js} +1 -1
- package/dist/{chunk-LAZKEJMS.js → chunk-DIXXEE3S.js} +840 -607
- package/dist/{chunk-FB343XQX.js → chunk-DJ7YXOTU.js} +2 -2
- package/dist/{chunk-YFCNWQAM.js → chunk-DJOIMFKV.js} +2 -2
- package/dist/{chunk-UTDMTQNH.js → chunk-DSJI76XL.js} +92 -14
- package/dist/{chunk-EEXX4UMQ.js → chunk-DZKPOSFD.js} +1 -1
- package/dist/{chunk-WSOA3WMC.js → chunk-E426DOSY.js} +161 -4
- package/dist/{chunk-ZISQOOER.js → chunk-EKE37MO4.js} +3 -3
- package/dist/{chunk-RIECV7SR.js → chunk-EKYYMIVT.js} +1 -1
- package/dist/{chunk-HIVZ53YB.js → chunk-EXTZSNWL.js} +2 -2
- package/dist/{chunk-YYA5LXCT.js → chunk-FNEZ4BWT.js} +1 -1
- package/dist/chunk-FTZET5YX.js +313 -0
- package/dist/chunk-GHR4ZPJ7.js +70 -0
- package/dist/{chunk-MINXPXAB.js → chunk-GJI77VDO.js} +3 -3
- package/dist/{chunk-AAMGHW5W.js → chunk-GLQEJOXE.js} +1 -0
- package/dist/{chunk-ADNBOKYZ.js → chunk-GM7TLYOK.js} +60 -18
- package/dist/{chunk-RUWWES2K.js → chunk-GNUAL43X.js} +2 -2
- package/dist/{chunk-QZL6E4UB.js → chunk-GOMQ7PVQ.js} +2 -2
- package/dist/{chunk-GB3AHV5F.js → chunk-GQOEWNVM.js} +1 -1
- package/dist/{chunk-NQZUSEAR.js → chunk-GS7LIT34.js} +246 -17
- package/dist/{chunk-XTWHNLGD.js → chunk-GZOG2MAX.js} +1 -1
- package/dist/{chunk-E5HAY3DQ.js → chunk-HDLAB7W3.js} +1 -1
- package/dist/{chunk-E5QQZDQN.js → chunk-HNWS7YHN.js} +358 -136
- package/dist/{chunk-VV5EZZNT.js → chunk-HTBEVCO2.js} +1 -1
- package/dist/{chunk-OZBII5E3.js → chunk-I4DNJFDR.js} +4 -4
- package/dist/{chunk-SI5SZAJA.js → chunk-IBS2FAI4.js} +21 -4
- package/dist/{chunk-J5EYOBFF.js → chunk-IHXW4VUI.js} +1 -1
- package/dist/{chunk-RUVO3NJC.js → chunk-IN6CAM4F.js} +9 -3
- package/dist/{chunk-53PYNYTW.js → chunk-IRVOXUSK.js} +5 -5
- package/dist/{chunk-THM7J6UI.js → chunk-JHI5WMK5.js} +5 -5
- package/dist/{chunk-3KTQWMHI.js → chunk-JSMDRKPJ.js} +12 -4
- package/dist/chunk-JTVIANUB.js +131 -0
- package/dist/{chunk-2ADYTOHQ.js → chunk-K4ESASPL.js} +1 -1
- package/dist/{chunk-YWUQVHDL.js → chunk-KTCXJWJG.js} +1 -1
- package/dist/{chunk-RGUDGC6A.js → chunk-KTU3EAWK.js} +5 -5
- package/dist/{chunk-DKBPCOU4.js → chunk-L6RXG36J.js} +2 -2
- package/dist/{chunk-T4EK56WI.js → chunk-LDNYSMPZ.js} +1 -1
- package/dist/{chunk-RIK5GRWI.js → chunk-LGS2V2XD.js} +3 -3
- package/dist/{chunk-JKXZGRWR.js → chunk-LP4GB656.js} +2 -2
- package/dist/{chunk-H74466TM.js → chunk-M7O7TGGU.js} +1 -1
- package/dist/chunk-MJCSEIFI.js +54 -0
- package/dist/{chunk-IDZPQG2D.js → chunk-MOGA4Q7F.js} +2 -2
- package/dist/{chunk-OYQNNKNS.js → chunk-MXPNVSXI.js} +37 -14
- package/dist/{chunk-UVW7YFAF.js → chunk-N7VEP5B3.js} +4 -4
- package/dist/{chunk-HHE2HV3V.js → chunk-NBXDXKLC.js} +1 -1
- package/dist/{chunk-LDL3S7AC.js → chunk-NKETJCB5.js} +7 -3
- package/dist/{chunk-ECHLVLA7.js → chunk-NMH5ZQPU.js} +1 -1
- package/dist/{chunk-U3XLRMJD.js → chunk-NT7W4RXD.js} +1 -1
- package/dist/{chunk-UQUUNGTP.js → chunk-NUK7EEN5.js} +11 -9
- package/dist/{chunk-GBNB4EWD.js → chunk-ORM7ZFKC.js} +16 -0
- package/dist/{chunk-O4ZFRK6W.js → chunk-OVEQE6YS.js} +244 -49
- package/dist/{chunk-4ZJ5BPY6.js → chunk-PB5S7YOF.js} +2 -2
- package/dist/{chunk-JR23BL74.js → chunk-PDVXWXTL.js} +4 -4
- package/dist/{chunk-3CIMDFXG.js → chunk-PFQ3VEEM.js} +1 -1
- package/dist/{chunk-SBJV2PCU.js → chunk-PHBU4LGO.js} +1 -1
- package/dist/{chunk-W25RLOQ2.js → chunk-PLZSZOZN.js} +3 -3
- package/dist/{chunk-THIJIOE2.js → chunk-PY3FOMRN.js} +18 -6
- package/dist/{chunk-AAUWFXOX.js → chunk-Q3AXXKDF.js} +3 -3
- package/dist/{chunk-VKXS2YGN.js → chunk-QHJR7IMX.js} +2 -2
- package/dist/chunk-QLZ46RCK.js +45 -0
- package/dist/{chunk-UHPMK6QZ.js → chunk-R3CZWFFC.js} +10 -2
- package/dist/{chunk-6XEYELCA.js → chunk-R4CBHKZ2.js} +1 -1
- package/dist/{chunk-5APCDDJZ.js → chunk-R522MWAR.js} +9 -9
- package/dist/chunk-RH4AH3YB.js +80 -0
- package/dist/{chunk-AMKSDKMK.js → chunk-S2WBS6BG.js} +22 -35
- package/dist/{chunk-WUKQVNLP.js → chunk-SEQIPGLU.js} +1 -1
- package/dist/{chunk-7WQRFSZI.js → chunk-SL6KQYDU.js} +26 -6
- package/dist/chunk-SLLLOOEI.js +103 -0
- package/dist/{chunk-5N4SG5NB.js → chunk-T35RCQFE.js} +3 -3
- package/dist/{chunk-7V6M3QUS.js → chunk-T3QUKNLI.js} +1 -1
- package/dist/{chunk-CW2UCKHS.js → chunk-TADAO3HV.js} +2 -2
- package/dist/{chunk-5DCCKHDY.js → chunk-TAMDAOCU.js} +109 -13
- package/dist/{chunk-ZXB56YR2.js → chunk-TFKCERAK.js} +101 -23
- package/dist/{chunk-RBYHJDAT.js → chunk-TQAZTRQ3.js} +3 -3
- package/dist/{chunk-OKGIOWOB.js → chunk-U6SUKSZR.js} +2 -2
- package/dist/{chunk-23QKEUF6.js → chunk-U73AC5AN.js} +34 -12
- package/dist/{chunk-PM6WSWVU.js → chunk-UBK7HK3J.js} +3 -3
- package/dist/{chunk-H37FSTF2.js → chunk-UBMZ3YQU.js} +1 -1
- package/dist/{chunk-JJUGI2YL.js → chunk-UCFC2AEI.js} +1 -1
- package/dist/{chunk-6IWLAQ7O.js → chunk-UCUOGVDX.js} +4 -0
- package/dist/{chunk-E7UCDSUE.js → chunk-UKLN57Q5.js} +2 -2
- package/dist/{chunk-ZYCLSEZT.js → chunk-ULIBLJ6W.js} +33 -11
- package/dist/{chunk-RLR6VVKV.js → chunk-UM3N6VND.js} +4 -4
- package/dist/{chunk-ZNUJFX7L.js → chunk-UQGSBM3U.js} +1 -1
- package/dist/{chunk-ZZXMB5F2.js → chunk-UZDF6FLJ.js} +1 -1
- package/dist/{chunk-LNOS24GV.js → chunk-V3WLC5TJ.js} +2 -95
- package/dist/{chunk-UXLO7VHX.js → chunk-VI4ATC6I.js} +1 -1
- package/dist/{chunk-77JP3LK4.js → chunk-VKJGHAZT.js} +92 -21
- package/dist/{chunk-7R73IAHU.js → chunk-VMTZ4RUW.js} +67 -150
- package/dist/{chunk-TGPQMMQP.js → chunk-VYQ7B7DI.js} +30 -17
- package/dist/{chunk-E2WEFZOR.js → chunk-W75GTHR3.js} +1 -1
- package/dist/{chunk-UKLF7F3Z.js → chunk-WJG344NC.js} +9 -9
- package/dist/{chunk-MBNIJXKI.js → chunk-WMEYJT4R.js} +18 -4
- package/dist/{chunk-R6FLELV6.js → chunk-XFTEX77S.js} +3 -3
- package/dist/{chunk-MDGYXUOV.js → chunk-XH7YL7JM.js} +1 -1
- package/dist/{chunk-JOO35RHR.js → chunk-XL5MFGMR.js} +3 -3
- package/dist/{chunk-OW5RZIDW.js → chunk-XNNECNDL.js} +95 -33
- package/dist/{chunk-5AHDQCTB.js → chunk-XXFKSJYA.js} +6 -6
- package/dist/{chunk-TGEPLJ66.js → chunk-XXOPG4BW.js} +3 -3
- package/dist/{chunk-PGLPUP37.js → chunk-XZ65ST2I.js} +1 -1
- package/dist/{chunk-3TTKAT6V.js → chunk-YKC7QDZH.js} +9 -4
- package/dist/{chunk-KXYFBBZR.js → chunk-YMP2QSOP.js} +20 -3
- package/dist/{chunk-TK4MI6F6.js → chunk-YSLGFPWX.js} +6 -6
- package/dist/{chunk-X2P43TB2.js → chunk-YTSXPJUQ.js} +1 -1
- package/dist/{chunk-HECXZGGZ.js → chunk-ZCERWHY2.js} +33 -35
- package/dist/{chunk-IVU3WN7S.js → chunk-ZG62LIK3.js} +74 -4
- package/dist/{chunk-GU62SNDH.js → chunk-ZGYNKA3O.js} +744 -111
- package/dist/{chunk-QMSEL5YS.js → chunk-ZIG7VPJV.js} +28 -76
- package/dist/{chunk-WZJVGA5I.js → chunk-ZL4AGWAH.js} +258 -13
- package/dist/{chunk-QR624DER.js → chunk-ZSODLMFV.js} +2 -2
- package/dist/{cleanup-graph-garbage-IPYDS5OX.js → cleanup-graph-garbage-DPJGO52H.js} +11 -10
- package/dist/{cloud-sync-command-JLNVG7H4.js → cloud-sync-command-JPNTT4OQ.js} +2 -2
- package/dist/{cloud-sync-db-init-JXUF6GI3.js → cloud-sync-db-init-V2LWIUQU.js} +1 -1
- package/dist/{cloud-sync-shutdown-Z4SBP6PL.js → cloud-sync-shutdown-BNALPWGX.js} +5 -4
- package/dist/{co-activation-74RDFTKS.js → co-activation-CMNSWS6B.js} +5 -4
- package/dist/{co-occurrence-CN3TBYVR.js → co-occurrence-WRI2NAN6.js} +9 -8
- package/dist/{code-context-index-D4MJ4VFX.js → code-context-index-NOJSHLPN.js} +12 -6
- package/dist/code-context-watcher-VZNZU3BO.js +118 -0
- package/dist/{codex-consolidation-PPXNBXCV.js → codex-consolidation-SH4UKFBQ.js} +2 -1
- package/dist/{conversation-entity-extractor-QGTLH4BP.js → conversation-entity-extractor-74252CAO.js} +4 -4
- package/dist/{conversation-wiki-populator-EBUZXYEU.js → conversation-wiki-populator-OS4C6W54.js} +1 -1
- package/dist/conversations-retention-C4BLKFEL.js +54 -0
- package/dist/coordinator-notice-MHUY7AQB.js +55 -0
- package/dist/{crdt-sync-7T4WCOKD.js → crdt-sync-B3JN6AIV.js} +1 -1
- package/dist/{crm-webhook-FTW3XZTO.js → crm-webhook-GYWQRFET.js} +2 -2
- package/dist/{cto-delegation-gate-SZCE34LB.js → cto-delegation-gate-LQHVLEMW.js} +29 -26
- package/dist/{daemon-auth-SSPY5ODS.js → daemon-auth-QZHEO7IQ.js} +3 -2
- package/dist/{daemon-orchestration-IGPHVNFR.js → daemon-orchestration-5HIQALZH.js} +36 -34
- package/dist/{daemon-ready-OT2VQLS7.js → daemon-ready-P2BR2XP5.js} +1 -1
- package/dist/{db-integrity-events-5DTASXJF.js → db-integrity-events-2DTFO37N.js} +3 -2
- package/dist/{db-restore-events-2FI4ZMKA.js → db-restore-events-G6NOQOVE.js} +2 -1
- package/dist/{destructive-data-guard-6I5MDFVD.js → destructive-data-guard-BVFWPPIS.js} +1 -1
- package/dist/doc-graph-extractor-PQHDHQPQ.js +367 -0
- package/dist/{documents-URULIXGM.js → documents-C4SSPJMC.js} +15 -13
- package/dist/{dreaming-VIZO3QDO.js → dreaming-XAO3FCBV.js} +33 -30
- package/dist/{employee-boot-brief-XLOG4ULP.js → employee-boot-brief-W4OHUMRQ.js} +37 -34
- package/dist/{exe-drift-TLQM3IHD.js → exe-drift-AG2KMKD5.js} +6 -5
- package/dist/{exe-export-7YWYWJHY.js → exe-export-IC5BT335.js} +17 -15
- package/dist/{exe-import-DW6VAUKW.js → exe-import-A35ZLDPG.js} +17 -15
- package/dist/{exe-key-TLJP2B4Y.js → exe-key-TCMJNYLA.js} +116 -112
- package/dist/{exe-org-LU46N36E.js → exe-org-JXCR3JYZ.js} +24 -23
- package/dist/{exe-snapshot-QHUZRS62.js → exe-snapshot-SV6SRIG6.js} +36 -33
- package/dist/{fast-db-init-SCTOOIUT.js → fast-db-init-L2RGKJWH.js} +1 -1
- package/dist/{founder-context-ZFZC4UHO.js → founder-context-D4HFIESJ.js} +4 -3
- package/dist/{founder-monitor-envelope-5KTKNL5G.js → founder-monitor-envelope-TFKRGCFE.js} +5 -4
- package/dist/{fts-integrity-LKAZEZJE.js → fts-integrity-4YB34KWX.js} +3 -2
- package/dist/gateway/index.js +19 -17
- package/dist/{git-main-guard-TN27QBAI.js → git-main-guard-VTW4N3CM.js} +7 -1
- package/dist/{git-staleness-GSGP344N.js → git-staleness-TN7CYGFG.js} +5 -4
- package/dist/{global-procedures-YAR7G4C6.js → global-procedures-YFDEIOG7.js} +7 -6
- package/dist/{graph-auto-extract-GNQS64F5.js → graph-auto-extract-3YR3TYRW.js} +9 -8
- package/dist/{graph-rag-RMSFLUOB.js → graph-rag-Z2KCJTPK.js} +6 -4
- package/dist/{hook-integrity-CF567MVN.js → hook-integrity-KGAV6ZC2.js} +14 -4
- package/dist/{hook-prefetch-36BK4F7U.js → hook-prefetch-K5DN3TOQ.js} +29 -26
- package/dist/hooks/bug-report-worker.js +34 -30
- package/dist/hooks/codex-stop-task-finalizer.js +36 -32
- package/dist/hooks/commit-complete.js +33 -30
- package/dist/hooks/error-recall.js +12 -11
- package/dist/hooks/exe-heartbeat-hook.js +8 -7
- package/dist/hooks/ingest-worker.js +23 -11
- package/dist/hooks/ingest.js +18 -17
- package/dist/hooks/instructions-loaded.js +10 -9
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +9 -8
- package/dist/hooks/post-compact.js +8 -7
- package/dist/hooks/post-tool-combined.js +26 -15
- package/dist/hooks/pre-compact.js +33 -30
- package/dist/hooks/pre-tool-use.js +404 -46
- package/dist/hooks/prompt-submit.js +57 -47
- package/dist/hooks/session-end.js +40 -41
- package/dist/hooks/session-start.js +21 -20
- package/dist/hooks/stop.js +40 -37
- package/dist/hooks/subagent-stop.js +38 -35
- package/dist/hooks/summary-worker.js +39 -36
- package/dist/hyde-MP4QNMU5.js +80 -0
- package/dist/index.js +44 -39
- package/dist/{installer-LUN4HQIC.js → installer-54HTN5RE.js} +8 -7
- package/dist/{installer-OC5VXNC7.js → installer-LK35OX6R.js} +8 -7
- package/dist/{installer-MPRFUQJE.js → installer-LLWKZI2S.js} +8 -7
- package/dist/intercom-ack-VCW755TN.js +13 -0
- package/dist/{intercom-queue-6DZ7BANV.js → intercom-queue-5NOT2RKI.js} +28 -25
- package/dist/{key-backup-status-6L2ARCXW.js → key-backup-status-LQPINQ3W.js} +2 -1
- package/dist/lib/agent-config.js +3 -2
- package/dist/lib/cloud-sync.js +26 -20
- package/dist/lib/config.js +2 -1
- package/dist/lib/consolidation.js +17 -10
- package/dist/lib/database.js +7 -4
- package/dist/lib/db-backup.js +17 -2
- package/dist/lib/db-daemon-client.js +8 -6
- package/dist/lib/db-write-worker-client.js +3 -1
- package/dist/lib/db-write-worker.js +20 -3
- package/dist/lib/db.js +7 -4
- package/dist/lib/device-registry.js +3 -2
- package/dist/lib/embed-worker.js +2 -1
- package/dist/lib/embedder.js +8 -6
- package/dist/lib/employee-templates.js +8 -7
- package/dist/lib/employees.js +5 -4
- package/dist/lib/exe-daemon-client.js +7 -5
- package/dist/lib/exe-daemon.js +467 -221
- package/dist/lib/hybrid-search.js +11 -8
- package/dist/lib/identity.js +5 -4
- package/dist/lib/license.js +4 -3
- package/dist/lib/messaging.js +29 -26
- package/dist/lib/post-tool-memory.js +16 -2
- package/dist/lib/registry-proxy.js +3 -1
- package/dist/lib/reminders.js +6 -5
- package/dist/lib/schedules.js +9 -8
- package/dist/lib/session-registry.js +7 -6
- package/dist/lib/skill-learning.js +16 -14
- package/dist/lib/stack-invariants.js +17 -1
- package/dist/lib/status-brief.js +5 -4
- package/dist/lib/store.js +10 -7
- package/dist/lib/task-router.js +6 -5
- package/dist/lib/tasks.js +31 -27
- package/dist/lib/tmux-routing.js +40 -25
- package/dist/lib/tmux-status.js +3 -1
- package/dist/lib/tmux-transport.js +2 -1
- package/dist/lib/token-spend.js +6 -5
- package/dist/lib/transport.js +3 -2
- package/dist/{license-gate-ESER24PJ.js → license-gate-JVMB6OFD.js} +5 -4
- package/dist/{license-sync-JVG5KNIY.js → license-sync-5VYS2F2W.js} +4 -3
- package/dist/mcp/register-tools.js +113 -109
- package/dist/mcp/server.js +118 -114
- package/dist/mcp/tools/complete-reminder.js +7 -6
- package/dist/mcp/tools/create-reminder.js +7 -6
- package/dist/mcp/tools/create-task.js +34 -30
- package/dist/mcp/tools/deactivate-behavior.js +18 -16
- package/dist/mcp/tools/list-reminders.js +7 -6
- package/dist/mcp/tools/list-tasks.js +34 -30
- package/dist/mcp/tools/send-message.js +32 -29
- package/dist/mcp/tools/update-task.js +32 -28
- package/dist/mcp-child-registry-OTG4MWO7.js +25 -0
- package/dist/{mcp-http-config-6K6IKH5F.js → mcp-http-config-DQWL7U2Z.js} +6 -5
- package/dist/{mcp-session-identity-LRC6WJSD.js → mcp-session-identity-277GASTQ.js} +28 -25
- package/dist/{memory-cards-V3OZHIYP.js → memory-cards-3S2ZQPR7.js} +17 -8
- package/dist/{memory-graph-extractor-JC2Z64L4.js → memory-graph-extractor-A6DFEFIN.js} +10 -9
- package/dist/{memory-poisoning-defense-YM4QJHCX.js → memory-poisoning-defense-ZVP4H6DY.js} +5 -4
- package/dist/{memory-pressure-watchdog-6F6KEQHN.js → memory-pressure-watchdog-6W5F6R7H.js} +29 -26
- package/dist/{memory-queue-QJNWKWKG.js → memory-queue-YF7IDSFY.js} +3 -2
- package/dist/{memory-queue-client-COA4ABEV.js → memory-queue-client-ADSDP6OX.js} +9 -7
- package/dist/{memory-reflection-QXHJPYPC.js → memory-reflection-BKB2YOE6.js} +52 -6
- package/dist/{monitor-grants-BW2WL4IU.js → monitor-grants-EV747HMW.js} +4 -3
- package/dist/{notification-ledger-6QRLQW4Q.js → notification-ledger-CLGM7ODF.js} +30 -27
- package/dist/{notifications-7B6D7Y5C.js → notifications-MACUZPYP.js} +28 -25
- package/dist/{oauth-server-TZFNQAFS.js → oauth-server-AYM37SQ4.js} +25 -4
- package/dist/{orchestration-events-GY5VP5LU.js → orchestration-events-N7LYMUGA.js} +6 -5
- package/dist/{orchestration-phase-UDS24CVQ.js → orchestration-phase-T2WD45UA.js} +3 -2
- package/dist/{orchestrator-JVPVOYWA.js → orchestrator-4X24EWHV.js} +30 -27
- package/dist/{org-brief-session-GRV5R5E3.js → org-brief-session-FZOFMO5U.js} +13 -12
- package/dist/{org-consent-policy-3FHHSYXV.js → org-consent-policy-O5E6HXBL.js} +1 -1
- package/dist/{org-join-BY4PV6UO.js → org-join-POWUG25F.js} +8 -7
- package/dist/{org-key-admin-WSCPT2CZ.js → org-key-admin-E7EMDOZZ.js} +9 -8
- package/dist/{org-key-pin-5WUSPGVN.js → org-key-pin-WHWHVWUO.js} +4 -3
- package/dist/{org-membership-ESALTLLN.js → org-membership-WRT2IHJK.js} +5 -4
- package/dist/{org-partner-config-YKHPDPZR.js → org-partner-config-B7T4CL2T.js} +7 -6
- package/dist/{org-partner-runtime-UJDGECZ4.js → org-partner-runtime-FODONVTM.js} +13 -12
- package/dist/{org-row-visibility-V5DXIHTE.js → org-row-visibility-VX4AVV5Q.js} +1 -1
- package/dist/{pipeline-router-YY5FQKTY.js → pipeline-router-6GGT2ZW3.js} +6 -5
- package/dist/{plan-limits-OVX6SN6B.js → plan-limits-FUN76LYY.js} +8 -7
- package/dist/{platform-procedures-I57ITMDC.js → platform-procedures-7YSCLF4Z.js} +1 -1
- package/dist/{postinstall-failure-B2JHNFPM.js → postinstall-failure-O6TSSNJB.js} +3 -2
- package/dist/{project-boot-LXLU2J32.js → project-boot-3Q65LCIP.js} +2 -1
- package/dist/project-name-LJVFMYTQ.js +30 -0
- package/dist/{projection-worker-D2472IOD.js → projection-worker-4OANGP5K.js} +5 -4
- package/dist/{prospective-memory-JXO2A6PL.js → prospective-memory-KR3P4T6N.js} +5 -4
- package/dist/{push-notifications-HR3FZRDI.js → push-notifications-NCZLH6DI.js} +29 -26
- package/dist/ref-provenance-gate-M4AQO4OT.js +269 -0
- package/dist/{reproject-worktree-memories-OSS3PGRF.js → reproject-worktree-memories-PWYKPB2U.js} +106 -23
- package/dist/{reranker-IMZRPYJ2.js → reranker-6V3C2AO4.js} +3 -2
- package/dist/resolve-image-labels-ZI6UBW5W.js +15 -0
- package/dist/{retrieval-health-W7OD44FE.js → retrieval-health-5D7O6VH6.js} +4 -3
- package/dist/{review-polling-2INP7H6M.js → review-polling-QW3LJ4KN.js} +29 -26
- package/dist/runtime/index.js +38 -34
- package/dist/{session-events-G4XUPVWV.js → session-events-T45SSTMF.js} +31 -26
- package/dist/{session-kill-telemetry-W7MHWJTQ.js → session-kill-telemetry-QSPVJGBF.js} +29 -26
- package/dist/{session-mcp-config-F7KATWOF.js → session-mcp-config-SC5BLWZG.js} +3 -2
- package/dist/{session-scope-LG2J4EMQ.js → session-scope-KRWE54C4.js} +28 -25
- package/dist/{setup-wizard-YQJI3SVM.js → setup-wizard-GP3TBTPF.js} +5 -4
- package/dist/{shard-manager-UROHZE46.js → shard-manager-OE36A4MY.js} +3 -2
- package/dist/{skill-refinement-RA37U756.js → skill-refinement-C7JTMLJG.js} +6 -5
- package/dist/stack-generate-manifest-MFMDWRUP.js +810 -0
- package/dist/{stack-promote-IYBRXHTA.js → stack-promote-EKSCYY63.js} +8 -6
- package/dist/{stack-release-OXWLVVG6.js → stack-release-2GZAAFO4.js} +8 -6
- package/dist/{stack-update-2CZEROPK.js → stack-update-J6JXSTAZ.js} +51 -5
- package/dist/{steward-gate-LTNFB3FY.js → steward-gate-BFCCMCSW.js} +6 -5
- package/dist/{subagent-brief-injection-OHLWLECQ.js → subagent-brief-injection-ED36PLYY.js} +44 -36
- package/dist/{subagent-spawn-ledger-USYZXU3M.js → subagent-spawn-ledger-E6U3OY6Z.js} +3 -2
- package/dist/{supervisor-handoff-CSJQMI53.js → supervisor-handoff-SGLEPOCY.js} +12 -3
- package/dist/{support-inbox-4KCEPAZQ.js → support-inbox-YW47SYJF.js} +20 -5
- package/dist/{task-enforcement-LGXWXGVZ.js → task-enforcement-CECTQX4U.js} +29 -26
- package/dist/{task-request-inbox-WHCOSUCP.js → task-request-inbox-Q5T4N5K7.js} +1 -1
- package/dist/{task-scope-KXF7Y6KA.js → task-scope-ERVEUEYU.js} +28 -25
- package/dist/{tasks-crud-5EM55WRZ.js → tasks-crud-ZRQNJXQ2.js} +28 -25
- package/dist/{tasks-notify-VFMAURTN.js → tasks-notify-MIZH4C2O.js} +29 -26
- package/dist/{tasks-review-MHGMBHUZ.js → tasks-review-Z7J2P6DG.js} +28 -25
- package/dist/{telemetry-upload-6NRTN7QJ.js → telemetry-upload-LVQUQYFP.js} +10 -9
- package/dist/{token-budget-LZPOYTRP.js → token-budget-BU4NOVZF.js} +6 -5
- package/dist/{tool-capability-index-ED5OAPMW.js → tool-capability-index-7F42MOJT.js} +1 -1
- package/dist/{tool-gates-7PLIOLAL.js → tool-gates-JWVSAYTW.js} +1 -1
- package/dist/{tool-gates-2OZY3GZM.js → tool-gates-VXITVRFQ.js} +1 -1
- package/dist/{tool-telemetry-ZX7VSAXK.js → tool-telemetry-O27EVMBR.js} +1 -1
- package/dist/tui/App.js +43 -40
- package/dist/{tui-data-CC5POSUV.js → tui-data-TPWWBIUQ.js} +28 -25
- package/dist/{update-support-KH6NSQON.js → update-support-F2F5537B.js} +22 -20
- package/dist/{whatsapp-QW3X7OCN.js → whatsapp-YM4A2CSK.js} +3 -1
- package/dist/{work-claims-MNK7PEPW.js → work-claims-B73X5NE7.js} +6 -5
- package/dist/{work-state-heartbeat-YEWT5NMZ.js → work-state-heartbeat-P4YX6E2R.js} +1 -1
- package/dist/{worker-gate-BOQSIU2Z.js → worker-gate-BEEIIVWA.js} +3 -2
- package/dist/{workflow-engine-MUUD7ZEX.js → workflow-engine-5X46PFCW.js} +4 -2
- package/dist/{worktree-GOGT2LZP.js → worktree-W47CIMJ6.js} +9 -6
- package/dist/worktree-ownership-guard-OUWJROW2.js +88 -0
- package/dist/{worktree-sweep-46VWPVQT.js → worktree-sweep-BNBD4RFX.js} +8 -7
- package/package.json +14 -5
- package/release-notes.json +5 -5
- package/src/commands/exe/call.md +1 -1
- package/stack.release.json +6 -0
- package/dist/chunk-KZXMP6JG.js +0 -319
- package/dist/chunk-TCTED52S.js +0 -120
- package/dist/chunk-W2IRQJ6R.js +0 -67
- package/dist/doc-graph-extractor-VPTRIEKN.js +0 -149
- package/dist/project-name-ILJTETUE.js +0 -14
- /package/dist/{chunk-3Y5THOU6.js → chunk-6WIFPD2P.js} +0 -0
- /package/dist/{chunk-7XGXRD22.js → chunk-AISULNGL.js} +0 -0
- /package/dist/{chunk-3F2UB4IQ.js → chunk-DN4YVV4Z.js} +0 -0
- /package/dist/{chunk-WV3TOOL7.js → chunk-KQ6RJWRW.js} +0 -0
- /package/dist/{chunk-HR2R2QER.js → chunk-M4IRPXSZ.js} +0 -0
- /package/dist/{chunk-ORA5BWCV.js → chunk-NRG7KZIP.js} +0 -0
- /package/dist/{chunk-7UTJA6HF.js → chunk-VZAFWPAY.js} +0 -0
- /package/dist/{chunk-VJSDGDNS.js → chunk-WSQASFLP.js} +0 -0
- /package/dist/{chunk-FZQWVQJG.js → chunk-YO4BO2CL.js} +0 -0
- /package/dist/{core-memory-6WBNT2HL.js → core-memory-7I7RE2SJ.js} +0 -0
- /package/dist/{entity-boost-MT336PI3.js → entity-boost-3UWGXG5O.js} +0 -0
- /package/dist/{memory-isolation-EJ45IXBH.js → memory-isolation-2TUZ7Z4K.js} +0 -0
- /package/dist/{message-queue-client-MVBTYCSV.js → message-queue-client-XQN7QABL.js} +0 -0
- /package/dist/{webhook-pipe-UQSEHFPO.js → webhook-pipe-W2FVXY7M.js} +0 -0
- /package/dist/{wiki-acl-WXRJGZ5Q.js → wiki-acl-IBQLLBTS.js} +0 -0
- /package/dist/{wiki-client-AXNM5FWY.js → wiki-client-W65IOSWF.js} +0 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readImageLabel
|
|
3
|
+
} from "./chunk-GHR4ZPJ7.js";
|
|
1
4
|
import {
|
|
2
5
|
clearDeployStalenessAlert,
|
|
6
|
+
deployStalenessAlertPath,
|
|
3
7
|
evaluateDeployStaleness,
|
|
4
8
|
writeDeployStalenessAlert
|
|
5
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-C5HUMAA7.js";
|
|
6
10
|
import {
|
|
7
11
|
REVISION_LABEL,
|
|
8
12
|
VERSION_LABEL,
|
|
@@ -11,77 +15,17 @@ import {
|
|
|
11
15
|
} from "./chunk-L25OAFOU.js";
|
|
12
16
|
import {
|
|
13
17
|
loadLicense
|
|
14
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-L6RXG36J.js";
|
|
15
19
|
|
|
16
20
|
// src/lib/stack-update.ts
|
|
17
|
-
import { execFileSync, spawnSync
|
|
21
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
18
22
|
import { createVerify, randomBytes, verify as verifySignature } from "crypto";
|
|
19
23
|
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
20
24
|
import http from "http";
|
|
21
25
|
import https from "https";
|
|
26
|
+
import net from "net";
|
|
22
27
|
import path from "path";
|
|
23
28
|
import { fileURLToPath } from "url";
|
|
24
|
-
|
|
25
|
-
// scripts/lib/resolve-image-labels.mjs
|
|
26
|
-
import { spawnSync } from "child_process";
|
|
27
|
-
var defaultRunner = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8", timeout: 6e4 });
|
|
28
|
-
function repositoryOf(ref) {
|
|
29
|
-
if (typeof ref !== "string") return ref;
|
|
30
|
-
const at = ref.indexOf("@");
|
|
31
|
-
if (at !== -1) return ref.slice(0, at);
|
|
32
|
-
const slash = ref.lastIndexOf("/");
|
|
33
|
-
const colon = ref.lastIndexOf(":");
|
|
34
|
-
if (colon > slash) return ref.slice(0, colon);
|
|
35
|
-
return ref;
|
|
36
|
-
}
|
|
37
|
-
function amd64ChildDigest(rawJson) {
|
|
38
|
-
let doc;
|
|
39
|
-
try {
|
|
40
|
-
doc = typeof rawJson === "string" ? JSON.parse(rawJson) : rawJson;
|
|
41
|
-
} catch {
|
|
42
|
-
return void 0;
|
|
43
|
-
}
|
|
44
|
-
if (!doc || !Array.isArray(doc.manifests)) return void 0;
|
|
45
|
-
const isLinuxAmd64 = (m) => m?.platform?.os === "linux" && m?.platform?.architecture === "amd64";
|
|
46
|
-
const anyAmd64 = (m) => m?.platform?.architecture === "amd64" && m?.platform?.os !== "unknown";
|
|
47
|
-
const pick = doc.manifests.find(isLinuxAmd64) ?? doc.manifests.find(anyAmd64);
|
|
48
|
-
const digest = pick?.digest;
|
|
49
|
-
return typeof digest === "string" && digest.length > 0 ? digest : void 0;
|
|
50
|
-
}
|
|
51
|
-
function resolvePlatformRef(imageRef, { runner } = {}) {
|
|
52
|
-
const run = runner ?? defaultRunner;
|
|
53
|
-
const res = run("docker", ["buildx", "imagetools", "inspect", imageRef, "--raw"]);
|
|
54
|
-
if (!res || res.status !== 0) return imageRef;
|
|
55
|
-
const raw = (res.stdout ?? "").toString();
|
|
56
|
-
const childDigest = amd64ChildDigest(raw);
|
|
57
|
-
if (!childDigest) return imageRef;
|
|
58
|
-
return `${repositoryOf(imageRef)}@${childDigest}`;
|
|
59
|
-
}
|
|
60
|
-
function readLabelOff(platformRef, label, { runner } = {}) {
|
|
61
|
-
const run = runner ?? defaultRunner;
|
|
62
|
-
const res = run("docker", [
|
|
63
|
-
"buildx",
|
|
64
|
-
"imagetools",
|
|
65
|
-
"inspect",
|
|
66
|
-
platformRef,
|
|
67
|
-
"--format",
|
|
68
|
-
`{{ index .Config.Labels "${label}" }}`
|
|
69
|
-
]);
|
|
70
|
-
if (!res || res.status !== 0) return void 0;
|
|
71
|
-
const out = (res.stdout ?? "").toString().trim();
|
|
72
|
-
return out === "" || out === "<no value>" ? void 0 : out;
|
|
73
|
-
}
|
|
74
|
-
function resolveImageLabels(imageRef, labels, { runner } = {}) {
|
|
75
|
-
const platformRef = resolvePlatformRef(imageRef, { runner });
|
|
76
|
-
const out = {};
|
|
77
|
-
for (const label of labels) out[label] = readLabelOff(platformRef, label, { runner });
|
|
78
|
-
return out;
|
|
79
|
-
}
|
|
80
|
-
function readImageLabel(imageRef, label, opts = {}) {
|
|
81
|
-
return resolveImageLabels(imageRef, [label], opts)[label];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/lib/stack-update.ts
|
|
85
29
|
var SLOW_SERVICE_HEALTH_DEFAULTS = {
|
|
86
30
|
// Frappe `bench migrate` on every boot can take well over the 180s default.
|
|
87
31
|
"exe-erp": 900
|
|
@@ -239,6 +183,69 @@ function isCloudflaredTunnelMisconfigured(composeFile, envFile) {
|
|
|
239
183
|
}
|
|
240
184
|
return true;
|
|
241
185
|
}
|
|
186
|
+
var INGRESS_DEFAULT_PORTS = { http: 80, https: 443 };
|
|
187
|
+
function stripYamlScalar(raw) {
|
|
188
|
+
let v = raw.trim();
|
|
189
|
+
v = v.replace(/\s+#.*$/, "").trim();
|
|
190
|
+
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
191
|
+
v = v.slice(1, -1);
|
|
192
|
+
}
|
|
193
|
+
return v.trim();
|
|
194
|
+
}
|
|
195
|
+
function parseCloudflaredIngressTargets(configYml) {
|
|
196
|
+
const lines = configYml.split(/\r?\n/);
|
|
197
|
+
const targets = [];
|
|
198
|
+
let inIngress = false;
|
|
199
|
+
let ingressIndent = -1;
|
|
200
|
+
let currentHostname;
|
|
201
|
+
const flushService = (serviceRaw) => {
|
|
202
|
+
const service = stripYamlScalar(serviceRaw);
|
|
203
|
+
const m = /^([a-z][a-z0-9+.\-]*):\/\/(?:\[([^\]]+)\]|([^/:\s]+))(?::(\d+))?/i.exec(service);
|
|
204
|
+
if (!m) return;
|
|
205
|
+
const scheme = (m[1] ?? "").toLowerCase();
|
|
206
|
+
const host = m[2] ?? m[3] ?? "";
|
|
207
|
+
const port = m[4] ? Number(m[4]) : INGRESS_DEFAULT_PORTS[scheme];
|
|
208
|
+
if (!host) return;
|
|
209
|
+
if (port === void 0 || !Number.isFinite(port) || port <= 0 || port > 65535) return;
|
|
210
|
+
targets.push({ hostname: currentHostname, service, host, port });
|
|
211
|
+
};
|
|
212
|
+
for (const raw of lines) {
|
|
213
|
+
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
214
|
+
const indent = raw.length - raw.replace(/^\s*/, "").length;
|
|
215
|
+
if (!inIngress) {
|
|
216
|
+
if (/^ingress\s*:\s*$/.test(raw.trim())) {
|
|
217
|
+
inIngress = true;
|
|
218
|
+
ingressIndent = indent;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (indent <= ingressIndent && !raw.trim().startsWith("-")) {
|
|
223
|
+
inIngress = false;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const t = raw.trim();
|
|
227
|
+
if (t.startsWith("-")) {
|
|
228
|
+
currentHostname = void 0;
|
|
229
|
+
const item = t.replace(/^-\s*/, "");
|
|
230
|
+
const hm = /^hostname\s*:\s*(.+)$/i.exec(item);
|
|
231
|
+
const sm = /^service\s*:\s*(.+)$/i.exec(item);
|
|
232
|
+
if (hm) currentHostname = stripYamlScalar(hm[1] ?? "");
|
|
233
|
+
else if (sm) flushService(sm[1] ?? "");
|
|
234
|
+
} else {
|
|
235
|
+
const hm = /^hostname\s*:\s*(.+)$/i.exec(t);
|
|
236
|
+
const sm = /^service\s*:\s*(.+)$/i.exec(t);
|
|
237
|
+
if (hm) currentHostname = stripYamlScalar(hm[1] ?? "");
|
|
238
|
+
else if (sm) flushService(sm[1] ?? "");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return targets;
|
|
242
|
+
}
|
|
243
|
+
async function findUnreachableIngressTargets(targets, probe) {
|
|
244
|
+
const results = await Promise.all(
|
|
245
|
+
targets.map(async (t) => ({ t, reachable: await probe(t.host, t.port) }))
|
|
246
|
+
);
|
|
247
|
+
return results.filter((r) => !r.reachable).map((r) => r.t);
|
|
248
|
+
}
|
|
242
249
|
function resolveTransitiveDeps(selected, depsMap) {
|
|
243
250
|
const resolved = new Set(selected);
|
|
244
251
|
const queue = [...selected];
|
|
@@ -448,9 +455,10 @@ async function rollbackStackUpdate(options) {
|
|
|
448
455
|
const rollbackEnvRaw = readFileSync(rollbackEnv, "utf8");
|
|
449
456
|
const rollbackEnvMap = parseEnv(rollbackEnvRaw);
|
|
450
457
|
const preUpdateImageDefaults = lockData?.preUpdateImageDefaults ?? {};
|
|
458
|
+
let processSyncKeys = [];
|
|
451
459
|
if (scopedServiceNames.length > 0 && lockData?.services) {
|
|
452
460
|
const scopedEnvKeys = scopedServiceNames.map((serviceName) => lockData.services[serviceName].env).filter((key) => Boolean(key));
|
|
453
|
-
const mutatedEnvKeys = lockData.mutatedEnvKeys ?? [];
|
|
461
|
+
const mutatedEnvKeys = options.isolateEnvScope ? [] : lockData.mutatedEnvKeys ?? [];
|
|
454
462
|
const restoreEnvKeys = [.../* @__PURE__ */ new Set([...scopedEnvKeys, ...mutatedEnvKeys])];
|
|
455
463
|
const scopedUpdates = {};
|
|
456
464
|
const scopedRemovals = [];
|
|
@@ -478,6 +486,7 @@ async function rollbackStackUpdate(options) {
|
|
|
478
486
|
}
|
|
479
487
|
const patched = patchEnv(readFileSync(options.envFile, "utf8"), scopedUpdates);
|
|
480
488
|
writeSecretFile(options.envFile, removeEnvKeys(patched, scopedRemovals));
|
|
489
|
+
processSyncKeys = restoreEnvKeys;
|
|
481
490
|
} else {
|
|
482
491
|
console.log(`[stack-update] Restoring env from ${rollbackEnv}`);
|
|
483
492
|
writeSecretFile(options.envFile, rollbackEnvRaw);
|
|
@@ -500,6 +509,50 @@ async function rollbackStackUpdate(options) {
|
|
|
500
509
|
writeSecretFile(options.envFile, patchEnv(readFileSync(options.envFile, "utf8"), repins));
|
|
501
510
|
}
|
|
502
511
|
}
|
|
512
|
+
processSyncKeys = Object.values(lockData?.services ?? {}).map((svc) => svc.env).filter((key) => Boolean(key));
|
|
513
|
+
}
|
|
514
|
+
{
|
|
515
|
+
const imagePinKeys = new Set(
|
|
516
|
+
Object.values(lockData?.services ?? {}).map((svc) => svc.env).filter((key) => Boolean(key))
|
|
517
|
+
);
|
|
518
|
+
const restored = syncImagePinsWithProcessEnv(
|
|
519
|
+
readFileSync(options.envFile, "utf8"),
|
|
520
|
+
processSyncKeys.filter((key) => imagePinKeys.has(key))
|
|
521
|
+
);
|
|
522
|
+
if (restored.pinned.length > 0 || restored.cleared.length > 0) {
|
|
523
|
+
console.log(
|
|
524
|
+
`[stack-update] ROLLBACK: re-synced image ref(s) in the deploy environment (restored: ${restored.pinned.join(", ") || "none"}; cleared: ${restored.cleared.join(", ") || "none"})`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
{
|
|
529
|
+
const restoredEnvRaw = readFileSync(options.envFile, "utf8");
|
|
530
|
+
const restoredEnvMap = parseEnv(restoredEnvRaw);
|
|
531
|
+
const binUpdates = {};
|
|
532
|
+
const binRemovals = [];
|
|
533
|
+
for (const serviceKey of ["monitorHub", "monitorAgent"]) {
|
|
534
|
+
const binKey = MONITOR_HEALTH_BIN_ENV[serviceKey];
|
|
535
|
+
const imageEnvKey = MONITOR_IMAGE_TAG_ENV[serviceKey];
|
|
536
|
+
const rollbackImage = restoredEnvMap.get(imageEnvKey)?.trim();
|
|
537
|
+
if (rollbackImage) {
|
|
538
|
+
binUpdates[binKey] = monitorHealthBinForImage(serviceKey, rollbackImage);
|
|
539
|
+
} else if (restoredEnvMap.has(binKey)) {
|
|
540
|
+
binRemovals.push(binKey);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (Object.keys(binUpdates).length > 0 || binRemovals.length > 0) {
|
|
544
|
+
const patched = patchEnv(restoredEnvRaw, binUpdates);
|
|
545
|
+
writeSecretFile(options.envFile, removeEnvKeys(patched, binRemovals));
|
|
546
|
+
}
|
|
547
|
+
const restoredBins = syncImagePinsWithProcessEnv(
|
|
548
|
+
readFileSync(options.envFile, "utf8"),
|
|
549
|
+
Object.values(MONITOR_HEALTH_BIN_ENV)
|
|
550
|
+
);
|
|
551
|
+
if (restoredBins.pinned.length > 0 || restoredBins.cleared.length > 0) {
|
|
552
|
+
console.log(
|
|
553
|
+
`[stack-update] ROLLBACK: re-synced monitor healthcheck bin path(s) in the deploy environment (restored: ${restoredBins.pinned.join(", ") || "none"}; cleared: ${restoredBins.cleared.join(", ") || "none"})`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
503
556
|
}
|
|
504
557
|
const composeArgs = composeArgsForStack(options.composeFile, options.envFile);
|
|
505
558
|
if (lockData?.services) {
|
|
@@ -519,8 +572,10 @@ async function rollbackStackUpdate(options) {
|
|
|
519
572
|
}
|
|
520
573
|
console.log("[stack-update] Restarting services with previous images\u2026");
|
|
521
574
|
if (scopedServiceNames.length > 0 && lockData?.services) {
|
|
522
|
-
|
|
523
|
-
|
|
575
|
+
const scopedComposeServices = expandComposeServicesWithSidecars(
|
|
576
|
+
scopedServiceNames.map((serviceName) => lockData.services[serviceName]?.composeService ?? serviceName)
|
|
577
|
+
);
|
|
578
|
+
for (const composeServiceName of scopedComposeServices) {
|
|
524
579
|
exec("docker", [...composeArgs, "up", "-d", "--remove-orphans", "--no-deps", composeServiceName]);
|
|
525
580
|
}
|
|
526
581
|
} else {
|
|
@@ -552,6 +607,43 @@ async function rollbackStackUpdate(options) {
|
|
|
552
607
|
);
|
|
553
608
|
}
|
|
554
609
|
}
|
|
610
|
+
const rolledBackComposeServices = scopedServiceNames.length > 0 && lockData?.services ? [...new Set(expandComposeServicesWithSidecars(scopedServiceNames.map((name) => lockData.services[name]?.composeService ?? name)))] : [...new Set(Object.entries(lockData?.services ?? {}).map(([name, svc]) => svc.composeService ?? name))];
|
|
611
|
+
try {
|
|
612
|
+
await verifyComposeServicesRunning(
|
|
613
|
+
composeArgs,
|
|
614
|
+
rolledBackComposeServices,
|
|
615
|
+
options.healthTimeoutSecs ?? 180,
|
|
616
|
+
options.composePsCapture
|
|
617
|
+
);
|
|
618
|
+
if (rolledBackComposeServices.length > 0) {
|
|
619
|
+
console.log(`[stack-update] \u2713 Post-rollback health verified: ${rolledBackComposeServices.length} service(s) running & healthy.`);
|
|
620
|
+
}
|
|
621
|
+
} catch (healthErr) {
|
|
622
|
+
const healthReason = healthErr instanceof Error ? healthErr.message : String(healthErr);
|
|
623
|
+
const runningVersion = lockData?.previousVersion ?? lockData?.stackVersion ?? "unknown";
|
|
624
|
+
const attemptedVersion = lockData?.targetVersion ?? lockData?.stackVersion ?? "unknown";
|
|
625
|
+
writeDeployStalenessAlert({
|
|
626
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
627
|
+
runningVersion,
|
|
628
|
+
targetVersion: attemptedVersion,
|
|
629
|
+
attemptedAt: lockData?.updatedAt,
|
|
630
|
+
failedServices: rolledBackComposeServices,
|
|
631
|
+
totalServices: rolledBackComposeServices.length,
|
|
632
|
+
sampleError: healthReason.slice(0, 240),
|
|
633
|
+
reason: `ROLLBACK LANDED UNHEALTHY: the stack was rolled back toward ${runningVersion} but the rolled-back service(s) did not come back running & healthy. The rollback did NOT succeed \u2014 production is likely degraded and needs manual attention (bug 879aad12).`,
|
|
634
|
+
recovery: {
|
|
635
|
+
inspect: "docker compose ps --all \u2014 inspect the rolled-back service(s) that failed to become healthy.",
|
|
636
|
+
retry: "Fix the underlying cause, then re-run the deploy or rollback; a healthy landing clears this alert.",
|
|
637
|
+
acknowledge: `Delete ${deployStalenessAlertPath()} to silence it deliberately.`
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
await postDeployAudit(options, "rollback_unhealthy", attemptedVersion, runningVersion, healthReason, {
|
|
641
|
+
failedServices: rolledBackComposeServices
|
|
642
|
+
});
|
|
643
|
+
throw new Error(
|
|
644
|
+
`Rollback restarted services but they did not become healthy \u2014 rollback FAILED (bug 879aad12): ${healthReason}`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
555
647
|
if (!options.serviceName && lockData) {
|
|
556
648
|
writeStackUpdateLock(lockFilePath, {
|
|
557
649
|
...lockData,
|
|
@@ -606,7 +698,7 @@ function isExecFormMigration(migrationCommand) {
|
|
|
606
698
|
}
|
|
607
699
|
function isComposeServiceRunning(composeArgs, composeServiceName) {
|
|
608
700
|
try {
|
|
609
|
-
const result =
|
|
701
|
+
const result = spawnSync("docker", [...composeArgs, "ps", "--quiet", composeServiceName], {
|
|
610
702
|
encoding: "utf8",
|
|
611
703
|
stdio: ["pipe", "pipe", "pipe"],
|
|
612
704
|
timeout: 1e4
|
|
@@ -638,6 +730,20 @@ function expandComposeServicesWithSidecars(primaryComposeServices) {
|
|
|
638
730
|
}
|
|
639
731
|
return expanded;
|
|
640
732
|
}
|
|
733
|
+
function resolveOwningManifestService(composeService, services) {
|
|
734
|
+
const direct = Object.entries(services).find(
|
|
735
|
+
([name, svc]) => name === composeService || (svc.composeService ?? name) === composeService
|
|
736
|
+
);
|
|
737
|
+
if (direct) return direct[0];
|
|
738
|
+
const primary = Object.entries(COMPOSE_SIDECARS).find(([, sidecars]) => sidecars.includes(composeService))?.[0];
|
|
739
|
+
if (primary) {
|
|
740
|
+
const owner = Object.entries(services).find(
|
|
741
|
+
([name, svc]) => name === primary || (svc.composeService ?? name) === primary
|
|
742
|
+
);
|
|
743
|
+
if (owner) return owner[0];
|
|
744
|
+
}
|
|
745
|
+
return composeService;
|
|
746
|
+
}
|
|
641
747
|
function cloneReleaseWithServices(release, serviceNames) {
|
|
642
748
|
const wanted = new Set(serviceNames);
|
|
643
749
|
const services = {};
|
|
@@ -896,7 +1002,18 @@ function parseRequiredEnvRefs(composeRaw) {
|
|
|
896
1002
|
}
|
|
897
1003
|
return [...refs.entries()].map(([key, message]) => ({ key, message }));
|
|
898
1004
|
}
|
|
899
|
-
function
|
|
1005
|
+
function resolveManifestImagePin(key, release) {
|
|
1006
|
+
if (!release) return void 0;
|
|
1007
|
+
for (const service of Object.values(release.services ?? {})) {
|
|
1008
|
+
if (service?.env !== key) continue;
|
|
1009
|
+
const image = service.image?.trim();
|
|
1010
|
+
if (image) return image;
|
|
1011
|
+
}
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
function deriveRequiredEnvValue(key, env, release) {
|
|
1015
|
+
const manifestPin = resolveManifestImagePin(key, release);
|
|
1016
|
+
if (manifestPin) return manifestPin;
|
|
900
1017
|
const nonEmpty = (k) => {
|
|
901
1018
|
const v = env.get(k);
|
|
902
1019
|
return v && v.trim() !== "" ? v.trim() : void 0;
|
|
@@ -935,14 +1052,34 @@ function deriveRequiredEnvValue(key, env) {
|
|
|
935
1052
|
return void 0;
|
|
936
1053
|
}
|
|
937
1054
|
}
|
|
938
|
-
function
|
|
1055
|
+
function requiredImagePinKeys(composeRaw, release) {
|
|
1056
|
+
return parseRequiredEnvRefs(composeRaw).map((ref) => ref.key).filter((key) => resolveManifestImagePin(key, release) !== void 0);
|
|
1057
|
+
}
|
|
1058
|
+
function syncImagePinsWithProcessEnv(envRaw, keys, processEnv = process.env) {
|
|
1059
|
+
const env = parseEnv(envRaw);
|
|
1060
|
+
const pinned = [];
|
|
1061
|
+
const cleared = [];
|
|
1062
|
+
for (const key of new Set(keys)) {
|
|
1063
|
+
const value = env.get(key)?.trim();
|
|
1064
|
+
if (value) {
|
|
1065
|
+
if (processEnv[key] === value) continue;
|
|
1066
|
+
processEnv[key] = value;
|
|
1067
|
+
pinned.push(key);
|
|
1068
|
+
} else if (processEnv[key] !== void 0) {
|
|
1069
|
+
delete processEnv[key];
|
|
1070
|
+
cleared.push(key);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return { pinned, cleared };
|
|
1074
|
+
}
|
|
1075
|
+
function detectMissingRequiredEnvVars(composeRaw, envRaw, release) {
|
|
939
1076
|
const env = parseEnv(envRaw);
|
|
940
1077
|
const derived = {};
|
|
941
1078
|
const unresolvable = [];
|
|
942
1079
|
for (const ref of parseRequiredEnvRefs(composeRaw)) {
|
|
943
1080
|
const current = env.get(ref.key);
|
|
944
1081
|
if (current !== void 0 && current.trim() !== "") continue;
|
|
945
|
-
const value = deriveRequiredEnvValue(ref.key, env);
|
|
1082
|
+
const value = deriveRequiredEnvValue(ref.key, env, release);
|
|
946
1083
|
if (value !== void 0 && value.trim() !== "") {
|
|
947
1084
|
derived[ref.key] = value;
|
|
948
1085
|
} else {
|
|
@@ -951,6 +1088,32 @@ function detectMissingRequiredEnvVars(composeRaw, envRaw) {
|
|
|
951
1088
|
}
|
|
952
1089
|
return { derived, unresolvable };
|
|
953
1090
|
}
|
|
1091
|
+
var MONITOR_HEALTH_BIN_ENV = {
|
|
1092
|
+
monitorHub: "MONITOR_HUB_HEALTH_BIN",
|
|
1093
|
+
monitorAgent: "MONITOR_AGENT_HEALTH_BIN"
|
|
1094
|
+
};
|
|
1095
|
+
var MONITOR_IMAGE_TAG_ENV = {
|
|
1096
|
+
monitorHub: "MONITOR_HUB_IMAGE_TAG",
|
|
1097
|
+
monitorAgent: "MONITOR_AGENT_IMAGE_TAG"
|
|
1098
|
+
};
|
|
1099
|
+
var STOCK_BESZEL_MONITOR_VERSIONS = /* @__PURE__ */ new Set(["0.9.5"]);
|
|
1100
|
+
function monitorImageVersion(imageRef) {
|
|
1101
|
+
const noDigest = imageRef.split("@")[0] ?? imageRef;
|
|
1102
|
+
const lastSlash = noDigest.lastIndexOf("/");
|
|
1103
|
+
const lastColon = noDigest.lastIndexOf(":");
|
|
1104
|
+
const tag = lastColon > lastSlash ? noDigest.slice(lastColon + 1) : "";
|
|
1105
|
+
const match = /^v?(\d+\.\d+\.\d+)/.exec(tag);
|
|
1106
|
+
return match ? match[1] : void 0;
|
|
1107
|
+
}
|
|
1108
|
+
function isStockBeszelMonitorImage(imageRef) {
|
|
1109
|
+
const version = monitorImageVersion(imageRef);
|
|
1110
|
+
return version !== void 0 && STOCK_BESZEL_MONITOR_VERSIONS.has(version);
|
|
1111
|
+
}
|
|
1112
|
+
function monitorHealthBinForImage(serviceKey, imageRef) {
|
|
1113
|
+
const stock = isStockBeszelMonitorImage(imageRef);
|
|
1114
|
+
if (serviceKey === "monitorHub") return stock ? "/beszel" : "/app/beszel";
|
|
1115
|
+
return stock ? "/agent" : "/app/beszel-agent";
|
|
1116
|
+
}
|
|
954
1117
|
function createStackUpdatePlan(manifest, envRaw, targetVersion, serviceNames, selection, composeRaw) {
|
|
955
1118
|
const version = targetVersion ?? manifest.latest;
|
|
956
1119
|
const manifestRelease = manifest.stacks[version];
|
|
@@ -967,6 +1130,16 @@ function createStackUpdatePlan(manifest, envRaw, targetVersion, serviceNames, se
|
|
|
967
1130
|
changes.push({ key: service.env, before, after, service: serviceName });
|
|
968
1131
|
}
|
|
969
1132
|
}
|
|
1133
|
+
for (const serviceKey of ["monitorHub", "monitorAgent"]) {
|
|
1134
|
+
const monitorService = release.services[serviceKey];
|
|
1135
|
+
if (!monitorService?.image) continue;
|
|
1136
|
+
const binKey = MONITOR_HEALTH_BIN_ENV[serviceKey];
|
|
1137
|
+
const binBefore = env.get(binKey);
|
|
1138
|
+
const binAfter = monitorHealthBinForImage(serviceKey, monitorService.image);
|
|
1139
|
+
if (binBefore !== binAfter) {
|
|
1140
|
+
changes.push({ key: binKey, before: binBefore, after: binAfter, service: serviceKey });
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
970
1143
|
return {
|
|
971
1144
|
manifest,
|
|
972
1145
|
release,
|
|
@@ -1021,6 +1194,18 @@ function stripImageDigest(image) {
|
|
|
1021
1194
|
function imagePullRef(image) {
|
|
1022
1195
|
return isThirdPartyImage(image) ? stripImageDigest(image) : image;
|
|
1023
1196
|
}
|
|
1197
|
+
function manifestInspectRef(image) {
|
|
1198
|
+
const trimmed = image.trim();
|
|
1199
|
+
const digest = imageDigest(trimmed);
|
|
1200
|
+
if (!digest) return trimmed;
|
|
1201
|
+
const name = stripImageDigest(trimmed);
|
|
1202
|
+
const lastSlash = name.lastIndexOf("/");
|
|
1203
|
+
const ref = lastSlash >= 0 ? name.slice(lastSlash + 1) : name;
|
|
1204
|
+
const colon = ref.indexOf(":");
|
|
1205
|
+
if (colon < 0) return trimmed;
|
|
1206
|
+
const tagless = lastSlash >= 0 ? name.slice(0, lastSlash + 1) + ref.slice(0, colon) : ref.slice(0, colon);
|
|
1207
|
+
return `${tagless}@${digest}`;
|
|
1208
|
+
}
|
|
1024
1209
|
function isMutableTag(tag) {
|
|
1025
1210
|
if (!tag) return true;
|
|
1026
1211
|
return /^(latest|stable|main|master|edge|nightly|dev|develop|rolling|current)$/i.test(tag);
|
|
@@ -1173,7 +1358,7 @@ function collectImageAvailabilityIssues(plan) {
|
|
|
1173
1358
|
let inspectOutput = "";
|
|
1174
1359
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1175
1360
|
try {
|
|
1176
|
-
const result =
|
|
1361
|
+
const result = spawnSync("docker", ["manifest", "inspect", manifestInspectRef(image), "--verbose"], {
|
|
1177
1362
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1178
1363
|
timeout: 3e4
|
|
1179
1364
|
});
|
|
@@ -1188,7 +1373,7 @@ function collectImageAvailabilityIssues(plan) {
|
|
|
1188
1373
|
console.log(
|
|
1189
1374
|
`[stack-update] Image ${image} not yet available on proxy (attempt ${attempt}/${maxAttempts}), retrying in ${delayMs / 1e3}s\u2026 (bug d11c9809)`
|
|
1190
1375
|
);
|
|
1191
|
-
|
|
1376
|
+
spawnSync("sleep", [String(delayMs / 1e3)], { stdio: "ignore" });
|
|
1192
1377
|
}
|
|
1193
1378
|
} catch {
|
|
1194
1379
|
inspected = true;
|
|
@@ -1253,6 +1438,165 @@ ${details}
|
|
|
1253
1438
|
Emergency override requires --break-glass <reason> and writes an audit file.`
|
|
1254
1439
|
);
|
|
1255
1440
|
}
|
|
1441
|
+
function parseComposeServiceImages(composeRaw) {
|
|
1442
|
+
const images = /* @__PURE__ */ new Map();
|
|
1443
|
+
let inServices = false;
|
|
1444
|
+
let current = null;
|
|
1445
|
+
for (const line of composeRaw.split(/\r?\n/)) {
|
|
1446
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
1447
|
+
const indent = line.length - line.trimStart().length;
|
|
1448
|
+
const trimmed = line.trim();
|
|
1449
|
+
if (indent === 0) {
|
|
1450
|
+
inServices = /^services:\s*$/.test(trimmed);
|
|
1451
|
+
current = null;
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
if (!inServices) continue;
|
|
1455
|
+
if (indent === 2 && /^[a-zA-Z0-9_.-]+:\s*$/.test(trimmed)) {
|
|
1456
|
+
current = trimmed.replace(/:\s*$/, "");
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
if (!current) continue;
|
|
1460
|
+
const match = trimmed.match(/^image:\s*(.+?)\s*(?:#.*)?$/);
|
|
1461
|
+
if (match) images.set(current, match[1].trim().replace(/^['"]|['"]$/g, ""));
|
|
1462
|
+
}
|
|
1463
|
+
return images;
|
|
1464
|
+
}
|
|
1465
|
+
function imageDigest(image) {
|
|
1466
|
+
return image.match(/@(sha256:[a-f0-9]{64})$/i)?.[1]?.toLowerCase();
|
|
1467
|
+
}
|
|
1468
|
+
function envRefsInImageExpression(expr) {
|
|
1469
|
+
return [...expr.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1]);
|
|
1470
|
+
}
|
|
1471
|
+
function manifestCoveredComposeServices(release, composeImages, env) {
|
|
1472
|
+
const covered = /* @__PURE__ */ new Set();
|
|
1473
|
+
const pinVars = /* @__PURE__ */ new Map();
|
|
1474
|
+
const manifestRefs = /* @__PURE__ */ new Set();
|
|
1475
|
+
const manifestDigests = /* @__PURE__ */ new Set();
|
|
1476
|
+
for (const [name, service] of Object.entries(release.services)) {
|
|
1477
|
+
const composeName = service.composeService ?? name;
|
|
1478
|
+
covered.add(name);
|
|
1479
|
+
covered.add(composeName);
|
|
1480
|
+
for (const sidecar of COMPOSE_SIDECARS[composeName] ?? []) covered.add(sidecar);
|
|
1481
|
+
if (service.env) pinVars.set(service.env, composeName);
|
|
1482
|
+
const image = service.image.trim();
|
|
1483
|
+
manifestRefs.add(image);
|
|
1484
|
+
manifestRefs.add(stripImageDigest(image));
|
|
1485
|
+
const digest = imageDigest(image);
|
|
1486
|
+
if (digest) manifestDigests.add(digest);
|
|
1487
|
+
}
|
|
1488
|
+
for (const [composeService, expr] of composeImages) {
|
|
1489
|
+
if (covered.has(composeService)) continue;
|
|
1490
|
+
if (envRefsInImageExpression(expr).some((key) => pinVars.has(key))) {
|
|
1491
|
+
covered.add(composeService);
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
const resolved = resolveComposeImageExpression(expr, env);
|
|
1495
|
+
if (!resolved) continue;
|
|
1496
|
+
const digest = imageDigest(resolved);
|
|
1497
|
+
if (digest && manifestDigests.has(digest)) covered.add(composeService);
|
|
1498
|
+
else if (manifestRefs.has(resolved)) covered.add(composeService);
|
|
1499
|
+
}
|
|
1500
|
+
return covered;
|
|
1501
|
+
}
|
|
1502
|
+
function collectManifestDeliveryIssues(input) {
|
|
1503
|
+
const issues = [];
|
|
1504
|
+
const env = parseEnv(input.envRaw);
|
|
1505
|
+
const composeImages = parseComposeServiceImages(input.composeRaw);
|
|
1506
|
+
const covered = manifestCoveredComposeServices(input.release, composeImages, env);
|
|
1507
|
+
const report = (service, origin, detail) => {
|
|
1508
|
+
issues.push({
|
|
1509
|
+
service,
|
|
1510
|
+
origin,
|
|
1511
|
+
reason: "absent-from-manifest",
|
|
1512
|
+
message: `${origin === "host" ? "Running service" : "Compose service"} "${service}" is NOT in signed stack manifest ${input.targetVersion}. ${detail} An image the signed manifest does not pin is not deployable. Publish it to the manifest (exe-os stack-release) and re-cut the manifest, or remove the service from the host.`
|
|
1513
|
+
});
|
|
1514
|
+
};
|
|
1515
|
+
for (const [composeService, expr] of composeImages) {
|
|
1516
|
+
if (covered.has(composeService)) continue;
|
|
1517
|
+
const resolved = resolveComposeImageExpression(expr, env);
|
|
1518
|
+
if (!resolved) continue;
|
|
1519
|
+
report(composeService, "compose", `It would deploy ${resolved}, which no manifest entry pins.`);
|
|
1520
|
+
}
|
|
1521
|
+
for (const hostService of input.hostServices ?? []) {
|
|
1522
|
+
if (covered.has(hostService)) continue;
|
|
1523
|
+
report(hostService, "host", "It is running on this host with no manifest entry behind it.");
|
|
1524
|
+
}
|
|
1525
|
+
for (const [name, service] of Object.entries(input.release.services)) {
|
|
1526
|
+
const image = service.image.trim();
|
|
1527
|
+
if (imageDigest(image)) continue;
|
|
1528
|
+
if (isThirdPartyImage(image)) continue;
|
|
1529
|
+
issues.push({
|
|
1530
|
+
service: service.composeService ?? name,
|
|
1531
|
+
origin: "compose",
|
|
1532
|
+
reason: "unpinned-image",
|
|
1533
|
+
message: `Manifest ${input.targetVersion} entry "${name}" pins image ${image} with no @sha256: digest. A tag can be repointed at different bytes after review, and a tag whose release failed does not resolve at all. Re-cut the manifest with a resolved digest for ${name}.`
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
return issues;
|
|
1537
|
+
}
|
|
1538
|
+
function manifestDeliveryBreakGlassServices(explicit) {
|
|
1539
|
+
const raw = explicit ?? process.env.EXE_STACK_ALLOW_UNMANIFESTED_SERVICES ?? "";
|
|
1540
|
+
return new Set(raw.split(",").map((s) => s.trim()).filter(Boolean));
|
|
1541
|
+
}
|
|
1542
|
+
function manifestDeliveryBreakGlassBanner(bypassed) {
|
|
1543
|
+
const border = "=".repeat(72);
|
|
1544
|
+
return [
|
|
1545
|
+
"",
|
|
1546
|
+
border,
|
|
1547
|
+
" !!! SIGNED-MANIFEST DELIVERY GATE BYPASSED !!!",
|
|
1548
|
+
border,
|
|
1549
|
+
" These services are being deployed WITHOUT being pinned by the signed",
|
|
1550
|
+
" stack manifest. Nobody vouched for the bytes they will run:",
|
|
1551
|
+
...bypassed.map((issue) => ` - ${issue.service} (${issue.origin}): ${issue.reason}`),
|
|
1552
|
+
"",
|
|
1553
|
+
" Authorised by --allow-unmanifested-services / EXE_STACK_ALLOW_UNMANIFESTED_SERVICES.",
|
|
1554
|
+
" Break-glass only. Publish these to the signed manifest and remove the override.",
|
|
1555
|
+
border,
|
|
1556
|
+
""
|
|
1557
|
+
].join("\n");
|
|
1558
|
+
}
|
|
1559
|
+
function assertSignedManifestDeliveryGate(input) {
|
|
1560
|
+
const warn = input.warn ?? ((message) => console.warn(message));
|
|
1561
|
+
if (!input.release || typeof input.release.services !== "object" || input.release.services === null) {
|
|
1562
|
+
throw new Error(
|
|
1563
|
+
`Signed-manifest delivery gate: stack release ${input.targetVersion} has no services block. Refusing to deploy \u2014 a manifest that cannot be read cannot pin anything.`
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
if (Object.keys(input.release.services).length === 0) {
|
|
1567
|
+
throw new Error(
|
|
1568
|
+
`Signed-manifest delivery gate: stack release ${input.targetVersion} pins ZERO services. Refusing to deploy \u2014 an empty manifest release vouches for nothing.`
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
if (input.hostServicesUnavailable) {
|
|
1572
|
+
warn(
|
|
1573
|
+
`[stack-update] \u26A0\uFE0F Signed-manifest delivery gate: could NOT enumerate running containers on this host. Services running outside compose cannot be checked against manifest ${input.targetVersion} on this run. The compose-declared half of the gate still applies.`
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
const issues = collectManifestDeliveryIssues({
|
|
1577
|
+
release: input.release,
|
|
1578
|
+
targetVersion: input.targetVersion,
|
|
1579
|
+
composeRaw: input.composeRaw,
|
|
1580
|
+
envRaw: input.envRaw,
|
|
1581
|
+
hostServices: input.hostServices
|
|
1582
|
+
});
|
|
1583
|
+
if (issues.length === 0) return;
|
|
1584
|
+
const allowed = manifestDeliveryBreakGlassServices(input.allowUnmanifestedServices);
|
|
1585
|
+
const bypassed = issues.filter((i) => i.reason === "absent-from-manifest" && allowed.has(i.service));
|
|
1586
|
+
const blocking = issues.filter((i) => !bypassed.includes(i));
|
|
1587
|
+
if (bypassed.length > 0) warn(manifestDeliveryBreakGlassBanner(bypassed));
|
|
1588
|
+
if (blocking.length === 0) return;
|
|
1589
|
+
const details = blocking.map((i) => `- [${i.reason}] ${i.message}`).join("\n");
|
|
1590
|
+
const absent = blocking.filter((i) => i.reason === "absent-from-manifest").map((i) => i.service);
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`Signed-manifest delivery gate REFUSED this deploy (stack ${input.targetVersion}).
|
|
1593
|
+
Every update goes to the signed stack manifest FIRST, then deploys. An image the manifest does not pin by digest is not deployable.
|
|
1594
|
+
${details}
|
|
1595
|
+
` + (absent.length > 0 ? `Emergency override for the ${absent.length} unmanifested service(s), which must be named explicitly:
|
|
1596
|
+
exe-os stack-update --allow-unmanifested-services ${absent.join(",")} --yes
|
|
1597
|
+
(or EXE_STACK_ALLOW_UNMANIFESTED_SERVICES=${absent.join(",")}). It is logged loudly at the point of use.` : `Re-cut the signed manifest with resolved digests, then re-run.`)
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1256
1600
|
function writeBreakGlassAudit(plan, issues, options) {
|
|
1257
1601
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1258
1602
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
@@ -1352,7 +1696,7 @@ async function verifyManifestImagesAvailable(plan, options) {
|
|
|
1352
1696
|
let ok = false;
|
|
1353
1697
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1354
1698
|
try {
|
|
1355
|
-
exec("docker", ["manifest", "inspect", image]);
|
|
1699
|
+
exec("docker", ["manifest", "inspect", manifestInspectRef(image)]);
|
|
1356
1700
|
ok = true;
|
|
1357
1701
|
break;
|
|
1358
1702
|
} catch (err) {
|
|
@@ -1539,7 +1883,7 @@ A first-party image whose version label does not match its tag cannot be traced
|
|
|
1539
1883
|
}
|
|
1540
1884
|
var PROBE_TIMEOUT_MS = 1e4;
|
|
1541
1885
|
function commandSucceeds(cmd, args = []) {
|
|
1542
|
-
const res =
|
|
1886
|
+
const res = spawnSync(cmd, args, {
|
|
1543
1887
|
stdio: "ignore",
|
|
1544
1888
|
timeout: PROBE_TIMEOUT_MS,
|
|
1545
1889
|
killSignal: "SIGKILL"
|
|
@@ -1547,7 +1891,7 @@ function commandSucceeds(cmd, args = []) {
|
|
|
1547
1891
|
return res.status === 0;
|
|
1548
1892
|
}
|
|
1549
1893
|
function shellSucceeds(command) {
|
|
1550
|
-
const res =
|
|
1894
|
+
const res = spawnSync("sh", ["-lc", command], {
|
|
1551
1895
|
stdio: "ignore",
|
|
1552
1896
|
timeout: PROBE_TIMEOUT_MS,
|
|
1553
1897
|
killSignal: "SIGKILL"
|
|
@@ -1953,7 +2297,7 @@ function hardenHost(exec) {
|
|
|
1953
2297
|
return result;
|
|
1954
2298
|
}
|
|
1955
2299
|
function hardenUfw(exec, result) {
|
|
1956
|
-
const whichUfw =
|
|
2300
|
+
const whichUfw = spawnSync("which", ["ufw"], { stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 });
|
|
1957
2301
|
const ufwInstalled = whichUfw.status === 0;
|
|
1958
2302
|
if (!ufwInstalled) {
|
|
1959
2303
|
if (!existsSync("/etc/os-release")) {
|
|
@@ -1978,7 +2322,7 @@ function hardenUfw(exec, result) {
|
|
|
1978
2322
|
return;
|
|
1979
2323
|
}
|
|
1980
2324
|
}
|
|
1981
|
-
const statusResult =
|
|
2325
|
+
const statusResult = spawnSync("ufw", ["status", "verbose"], {
|
|
1982
2326
|
encoding: "utf8",
|
|
1983
2327
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1984
2328
|
timeout: 1e4
|
|
@@ -2130,7 +2474,7 @@ function hardenSsh(exec, result) {
|
|
|
2130
2474
|
}
|
|
2131
2475
|
function areStackContainersRunning(composeFile, envFile) {
|
|
2132
2476
|
try {
|
|
2133
|
-
const result =
|
|
2477
|
+
const result = spawnSync("docker", [...composeArgsForStack(composeFile, envFile), "ps", "-q"], {
|
|
2134
2478
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2135
2479
|
timeout: 15e3
|
|
2136
2480
|
});
|
|
@@ -2174,7 +2518,7 @@ function assertBindMountsExist(stackDir) {
|
|
|
2174
2518
|
function assertVolumesIntact() {
|
|
2175
2519
|
const missing = [];
|
|
2176
2520
|
for (const vol of CRITICAL_VOLUMES) {
|
|
2177
|
-
const result =
|
|
2521
|
+
const result = spawnSync("docker", ["volume", "inspect", `exe-os_${vol}`], {
|
|
2178
2522
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2179
2523
|
timeout: 5e3,
|
|
2180
2524
|
killSignal: "SIGKILL"
|
|
@@ -2190,13 +2534,13 @@ function assertVolumesIntact() {
|
|
|
2190
2534
|
}
|
|
2191
2535
|
}
|
|
2192
2536
|
function warnLegacyDaemonVolume() {
|
|
2193
|
-
const legacy =
|
|
2537
|
+
const legacy = spawnSync("docker", ["volume", "inspect", "exe-os_exed_data"], {
|
|
2194
2538
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2195
2539
|
timeout: 5e3,
|
|
2196
2540
|
killSignal: "SIGKILL"
|
|
2197
2541
|
// SIGTERM'd docker CLIs on a hung daemon linger as orphans
|
|
2198
2542
|
});
|
|
2199
|
-
const current =
|
|
2543
|
+
const current = spawnSync("docker", ["volume", "inspect", "exe-os_exe_os_data"], {
|
|
2200
2544
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2201
2545
|
timeout: 5e3,
|
|
2202
2546
|
killSignal: "SIGKILL"
|
|
@@ -2378,7 +2722,7 @@ async function runStackUpdate(options) {
|
|
|
2378
2722
|
});
|
|
2379
2723
|
assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
|
|
2380
2724
|
assertDeploymentScopeAllowed(plan, options.deploymentPersona ?? "customer");
|
|
2381
|
-
const requiredEnvValidation = options.dryRun ? { derived: {}, unresolvable: [] } : detectMissingRequiredEnvVars(stagedCompose.raw, envRaw);
|
|
2725
|
+
const requiredEnvValidation = options.dryRun ? { derived: {}, unresolvable: [] } : detectMissingRequiredEnvVars(stagedCompose.raw, envRaw, targetRelease);
|
|
2382
2726
|
const migratedEnvVars = requiredEnvValidation.derived;
|
|
2383
2727
|
if (requiredEnvValidation.unresolvable.length > 0) {
|
|
2384
2728
|
const lines = requiredEnvValidation.unresolvable.map((r) => ` - ${r.key}${r.message ? ` (${r.message})` : ""}`);
|
|
@@ -2392,6 +2736,18 @@ ${lines.join("\n")}`
|
|
|
2392
2736
|
...migratedEnvVars,
|
|
2393
2737
|
...Object.fromEntries(plan.changes.map((c) => [c.key, c.after]))
|
|
2394
2738
|
});
|
|
2739
|
+
const processPinned = syncImagePinsWithProcessEnv(plannedEnvRaw, requiredImagePinKeys(stagedCompose.raw, targetRelease));
|
|
2740
|
+
if (processPinned.pinned.length > 0) {
|
|
2741
|
+
console.log(
|
|
2742
|
+
`[stack-update] Pinned validated image ref(s) into the deploy environment: ${processPinned.pinned.join(", ")}`
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
const processHealthBins = syncImagePinsWithProcessEnv(plannedEnvRaw, Object.values(MONITOR_HEALTH_BIN_ENV));
|
|
2746
|
+
if (processHealthBins.pinned.length > 0 || processHealthBins.cleared.length > 0) {
|
|
2747
|
+
console.log(
|
|
2748
|
+
`[stack-update] Synced monitor healthcheck bin path(s) into the deploy environment: ${[...processHealthBins.pinned, ...processHealthBins.cleared].join(", ")}`
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2395
2751
|
assertProductionDeployGate(plan, plannedEnvRaw, composeRaw, {
|
|
2396
2752
|
breakGlassReason: options.breakGlassReason,
|
|
2397
2753
|
breakGlassAuditFile: options.breakGlassAuditFile,
|
|
@@ -2399,6 +2755,21 @@ ${lines.join("\n")}`
|
|
|
2399
2755
|
now,
|
|
2400
2756
|
envFile: options.envFile
|
|
2401
2757
|
});
|
|
2758
|
+
{
|
|
2759
|
+
const hostState = listHostComposeServices(
|
|
2760
|
+
composeArgsForStack(options.composeFile, options.envFile),
|
|
2761
|
+
options.composePsCapture ?? defaultComposePsCapture
|
|
2762
|
+
);
|
|
2763
|
+
assertSignedManifestDeliveryGate({
|
|
2764
|
+
release: manifest.stacks[plan.targetVersion],
|
|
2765
|
+
targetVersion: plan.targetVersion,
|
|
2766
|
+
composeRaw,
|
|
2767
|
+
envRaw: plannedEnvRaw,
|
|
2768
|
+
hostServices: hostState.services,
|
|
2769
|
+
hostServicesUnavailable: hostState.unavailable,
|
|
2770
|
+
allowUnmanifestedServices: options.allowUnmanifestedServices
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2402
2773
|
const previousVersion = readCurrentStackVersion(lockFile);
|
|
2403
2774
|
if (options.verifyImages) {
|
|
2404
2775
|
const endPhase = startPhase("verify images");
|
|
@@ -2754,9 +3125,14 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2754
3125
|
...RESTART_ORDER.filter((service) => scopedComposeServices.includes(service)),
|
|
2755
3126
|
...scopedComposeServices.filter((service) => !RESTART_ORDER.includes(service))
|
|
2756
3127
|
];
|
|
2757
|
-
const preSnapshot =
|
|
3128
|
+
const preSnapshot = spawnSync("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
|
|
2758
3129
|
const preContainerCount = preSnapshot.stdout?.split("\n").filter(Boolean).length ?? 0;
|
|
2759
3130
|
console.log(`[stack-update] Pre-update snapshot: ${preContainerCount} containers`);
|
|
3131
|
+
const bundledIsolation = Object.keys(plan.release.services).length > 1;
|
|
3132
|
+
const failedManifestServices = /* @__PURE__ */ new Set();
|
|
3133
|
+
const failedComposeServices = /* @__PURE__ */ new Set();
|
|
3134
|
+
const isolationRollbackErrors = [];
|
|
3135
|
+
const partialHealthWarnings = [];
|
|
2760
3136
|
let restartedAnyService = false;
|
|
2761
3137
|
progress.resetCounter(restartOrder.length);
|
|
2762
3138
|
progress.startPhase("restart");
|
|
@@ -2765,15 +3141,18 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2765
3141
|
restartIndex++;
|
|
2766
3142
|
const endRestart = startPhase(`restart ${service}`);
|
|
2767
3143
|
const manifestServiceName = Object.entries(plan.release.services).find(([name, svc]) => (svc.composeService ?? name) === service)?.[0] ?? service;
|
|
2768
|
-
|
|
3144
|
+
if (bundledIsolation && (failedComposeServices.has(service) || failedManifestServices.has(resolveOwningManifestService(service, plan.release.services)))) {
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
const serviceDefined = options.composeServiceExists ? options.composeServiceExists(service) : spawnSync("docker", [...composeArgs, "ps", "--quiet", service], {
|
|
2769
3148
|
encoding: "utf8",
|
|
2770
3149
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2771
3150
|
timeout: 1e4
|
|
2772
|
-
});
|
|
2773
|
-
if (
|
|
3151
|
+
}).status === 0;
|
|
3152
|
+
if (!serviceDefined) continue;
|
|
2774
3153
|
progress.logServiceStep(restartIndex, service, "Restarting");
|
|
2775
3154
|
if (service === "exe-wiki") {
|
|
2776
|
-
const guard =
|
|
3155
|
+
const guard = spawnSync(
|
|
2777
3156
|
"docker",
|
|
2778
3157
|
[...composeArgs, "run", "--rm", "--no-deps", "exe-wiki-schema-guard"],
|
|
2779
3158
|
{ encoding: "utf8", stdio: ["pipe", "inherit", "inherit"], timeout: 12e4 }
|
|
@@ -2788,19 +3167,19 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2788
3167
|
restartedAnyService = true;
|
|
2789
3168
|
updateServiceStatus(lockFile, manifestServiceName, "restarted", now);
|
|
2790
3169
|
const maxWait = resolveServiceHealthTimeout(service, plan.release, options.healthTimeoutSecs);
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
const inspectResult = spawnSync2(
|
|
3170
|
+
const probeHealth = options.inspectContainerHealth ?? ((svc) => {
|
|
3171
|
+
const inspectResult = spawnSync(
|
|
2794
3172
|
"docker",
|
|
2795
|
-
["inspect", "--format", "{{.State.Health.Status}}",
|
|
3173
|
+
["inspect", "--format", "{{.State.Health.Status}}", svc],
|
|
2796
3174
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2797
3175
|
);
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
3176
|
+
if (inspectResult.status !== 0) return null;
|
|
3177
|
+
return inspectResult.stdout?.trim() ?? "";
|
|
3178
|
+
});
|
|
3179
|
+
let healthy = false;
|
|
3180
|
+
for (let i = 0; i < maxWait; i++) {
|
|
3181
|
+
const status = probeHealth(service);
|
|
3182
|
+
if (status === null || status === "" || status === "healthy") {
|
|
2804
3183
|
healthy = true;
|
|
2805
3184
|
break;
|
|
2806
3185
|
}
|
|
@@ -2810,7 +3189,27 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2810
3189
|
const failMsg = `Service ${service} failed health check after ${maxWait}s`;
|
|
2811
3190
|
progress.logServiceFailure(service, failMsg, options.composeFile);
|
|
2812
3191
|
updateServiceStatus(lockFile, manifestServiceName, "failed", now, failMsg);
|
|
2813
|
-
|
|
3192
|
+
if (!bundledIsolation) {
|
|
3193
|
+
throw new Error(`${failMsg} \u2014 aborting update`);
|
|
3194
|
+
}
|
|
3195
|
+
const owner = resolveOwningManifestService(service, plan.release.services);
|
|
3196
|
+
console.error(
|
|
3197
|
+
`[stack-update] ISOLATED ROLLBACK (bug a5e29168): ${service} failed its health gate \u2014 reverting only ${owner} and its sidecars; the rest of the batch stays deployed.`
|
|
3198
|
+
);
|
|
3199
|
+
try {
|
|
3200
|
+
await rollbackStackUpdate({ ...options, lockFile, serviceName: owner, isolateEnvScope: true });
|
|
3201
|
+
console.error(`[stack-update] Isolated rollback of ${owner} complete; batch continues.`);
|
|
3202
|
+
} catch (isoErr) {
|
|
3203
|
+
const isoReason = isoErr instanceof Error ? isoErr.message : String(isoErr);
|
|
3204
|
+
console.error(`[stack-update] Isolated rollback of ${owner} did not land healthy: ${isoReason}`);
|
|
3205
|
+
isolationRollbackErrors.push(`${owner}: ${isoReason}`);
|
|
3206
|
+
}
|
|
3207
|
+
failedManifestServices.add(owner);
|
|
3208
|
+
for (const cs of expandComposeServicesWithSidecars([plan.release.services[owner]?.composeService ?? owner])) {
|
|
3209
|
+
failedComposeServices.add(cs);
|
|
3210
|
+
}
|
|
3211
|
+
updateServiceStatus(lockFile, owner, "failed", now, failMsg);
|
|
3212
|
+
continue;
|
|
2814
3213
|
}
|
|
2815
3214
|
if (service === "exe-db") {
|
|
2816
3215
|
applyPostgresInitSql(composeArgs, options.envFile, options.composeFile, exec);
|
|
@@ -2823,7 +3222,7 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2823
3222
|
if (!restartedAnyService) {
|
|
2824
3223
|
exec("docker", [...composeArgs, "up", "-d", "--remove-orphans"]);
|
|
2825
3224
|
}
|
|
2826
|
-
const postSnapshot =
|
|
3225
|
+
const postSnapshot = spawnSync("docker", ["ps", "--format", "json"], { encoding: "utf8", timeout: 1e4 });
|
|
2827
3226
|
const postContainerCount = postSnapshot.stdout?.split("\n").filter(Boolean).length ?? 0;
|
|
2828
3227
|
console.log(`[stack-update] Post-update snapshot: ${postContainerCount} containers (was ${preContainerCount})`);
|
|
2829
3228
|
if (postContainerCount < preContainerCount) {
|
|
@@ -2834,7 +3233,7 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2834
3233
|
try {
|
|
2835
3234
|
await verifyComposeServicesRunning(
|
|
2836
3235
|
composeArgs,
|
|
2837
|
-
restartOrder,
|
|
3236
|
+
restartOrder.filter((s) => !failedComposeServices.has(s)),
|
|
2838
3237
|
options.healthTimeoutSecs ?? 180,
|
|
2839
3238
|
options.composePsCapture
|
|
2840
3239
|
);
|
|
@@ -2842,9 +3241,38 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2842
3241
|
endPhase3();
|
|
2843
3242
|
}
|
|
2844
3243
|
}
|
|
3244
|
+
{
|
|
3245
|
+
const cfIngressConfigPath = path.join(path.dirname(options.composeFile), "cloudflared", "config.yml");
|
|
3246
|
+
const cfIngressConfigYml = existsSync(cfIngressConfigPath) ? readFileSync(cfIngressConfigPath, "utf8") : null;
|
|
3247
|
+
await verifyIngressTargetsReachable(
|
|
3248
|
+
cfIngressConfigYml,
|
|
3249
|
+
defaultIngressReachabilityProbe,
|
|
3250
|
+
makeDefaultComposeServiceProbe(composeArgs)
|
|
3251
|
+
);
|
|
3252
|
+
}
|
|
2845
3253
|
const stackEnv = existsSync(options.envFile) ? parseEnv(readFileSync(options.envFile, "utf8")) : void 0;
|
|
2846
|
-
|
|
2847
|
-
|
|
3254
|
+
const partialRun = failedManifestServices.size > 0;
|
|
3255
|
+
if (partialRun) {
|
|
3256
|
+
console.error(
|
|
3257
|
+
`[stack-update] SKIPPING RELEASE-LEVEL POST-DEPLOY WORK (bug a5e29168): ${failedManifestServices.size} service(s) were rolled back in isolation (${[...failedManifestServices].join(", ")}), so the stack is mixed-version and has NOT fully reached ${plan.targetVersion}.
|
|
3258
|
+
Skipped: release post-migrations, post-deploy health gate, volume-integrity assert, post-deploy verification, host hardening. The release health-URL probe still runs for the services that stayed deployed, but only to report \u2014 it can no longer abort the batch.
|
|
3259
|
+
Re-run (or --resume) once the rolled-back service(s) are fixed so these run against a uniform ${plan.targetVersion} stack.`
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
if (partialRun) {
|
|
3263
|
+
try {
|
|
3264
|
+
await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3, stackEnv, failedManifestServices);
|
|
3265
|
+
} catch (healthErr) {
|
|
3266
|
+
const reason = healthErr instanceof Error ? healthErr.message : String(healthErr);
|
|
3267
|
+
console.error(
|
|
3268
|
+
`[stack-update] Release health probe failed on the partial stack (NOT rolling back the batch \u2014 bug a5e29168): ${reason}`
|
|
3269
|
+
);
|
|
3270
|
+
partialHealthWarnings.push(reason);
|
|
3271
|
+
}
|
|
3272
|
+
} else {
|
|
3273
|
+
await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3, stackEnv, failedManifestServices);
|
|
3274
|
+
}
|
|
3275
|
+
const releasePostMigrations = partialRun ? void 0 : plan.release.migrations?.post;
|
|
2848
3276
|
if (releasePostMigrations && releasePostMigrations.length > 0) {
|
|
2849
3277
|
const endReleasePost = startPhase("release post-migrations");
|
|
2850
3278
|
try {
|
|
@@ -2864,8 +3292,49 @@ Push the missing image(s) to the registry, or re-run with --break-glass <reason>
|
|
|
2864
3292
|
console.warn(`[stack-update] \u26A0 Overlay re-apply step failed (non-fatal): ${reason}`);
|
|
2865
3293
|
}
|
|
2866
3294
|
for (const serviceName of Object.keys(plan.release.services)) {
|
|
3295
|
+
if (failedManifestServices.has(serviceName)) continue;
|
|
2867
3296
|
updateServiceStatus(lockFile, serviceName, "completed", now);
|
|
2868
3297
|
}
|
|
3298
|
+
if (partialRun) {
|
|
3299
|
+
progress.logOverallSummary();
|
|
3300
|
+
printPhaseSummary();
|
|
3301
|
+
const failedList = [...failedManifestServices];
|
|
3302
|
+
const appliedList = Object.keys(plan.release.services).filter((s) => !failedManifestServices.has(s));
|
|
3303
|
+
console.error(
|
|
3304
|
+
`[stack-update] PARTIAL UPDATE (bug a5e29168): ${appliedList.length} service(s) deployed to ${plan.targetVersion}; ${failedList.length} rolled back in isolation and left on the previous image.
|
|
3305
|
+
Applied: ${appliedList.join(", ") || "(none)"}
|
|
3306
|
+
Rolled back: ${failedList.join(", ")}` + (isolationRollbackErrors.length > 0 ? `
|
|
3307
|
+
Rollback warnings:
|
|
3308
|
+
- ${isolationRollbackErrors.join("\n - ")}` : ""),
|
|
3309
|
+
partialHealthWarnings.length > 0 ? `
|
|
3310
|
+
Health-probe warnings (services left deployed but NOT answering their health URL):
|
|
3311
|
+
- ${partialHealthWarnings.join("\n - ")}` : ""
|
|
3312
|
+
);
|
|
3313
|
+
const partialLock = readStackUpdateLock(lockFile) ?? {};
|
|
3314
|
+
writeStackUpdateLock(lockFile, {
|
|
3315
|
+
...partialLock,
|
|
3316
|
+
previousVersion,
|
|
3317
|
+
updatedAt: now().toISOString(),
|
|
3318
|
+
backupEnvFile: partialLock.backupEnvFile ?? backupEnvFile,
|
|
3319
|
+
backupComposeFile: partialLock.backupComposeFile ?? backupComposeFile,
|
|
3320
|
+
backupComposeOverrideFile: partialLock.backupComposeOverrideFile ?? backupComposeOverrideFile,
|
|
3321
|
+
hadComposeOverrideFile: partialLock.hadComposeOverrideFile ?? hadComposeOverrideFile,
|
|
3322
|
+
mutatedEnvKeys: partialLock.mutatedEnvKeys ?? mutatedEnvKeys,
|
|
3323
|
+
preUpdateImageDefaults: partialLock.preUpdateImageDefaults ?? configBackup.preUpdateImageDefaults,
|
|
3324
|
+
services: { ...fullTargetPlan.release.services, ...partialLock.services ?? {}, ...plan.release.services }
|
|
3325
|
+
});
|
|
3326
|
+
const partialStaleness = evaluateDeployStaleness(readStackUpdateLock(lockFile), now);
|
|
3327
|
+
if (partialStaleness) writeDeployStalenessAlert(partialStaleness);
|
|
3328
|
+
await postDeployAudit(
|
|
3329
|
+
options,
|
|
3330
|
+
"partial",
|
|
3331
|
+
plan.targetVersion,
|
|
3332
|
+
previousVersion,
|
|
3333
|
+
`Isolated rollback of: ${failedList.join(", ")}`,
|
|
3334
|
+
{ applied: appliedList.length, rolledBack: failedList.length }
|
|
3335
|
+
);
|
|
3336
|
+
return { status: "partial", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile, serviceStatus: readStackUpdateLock(lockFile)?.serviceStatus };
|
|
3337
|
+
}
|
|
2869
3338
|
try {
|
|
2870
3339
|
if (!options.healthGate) {
|
|
2871
3340
|
throw new Error("healthGate hook not provided");
|
|
@@ -3097,9 +3566,10 @@ function resolveHealthHost(serviceName, service, env) {
|
|
|
3097
3566
|
}
|
|
3098
3567
|
return expandEnvRefs(service.healthHost, env ?? /* @__PURE__ */ new Map());
|
|
3099
3568
|
}
|
|
3100
|
-
async function verifyReleaseHealth(release, retries, delayMs, env) {
|
|
3569
|
+
async function verifyReleaseHealth(release, retries, delayMs, env, skipServices) {
|
|
3101
3570
|
for (const [serviceName, service] of Object.entries(release.services)) {
|
|
3102
3571
|
if (!service.healthUrl) continue;
|
|
3572
|
+
if (skipServices?.has(serviceName)) continue;
|
|
3103
3573
|
const hostHeader = resolveHealthHost(serviceName, service, env);
|
|
3104
3574
|
await waitForHttpOk(service.healthUrl, retries, delayMs, serviceName, hostHeader);
|
|
3105
3575
|
}
|
|
@@ -3107,7 +3577,7 @@ async function verifyReleaseHealth(release, retries, delayMs, env) {
|
|
|
3107
3577
|
var defaultComposePsCapture = (composeArgs) => {
|
|
3108
3578
|
let result;
|
|
3109
3579
|
try {
|
|
3110
|
-
result =
|
|
3580
|
+
result = spawnSync("docker", [...composeArgs, "ps", "--all", "--format", "json"], {
|
|
3111
3581
|
encoding: "utf8",
|
|
3112
3582
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3113
3583
|
timeout: 2e4
|
|
@@ -3118,6 +3588,37 @@ var defaultComposePsCapture = (composeArgs) => {
|
|
|
3118
3588
|
if (result.error || typeof result.status !== "number" || result.status !== 0) return null;
|
|
3119
3589
|
return result.stdout?.toString() ?? "";
|
|
3120
3590
|
};
|
|
3591
|
+
function listHostComposeServices(composeArgs, capture = defaultComposePsCapture) {
|
|
3592
|
+
const stdout = capture(composeArgs);
|
|
3593
|
+
if (stdout === null) return { services: [], unavailable: true };
|
|
3594
|
+
const trimmed = stdout.trim().replace(/^/, "");
|
|
3595
|
+
if (!trimmed) return { services: [], unavailable: false };
|
|
3596
|
+
const records = [];
|
|
3597
|
+
if (trimmed.startsWith("[")) {
|
|
3598
|
+
try {
|
|
3599
|
+
const parsed = JSON.parse(trimmed);
|
|
3600
|
+
if (Array.isArray(parsed)) records.push(...parsed);
|
|
3601
|
+
} catch {
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
if (records.length === 0) {
|
|
3605
|
+
for (const line of trimmed.split("\n")) {
|
|
3606
|
+
const l = line.trim();
|
|
3607
|
+
if (!l) continue;
|
|
3608
|
+
try {
|
|
3609
|
+
records.push(JSON.parse(l));
|
|
3610
|
+
} catch {
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
if (records.length === 0) return { services: [], unavailable: true };
|
|
3615
|
+
const services = /* @__PURE__ */ new Set();
|
|
3616
|
+
for (const obj of records) {
|
|
3617
|
+
const service = String(obj.Service ?? "").trim();
|
|
3618
|
+
if (service) services.add(service);
|
|
3619
|
+
}
|
|
3620
|
+
return { services: [...services], unavailable: false };
|
|
3621
|
+
}
|
|
3121
3622
|
function snapshotComposeServiceStates(composeArgs, wantedServices, capture) {
|
|
3122
3623
|
const stdout = capture(composeArgs);
|
|
3123
3624
|
if (stdout === null) return null;
|
|
@@ -3215,6 +3716,116 @@ Inspect with: docker compose ps --all`
|
|
|
3215
3716
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
3216
3717
|
}
|
|
3217
3718
|
}
|
|
3719
|
+
var INGRESS_PROBE_TIMEOUT_MS = 4e3;
|
|
3720
|
+
var defaultDockerCapture = (args) => {
|
|
3721
|
+
let result;
|
|
3722
|
+
try {
|
|
3723
|
+
result = spawnSync("docker", args, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 2e4 });
|
|
3724
|
+
} catch (err) {
|
|
3725
|
+
return { unavailable: true, status: null, stdout: "", stderr: err instanceof Error ? err.message : String(err) };
|
|
3726
|
+
}
|
|
3727
|
+
if (result.error) {
|
|
3728
|
+
return { unavailable: true, status: null, stdout: "", stderr: result.error.message };
|
|
3729
|
+
}
|
|
3730
|
+
return {
|
|
3731
|
+
unavailable: false,
|
|
3732
|
+
status: typeof result.status === "number" ? result.status : null,
|
|
3733
|
+
stdout: result.stdout?.toString() ?? "",
|
|
3734
|
+
stderr: result.stderr?.toString() ?? ""
|
|
3735
|
+
};
|
|
3736
|
+
};
|
|
3737
|
+
function isDockerDaemonUnavailable(stderr) {
|
|
3738
|
+
const s = stderr.toLowerCase();
|
|
3739
|
+
return s.includes("cannot connect to the docker daemon") || s.includes("is the docker daemon running") || s.includes("docker daemon is not running") || s.includes("error during connect") || s.includes("permission denied while trying to connect to the docker daemon");
|
|
3740
|
+
}
|
|
3741
|
+
function isComposeNetworkHost(host) {
|
|
3742
|
+
const h = host.trim().toLowerCase();
|
|
3743
|
+
if (h === "" || h === "localhost") return false;
|
|
3744
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(h)) return false;
|
|
3745
|
+
if (h.includes(":")) return false;
|
|
3746
|
+
if (h.includes(".")) return false;
|
|
3747
|
+
return true;
|
|
3748
|
+
}
|
|
3749
|
+
function isCloudflaredLoopbackHost(host) {
|
|
3750
|
+
let h = host.trim().toLowerCase();
|
|
3751
|
+
if (h === "") return false;
|
|
3752
|
+
if (h === "localhost") return true;
|
|
3753
|
+
if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
|
|
3754
|
+
if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
|
|
3755
|
+
h = h.replace(/^::ffff:/, "");
|
|
3756
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(h)) return true;
|
|
3757
|
+
return false;
|
|
3758
|
+
}
|
|
3759
|
+
function makeDefaultComposeServiceProbe(composeArgs, capture = defaultDockerCapture, hostProbe = defaultIngressReachabilityProbe) {
|
|
3760
|
+
return async (service, port) => {
|
|
3761
|
+
const ps = capture([...composeArgs, "ps", "-q", service]);
|
|
3762
|
+
if (ps.unavailable || ps.status !== 0 && isDockerDaemonUnavailable(ps.stderr)) {
|
|
3763
|
+
console.warn(`[stack-update] cloudflared ingress: cannot query docker for compose service '${service}'; treating as reachable (indeterminate)`);
|
|
3764
|
+
return true;
|
|
3765
|
+
}
|
|
3766
|
+
const containerId = ps.stdout.trim().split(/\r?\n/)[0]?.trim() ?? "";
|
|
3767
|
+
if (!containerId) return false;
|
|
3768
|
+
const ins = capture(["inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}", containerId]);
|
|
3769
|
+
if (ins.unavailable || ins.status !== 0 && isDockerDaemonUnavailable(ins.stderr)) {
|
|
3770
|
+
console.warn(`[stack-update] cloudflared ingress: cannot inspect container for compose service '${service}'; treating as reachable (indeterminate)`);
|
|
3771
|
+
return true;
|
|
3772
|
+
}
|
|
3773
|
+
const ip = ins.stdout.trim().split(/\s+/).find((x) => x !== "") ?? "";
|
|
3774
|
+
if (!ip) return false;
|
|
3775
|
+
return hostProbe(ip, port);
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
function defaultIngressReachabilityProbe(host, port) {
|
|
3779
|
+
return new Promise((resolve) => {
|
|
3780
|
+
let settled = false;
|
|
3781
|
+
const socket = net.connect({ host, port });
|
|
3782
|
+
const done = (reachable) => {
|
|
3783
|
+
if (settled) return;
|
|
3784
|
+
settled = true;
|
|
3785
|
+
socket.destroy();
|
|
3786
|
+
resolve(reachable);
|
|
3787
|
+
};
|
|
3788
|
+
socket.setTimeout(INGRESS_PROBE_TIMEOUT_MS);
|
|
3789
|
+
socket.once("connect", () => done(true));
|
|
3790
|
+
socket.once("timeout", () => done(false));
|
|
3791
|
+
socket.once("error", (err) => {
|
|
3792
|
+
if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) done(true);
|
|
3793
|
+
else done(false);
|
|
3794
|
+
});
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
async function verifyIngressTargetsReachable(configYml, probe = defaultIngressReachabilityProbe, composeProbe) {
|
|
3798
|
+
if (!configYml || configYml.trim() === "") return;
|
|
3799
|
+
const targets = parseCloudflaredIngressTargets(configYml);
|
|
3800
|
+
if (targets.length === 0) return;
|
|
3801
|
+
const loopbackTargets = targets.filter((t) => isCloudflaredLoopbackHost(t.host));
|
|
3802
|
+
const probeTargets = targets.filter((t) => !isCloudflaredLoopbackHost(t.host));
|
|
3803
|
+
const effectiveProbe = composeProbe ? (host, port) => isComposeNetworkHost(host) ? composeProbe(host, port) : probe(host, port) : probe;
|
|
3804
|
+
const probedUnreachable = await findUnreachableIngressTargets(probeTargets, effectiveProbe);
|
|
3805
|
+
if (loopbackTargets.length === 0 && probedUnreachable.length === 0) {
|
|
3806
|
+
console.log(`[stack-update] \u2713 cloudflared ingress: all ${targets.length} target(s) reachable`);
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
const fmt = (t) => `${t.host}:${t.port}${t.hostname ? ` (hostname ${t.hostname})` : ""} [service ${t.service}]`;
|
|
3810
|
+
const sections = [];
|
|
3811
|
+
if (probedUnreachable.length > 0) {
|
|
3812
|
+
sections.push(
|
|
3813
|
+
`target(s) not accepting TCP connections:
|
|
3814
|
+
- ${probedUnreachable.map(fmt).join("\n - ")}
|
|
3815
|
+
\`cloudflared tunnel ingress validate\` only checks rule syntax; a rule pointing at a dead port validates cleanly and then 502s every request. Fix the target service/port (or remove the rule) and re-deploy.`
|
|
3816
|
+
);
|
|
3817
|
+
}
|
|
3818
|
+
if (loopbackTargets.length > 0) {
|
|
3819
|
+
sections.push(
|
|
3820
|
+
`loopback target(s) unreachable from the cloudflared container:
|
|
3821
|
+
- ${loopbackTargets.map(fmt).join("\n - ")}
|
|
3822
|
+
cloudflared runs as a bridge-network container, so 127.0.0.1/localhost resolves to the tunnel container itself \u2014 not the deploy host where the app port is published \u2014 and every request 502s. Address the service by its docker-compose service name (e.g. \`http://exe-dashboard:PORT\`) and re-deploy.`
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
throw new Error(
|
|
3826
|
+
`Cloudflared ingress reachability check failed (bug 57fcb5bf) \u2014 ${sections.join("\n\n")}`
|
|
3827
|
+
);
|
|
3828
|
+
}
|
|
3218
3829
|
async function waitForHttpOk(url, retries, delayMs, label, hostHeader) {
|
|
3219
3830
|
let last = "";
|
|
3220
3831
|
for (let i = 0; i < retries; i++) {
|
|
@@ -3329,7 +3940,7 @@ function parseComposeVolumeNames(composeRaw) {
|
|
|
3329
3940
|
return names;
|
|
3330
3941
|
}
|
|
3331
3942
|
function volumeHasComposeProjectLabel(volumeName, projectName) {
|
|
3332
|
-
const res =
|
|
3943
|
+
const res = spawnSync(
|
|
3333
3944
|
"docker",
|
|
3334
3945
|
["volume", "inspect", volumeName, "--format", '{{ index .Labels "com.docker.compose.project" }}'],
|
|
3335
3946
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
@@ -3358,7 +3969,7 @@ function detectDanglingVolumes(scope) {
|
|
|
3358
3969
|
byName.set(name, { name, driver: driver ?? "local", size: "" });
|
|
3359
3970
|
}
|
|
3360
3971
|
};
|
|
3361
|
-
const labeled =
|
|
3972
|
+
const labeled = spawnSync(
|
|
3362
3973
|
"docker",
|
|
3363
3974
|
[
|
|
3364
3975
|
"volume",
|
|
@@ -3373,7 +3984,7 @@ function detectDanglingVolumes(scope) {
|
|
|
3373
3984
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
3374
3985
|
);
|
|
3375
3986
|
if (labeled.status === 0 && labeled.stdout?.trim()) ingest(labeled.stdout, false);
|
|
3376
|
-
const all =
|
|
3987
|
+
const all = spawnSync(
|
|
3377
3988
|
"docker",
|
|
3378
3989
|
["volume", "ls", "--filter", "dangling=true", "--format", "{{.Name}} {{.Driver}}"],
|
|
3379
3990
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
@@ -3388,14 +3999,14 @@ function detectDanglingVolumes(scope) {
|
|
|
3388
3999
|
}
|
|
3389
4000
|
function getVolumeSize(volumeName) {
|
|
3390
4001
|
try {
|
|
3391
|
-
const inspectResult =
|
|
4002
|
+
const inspectResult = spawnSync(
|
|
3392
4003
|
"docker",
|
|
3393
4004
|
["volume", "inspect", volumeName, "--format", "{{.Mountpoint}}"],
|
|
3394
4005
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
3395
4006
|
);
|
|
3396
4007
|
const mountpoint = inspectResult.stdout?.trim();
|
|
3397
4008
|
if (!mountpoint || inspectResult.status !== 0) return "unknown";
|
|
3398
|
-
const duResult =
|
|
4009
|
+
const duResult = spawnSync(
|
|
3399
4010
|
"du",
|
|
3400
4011
|
["-sh", mountpoint],
|
|
3401
4012
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
@@ -3424,7 +4035,7 @@ function removeDanglingVolumes(volumes, scope) {
|
|
|
3424
4035
|
);
|
|
3425
4036
|
continue;
|
|
3426
4037
|
}
|
|
3427
|
-
const checkResult =
|
|
4038
|
+
const checkResult = spawnSync(
|
|
3428
4039
|
"docker",
|
|
3429
4040
|
["volume", "ls", "--filter", "dangling=true", "--filter", `name=^${vol.name}$`, "--format", "{{.Name}}"],
|
|
3430
4041
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
@@ -3512,6 +4123,8 @@ export {
|
|
|
3512
4123
|
parseComposeServiceNames,
|
|
3513
4124
|
isUsableTunnelToken,
|
|
3514
4125
|
isCloudflaredTunnelMisconfigured,
|
|
4126
|
+
parseCloudflaredIngressTargets,
|
|
4127
|
+
findUnreachableIngressTargets,
|
|
3515
4128
|
resolveTransitiveDeps,
|
|
3516
4129
|
filterServicesBySelection,
|
|
3517
4130
|
buildDefaultServiceSelection,
|
|
@@ -3525,6 +4138,7 @@ export {
|
|
|
3525
4138
|
runReleaseMigrations,
|
|
3526
4139
|
isExecFormMigration,
|
|
3527
4140
|
expandComposeServicesWithSidecars,
|
|
4141
|
+
resolveOwningManifestService,
|
|
3528
4142
|
composeArgsForStack,
|
|
3529
4143
|
parseStackManifest,
|
|
3530
4144
|
loadStackManifest,
|
|
@@ -3538,7 +4152,13 @@ export {
|
|
|
3538
4152
|
resolveServerImageOverride,
|
|
3539
4153
|
applyServerImageOverrideResolution,
|
|
3540
4154
|
parseRequiredEnvRefs,
|
|
4155
|
+
resolveManifestImagePin,
|
|
4156
|
+
requiredImagePinKeys,
|
|
4157
|
+
syncImagePinsWithProcessEnv,
|
|
3541
4158
|
detectMissingRequiredEnvVars,
|
|
4159
|
+
MONITOR_HEALTH_BIN_ENV,
|
|
4160
|
+
MONITOR_IMAGE_TAG_ENV,
|
|
4161
|
+
monitorHealthBinForImage,
|
|
3542
4162
|
createStackUpdatePlan,
|
|
3543
4163
|
isUnpinnedImageBreakGlassEnabled,
|
|
3544
4164
|
isStrictDigestEnabled,
|
|
@@ -3546,10 +4166,17 @@ export {
|
|
|
3546
4166
|
isThirdPartyImage,
|
|
3547
4167
|
stripImageDigest,
|
|
3548
4168
|
imagePullRef,
|
|
4169
|
+
manifestInspectRef,
|
|
3549
4170
|
collectProductionDeployGateIssues,
|
|
3550
4171
|
collectImageAvailabilityIssues,
|
|
3551
4172
|
assertDeploymentScopeAllowed,
|
|
3552
4173
|
assertProductionDeployGate,
|
|
4174
|
+
parseComposeServiceImages,
|
|
4175
|
+
manifestCoveredComposeServices,
|
|
4176
|
+
collectManifestDeliveryIssues,
|
|
4177
|
+
manifestDeliveryBreakGlassServices,
|
|
4178
|
+
manifestDeliveryBreakGlassBanner,
|
|
4179
|
+
assertSignedManifestDeliveryGate,
|
|
3553
4180
|
assertRolloutStageAllowed,
|
|
3554
4181
|
assertBreakingChangesAllowed,
|
|
3555
4182
|
verifyManifestImagesAvailable,
|
|
@@ -3569,7 +4196,13 @@ export {
|
|
|
3569
4196
|
expandEnvRefs,
|
|
3570
4197
|
resolveHealthHost,
|
|
3571
4198
|
verifyReleaseHealth,
|
|
4199
|
+
listHostComposeServices,
|
|
3572
4200
|
verifyComposeServicesRunning,
|
|
4201
|
+
isComposeNetworkHost,
|
|
4202
|
+
isCloudflaredLoopbackHost,
|
|
4203
|
+
makeDefaultComposeServiceProbe,
|
|
4204
|
+
defaultIngressReachabilityProbe,
|
|
4205
|
+
verifyIngressTargetsReachable,
|
|
3573
4206
|
waitForHttpOk,
|
|
3574
4207
|
httpStatus,
|
|
3575
4208
|
defaultStackPaths,
|