@askexenow/exe-os 0.9.302 → 0.9.303
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/README.md +7 -3
- package/deploy/compose/add-routes.sh +107 -0
- package/deploy/compose/docker-compose.yml +80 -14
- package/deploy/compose/gateway-watchdog.sh +223 -0
- package/deploy/compose/setup.sh +7 -8
- package/deploy/stack-manifests/v0.9.json +44 -1
- package/dist/{active-agent-GGQAP4QE.js → active-agent-F3TFM4GE.js} +4 -3
- package/dist/{active-agent-ODI6GGVD.js → active-agent-SJ2EH6WL.js} +4 -3
- package/dist/{agentic-ontology-M7T72M6V.js → agentic-ontology-B36CUQLH.js} +1 -1
- package/dist/{backfill-metadata-GSZP2BEW.js → backfill-metadata-HPLVG42J.js} +5 -4
- package/dist/{behaviors-JM6KFSXL.js → behaviors-KRP2Q5XL.js} +6 -5
- package/dist/bin/agentic-ontology-backfill.js +6 -5
- package/dist/bin/agentic-reflection-backfill.js +7 -6
- package/dist/bin/agentic-semantic-label.js +6 -5
- package/dist/bin/backfill-conversations.js +7 -6
- package/dist/bin/backfill-responses.js +7 -6
- package/dist/bin/backfill-vectors.js +9 -8
- package/dist/bin/bulk-sync-postgres.js +13 -12
- package/dist/bin/cc-doctor.js +5 -4
- package/dist/bin/cleanup-stale-review-tasks.js +13 -12
- package/dist/bin/cli.js +16 -16
- package/dist/bin/exe-agent-config.js +3 -2
- package/dist/bin/exe-agent.js +14 -13
- package/dist/bin/exe-assign.js +9 -8
- package/dist/bin/exe-boot.js +21 -20
- package/dist/bin/exe-call.js +5 -4
- package/dist/bin/exe-cloud.js +13 -12
- package/dist/bin/exe-config-dump.js +525 -0
- package/dist/bin/exe-dispatch.js +13 -12
- package/dist/bin/exe-doctor.js +2 -2
- package/dist/bin/exe-export-behaviors.js +8 -7
- package/dist/bin/exe-forget.js +7 -6
- package/dist/bin/exe-gateway.js +8 -7
- package/dist/bin/exe-healthcheck.js +7 -4
- package/dist/bin/exe-heartbeat.js +13 -12
- package/dist/bin/exe-kill.js +16 -15
- package/dist/bin/exe-launch-agent.js +40 -24
- package/dist/bin/exe-new-employee.js +9 -8
- package/dist/bin/exe-pending-messages.js +14 -13
- package/dist/bin/exe-pending-notifications.js +13 -12
- package/dist/bin/exe-pending-reviews.js +13 -12
- package/dist/bin/exe-rename.js +5 -4
- package/dist/bin/exe-review.js +15 -14
- package/dist/bin/exe-search-quality.js +282 -0
- package/dist/bin/exe-search.js +6 -5
- package/dist/bin/exe-session-cleanup.js +18 -17
- package/dist/bin/exe-settings.js +14 -13
- package/dist/bin/exe-start-codex.js +13 -12
- package/dist/bin/exe-start-opencode.js +9 -8
- package/dist/bin/exe-status.js +14 -13
- package/dist/bin/exe-support.js +3 -3
- package/dist/bin/exe-team.js +4 -3
- package/dist/bin/exe-timers.js +237 -0
- package/dist/bin/git-sweep.js +14 -13
- package/dist/bin/graph-backfill.js +7 -6
- package/dist/bin/graph-export.js +6 -5
- package/dist/bin/import-history.js +10 -9
- package/dist/bin/install-launchd.js +2 -2
- package/dist/bin/install.js +10 -9
- package/dist/bin/intercom-check.js +4 -4
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +5 -4
- package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
- package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
- package/dist/bin/scan-tasks.js +13 -12
- package/dist/bin/setup.js +1 -1
- package/dist/bin/shard-migrate.js +5 -4
- package/dist/bin/stack-update.js +4 -4
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{capability-cards-BCTFKT4G.js → capability-cards-7FFJF4HF.js} +3 -2
- package/dist/{capacity-monitor-B4S3XEDO.js → capacity-monitor-G4XO7GNU.js} +14 -13
- package/dist/{catchup-brief-I35KHK62.js → catchup-brief-NBAN42X7.js} +16 -15
- package/dist/{chunk-HZEAAKCR.js → chunk-2DDRLHWF.js} +3 -3
- package/dist/{chunk-AAPPSMPH.js → chunk-2JESQBYU.js} +1 -1
- package/dist/{chunk-V6P7TPPA.js → chunk-3BE52GPB.js} +148 -2
- package/dist/{chunk-7FNELLMX.js → chunk-4GBTBZFC.js} +8 -8
- package/dist/{chunk-BEIYF4NS.js → chunk-4NWEVZKT.js} +3 -3
- package/dist/{chunk-MM7SWUQ4.js → chunk-4OTIVBY3.js} +1 -1
- package/dist/{chunk-MY36XDUK.js → chunk-4VHQ6QQ3.js} +1 -1
- package/dist/{chunk-4AN6KMVG.js → chunk-53LLDHLD.js} +1 -1
- package/dist/{chunk-YNALY3SX.js → chunk-5SYMJCBM.js} +1 -1
- package/dist/{chunk-7I43GVER.js → chunk-5Y2DO57X.js} +3 -3
- package/dist/{chunk-W65Z4SJS.js → chunk-624YJGMR.js} +1 -1
- package/dist/{chunk-3IJCP6ZH.js → chunk-6U6HNE2Y.js} +1 -1
- package/dist/{chunk-PBCRUNIK.js → chunk-75SBA2D4.js} +463 -137
- package/dist/{chunk-LD2JKEJT.js → chunk-7CMIKNAB.js} +4 -4
- package/dist/{chunk-RO7EXVSJ.js → chunk-7DAUFPDV.js} +1 -1
- package/dist/{chunk-KA5RK5HZ.js → chunk-7RGIYC7W.js} +2 -2
- package/dist/{chunk-OZRO37JW.js → chunk-7RMIXFMD.js} +4 -4
- package/dist/{chunk-EI7PBUIA.js → chunk-7WQ36FAD.js} +1 -1
- package/dist/{chunk-LTI2DLHY.js → chunk-ALVJ3CT3.js} +2 -2
- package/dist/{chunk-BUGMW7YF.js → chunk-B7BDXCKK.js} +11 -11
- package/dist/{chunk-P33JVGSP.js → chunk-BM3YRAE5.js} +1 -1
- package/dist/{chunk-OMCNFTSI.js → chunk-C5CR4XDV.js} +1 -1
- package/dist/{chunk-N76VTMMF.js → chunk-CCYRCRLW.js} +2 -2
- package/dist/{chunk-6X2YNVLB.js → chunk-CTIEJGO4.js} +3 -3
- package/dist/{chunk-WIL6LXNG.js → chunk-CTUQZ7LF.js} +1 -1
- package/dist/{chunk-35U72CD2.js → chunk-D3IYBLWN.js} +3 -3
- package/dist/{chunk-QNJPDIWY.js → chunk-D6INBP4F.js} +1 -1
- package/dist/{chunk-KHZQVLNZ.js → chunk-EW2P62CV.js} +46 -0
- package/dist/{chunk-OR7J7TIA.js → chunk-EWIW3R6V.js} +1 -1
- package/dist/{chunk-OVKNFTCL.js → chunk-FMJ4D5G6.js} +133 -22
- package/dist/{chunk-W3E4VEEJ.js → chunk-FTOUQEYH.js} +1 -1
- package/dist/{chunk-2LJDLQNO.js → chunk-G4SVYLPT.js} +1 -1
- package/dist/{chunk-AYMSIV7X.js → chunk-HDCP6CGI.js} +1 -1
- package/dist/{chunk-RHMWSUJB.js → chunk-HHKI2FBV.js} +2 -2
- package/dist/{chunk-LE7CDIOA.js → chunk-HU5SQUZX.js} +1 -1
- package/dist/{chunk-H2ISTRWU.js → chunk-HUO7WZFC.js} +1 -1
- package/dist/{chunk-HNLBHP6P.js → chunk-JAPPHNDZ.js} +3 -3
- package/dist/{chunk-TCJEFW5V.js → chunk-JBWYZAQE.js} +1 -1
- package/dist/{chunk-4LZ54YGA.js → chunk-JLOB72OV.js} +5 -5
- package/dist/{chunk-T62GAN7I.js → chunk-JPGU7XUA.js} +7 -7
- package/dist/{chunk-ZQ2QHGWE.js → chunk-KCJZJJJG.js} +20 -24
- package/dist/{chunk-KVHPBQV4.js → chunk-KLAVORDC.js} +1 -1
- package/dist/{chunk-ORNKGGFX.js → chunk-MDO5LEP7.js} +1 -1
- package/dist/{chunk-6ERZFDBS.js → chunk-MGKZPSXW.js} +1 -1
- package/dist/{chunk-PJTRAFL4.js → chunk-MO3G76UK.js} +3 -3
- package/dist/{chunk-P3SIFDES.js → chunk-N5G4T47B.js} +16 -4
- package/dist/{chunk-D6UICP3C.js → chunk-NME5E325.js} +81 -3
- package/dist/{chunk-ST3X3YMZ.js → chunk-NWUPOAFV.js} +4 -4
- package/dist/{chunk-US64UR5O.js → chunk-O2KITXII.js} +3 -3
- package/dist/{chunk-3YJMI422.js → chunk-OVQDDRGZ.js} +4 -4
- package/dist/{chunk-WHOT5NZ7.js → chunk-P4WXINQT.js} +14 -2
- package/dist/{chunk-RQUHAXMM.js → chunk-PHFPIUWU.js} +2 -0
- package/dist/{chunk-U724Z3S3.js → chunk-PNYUSBLA.js} +100 -37
- package/dist/{chunk-GSWGOGP5.js → chunk-PRU6NRJY.js} +5 -5
- package/dist/{chunk-SXJACGQ5.js → chunk-PTR4DP7H.js} +4 -4
- package/dist/{chunk-XKGNKH6V.js → chunk-PY6RG2DV.js} +1 -1
- package/dist/{chunk-QWLPAEG5.js → chunk-Q32XYR7C.js} +3 -3
- package/dist/{chunk-H7ZNSQ2E.js → chunk-QPA5UAXG.js} +2 -2
- package/dist/{chunk-YMSWCERY.js → chunk-QTDS7LFB.js} +3 -3
- package/dist/chunk-RQ7OVXUZ.js +81 -0
- package/dist/{chunk-IMJNYREY.js → chunk-S4OOC2EM.js} +1 -1
- package/dist/{chunk-Z3EXECOX.js → chunk-S76SQ6R5.js} +1 -1
- package/dist/{chunk-74TUYU5A.js → chunk-SCUUQ3VB.js} +1 -1
- package/dist/{chunk-7ZWC5HVG.js → chunk-UFYO4DLS.js} +1 -1
- package/dist/{chunk-ALH6QLYF.js → chunk-UI5WA22X.js} +2 -2
- package/dist/{chunk-BR74NYEF.js → chunk-UJJIFFIJ.js} +11 -11
- package/dist/{chunk-VME3UH4D.js → chunk-UN6KSHLI.js} +1 -1
- package/dist/{chunk-CXKR5Z5F.js → chunk-V7E4I7J5.js} +4 -0
- package/dist/chunk-VQRTO7IS.js +290 -0
- package/dist/{chunk-VDHUHXFF.js → chunk-VTMPER4B.js} +2 -2
- package/dist/{chunk-Y5ASPRP7.js → chunk-X37ENEN5.js} +17 -13
- package/dist/{chunk-TLOILHSL.js → chunk-XHJESIVW.js} +32 -14
- package/dist/chunk-XXJRZ75E.js +43 -0
- package/dist/{chunk-4FG6IX7U.js → chunk-Y4TYFJ37.js} +1 -1
- package/dist/{chunk-VLSYKMCJ.js → chunk-YHYWYSWK.js} +1 -1
- package/dist/{chunk-5YL4Y27D.js → chunk-YLAYKXX3.js} +1 -1
- package/dist/{chunk-RMRZXPUU.js → chunk-YLLUDO54.js} +1 -1
- package/dist/{token-budget-UMHRRDBT.js → chunk-ZXZU6NCE.js} +2 -9
- package/dist/{co-activation-OKAHEMPE.js → co-activation-W3DTVIXG.js} +3 -2
- package/dist/{co-occurrence-IHSPB53O.js → co-occurrence-JP6QO7DC.js} +5 -4
- package/dist/{code-context-index-KGY4LKCA.js → code-context-index-6SWHYPLZ.js} +3 -3
- package/dist/{conversation-entity-extractor-KXJSHRGJ.js → conversation-entity-extractor-KYOBXNID.js} +2 -2
- package/dist/{crdt-sync-K2G3C6XY.js → crdt-sync-V4MYKLOX.js} +1 -1
- package/dist/{crm-webhook-ERZSQ3CW.js → crm-webhook-PJZ37A2Q.js} +2 -2
- package/dist/{cto-delegation-gate-ZCJ53LBQ.js → cto-delegation-gate-72TPHIVT.js} +12 -11
- package/dist/daemon-auth-N6P2MZAV.js +17 -0
- package/dist/{daemon-orchestration-V6HALVDS.js → daemon-orchestration-7ESTK6HS.js} +16 -15
- package/dist/daemon-ready-signal-3L2MJT7C.js +13 -0
- package/dist/{db-backup-VZHSFUVA.js → db-backup-3CT2GJK2.js} +1 -1
- package/dist/{doc-graph-extractor-LQ2IFND2.js → doc-graph-extractor-ECZF4H6G.js} +5 -4
- package/dist/{dreaming-QAKUASYV.js → dreaming-BZZAZHL5.js} +13 -12
- package/dist/{exe-drift-H3UWNFFY.js → exe-drift-24KYGBPH.js} +4 -3
- package/dist/{exe-export-MN5D4JMT.js → exe-export-SONG5KUF.js} +7 -6
- package/dist/{exe-import-IFFMEVD7.js → exe-import-JJJL7E5D.js} +7 -6
- package/dist/{exe-key-RSZC72QK.js → exe-key-BRKHDMIX.js} +3 -2
- package/dist/{exe-snapshot-I622MPLS.js → exe-snapshot-436XVPEC.js} +16 -15
- package/dist/{fast-db-init-QEFVT7Q3.js → fast-db-init-4VL3ZQIH.js} +1 -1
- package/dist/{founder-context-3SOKEW76.js → founder-context-6KTVHGVC.js} +2 -2
- package/dist/gateway/index.js +18 -17
- package/dist/{git-staleness-E7RPQA5U.js → git-staleness-VFDGCAMO.js} +3 -2
- package/dist/{git-task-sweep-D3A2C5BW.js → git-task-sweep-KZ27TCNB.js} +13 -12
- package/dist/{global-procedures-POG4V6IK.js → global-procedures-LDKZ2IPH.js} +4 -3
- package/dist/{graph-auto-extract-E7KALNWA.js → graph-auto-extract-VA3SFHPF.js} +5 -4
- package/dist/{graph-rag-3RZN7JR3.js → graph-rag-HITZZ63L.js} +2 -2
- package/dist/hooks/bug-report-worker.js +15 -14
- package/dist/hooks/codex-stop-task-finalizer.js +15 -14
- package/dist/hooks/commit-complete.js +15 -14
- package/dist/hooks/error-recall.js +8 -7
- package/dist/hooks/exe-heartbeat-hook.js +4 -3
- package/dist/hooks/ingest-worker.js +3 -3
- package/dist/hooks/ingest.js +8 -7
- package/dist/hooks/instructions-loaded.js +5 -4
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +5 -4
- package/dist/hooks/post-compact.js +14 -13
- package/dist/hooks/post-tool-combined.js +7 -7
- package/dist/hooks/pre-compact.js +18 -17
- package/dist/hooks/pre-tool-use.js +18 -17
- package/dist/hooks/prompt-submit.js +28 -27
- package/dist/hooks/session-end.js +23 -22
- package/dist/hooks/session-start.js +46 -22
- package/dist/hooks/stop.js +22 -21
- package/dist/hooks/subagent-stop.js +14 -13
- package/dist/hooks/summary-worker.js +21 -20
- package/dist/index.js +25 -23
- package/dist/{installer-IXUX73JN.js → installer-7LYVKNUK.js} +10 -9
- package/dist/{installer-RCUPPZ7Y.js → installer-B6PUJTSR.js} +10 -9
- package/dist/{installer-HDD6OH5Z.js → installer-MSAJOOHK.js} +7 -6
- package/dist/lib/cloud-sync.js +13 -12
- package/dist/lib/consolidation.js +8 -7
- package/dist/lib/database.js +3 -2
- package/dist/lib/db-daemon-client.js +3 -3
- package/dist/lib/db.js +3 -2
- package/dist/lib/device-registry.js +3 -1
- package/dist/lib/embedder.js +3 -3
- package/dist/lib/employee-templates.js +5 -4
- package/dist/lib/employees.js +3 -2
- package/dist/lib/exe-daemon-client.js +2 -2
- package/dist/lib/exe-daemon.js +252 -402
- package/dist/lib/hybrid-search.js +6 -5
- package/dist/lib/identity.js +3 -2
- package/dist/lib/license.js +2 -2
- package/dist/lib/messaging.js +13 -12
- package/dist/lib/reminders.js +4 -3
- package/dist/lib/schedules.js +6 -5
- package/dist/lib/session-registry.js +5 -4
- package/dist/lib/skill-learning.js +7 -6
- package/dist/lib/store.js +7 -4
- package/dist/lib/task-router.js +4 -3
- package/dist/lib/tasks.js +14 -13
- package/dist/lib/tmux-routing.js +16 -11
- package/dist/lib/token-spend.js +4 -3
- package/dist/{license-gate-V2UCK4KJ.js → license-gate-U7TC44UX.js} +3 -3
- package/dist/mcp/register-tools.js +70 -69
- package/dist/mcp/server.js +73 -72
- package/dist/mcp/tools/complete-reminder.js +5 -4
- package/dist/mcp/tools/create-reminder.js +5 -4
- package/dist/mcp/tools/create-task.js +16 -15
- package/dist/mcp/tools/deactivate-behavior.js +8 -7
- package/dist/mcp/tools/list-reminders.js +5 -4
- package/dist/mcp/tools/list-tasks.js +16 -15
- package/dist/mcp/tools/send-message.js +15 -14
- package/dist/mcp/tools/update-task.js +15 -14
- package/dist/{mcp-http-config-7LFFPNGV.js → mcp-http-config-DKDEXFVS.js} +4 -3
- package/dist/{memory-cards-BGI6MJRN.js → memory-cards-OMPYI2GY.js} +3 -2
- package/dist/{memory-graph-extractor-DTRRNWHZ.js → memory-graph-extractor-QARQCG53.js} +6 -5
- package/dist/{memory-poisoning-defense-MOCWJK6S.js → memory-poisoning-defense-4J5CPLJT.js} +3 -2
- package/dist/{memory-queue-client-DD4E5SIZ.js → memory-queue-client-2NURVA62.js} +3 -3
- package/dist/{memory-reflection-UKKTGZYR.js → memory-reflection-KSOFEZNV.js} +3 -2
- package/dist/{notifications-AMJ2K3MM.js → notifications-FAHXEFR2.js} +12 -11
- package/dist/{orchestration-events-MB4QVHTD.js → orchestration-events-2MVWZ2ET.js} +4 -3
- package/dist/orchestration-health-read-EJWVUARG.js +191 -0
- package/dist/{orchestrator-M7DJH6TN.js → orchestrator-DMM6AJVL.js} +14 -13
- package/dist/{pipeline-router-4LT5YG5P.js → pipeline-router-M3ESJZYK.js} +4 -3
- package/dist/{plan-limits-ENS7OJWP.js → plan-limits-BDOAH4DJ.js} +6 -5
- package/dist/{project-boot-6W5JPGSB.js → project-boot-DQZXRY5C.js} +25 -1
- package/dist/{projection-worker-WWXZK7QD.js → projection-worker-UBPNRKPX.js} +2 -2
- package/dist/{prospective-memory-VNGLPMS6.js → prospective-memory-OALGUYBS.js} +3 -2
- package/dist/{reranker-6XBS55FA.js → reranker-CTZ4SYCN.js} +1 -1
- package/dist/{retrieval-health-ATX5TIQ2.js → retrieval-health-P4ZMY4H3.js} +1 -1
- package/dist/{review-polling-E4ALHTQE.js → review-polling-VQUPLRWQ.js} +13 -12
- package/dist/runtime/index.js +20 -18
- package/dist/{session-events-3J6APMKN.js → session-events-4GHYBXXH.js} +13 -12
- package/dist/session-kill-telemetry-S445CSOH.js +63 -0
- package/dist/{session-scope-E54VIYTJ.js → session-scope-PPGKSJEL.js} +12 -11
- package/dist/{setup-wizard-XV736U5D.js → setup-wizard-ERES36LR.js} +1 -1
- package/dist/shared-rate-limit-store-QZPQEVE2.js +80 -0
- package/dist/{skill-refinement-FTURVWFN.js → skill-refinement-KITOKH6T.js} +3 -2
- package/dist/{stack-update-BIK2QHOE.js → stack-update-V7AJ425P.js} +5 -3
- package/dist/{steward-gate-YGU4VXPP.js → steward-gate-VLN2FTQD.js} +4 -3
- package/dist/{task-enforcement-GY3VTY6L.js → task-enforcement-XAPBTPBF.js} +12 -11
- package/dist/{task-scope-PYOK7OQ6.js → task-scope-ZGFHVDG5.js} +12 -11
- package/dist/{tasks-crud-HLJEJ2UU.js → tasks-crud-KIZREZSN.js} +12 -11
- package/dist/{tasks-notify-NGS3DWUJ.js → tasks-notify-3W3YIVF4.js} +13 -12
- package/dist/{tasks-review-BP5V2X34.js → tasks-review-F5PPITAJ.js} +12 -11
- package/dist/{telemetry-upload-AXYPFAPS.js → telemetry-upload-ZUFI5VH4.js} +8 -7
- package/dist/token-budget-7BD6ROHF.js +16 -0
- package/dist/{tool-capability-index-RHPVMMDD.js → tool-capability-index-335P663V.js} +1 -1
- package/dist/{tool-telemetry-BLSVABFJ.js → tool-telemetry-P6XVPFOF.js} +1 -1
- package/dist/tui/App.js +20 -19
- package/dist/{tui-data-EYPOFYLS.js → tui-data-QWJVWVTP.js} +12 -11
- package/dist/{worker-gate-SMFOBMMX.js → worker-gate-IHCMOXDF.js} +1 -1
- package/dist/{workflow-engine-TDJZ3TXB.js → workflow-engine-PJ7JAO6E.js} +2 -2
- package/dist/{worktree-XISIYRM4.js → worktree-4BR3ZQWN.js} +5 -4
- package/dist/{worktree-sweep-AS6GAR6X.js → worktree-sweep-XCPOF3RD.js} +6 -5
- package/package.json +8 -4
- package/release-notes.json +50 -88
- package/stack.release.json +47 -0
- package/dist/chunk-PHOQAVTK.js +0 -42
- package/dist/daemon-auth-BK7ON5ZH.js +0 -13
- package/dist/session-kill-telemetry-KOAHLRBT.js +0 -31
- package/dist/{chunk-NLHUA3YB.js → chunk-6JJBGPMF.js} +0 -0
- package/dist/{chunk-XDE5IGBX.js → chunk-ERAHOO7R.js} +0 -0
- package/dist/{chunk-LUGWK6QB.js → chunk-FF5DA4XP.js} +3 -3
- /package/dist/{chunk-FKFVNV7X.js → chunk-KYF3KSFT.js} +0 -0
- /package/dist/{chunk-GZSG2ZOX.js → chunk-O573H7LS.js} +0 -0
- /package/dist/{chunk-JTMKRUX6.js → chunk-PCU4ZMUA.js} +0 -0
- /package/dist/{chunk-PTDBHQX7.js → chunk-PGQMMD5P.js} +0 -0
- /package/dist/{chunk-GBV5PV5T.js → chunk-PP3RXZID.js} +0 -0
- /package/dist/{chunk-BGOMFGSW.js → chunk-Q5V3B6BP.js} +0 -0
- /package/dist/{chunk-UZATXREL.js → chunk-YH2IYVPW.js} +0 -0
- /package/dist/{chunk-YM3367VC.js → chunk-Z77S2QQU.js} +0 -0
- /package/dist/{core-memory-4OM7LR77.js → core-memory-VIWP7GJL.js} +0 -0
- /package/dist/{entity-boost-SFHIRXT5.js → entity-boost-VZ7ZEDVR.js} +0 -0
- /package/dist/{message-queue-client-QTC3VHJT.js → message-queue-client-32HOVUK7.js} +0 -0
- /package/dist/{oauth-server-BPN55EJM.js → oauth-server-V4H3SY7H.js} +0 -0
- /package/dist/{wiki-acl-45SIGG4O.js → wiki-acl-HNP5GGDA.js} +0 -0
package/deploy/compose/README.md
CHANGED
|
@@ -6,9 +6,13 @@ Customer VPS deployments (Hygo/High/etc.) must start from `.env.customer.example
|
|
|
6
6
|
|
|
7
7
|
Full production stack for a single client VPS: CRM + wiki + gateway + exed
|
|
8
8
|
backed by Postgres, ClickHouse, and Redis. Pinned image tags, healthchecks on
|
|
9
|
-
|
|
9
|
+
all core services, named volumes for persistence, and host-nginx-friendly port
|
|
10
10
|
publishing on `127.0.0.1`.
|
|
11
11
|
|
|
12
|
+
> **Note:** `exe-otel-collector` does not have a container-level healthcheck
|
|
13
|
+
> (scratch image with no shell) — it exposes a health extension on `:13133`
|
|
14
|
+
> which is monitored externally.
|
|
15
|
+
|
|
12
16
|
This is the **Lane A-2** deliverable from the v1.6 execution plan
|
|
13
17
|
(`exe/output/v16-execution-plan.md`). Pairs with **Lane A-1** Ansible roles
|
|
14
18
|
which provision the host and install nginx-tls; the `nginx-tls` role includes
|
|
@@ -56,8 +60,8 @@ nginx (Lane A-1 `nginx-tls`) can reverse-proxy them. Override the host port via
|
|
|
56
60
|
|
|
57
61
|
`exe-monitor-agent` reports host/container health to the monitoring hub, but it
|
|
58
62
|
needs **sensitive read-only host access** to do so: `/var/run/docker.sock`
|
|
59
|
-
(root-equivalent — can inspect every container)
|
|
60
|
-
|
|
63
|
+
(root-equivalent — can inspect every container) and `/etc/os-release`.
|
|
64
|
+
Because that access is effectively root on the box, the agent
|
|
61
65
|
is **gated behind the `monitor-agent` compose profile** and does **not** start by
|
|
62
66
|
default.
|
|
63
67
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# exe-os overlay-apply / post-deploy hook (add-routes.sh)
|
|
3
|
+
#
|
|
4
|
+
# Re-applies CUSTOMER OVERLAY files into the exe-gateway container after every
|
|
5
|
+
# image upgrade / container recreate, then reloads the gateway so the overlays
|
|
6
|
+
# take effect.
|
|
7
|
+
#
|
|
8
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
# BUG HISTORY (02bc1bb8, P1)
|
|
10
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
# On a stack image upgrade the gateway container is RECREATED. Anything that lived
|
|
12
|
+
# only inside the old container's writable layer (a customer overlay such as
|
|
13
|
+
# po-intake.js, copied in by hand) is DROPPED with no warning. HYGO's working
|
|
14
|
+
# po-intake.js overlay — 37 orders processed — was killed on v0.9.17 → v0.9.22.
|
|
15
|
+
# The intended post-deploy hook (this file) shipped as a 0-BYTE empty file, and
|
|
16
|
+
# /opt/exe-stack/overlay/ existed on the host but was never mounted into the
|
|
17
|
+
# container, so nothing re-applied it.
|
|
18
|
+
#
|
|
19
|
+
# This script + the compose bind-mount (`./overlay:/data/overlay:ro`) close the
|
|
20
|
+
# gap two ways:
|
|
21
|
+
# (a) overlays survive recreate via the persistent host bind-mount, AND
|
|
22
|
+
# (b) this hook re-applies them into the container's app dir + reloads after
|
|
23
|
+
# every recreate. Idempotent + safe: a no-op when no overlays exist.
|
|
24
|
+
#
|
|
25
|
+
# Mechanism:
|
|
26
|
+
# Host overlay dir : ${OVERLAY_DIR:-/opt/exe-stack/overlay}
|
|
27
|
+
# In-container view : /data/overlay (read-only bind-mount, always present)
|
|
28
|
+
# Applied into : ${OVERLAY_TARGET:-/app/overlay} inside the container
|
|
29
|
+
#
|
|
30
|
+
# Overlays are loaded by the gateway from EXE_GATEWAY_OVERLAY_DIR (defaults to
|
|
31
|
+
# /data/overlay — the bind-mount — so on a modern gateway no copy is even needed;
|
|
32
|
+
# the copy below is belt-and-suspenders for gateway builds that load from /app).
|
|
33
|
+
|
|
34
|
+
set -uo pipefail
|
|
35
|
+
|
|
36
|
+
STACK_DIR="${EXE_STACK_DIR:-/opt/exe-stack}"
|
|
37
|
+
OVERLAY_DIR="${OVERLAY_DIR:-${STACK_DIR}/overlay}"
|
|
38
|
+
CONTAINER="${EXE_GATEWAY_CONTAINER:-exe-gateway}"
|
|
39
|
+
# Where the gateway image expects overlays if it does NOT read the bind-mount directly.
|
|
40
|
+
OVERLAY_TARGET="${OVERLAY_TARGET:-/app/overlay}"
|
|
41
|
+
|
|
42
|
+
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
43
|
+
log() { echo "[$(ts)] add-routes: $*"; }
|
|
44
|
+
|
|
45
|
+
# Nothing to do if there is no overlay dir or it is empty. This is the common
|
|
46
|
+
# (no customer overlay) case — exit cleanly so the post-deploy step is a no-op.
|
|
47
|
+
if [[ ! -d "$OVERLAY_DIR" ]]; then
|
|
48
|
+
log "no overlay dir at ${OVERLAY_DIR} — nothing to apply."
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
shopt -s nullglob dotglob
|
|
53
|
+
overlay_files=("$OVERLAY_DIR"/*)
|
|
54
|
+
shopt -u nullglob dotglob
|
|
55
|
+
if [[ ${#overlay_files[@]} -eq 0 ]]; then
|
|
56
|
+
log "overlay dir ${OVERLAY_DIR} is empty — nothing to apply."
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Gateway must be running to receive the overlay copy + reload.
|
|
61
|
+
running=$(docker inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false")
|
|
62
|
+
if [[ "$running" != "true" ]]; then
|
|
63
|
+
log "WARN gateway container ${CONTAINER} is not running — cannot apply overlays now. They remain at ${OVERLAY_DIR} and the read-only bind-mount (/data/overlay) still exposes them once the gateway is up."
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
log "applying ${#overlay_files[@]} overlay file(s) from ${OVERLAY_DIR} into ${CONTAINER}:${OVERLAY_TARGET}"
|
|
68
|
+
|
|
69
|
+
# Ensure the in-container target dir exists, then copy each overlay file in.
|
|
70
|
+
# `docker cp` works even on a read-only-bind-mounted source because we copy from
|
|
71
|
+
# the HOST path, not from the (ro) /data/overlay mount.
|
|
72
|
+
docker exec "$CONTAINER" mkdir -p "$OVERLAY_TARGET" >/dev/null 2>&1 || {
|
|
73
|
+
log "ERROR could not create ${OVERLAY_TARGET} inside ${CONTAINER}."
|
|
74
|
+
exit 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
applied=0
|
|
78
|
+
for f in "${overlay_files[@]}"; do
|
|
79
|
+
[[ -f "$f" ]] || continue # skip sub-directories for the flat copy
|
|
80
|
+
base=$(basename "$f")
|
|
81
|
+
if docker cp "$f" "${CONTAINER}:${OVERLAY_TARGET}/${base}" >/dev/null 2>&1; then
|
|
82
|
+
log " applied ${base}"
|
|
83
|
+
applied=$((applied + 1))
|
|
84
|
+
else
|
|
85
|
+
log " ERROR failed to apply ${base}"
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
if [[ "$applied" -eq 0 ]]; then
|
|
90
|
+
log "no overlay files applied."
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Reload the gateway so the overlays take effect. A graceful in-container reload is
|
|
95
|
+
# preferred; fall back to a container restart if the image exposes no reload hook.
|
|
96
|
+
# (The restart here is INTENTIONAL + one-shot after a deploy — not the watchdog
|
|
97
|
+
# loop — and only happens when overlays were actually applied.)
|
|
98
|
+
if docker exec "$CONTAINER" sh -c 'test -x /app/reload.sh' >/dev/null 2>&1; then
|
|
99
|
+
log "reloading gateway via /app/reload.sh"
|
|
100
|
+
docker exec "$CONTAINER" /app/reload.sh >/dev/null 2>&1 || log "WARN /app/reload.sh exited non-zero"
|
|
101
|
+
else
|
|
102
|
+
log "no in-container reload hook; restarting ${CONTAINER} once to load overlays"
|
|
103
|
+
docker restart "$CONTAINER" >/dev/null 2>&1 || log "WARN docker restart ${CONTAINER} failed"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
log "applied ${applied} overlay file(s)."
|
|
107
|
+
exit 0
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
# Services: exe-db (postgres) + clickhouse + redis + exe-crm + exe-wiki + exe-os + exe-gateway + exe-erp + otel-collector
|
|
4
4
|
# ONE postgres (exe-db) — all services connect to it via DATABASE_URL.
|
|
5
5
|
# Optional: exe-monitor-agent reports fleet health to the monitoring hub. It needs
|
|
6
|
-
# sensitive host access (docker.sock, /
|
|
6
|
+
# sensitive host access (docker.sock, /etc/os-release) so it is gated behind the
|
|
7
7
|
# "monitor-agent" compose profile and stays OFF until the operator consents.
|
|
8
8
|
# See deploy/monitor/HOST-ACCESS-CONSENT.md.
|
|
9
|
-
# All image tags pinned per-client via .env (no :latest). Healthchecks on
|
|
9
|
+
# All image tags pinned per-client via .env (no :latest). Healthchecks on all core services.
|
|
10
10
|
# Named volumes for state; explicit subnets; depends_on with service_healthy gates.
|
|
11
11
|
#
|
|
12
12
|
# Validate without secrets:
|
|
@@ -26,7 +26,10 @@ services:
|
|
|
26
26
|
# ------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
exe-db:
|
|
29
|
-
|
|
29
|
+
# bug ee16e7ef: pin a versioned tag, not the mutable :pg16 rolling tag.
|
|
30
|
+
# pgvector/pgvector:pg16 can change upstream without notice; 0.8.0-pg16
|
|
31
|
+
# is the tested stable release. Override via EXE_DB_IMAGE in .env.
|
|
32
|
+
image: ${EXE_DB_IMAGE:-pgvector/pgvector:0.8.0-pg16}
|
|
30
33
|
container_name: exe-db
|
|
31
34
|
restart: unless-stopped
|
|
32
35
|
# SECURITY (bug 67d62490): no blanket `env_file: .env`. Each service
|
|
@@ -236,17 +239,21 @@ services:
|
|
|
236
239
|
image: ${MONITOR_HUB_IMAGE_TAG:-ghcr.io/askexe/exe-monitor-hub:v0.9.4}
|
|
237
240
|
container_name: exe-monitor-hub
|
|
238
241
|
restart: unless-stopped
|
|
239
|
-
# bug d3d49101: PocketBase's data dir for this hub is a RELATIVE
|
|
240
|
-
# (DefaultDataDir="exe-monitor_data", see exe-monitor internal/cmd/hub).
|
|
241
|
-
# The
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
242
|
+
# bug d3d49101 / 1fdb8be9: PocketBase's data dir for this hub is a RELATIVE
|
|
243
|
+
# path (DefaultDataDir="exe-monitor_data", see exe-monitor internal/cmd/hub).
|
|
244
|
+
# The image's WORKDIR is /app, so DefaultDataDir resolves to /app/exe-monitor_data.
|
|
245
|
+
# Pin it explicitly with --dir=/app/exe-monitor_data so the binary writes to
|
|
246
|
+
# the same path the named volume is mounted at, so data survives container
|
|
247
|
+
# recreation. Previous bug: mount was at /beszel_data but data went to
|
|
248
|
+
# /app/exe-monitor_data → writable layer → wiped on every recreate.
|
|
246
249
|
# bug 182c6da3 / a125785d: EXE_MONITOR_KEY enables /api/exe-monitor/errors
|
|
247
250
|
# ingestion (fail-closed in the hub when unset) and GATEWAY_ALERT_URL enables
|
|
248
251
|
# error-spike alerts (silently disabled in the hub when unset).
|
|
249
|
-
|
|
252
|
+
# bug 1fdb8be9: the custom hub binary resolves DefaultDataDir="exe-monitor_data"
|
|
253
|
+
# relative to the WORKDIR (/app), so actual data lives at /app/exe-monitor_data.
|
|
254
|
+
# --dir must point there (NOT /beszel_data) and the volume must mount there too,
|
|
255
|
+
# otherwise container recreates wipe all monitor history.
|
|
256
|
+
command: ["serve", "--http=0.0.0.0:8090", "--dir=/app/exe-monitor_data"]
|
|
250
257
|
environment:
|
|
251
258
|
EXE_MONITOR_ADMIN_TOKEN: ${EXE_MONITOR_ADMIN_TOKEN:-}
|
|
252
259
|
# bug a125785d: shared secret for POST /api/exe-monitor/errors. The hub
|
|
@@ -266,7 +273,11 @@ services:
|
|
|
266
273
|
ports:
|
|
267
274
|
- "127.0.0.1:${MONITOR_HUB_PORT:-8090}:8090"
|
|
268
275
|
volumes:
|
|
269
|
-
|
|
276
|
+
# bug 1fdb8be9: mount at the ACTUAL data dir the hub binary writes to.
|
|
277
|
+
# Was /beszel_data (stock Beszel path), but the exe-monitor fork uses
|
|
278
|
+
# /app/exe-monitor_data. Mismatch meant data lived in the writable layer
|
|
279
|
+
# and was lost on every container recreate / stack-update.
|
|
280
|
+
- monitor_hub_data:/app/exe-monitor_data
|
|
270
281
|
networks:
|
|
271
282
|
- backend
|
|
272
283
|
healthcheck:
|
|
@@ -329,6 +340,11 @@ services:
|
|
|
329
340
|
NODE_PORT: "3000"
|
|
330
341
|
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
331
342
|
SERVER_URL: ${CRM_SERVER_URL:-https://crm.askexe.com}
|
|
343
|
+
# bug b99d3762 / 83192765: the CRM error-forwarder POSTs to the monitor hub and must
|
|
344
|
+
# send X-Monitor-Key once the hub requires it on every path (mirrors gateway/erp).
|
|
345
|
+
ERROR_REPORTING_ENABLED: "true"
|
|
346
|
+
MONITOR_ERROR_URL: http://exe-monitor-hub:8090/api/exe-monitor/errors
|
|
347
|
+
MONITOR_API_KEY: ${MONITOR_API_KEY:-${EXE_MONITOR_KEY:-}}
|
|
332
348
|
APP_SECRET: ${CRM_APP_SECRET:?CRM_APP_SECRET is required}
|
|
333
349
|
EXE_CRM_ADMIN_TOKEN: ${EXE_CRM_ADMIN_TOKEN:-}
|
|
334
350
|
EXE_CRM_ADMIN_EMAIL: ${EXE_CRM_ADMIN_EMAIL:-}
|
|
@@ -444,6 +460,11 @@ services:
|
|
|
444
460
|
SERVER_PORT: "3001"
|
|
445
461
|
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
446
462
|
STORAGE_DIR: /app/server/storage
|
|
463
|
+
# bug b99d3762 / 83192765: the wiki error-reporter POSTs to the monitor hub and must
|
|
464
|
+
# send X-Monitor-Key once the hub requires it on every path (mirrors gateway/erp).
|
|
465
|
+
ERROR_REPORTING_ENABLED: "true"
|
|
466
|
+
MONITOR_ERROR_URL: http://exe-monitor-hub:8090/api/exe-monitor/errors
|
|
467
|
+
MONITOR_API_KEY: ${MONITOR_API_KEY:-${EXE_MONITOR_KEY:-}}
|
|
447
468
|
DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@exe-db:5432/${POSTGRES_DB:-exedb}?schema=${WIKI_DB_SCHEMA:-wiki}&sslmode=disable
|
|
448
469
|
AUTH_TOKEN: ${WIKI_AUTH_TOKEN:?WIKI_AUTH_TOKEN is required}
|
|
449
470
|
EXE_WIKI_ADMIN_TOKEN: ${EXE_WIKI_ADMIN_TOKEN:-}
|
|
@@ -597,6 +618,14 @@ services:
|
|
|
597
618
|
- exe_os_data:/home/exed/.exe-os
|
|
598
619
|
networks:
|
|
599
620
|
- backend
|
|
621
|
+
healthcheck:
|
|
622
|
+
# The worker is a long-running node process (embed-worker.js). Verify the
|
|
623
|
+
# PID-1 node process is alive via /proc/1/cmdline.
|
|
624
|
+
test: ["CMD-SHELL", "test -f /proc/1/cmdline"]
|
|
625
|
+
interval: 30s
|
|
626
|
+
timeout: 5s
|
|
627
|
+
start_period: 30s
|
|
628
|
+
retries: 3
|
|
600
629
|
deploy:
|
|
601
630
|
resources:
|
|
602
631
|
limits:
|
|
@@ -637,6 +666,13 @@ services:
|
|
|
637
666
|
NODE_ENV: production
|
|
638
667
|
EXE_GATEWAY_HOME: /data
|
|
639
668
|
EXE_GATEWAY_CONFIG: /data/gateway.json
|
|
669
|
+
# bug 02bc1bb8: customer overlay dir. The host ./overlay is bind-mounted
|
|
670
|
+
# read-only at /data/overlay (see volumes below) so customer overlays
|
|
671
|
+
# (e.g. po-intake.js) SURVIVE every image upgrade / container recreate
|
|
672
|
+
# instead of being silently dropped from the old container layer. The
|
|
673
|
+
# gateway loads overlays from this dir; the post-deploy add-routes.sh hook
|
|
674
|
+
# additionally re-applies them for gateway builds that load from /app.
|
|
675
|
+
EXE_GATEWAY_OVERLAY_DIR: /data/overlay
|
|
640
676
|
DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@exe-db:5432/${POSTGRES_DB:-exedb}?sslmode=disable
|
|
641
677
|
EXE_GATEWAY_PORT: "3100"
|
|
642
678
|
EXE_GATEWAY_HOST: "0.0.0.0"
|
|
@@ -675,6 +711,14 @@ services:
|
|
|
675
711
|
- gateway_data:/data
|
|
676
712
|
- gateway_whatsapp_auth:/app/auth_info
|
|
677
713
|
- ./gateway.json:/data/gateway.json:ro
|
|
714
|
+
# bug 02bc1bb8: PERSISTENT customer-overlay bind-mount. Anything the
|
|
715
|
+
# customer drops in the host ./overlay dir (e.g. po-intake.js) is exposed
|
|
716
|
+
# read-only inside the container at /data/overlay and therefore SURVIVES
|
|
717
|
+
# image upgrades / `docker compose up` recreate — unlike files that only
|
|
718
|
+
# ever lived in the container's writable layer (which a recreate drops).
|
|
719
|
+
# bootstrapStackHost ensures ./overlay exists so docker never auto-creates
|
|
720
|
+
# the bind path as a stray file. Empty for customers with no overlay.
|
|
721
|
+
- ./overlay:/data/overlay:ro
|
|
678
722
|
networks:
|
|
679
723
|
- backend
|
|
680
724
|
- frontend
|
|
@@ -718,8 +762,9 @@ services:
|
|
|
718
762
|
exe-monitor-hub:
|
|
719
763
|
condition: service_healthy
|
|
720
764
|
environment:
|
|
721
|
-
# Reports to the local hub by default.
|
|
722
|
-
#
|
|
765
|
+
# Reports to the local hub by default. The agent must be paired manually
|
|
766
|
+
# from the hub UI (Settings → Add System) — the hub does not expose a
|
|
767
|
+
# REST registration endpoint. Write the generated TOKEN + KEY into .env.
|
|
723
768
|
HUB_URL: ${MONITOR_HUB_URL:-http://exe-monitor-hub:8090}
|
|
724
769
|
# TOKEN and KEY are written automatically by `exe-os stack-update` during
|
|
725
770
|
# bootstrap. They start empty; the agent retries connecting until the hub
|
|
@@ -1154,6 +1199,13 @@ services:
|
|
|
1154
1199
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1155
1200
|
networks:
|
|
1156
1201
|
- backend
|
|
1202
|
+
healthcheck:
|
|
1203
|
+
# Verify the socketio node process is listening on port 9000.
|
|
1204
|
+
test: ["CMD-SHELL", "node -e \"const s=require('net').connect(9000,'127.0.0.1',()=>{s.destroy();process.exit(0)});s.setTimeout(4000,()=>{s.destroy();process.exit(1)});s.on('error',()=>process.exit(1))\""]
|
|
1205
|
+
interval: 30s
|
|
1206
|
+
timeout: 5s
|
|
1207
|
+
start_period: 30s
|
|
1208
|
+
retries: 3
|
|
1157
1209
|
deploy:
|
|
1158
1210
|
resources:
|
|
1159
1211
|
limits:
|
|
@@ -1193,6 +1245,13 @@ services:
|
|
|
1193
1245
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1194
1246
|
networks:
|
|
1195
1247
|
- backend
|
|
1248
|
+
healthcheck:
|
|
1249
|
+
# Verify the bench worker process is alive and Redis queue is reachable.
|
|
1250
|
+
test: ["CMD-SHELL", "pgrep -f 'rq worker' > /dev/null"]
|
|
1251
|
+
interval: 30s
|
|
1252
|
+
timeout: 5s
|
|
1253
|
+
start_period: 30s
|
|
1254
|
+
retries: 3
|
|
1196
1255
|
deploy:
|
|
1197
1256
|
resources:
|
|
1198
1257
|
limits:
|
|
@@ -1232,6 +1291,13 @@ services:
|
|
|
1232
1291
|
- erp_assets:/home/frappe/frappe-bench/sites/assets
|
|
1233
1292
|
networks:
|
|
1234
1293
|
- backend
|
|
1294
|
+
healthcheck:
|
|
1295
|
+
# Verify the bench schedule process is alive.
|
|
1296
|
+
test: ["CMD-SHELL", "pgrep -f 'bench schedule' > /dev/null"]
|
|
1297
|
+
interval: 30s
|
|
1298
|
+
timeout: 5s
|
|
1299
|
+
start_period: 30s
|
|
1300
|
+
retries: 3
|
|
1235
1301
|
deploy:
|
|
1236
1302
|
resources:
|
|
1237
1303
|
limits:
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# exe-os gateway watchdog — verifies the exe-gateway WhatsApp adapter is actually
|
|
3
|
+
# CONNECTED (not just that the HTTP server answers) and restarts the gateway only
|
|
4
|
+
# when it is genuinely wedged.
|
|
5
|
+
#
|
|
6
|
+
# Install (cron, every 2 min):
|
|
7
|
+
# chmod +x /opt/exe-stack/gateway-watchdog.sh
|
|
8
|
+
# crontab -l | { cat; echo "*/2 * * * * /opt/exe-stack/gateway-watchdog.sh 2>&1 | logger -t exe-gw-watchdog"; } | crontab -
|
|
9
|
+
#
|
|
10
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
# BUG HISTORY (facd5078, P0)
|
|
12
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
# The previous watchdog curled the UNAUTHENTICATED http://localhost:3100/health
|
|
14
|
+
# and grepped for `"connected":true`. But the unauth /health only returns
|
|
15
|
+
# {status, uptime, adapterCount} — it carries NO adapter connection state — so the
|
|
16
|
+
# grep ALWAYS failed → 3 strikes every ~6 min → `docker restart exe-gateway`
|
|
17
|
+
# 24/7 (1,668 restarts in 72h on HYGO). `docker restart` does NOT increment the
|
|
18
|
+
# container RestartCount, which masked the loop entirely.
|
|
19
|
+
#
|
|
20
|
+
# This rewrite makes the check ACCURATE and FAIL-SAFE:
|
|
21
|
+
# 1. AUTHENTICATED probe of /admin/webhooks/health (Bearer EXE_GATEWAY_AUTH_TOKEN),
|
|
22
|
+
# which reports real per-adapter state. Preferred — correct regardless of
|
|
23
|
+
# gateway version.
|
|
24
|
+
# 2. If the running gateway already exposes `adapters_connected` on the UNAUTH
|
|
25
|
+
# /health (exe-gateway bug 7e9fc713), that is honored too — but we do NOT
|
|
26
|
+
# hard-depend on it shipping first.
|
|
27
|
+
# 3. FAIL SAFE: if EXE_GATEWAY_AUTH_TOKEN is unset, OR the health body cannot be
|
|
28
|
+
# parsed into a definite connected/disconnected verdict, the result is
|
|
29
|
+
# INCONCLUSIVE → we LOG and do NOT restart. A watchdog must never restart on
|
|
30
|
+
# an unknown.
|
|
31
|
+
# 4. STARTUP GRACE: the gateway is given GRACE_SECS of container uptime before
|
|
32
|
+
# any strike is counted, so a gateway that is merely (re)starting / pairing
|
|
33
|
+
# WhatsApp is never restart-looped.
|
|
34
|
+
# 5. BACKOFF: after a restart we refuse to restart again for COOLDOWN_SECS, so we
|
|
35
|
+
# never hammer a gateway that needs time to reconnect.
|
|
36
|
+
|
|
37
|
+
set -uo pipefail
|
|
38
|
+
|
|
39
|
+
# ── Config ───────────────────────────────────────────────────────────────────
|
|
40
|
+
STACK_DIR="${EXE_STACK_DIR:-/opt/exe-stack}"
|
|
41
|
+
ENV_FILE="${EXE_STACK_ENV_FILE:-${STACK_DIR}/.env}"
|
|
42
|
+
CONTAINER="${EXE_GATEWAY_CONTAINER:-exe-gateway}"
|
|
43
|
+
PORT="${EXE_GATEWAY_HTTP_PORT:-3100}"
|
|
44
|
+
HOST="${EXE_GATEWAY_HOST:-127.0.0.1}"
|
|
45
|
+
TIMEOUT="${EXE_GATEWAY_WATCHDOG_TIMEOUT:-5}"
|
|
46
|
+
|
|
47
|
+
# Strikes before a restart. Each cron tick = one probe.
|
|
48
|
+
MAX_STRIKES="${EXE_GATEWAY_WATCHDOG_STRIKES:-3}"
|
|
49
|
+
# Don't count strikes until the container has been up at least this long.
|
|
50
|
+
GRACE_SECS="${EXE_GATEWAY_WATCHDOG_GRACE_SECS:-120}"
|
|
51
|
+
# After a restart, refuse to restart again for this long (let it reconnect).
|
|
52
|
+
COOLDOWN_SECS="${EXE_GATEWAY_WATCHDOG_COOLDOWN_SECS:-300}"
|
|
53
|
+
|
|
54
|
+
STATE_DIR="${EXE_GATEWAY_WATCHDOG_STATE_DIR:-${STACK_DIR}/.gateway-watchdog}"
|
|
55
|
+
STRIKE_FILE="${STATE_DIR}/strikes"
|
|
56
|
+
LAST_RESTART_FILE="${STATE_DIR}/last-restart"
|
|
57
|
+
|
|
58
|
+
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
59
|
+
|
|
60
|
+
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
61
|
+
log() { echo "[$(ts)] gateway-watchdog: $*"; }
|
|
62
|
+
|
|
63
|
+
# Read a single KEY=VALUE from the stack .env WITHOUT sourcing it (the .env holds
|
|
64
|
+
# many secrets and arbitrary values; sourcing could execute / mangle them).
|
|
65
|
+
read_env() {
|
|
66
|
+
local key="$1"
|
|
67
|
+
[[ -f "$ENV_FILE" ]] || return 0
|
|
68
|
+
# Last assignment wins; strip surrounding quotes; ignore comments.
|
|
69
|
+
sed -n "s/^[[:space:]]*${key}=//p" "$ENV_FILE" 2>/dev/null | tail -n1 \
|
|
70
|
+
| sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
reset_strikes() { echo 0 > "$STRIKE_FILE" 2>/dev/null || true; }
|
|
74
|
+
read_strikes() { local s; s=$(cat "$STRIKE_FILE" 2>/dev/null || echo 0); [[ "$s" =~ ^[0-9]+$ ]] && echo "$s" || echo 0; }
|
|
75
|
+
bump_strikes() { local s; s=$(read_strikes); echo $((s + 1)) > "$STRIKE_FILE" 2>/dev/null || true; echo $((s + 1)); }
|
|
76
|
+
|
|
77
|
+
# Container uptime in seconds (0 if not running / unknown).
|
|
78
|
+
container_uptime_secs() {
|
|
79
|
+
local started running
|
|
80
|
+
running=$(docker inspect --format '{{.State.Running}}' "$CONTAINER" 2>/dev/null || echo "false")
|
|
81
|
+
[[ "$running" == "true" ]] || { echo 0; return; }
|
|
82
|
+
started=$(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER" 2>/dev/null || echo "")
|
|
83
|
+
[[ -n "$started" ]] || { echo 0; return; }
|
|
84
|
+
local start_epoch now_epoch
|
|
85
|
+
start_epoch=$(date -u -d "$started" +%s 2>/dev/null || date -u -j -f "%Y-%m-%dT%H:%M:%S" "${started%%.*}" +%s 2>/dev/null || echo 0)
|
|
86
|
+
now_epoch=$(date -u +%s)
|
|
87
|
+
if [[ "$start_epoch" -gt 0 && "$now_epoch" -ge "$start_epoch" ]]; then
|
|
88
|
+
echo $((now_epoch - start_epoch))
|
|
89
|
+
else
|
|
90
|
+
echo 0
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
seconds_since_last_restart() {
|
|
95
|
+
local last now
|
|
96
|
+
last=$(cat "$LAST_RESTART_FILE" 2>/dev/null || echo 0)
|
|
97
|
+
[[ "$last" =~ ^[0-9]+$ ]] || last=0
|
|
98
|
+
now=$(date -u +%s)
|
|
99
|
+
echo $((now - last))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ── Adapter-state probe ──────────────────────────────────────────────────────
|
|
103
|
+
# Echoes exactly one of: connected | disconnected | inconclusive
|
|
104
|
+
probe_adapter_state() {
|
|
105
|
+
local token unauth auth
|
|
106
|
+
|
|
107
|
+
# 1) Forward-compat: unauth /health MAY expose adapters_connected (gw bug 7e9fc713).
|
|
108
|
+
unauth=$(curl -sk --max-time "$TIMEOUT" "http://${HOST}:${PORT}/health" 2>/dev/null || echo "")
|
|
109
|
+
if [[ -n "$unauth" ]]; then
|
|
110
|
+
if echo "$unauth" | grep -Eq '"adapters_connected"[[:space:]]*:[[:space:]]*true'; then
|
|
111
|
+
echo "connected"; return
|
|
112
|
+
fi
|
|
113
|
+
if echo "$unauth" | grep -Eq '"adapters_connected"[[:space:]]*:[[:space:]]*false'; then
|
|
114
|
+
echo "disconnected"; return
|
|
115
|
+
fi
|
|
116
|
+
# No adapters_connected field → this gateway predates 7e9fc713. Fall through to
|
|
117
|
+
# the authenticated probe; the bare {status,uptime,adapterCount} body is NOT a
|
|
118
|
+
# connection verdict and must never be treated as one.
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# 2) Authenticated /admin/webhooks/health — reports real per-adapter state.
|
|
122
|
+
token=$(read_env EXE_GATEWAY_AUTH_TOKEN)
|
|
123
|
+
if [[ -z "$token" ]]; then
|
|
124
|
+
# FAIL SAFE: no token → we cannot make an authoritative judgement. Never restart.
|
|
125
|
+
echo "inconclusive"; return
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
auth=$(curl -sk --max-time "$TIMEOUT" \
|
|
129
|
+
-H "Authorization: Bearer ${token}" \
|
|
130
|
+
"http://${HOST}:${PORT}/admin/webhooks/health" 2>/dev/null || echo "")
|
|
131
|
+
|
|
132
|
+
if [[ -z "$auth" ]]; then
|
|
133
|
+
# Couldn't reach the authed endpoint at all → inconclusive (could be a transient
|
|
134
|
+
# network blip; the HTTP layer may still be fine). Don't restart on this alone.
|
|
135
|
+
echo "inconclusive"; return
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Real connection verdict. The authed health reports adapter connection via
|
|
139
|
+
# "connected":true and/or a per-account "whatsapp":{... "connected":true}.
|
|
140
|
+
if echo "$auth" | grep -Eq '"connected"[[:space:]]*:[[:space:]]*true'; then
|
|
141
|
+
echo "connected"; return
|
|
142
|
+
fi
|
|
143
|
+
if echo "$auth" | grep -Eq '"handlerRegistered"[[:space:]]*:[[:space:]]*true' \
|
|
144
|
+
&& echo "$auth" | grep -Eq '"connected"[[:space:]]*:[[:space:]]*false'; then
|
|
145
|
+
echo "disconnected"; return
|
|
146
|
+
fi
|
|
147
|
+
# An authed body we can't classify (schema drift) is NOT grounds to restart.
|
|
148
|
+
echo "inconclusive"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# ── Main ─────────────────────────────────────────────────────────────────────
|
|
152
|
+
main() {
|
|
153
|
+
# Gateway not running at all? Let Docker's own `restart: unless-stopped` handle
|
|
154
|
+
# a crashed container — the watchdog only acts on a RUNNING-but-wedged gateway.
|
|
155
|
+
local uptime
|
|
156
|
+
uptime=$(container_uptime_secs)
|
|
157
|
+
if [[ "$uptime" -eq 0 ]]; then
|
|
158
|
+
log "container ${CONTAINER} not running (or uptime unknown) — deferring to Docker restart policy; not counting a strike."
|
|
159
|
+
reset_strikes
|
|
160
|
+
exit 0
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
local state
|
|
164
|
+
state=$(probe_adapter_state)
|
|
165
|
+
|
|
166
|
+
case "$state" in
|
|
167
|
+
connected)
|
|
168
|
+
reset_strikes
|
|
169
|
+
log "OK adapters connected (uptime ${uptime}s)."
|
|
170
|
+
exit 0
|
|
171
|
+
;;
|
|
172
|
+
inconclusive)
|
|
173
|
+
# NEVER restart on an inconclusive check. Log and reset so a transient
|
|
174
|
+
# unknown can't accumulate toward a restart.
|
|
175
|
+
reset_strikes
|
|
176
|
+
log "INCONCLUSIVE health check (token unset or unparseable body) — NOT restarting."
|
|
177
|
+
exit 0
|
|
178
|
+
;;
|
|
179
|
+
disconnected)
|
|
180
|
+
: # fall through to strike logic below
|
|
181
|
+
;;
|
|
182
|
+
*)
|
|
183
|
+
reset_strikes
|
|
184
|
+
log "unexpected probe result '${state}' — treating as inconclusive, NOT restarting."
|
|
185
|
+
exit 0
|
|
186
|
+
;;
|
|
187
|
+
esac
|
|
188
|
+
|
|
189
|
+
# ── disconnected ──
|
|
190
|
+
# Startup grace: a gateway that is merely (re)starting / re-pairing WhatsApp must
|
|
191
|
+
# not be counted against. Require GRACE_SECS of uptime before any strike.
|
|
192
|
+
if [[ "$uptime" -lt "$GRACE_SECS" ]]; then
|
|
193
|
+
log "adapter disconnected but within startup grace (uptime ${uptime}s < ${GRACE_SECS}s) — not counting a strike."
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Backoff: don't restart again within the cooldown window — give the gateway time
|
|
198
|
+
# to reconnect after the previous restart.
|
|
199
|
+
local since_restart
|
|
200
|
+
since_restart=$(seconds_since_last_restart)
|
|
201
|
+
if [[ "$since_restart" -lt "$COOLDOWN_SECS" ]]; then
|
|
202
|
+
log "adapter disconnected but within post-restart cooldown (${since_restart}s < ${COOLDOWN_SECS}s) — backing off, not restarting."
|
|
203
|
+
exit 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
local strikes
|
|
207
|
+
strikes=$(bump_strikes)
|
|
208
|
+
log "adapter DISCONNECTED — strike ${strikes}/${MAX_STRIKES} (uptime ${uptime}s)."
|
|
209
|
+
|
|
210
|
+
if [[ "$strikes" -ge "$MAX_STRIKES" ]]; then
|
|
211
|
+
log "ALERT strike threshold reached — restarting ${CONTAINER}."
|
|
212
|
+
if docker restart "$CONTAINER" >/dev/null 2>&1; then
|
|
213
|
+
date -u +%s > "$LAST_RESTART_FILE" 2>/dev/null || true
|
|
214
|
+
reset_strikes
|
|
215
|
+
log "restarted ${CONTAINER}; entering ${COOLDOWN_SECS}s cooldown."
|
|
216
|
+
else
|
|
217
|
+
log "ERROR docker restart ${CONTAINER} failed."
|
|
218
|
+
fi
|
|
219
|
+
fi
|
|
220
|
+
exit 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main "$@"
|
package/deploy/compose/setup.sh
CHANGED
|
@@ -52,11 +52,10 @@ info "Step 2: Generating .env file"
|
|
|
52
52
|
if [[ -f .env ]]; then
|
|
53
53
|
warn ".env already exists — skipping generation. Delete .env to regenerate."
|
|
54
54
|
else
|
|
55
|
-
if command -v node >/dev/null 2>&1; then
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
" > .env 2>/dev/null || {
|
|
55
|
+
if command -v node >/dev/null 2>&1 && command -v npx >/dev/null 2>&1; then
|
|
56
|
+
# generate-env.ts is a TypeScript ESM file shipped at deploy/compose/generate-env.ts.
|
|
57
|
+
# Run it via npx tsx (the project is pure ESM — require() is forbidden).
|
|
58
|
+
npx --yes tsx "$SCRIPT_DIR/generate-env.ts" "$CLIENT" "$DOMAIN" "${LICENSE:-}" > .env 2>/dev/null || {
|
|
60
59
|
# Fallback: generate inline
|
|
61
60
|
info "Generating secrets inline..."
|
|
62
61
|
gen() { openssl rand -hex "$1"; }
|
|
@@ -177,8 +176,10 @@ fi
|
|
|
177
176
|
# exe-monitor-agent reports fleet health to the monitoring hub, but to do so it
|
|
178
177
|
# needs SENSITIVE read-only host access:
|
|
179
178
|
# - /var/run/docker.sock (root-equivalent: enumerate/inspect ALL containers)
|
|
180
|
-
# - /proc, /sys (host process list + system-wide CPU/mem/hardware)
|
|
181
179
|
# - /etc/os-release (host OS identity)
|
|
180
|
+
# (bug 107cde54: /proc and /sys mounts were REMOVED — the Beszel-based agent
|
|
181
|
+
# does not read bind-mounted host /proc or /sys, so they were pure attack
|
|
182
|
+
# surface with no metrics benefit.)
|
|
182
183
|
# The service is gated behind the "monitor-agent" compose profile and stays OFF
|
|
183
184
|
# until the operator explicitly acknowledges this scope here. Acknowledgement is
|
|
184
185
|
# recorded by adding "monitor-agent" to COMPOSE_PROFILES in .env. Pre-answer in
|
|
@@ -196,8 +197,6 @@ else
|
|
|
196
197
|
├──────────────────────────────────────────────────────────────────────┤
|
|
197
198
|
│ Enabling the monitor agent grants it READ-ONLY access to the host: │
|
|
198
199
|
│ • /var/run/docker.sock — inspect ALL containers (root-equivalent) │
|
|
199
|
-
│ • /proc — host process list + system CPU/mem stats │
|
|
200
|
-
│ • /sys — host hardware/kernel metrics │
|
|
201
200
|
│ • /etc/os-release — host OS name/version │
|
|
202
201
|
│ │
|
|
203
202
|
│ The Docker socket is effectively root on this machine. Only enable │
|
|
@@ -1344,12 +1344,13 @@
|
|
|
1344
1344
|
"breakingChanges": [],
|
|
1345
1345
|
"services": {
|
|
1346
1346
|
"exe-db": {
|
|
1347
|
-
"image": "pgvector/pgvector:pg16",
|
|
1347
|
+
"image": "pgvector/pgvector:0.8.0-pg16",
|
|
1348
1348
|
"env": "EXE_DB_IMAGE",
|
|
1349
1349
|
"composeService": "exe-db",
|
|
1350
1350
|
"required": true,
|
|
1351
1351
|
"category": "core",
|
|
1352
1352
|
"description": "Postgres (pgvector) \u2014 shared system of record for all services",
|
|
1353
|
+
"_comment": "bug ee16e7ef: pinned to versioned 0.8.0-pg16 tag \u2014 the mutable :pg16 rolling tag can change upstream without notice.",
|
|
1353
1354
|
"migrations": {
|
|
1354
1355
|
"command": "exec -T exe-db psql -v ON_ERROR_STOP=1 -U ${POSTGRES_USER:-exe} -d ${POSTGRES_DB:-exedb} -f /docker-entrypoint-initdb.d/01-init.sql"
|
|
1355
1356
|
}
|
|
@@ -1438,6 +1439,48 @@
|
|
|
1438
1439
|
"category": "core",
|
|
1439
1440
|
"description": "GoTrue auth backend \u2014 issues SSO/login tokens for CRM/Wiki/ERP",
|
|
1440
1441
|
"_comment": "bug 2976868a: the stack health gate previously omitted GoTrue, so SSO/login could be fully down while the gate reported green. Gate the backend directly."
|
|
1442
|
+
},
|
|
1443
|
+
"redis": {
|
|
1444
|
+
"image": "redis:7.4-alpine",
|
|
1445
|
+
"composeService": "redis",
|
|
1446
|
+
"required": true,
|
|
1447
|
+
"category": "infra",
|
|
1448
|
+
"description": "Redis \u2014 session cache, job queues (CRM, gateway, ERP)",
|
|
1449
|
+
"_comment": "bug 4948fc48: compose-pinned infra dep \u2014 no env override, image tag lives in docker-compose.yml."
|
|
1450
|
+
},
|
|
1451
|
+
"clickhouse": {
|
|
1452
|
+
"image": "clickhouse/clickhouse-server:24.8.4.13-alpine",
|
|
1453
|
+
"env": "CLICKHOUSE_IMAGE",
|
|
1454
|
+
"composeService": "clickhouse",
|
|
1455
|
+
"required": true,
|
|
1456
|
+
"category": "infra",
|
|
1457
|
+
"description": "ClickHouse \u2014 analytics and event storage (CRM)",
|
|
1458
|
+
"_comment": "bug 4948fc48: compose-pinned infra dep required by CRM."
|
|
1459
|
+
},
|
|
1460
|
+
"cloudflared": {
|
|
1461
|
+
"image": "cloudflare/cloudflared@sha256:ba461b8aa9c042156dbd39c38657fe7431bafa063220eab8d5330a523863da9f",
|
|
1462
|
+
"composeService": "cloudflared",
|
|
1463
|
+
"required": true,
|
|
1464
|
+
"category": "infra",
|
|
1465
|
+
"description": "Cloudflare Tunnel \u2014 secure ingress proxy (replaces nginx + SSL certs)",
|
|
1466
|
+
"_comment": "bug 4948fc48: digest-pinned in compose, no env override."
|
|
1467
|
+
},
|
|
1468
|
+
"exe-sso-edge": {
|
|
1469
|
+
"image": "nginx:alpine",
|
|
1470
|
+
"composeService": "exe-sso-edge",
|
|
1471
|
+
"required": true,
|
|
1472
|
+
"category": "infra",
|
|
1473
|
+
"description": "SSO edge \u2014 reverse proxy that gates CRM/wiki/ERP behind unified auth",
|
|
1474
|
+
"_comment": "bug 4948fc48: stock nginx:alpine, config templated from DOMAIN."
|
|
1475
|
+
},
|
|
1476
|
+
"exe-otel-collector": {
|
|
1477
|
+
"image": "otel/opentelemetry-collector-contrib:0.114.0",
|
|
1478
|
+
"env": "OTEL_COLLECTOR_IMAGE",
|
|
1479
|
+
"composeService": "exe-otel-collector",
|
|
1480
|
+
"required": false,
|
|
1481
|
+
"category": "observability",
|
|
1482
|
+
"description": "OpenTelemetry Collector \u2014 receives OTLP traces/metrics/logs from all services",
|
|
1483
|
+
"_comment": "bug 4948fc48: compose-pinned observability infra."
|
|
1441
1484
|
}
|
|
1442
1485
|
}
|
|
1443
1486
|
}
|