@geekmidas/cli 10.0.0-alpha.11 → 10.0.0-alpha.110
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/README.md +315 -414
- package/bin/adjacent-tsconfig.mjs +46 -8
- package/bin/gkm.mjs +11 -3
- package/bin/owning-tsconfig-jsx-register.mjs +18 -0
- package/bin/owning-tsconfig-jsx.mjs +108 -0
- package/dist/EndpointGenerator-BwbYTMtB.cjs +1090 -0
- package/dist/EndpointGenerator-BwbYTMtB.cjs.map +1 -0
- package/dist/EndpointGenerator-CvLX8bpx.mjs +1031 -0
- package/dist/EndpointGenerator-CvLX8bpx.mjs.map +1 -0
- package/dist/GoDaddyProvider-CjeOduia.mjs +388 -0
- package/dist/GoDaddyProvider-CjeOduia.mjs.map +1 -0
- package/dist/GoDaddyProvider-qNUzTXI7.cjs +388 -0
- package/dist/GoDaddyProvider-qNUzTXI7.cjs.map +1 -0
- package/dist/{HostingerProvider-BcyFRbqV.mjs → HostingerProvider-BNpS9aRJ.mjs} +29 -25
- package/dist/HostingerProvider-BNpS9aRJ.mjs.map +1 -0
- package/dist/{HostingerProvider-BOvsgATO.cjs → HostingerProvider-DR1iws1P.cjs} +29 -25
- package/dist/HostingerProvider-DR1iws1P.cjs.map +1 -0
- package/dist/LegacyStateStore-MxiSu7KJ.cjs +124 -0
- package/dist/LegacyStateStore-MxiSu7KJ.cjs.map +1 -0
- package/dist/LegacyStateStore-pgscYsiG.mjs +124 -0
- package/dist/LegacyStateStore-pgscYsiG.mjs.map +1 -0
- package/dist/LocalStateStore-BcvyKbAw.mjs +167 -0
- package/dist/LocalStateStore-BcvyKbAw.mjs.map +1 -0
- package/dist/LocalStateStore-Bm8Vmwjb.cjs +178 -0
- package/dist/LocalStateStore-Bm8Vmwjb.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-BD6ZOQ51.cjs +541 -0
- package/dist/OpenApiTsGenerator-BD6ZOQ51.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-DXXFXSbO.mjs +536 -0
- package/dist/OpenApiTsGenerator-DXXFXSbO.mjs.map +1 -0
- package/dist/{Route53Provider-CH9XH7bz.cjs → Route53Provider-Bllus2NR.cjs} +21 -22
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-C0xmer8U.mjs → Route53Provider-QXxjkZO1.mjs} +13 -13
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/S3StateStore-DXHSBVsV.cjs +222 -0
- package/dist/S3StateStore-DXHSBVsV.cjs.map +1 -0
- package/dist/S3StateStore-H2XsAzpJ.mjs +223 -0
- package/dist/S3StateStore-H2XsAzpJ.mjs.map +1 -0
- package/dist/SSMStateStore-BLPSNl-U.cjs +204 -0
- package/dist/SSMStateStore-BLPSNl-U.cjs.map +1 -0
- package/dist/SSMStateStore-CENciQEM.mjs +205 -0
- package/dist/SSMStateStore-CENciQEM.mjs.map +1 -0
- package/dist/SubscriberGenerator-CeH7wbYt.cjs +571 -0
- package/dist/SubscriberGenerator-CeH7wbYt.cjs.map +1 -0
- package/dist/SubscriberGenerator-DHnIYwUU.mjs +518 -0
- package/dist/SubscriberGenerator-DHnIYwUU.mjs.map +1 -0
- package/dist/add-Cdg1kEqN.cjs +719 -0
- package/dist/add-Cdg1kEqN.cjs.map +1 -0
- package/dist/add-snhdVmR-.mjs +717 -0
- package/dist/add-snhdVmR-.mjs.map +1 -0
- package/dist/appPorts-BoXMwolc.mjs +249 -0
- package/dist/appPorts-BoXMwolc.mjs.map +1 -0
- package/dist/appPorts-IW-OZmRi.cjs +296 -0
- package/dist/appPorts-IW-OZmRi.cjs.map +1 -0
- package/dist/aws-8QQ8n-t6.mjs +89 -0
- package/dist/aws-8QQ8n-t6.mjs.map +1 -0
- package/dist/aws-DY9NqQIT.cjs +100 -0
- package/dist/aws-DY9NqQIT.cjs.map +1 -0
- package/dist/awsStore-Bx5nCFnS.mjs +121 -0
- package/dist/awsStore-Bx5nCFnS.mjs.map +1 -0
- package/dist/awsStore-u3ehhMoY.cjs +138 -0
- package/dist/awsStore-u3ehhMoY.cjs.map +1 -0
- package/dist/{backup-provisioner-DXXg6FIL.mjs → backup-provisioner-C8DnCmxK.mjs} +11 -8
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-cE2PuCTb.cjs → backup-provisioner-oDEsPj02.cjs} +22 -20
- package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
- package/dist/backups/runner.cjs +519 -0
- package/dist/backups/runner.cjs.map +1 -0
- package/dist/backups/runner.d.cts +102 -0
- package/dist/backups/runner.d.cts.map +1 -0
- package/dist/backups/runner.d.mts +102 -0
- package/dist/backups/runner.d.mts.map +1 -0
- package/dist/backups/runner.mjs +506 -0
- package/dist/backups/runner.mjs.map +1 -0
- package/dist/{bundler-CaTGtiaO.cjs → bundler-B4GDxImV.cjs} +38 -35
- package/dist/bundler-B4GDxImV.cjs.map +1 -0
- package/dist/{bundler-S3ek7ea-.mjs → bundler-Wd9H4QLJ.mjs} +36 -32
- package/dist/bundler-Wd9H4QLJ.mjs.map +1 -0
- package/dist/cli-BZYz55tO.cjs +71 -0
- package/dist/cli-BZYz55tO.cjs.map +1 -0
- package/dist/cli-BzrDSNSg.mjs +71 -0
- package/dist/cli-BzrDSNSg.mjs.map +1 -0
- package/dist/compose-DN9zySvQ.cjs +3330 -0
- package/dist/compose-DN9zySvQ.cjs.map +1 -0
- package/dist/compose-Gsd1Nq7R.mjs +3187 -0
- package/dist/compose-Gsd1Nq7R.mjs.map +1 -0
- package/dist/config-D48erO2j.d.mts +269 -0
- package/dist/config-D48erO2j.d.mts.map +1 -0
- package/dist/config-DHDtsQrB.d.cts +269 -0
- package/dist/config-DHDtsQrB.d.cts.map +1 -0
- package/dist/config.cjs +421 -11
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +4 -120
- package/dist/config.d.mts +4 -120
- package/dist/config.mjs +405 -4
- package/dist/config.mjs.map +1 -0
- package/dist/credentialSchemas-Cf5Pf8VE.mjs +237 -0
- package/dist/credentialSchemas-Cf5Pf8VE.mjs.map +1 -0
- package/dist/credentialSchemas-D_VUCDUh.cjs +272 -0
- package/dist/credentialSchemas-D_VUCDUh.cjs.map +1 -0
- package/dist/credentials-B5N5yKk6.mjs +267 -0
- package/dist/credentials-B5N5yKk6.mjs.map +1 -0
- package/dist/credentials-Zqr3QQYB.cjs +350 -0
- package/dist/credentials-Zqr3QQYB.cjs.map +1 -0
- package/dist/databases-BeGP5eDh.mjs +495 -0
- package/dist/databases-BeGP5eDh.mjs.map +1 -0
- package/dist/databases-DGALFDG3.cjs +598 -0
- package/dist/databases-DGALFDG3.cjs.map +1 -0
- package/dist/define-6poNqm5O.mjs +106 -0
- package/dist/define-6poNqm5O.mjs.map +1 -0
- package/dist/define-crmMcXFS.cjs +153 -0
- package/dist/define-crmMcXFS.cjs.map +1 -0
- package/dist/deploy/public.cjs +212 -0
- package/dist/deploy/public.cjs.map +1 -0
- package/dist/deploy/public.d.cts +653 -0
- package/dist/deploy/public.d.cts.map +1 -0
- package/dist/deploy/public.d.mts +653 -0
- package/dist/deploy/public.d.mts.map +1 -0
- package/dist/deploy/public.mjs +145 -0
- package/dist/deploy/public.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-envparser-worker.cjs +54 -0
- package/dist/deploy/sniffer-envparser-worker.cjs.map +1 -0
- package/dist/deploy/sniffer-envparser-worker.d.cts +1 -0
- package/dist/deploy/sniffer-envparser-worker.d.mts +1 -0
- package/dist/deploy/sniffer-envparser-worker.mjs +55 -0
- package/dist/deploy/sniffer-envparser-worker.mjs.map +1 -0
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +10 -3
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +14 -3
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +4 -4
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/deploy-CFvq8daZ.cjs +2390 -0
- package/dist/deploy-CFvq8daZ.cjs.map +1 -0
- package/dist/deploy-H2S8_mTx.mjs +2317 -0
- package/dist/deploy-H2S8_mTx.mjs.map +1 -0
- package/dist/derive-B4vDpzgP.mjs +150 -0
- package/dist/derive-B4vDpzgP.mjs.map +1 -0
- package/dist/derive-DAbo49sm.cjs +161 -0
- package/dist/derive-DAbo49sm.cjs.map +1 -0
- package/dist/discover-Bsr7HjqJ.cjs +616 -0
- package/dist/discover-Bsr7HjqJ.cjs.map +1 -0
- package/dist/discover-Ca8U75dl.mjs +537 -0
- package/dist/discover-Ca8U75dl.mjs.map +1 -0
- package/dist/discover-CvTtV5ZD.d.cts +108 -0
- package/dist/discover-CvTtV5ZD.d.cts.map +1 -0
- package/dist/discover-W2kab9Lc.d.mts +108 -0
- package/dist/discover-W2kab9Lc.d.mts.map +1 -0
- package/dist/dns-BdETzP10.cjs +1057 -0
- package/dist/dns-BdETzP10.cjs.map +1 -0
- package/dist/dns-pFMShDqW.mjs +926 -0
- package/dist/dns-pFMShDqW.mjs.map +1 -0
- package/dist/dokploy-HioAjP7K.cjs +38 -0
- package/dist/dokploy-HioAjP7K.cjs.map +1 -0
- package/dist/dokploy-oDJbnvCb.mjs +38 -0
- package/dist/dokploy-oDJbnvCb.mjs.map +1 -0
- package/dist/drivers-D11JHxrD.mjs +177 -0
- package/dist/drivers-D11JHxrD.mjs.map +1 -0
- package/dist/drivers-GKaA6ITM.cjs +200 -0
- package/dist/drivers-GKaA6ITM.cjs.map +1 -0
- package/dist/{emit-CIlvnDvV.mjs → emit-C1e8qD08.mjs} +13 -26
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-CUD1BWls.cjs → emit-Dmc1nvdS.cjs} +31 -57
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-D5pDJyp2.mjs +89 -0
- package/dist/encryption-D5pDJyp2.mjs.map +1 -0
- package/dist/encryption-DVCxD-gV.cjs +90 -0
- package/dist/encryption-DVCxD-gV.cjs.map +1 -0
- package/dist/engine-BaJGPzte.cjs +3196 -0
- package/dist/engine-BaJGPzte.cjs.map +1 -0
- package/dist/engine-CfAKgr2_.mjs +3083 -0
- package/dist/engine-CfAKgr2_.mjs.map +1 -0
- package/dist/errors-D8Gwp5kU.mjs +23 -0
- package/dist/errors-D8Gwp5kU.mjs.map +1 -0
- package/dist/errors-DMC4Umsb.cjs +34 -0
- package/dist/errors-DMC4Umsb.cjs.map +1 -0
- package/dist/github-D902Vwr9.cjs +829 -0
- package/dist/github-D902Vwr9.cjs.map +1 -0
- package/dist/github-E-ZxqFPq.mjs +829 -0
- package/dist/github-E-ZxqFPq.mjs.map +1 -0
- package/dist/identity-CBXr4wNi.mjs +129 -0
- package/dist/identity-CBXr4wNi.mjs.map +1 -0
- package/dist/identity-CbLfgQNT.cjs +182 -0
- package/dist/identity-CbLfgQNT.cjs.map +1 -0
- package/dist/{index-Ayxg_DQY.d.mts → index-8OfpOtSI.d.cts} +39 -24
- package/dist/index-8OfpOtSI.d.cts.map +1 -0
- package/dist/{index-CR5urhWX.d.cts → index-Dpji6Bxq.d.mts} +39 -24
- package/dist/index-Dpji6Bxq.d.mts.map +1 -0
- package/dist/index.cjs +13764 -12651
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +13654 -12553
- package/dist/index.mjs.map +1 -1
- package/dist/local-BwC2v2Y8.mjs +224 -0
- package/dist/local-BwC2v2Y8.mjs.map +1 -0
- package/dist/local-DNhnkugT.cjs +241 -0
- package/dist/local-DNhnkugT.cjs.map +1 -0
- package/dist/migrate-BZ0GnSZQ.mjs +181 -0
- package/dist/migrate-BZ0GnSZQ.mjs.map +1 -0
- package/dist/migrate-CTqhWtev.cjs +271 -0
- package/dist/migrate-CTqhWtev.cjs.map +1 -0
- package/dist/migrate-DCXwHcWX.mjs +266 -0
- package/dist/migrate-DCXwHcWX.mjs.map +1 -0
- package/dist/migrate-Df81ew0k.cjs +181 -0
- package/dist/migrate-Df81ew0k.cjs.map +1 -0
- package/dist/openapi.cjs +214 -10
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +38 -17
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +38 -17
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +208 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/projectBucket-BBbQ1lHF.cjs +263 -0
- package/dist/projectBucket-BBbQ1lHF.cjs.map +1 -0
- package/dist/projectBucket-C_DN4WzF.mjs +222 -0
- package/dist/projectBucket-C_DN4WzF.mjs.map +1 -0
- package/dist/provider-BqpjG9QM.cjs +57 -0
- package/dist/provider-BqpjG9QM.cjs.map +1 -0
- package/dist/provider-CEOKk8NS.mjs +46 -0
- package/dist/provider-CEOKk8NS.mjs.map +1 -0
- package/dist/ready-BMFjxBYA.cjs +404 -0
- package/dist/ready-BMFjxBYA.cjs.map +1 -0
- package/dist/ready-CIt5SOtg.mjs +375 -0
- package/dist/ready-CIt5SOtg.mjs.map +1 -0
- package/dist/reconcile/public.cjs +3 -5
- package/dist/reconcile/public.d.cts +11 -87
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +11 -87
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{reconcile-C13ERt1U.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-CYMhFSYy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/resolve-BMpPd8x3.mjs +263 -0
- package/dist/resolve-BMpPd8x3.mjs.map +1 -0
- package/dist/resolve-D043YmW9.cjs +316 -0
- package/dist/resolve-D043YmW9.cjs.map +1 -0
- package/dist/rollback-CBiYsQ3T.cjs +165 -0
- package/dist/rollback-CBiYsQ3T.cjs.map +1 -0
- package/dist/rollback-CDs_XNb0.mjs +136 -0
- package/dist/rollback-CDs_XNb0.mjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/run-BJLIerMM.mjs +133 -0
- package/dist/run-BJLIerMM.mjs.map +1 -0
- package/dist/run-fVZ-rLnc.cjs +174 -0
- package/dist/run-fVZ-rLnc.cjs.map +1 -0
- package/dist/runtime-ACdfK59n.d.cts +82 -0
- package/dist/runtime-ACdfK59n.d.cts.map +1 -0
- package/dist/runtime-YyQuekxi.d.mts +82 -0
- package/dist/runtime-YyQuekxi.d.mts.map +1 -0
- package/dist/s3-CrPoA89T.cjs +215 -0
- package/dist/s3-CrPoA89T.cjs.map +1 -0
- package/dist/s3-DQXZdmCM.mjs +215 -0
- package/dist/s3-DQXZdmCM.mjs.map +1 -0
- package/dist/sandbox/config-worker.cjs +43 -0
- package/dist/sandbox/config-worker.cjs.map +1 -0
- package/dist/sandbox/config-worker.d.cts +1 -0
- package/dist/sandbox/config-worker.d.mts +1 -0
- package/dist/sandbox/config-worker.mjs +44 -0
- package/dist/sandbox/config-worker.mjs.map +1 -0
- package/dist/sandbox/credentials-worker.cjs +54 -0
- package/dist/sandbox/credentials-worker.cjs.map +1 -0
- package/dist/sandbox/credentials-worker.d.cts +1 -0
- package/dist/sandbox/credentials-worker.d.mts +1 -0
- package/dist/sandbox/credentials-worker.mjs +55 -0
- package/dist/sandbox/credentials-worker.mjs.map +1 -0
- package/dist/sandbox/discover-worker.cjs +47 -0
- package/dist/sandbox/discover-worker.cjs.map +1 -0
- package/dist/sandbox/discover-worker.d.cts +1 -0
- package/dist/sandbox/discover-worker.d.mts +1 -0
- package/dist/sandbox/discover-worker.mjs +48 -0
- package/dist/sandbox/discover-worker.mjs.map +1 -0
- package/dist/sandbox/migrate-worker.cjs +75 -0
- package/dist/sandbox/migrate-worker.cjs.map +1 -0
- package/dist/sandbox/migrate-worker.d.cts +1 -0
- package/dist/sandbox/migrate-worker.d.mts +1 -0
- package/dist/sandbox/migrate-worker.mjs +76 -0
- package/dist/sandbox/migrate-worker.mjs.map +1 -0
- package/dist/sandbox-C4g1eG31.d.mts +104 -0
- package/dist/sandbox-C4g1eG31.d.mts.map +1 -0
- package/dist/sandbox-CHv3OYXg.cjs +165 -0
- package/dist/sandbox-CHv3OYXg.cjs.map +1 -0
- package/dist/sandbox-CbVB7uOD.mjs +118 -0
- package/dist/sandbox-CbVB7uOD.mjs.map +1 -0
- package/dist/sandbox-CdKgQ3XU.d.cts +104 -0
- package/dist/sandbox-CdKgQ3XU.d.cts.map +1 -0
- package/dist/{workspace-C-a3G_U0.cjs → schemas-BCwKKA42.mjs} +958 -1868
- package/dist/schemas-BCwKKA42.mjs.map +1 -0
- package/dist/{workspace-DaEX7FEg.mjs → schemas-BQ_v0lEX.cjs} +1048 -1678
- package/dist/schemas-BQ_v0lEX.cjs.map +1 -0
- package/dist/secretsManager-CQORubp9.mjs +97 -0
- package/dist/secretsManager-CQORubp9.mjs.map +1 -0
- package/dist/secretsManager-RAGcEDX1.cjs +108 -0
- package/dist/secretsManager-RAGcEDX1.cjs.map +1 -0
- package/dist/seeds-BjcO4DDa.mjs +8194 -0
- package/dist/seeds-BjcO4DDa.mjs.map +1 -0
- package/dist/seeds-DNqRA1aQ.cjs +9173 -0
- package/dist/seeds-DNqRA1aQ.cjs.map +1 -0
- package/dist/sniffer-DipSS8r8.cjs +1998 -0
- package/dist/sniffer-DipSS8r8.cjs.map +1 -0
- package/dist/sniffer-b0XPmSyT.mjs +1825 -0
- package/dist/sniffer-b0XPmSyT.mjs.map +1 -0
- package/dist/sst-B-Uz4cvm.cjs +464 -0
- package/dist/sst-B-Uz4cvm.cjs.map +1 -0
- package/dist/sst-C1pxbFEd.mjs +441 -0
- package/dist/sst-C1pxbFEd.mjs.map +1 -0
- package/dist/stage-C_y2UovO.cjs +266 -0
- package/dist/stage-C_y2UovO.cjs.map +1 -0
- package/dist/stage-DrDmZusd.mjs +266 -0
- package/dist/stage-DrDmZusd.mjs.map +1 -0
- package/dist/stages-DJrOYLZ-.mjs +176 -0
- package/dist/stages-DJrOYLZ-.mjs.map +1 -0
- package/dist/stages-_3RVNG83.cjs +176 -0
- package/dist/stages-_3RVNG83.cjs.map +1 -0
- package/dist/target/public.cjs +15 -0
- package/dist/target/public.d.cts +28 -0
- package/dist/target/public.d.cts.map +1 -0
- package/dist/target/public.d.mts +28 -0
- package/dist/target/public.d.mts.map +1 -0
- package/dist/target/public.mjs +4 -0
- package/dist/terminal-CvRLxz6g.mjs +85 -0
- package/dist/terminal-CvRLxz6g.mjs.map +1 -0
- package/dist/terminal-D_NRPW4W.cjs +85 -0
- package/dist/terminal-D_NRPW4W.cjs.map +1 -0
- package/dist/types-B6UXlEkf.d.cts +7650 -0
- package/dist/types-B6UXlEkf.d.cts.map +1 -0
- package/dist/types-HqVPddRK.d.mts +7649 -0
- package/dist/types-HqVPddRK.d.mts.map +1 -0
- package/dist/utils-D2Nk1Nv2.cjs +205 -0
- package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
- package/dist/utils-D_fxUqvU.mjs +156 -0
- package/dist/utils-D_fxUqvU.mjs.map +1 -0
- package/dist/vitest.cjs +65 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +29 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +29 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +65 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workerRuntime-BXfhbe13.cjs +108 -0
- package/dist/workerRuntime-BXfhbe13.cjs.map +1 -0
- package/dist/workerRuntime-CsOyBRx2.mjs +79 -0
- package/dist/workerRuntime-CsOyBRx2.mjs.map +1 -0
- package/dist/workspace/index.cjs +8 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/workspace-BwqQnool.cjs +2806 -0
- package/dist/workspace-BwqQnool.cjs.map +1 -0
- package/dist/workspace-D1QbNd55.mjs +2394 -0
- package/dist/workspace-D1QbNd55.mjs.map +1 -0
- package/package.json +54 -22
- package/dist/CachedStateProvider-BEBl2wQT.cjs +0 -60
- package/dist/CachedStateProvider-BEBl2wQT.cjs.map +0 -1
- package/dist/CachedStateProvider-C-8wtlto.mjs +0 -3
- package/dist/CachedStateProvider-Co4n5Wvs.mjs +0 -54
- package/dist/CachedStateProvider-Co4n5Wvs.mjs.map +0 -1
- package/dist/CachedStateProvider-DqhGpzsm.cjs +0 -3
- package/dist/HostingerProvider-BOvsgATO.cjs.map +0 -1
- package/dist/HostingerProvider-BcyFRbqV.mjs.map +0 -1
- package/dist/LocalStateProvider-Bq-0C0sg.mjs +0 -42
- package/dist/LocalStateProvider-Bq-0C0sg.mjs.map +0 -1
- package/dist/LocalStateProvider-yjXZXZDM.cjs +0 -43
- package/dist/LocalStateProvider-yjXZXZDM.cjs.map +0 -1
- package/dist/Route53Provider-C0xmer8U.mjs.map +0 -1
- package/dist/Route53Provider-CH9XH7bz.cjs.map +0 -1
- package/dist/SSMStateProvider-CXtNgPfy.cjs +0 -66
- package/dist/SSMStateProvider-CXtNgPfy.cjs.map +0 -1
- package/dist/SSMStateProvider-DOZifZfE.mjs +0 -66
- package/dist/SSMStateProvider-DOZifZfE.mjs.map +0 -1
- package/dist/backup-provisioner-DXXg6FIL.mjs.map +0 -1
- package/dist/backup-provisioner-cE2PuCTb.cjs.map +0 -1
- package/dist/bundler-CaTGtiaO.cjs.map +0 -1
- package/dist/bundler-S3ek7ea-.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-B0L2tvvC.mjs +0 -402
- package/dist/config-B0L2tvvC.mjs.map +0 -1
- package/dist/config-HhDpJmLO.cjs +0 -462
- package/dist/config-HhDpJmLO.cjs.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.mts.map +0 -1
- package/dist/credentials-B2Pyh3bJ.cjs +0 -2681
- package/dist/credentials-B2Pyh3bJ.cjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-CZrys9C5.mjs +0 -2549
- package/dist/credentials-CZrys9C5.mjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs +0 -229
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs +0 -271
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-5cK2InBm.cjs +0 -3
- package/dist/dokploy-api-D2Ny4Jrc.mjs +0 -3
- package/dist/dokploy-api-pdaBryoY.mjs +0 -706
- package/dist/dokploy-api-pdaBryoY.mjs.map +0 -1
- package/dist/dokploy-api-zqKe9lHH.cjs +0 -718
- package/dist/dokploy-api-zqKe9lHH.cjs.map +0 -1
- package/dist/emit-CIlvnDvV.mjs.map +0 -1
- package/dist/emit-CUD1BWls.cjs.map +0 -1
- package/dist/encryption-D2pL3tHH.cjs +0 -54
- package/dist/encryption-D2pL3tHH.cjs.map +0 -1
- package/dist/encryption-YDsXXi6w.mjs +0 -3
- package/dist/encryption-_4zwjOAR.mjs +0 -42
- package/dist/encryption-_4zwjOAR.mjs.map +0 -1
- package/dist/encryption-sQ2Oxx0d.cjs +0 -4
- package/dist/fullstack-secrets-D6MyHhQP.cjs +0 -111
- package/dist/fullstack-secrets-D6MyHhQP.cjs.map +0 -1
- package/dist/fullstack-secrets-METaRLS1.mjs +0 -87
- package/dist/fullstack-secrets-METaRLS1.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/github-BrvaPkFf.cjs +0 -197
- package/dist/github-BrvaPkFf.cjs.map +0 -1
- package/dist/github-CrpsXSfV.mjs +0 -196
- package/dist/github-CrpsXSfV.mjs.map +0 -1
- package/dist/index-Ayxg_DQY.d.mts.map +0 -1
- package/dist/index-CR5urhWX.d.cts.map +0 -1
- package/dist/keystore-C9rint2E.cjs +0 -101
- package/dist/keystore-C9rint2E.cjs.map +0 -1
- package/dist/keystore-TxeYM7an.mjs +0 -83
- package/dist/keystore-TxeYM7an.mjs.map +0 -1
- package/dist/openapi-DaKlTKxz.cjs +0 -2075
- package/dist/openapi-DaKlTKxz.cjs.map +0 -1
- package/dist/openapi-react-query-BKH334Ug.cjs +0 -173
- package/dist/openapi-react-query-BKH334Ug.cjs.map +0 -1
- package/dist/openapi-react-query-DoR5CmzQ.mjs +0 -167
- package/dist/openapi-react-query-DoR5CmzQ.mjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/openapi-y1d7Kalt.mjs +0 -2021
- package/dist/openapi-y1d7Kalt.mjs.map +0 -1
- package/dist/reconcile-C13ERt1U.mjs.map +0 -1
- package/dist/reconcile-CYMhFSYy.cjs.map +0 -1
- package/dist/setup-BWgMs4gg.mjs +0 -12
- package/dist/setup-DOv92pIu.cjs +0 -473
- package/dist/setup-DOv92pIu.cjs.map +0 -1
- package/dist/setup-DQo_h_yK.mjs +0 -443
- package/dist/setup-DQo_h_yK.mjs.map +0 -1
- package/dist/setup-lCx_DcUo.cjs +0 -12
- package/dist/storage-B020InPW.mjs +0 -222
- package/dist/storage-B020InPW.mjs.map +0 -1
- package/dist/storage-CSNplaJn.cjs +0 -282
- package/dist/storage-CSNplaJn.cjs.map +0 -1
- package/dist/storage-DGBzMLZa.mjs +0 -4
- package/dist/storage-DlVol_oj.cjs +0 -8
- package/dist/sync-0ILCb-1S.cjs +0 -93
- package/dist/sync-0ILCb-1S.cjs.map +0 -1
- package/dist/sync-CzHXiR9c.mjs +0 -76
- package/dist/sync-CzHXiR9c.mjs.map +0 -1
- package/dist/sync-DYENfOt4.cjs +0 -4
- package/dist/sync-HAau6h_R.mjs +0 -3
- package/dist/types-CL7jPFM2.d.mts +0 -2220
- package/dist/types-CL7jPFM2.d.mts.map +0 -1
- package/dist/types-Y3fKQSAK.d.cts +0 -2220
- package/dist/types-Y3fKQSAK.d.cts.map +0 -1
- package/dist/workspace-C-a3G_U0.cjs.map +0 -1
- package/dist/workspace-DQxxs7Jk.cjs +0 -10
- package/dist/workspace-DaEX7FEg.mjs.map +0 -1
- package/dist/workspace-DbI580qS.mjs +0 -9
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync, readFileSync, rmdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash, createHmac } from "node:crypto";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import { createGzip } from "node:zlib";
|
|
6
|
+
import https from "node:https";
|
|
7
|
+
//#region src/backups/runner.ts
|
|
8
|
+
/**
|
|
9
|
+
* The backups container's program: `pg_dump` each of the stack's databases
|
|
10
|
+
* on schedule and stream it, gzipped, to the stage's backups prefix.
|
|
11
|
+
*
|
|
12
|
+
* It ships alone — copied into an image built `FROM postgres:<major>-alpine`
|
|
13
|
+
* with Node added — so it imports nothing but Node's own modules: S3 is
|
|
14
|
+
* spoken to with a SigV4 signature made here, not the AWS SDK.
|
|
15
|
+
*
|
|
16
|
+
* node backup.mjs serve run on schedule, forever (the container's command)
|
|
17
|
+
* node backup.mjs run one backup now (`gkm backup:now`)
|
|
18
|
+
* node backup.mjs health exit 1 when the last good run is too old
|
|
19
|
+
*
|
|
20
|
+
* Its key may only put objects under the prefix: it never reads, lists or
|
|
21
|
+
* deletes, so nothing here does either. A dump is uploaded in parts as it is
|
|
22
|
+
* made, so no database is ever held on disk or in memory whole.
|
|
23
|
+
*
|
|
24
|
+
* Environment:
|
|
25
|
+
*
|
|
26
|
+
* - `BACKUPS_URL` — `s3://KEY:SECRET@bucket?region=…`, the stage's backups key
|
|
27
|
+
* - `BACKUPS_PREFIX` — `gkm/<project>/<stage>/backups`
|
|
28
|
+
* - `BACKUPS_DATABASES` — `[{"file":"auth-database","name":"auth_database_production"}]`
|
|
29
|
+
* - `BACKUPS_SCHEDULE` — `{"kind":"every","seconds":86400,"offset":7200}`, or
|
|
30
|
+
* a cron expanded: `{"kind":"cron","minutes":[0],"hours":[0,6,12,18],…}`
|
|
31
|
+
* - `BACKUPS_MAX_GAP_SECONDS` — the longest the schedule goes between runs
|
|
32
|
+
* - `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` — read by `pg_dump` itself
|
|
33
|
+
* - `OTEL_EXPORTER_OTLP_ENDPOINT`/`_HEADERS` — where each run's result is
|
|
34
|
+
* also sent as an OTLP log, when the stage's telemetry is on
|
|
35
|
+
*/
|
|
36
|
+
/** Where the runner keeps what its health check reads. */
|
|
37
|
+
const STATE_DIR = process.env.BACKUPS_STATE_DIR ?? "/tmp/gkm-backups";
|
|
38
|
+
/** How long a container may go without a good run before it has had one. */
|
|
39
|
+
const STARTUP_GRACE_SECONDS = 900;
|
|
40
|
+
/** S3's smallest part, but the last; and the size each part is cut at. */
|
|
41
|
+
const PART_BYTES = Number(process.env.BACKUPS_PART_BYTES) || 16777216;
|
|
42
|
+
/** Four years: how far ahead a cron schedule is looked for its next run. */
|
|
43
|
+
const CRON_HORIZON_DAYS = 1464;
|
|
44
|
+
function cronDay(schedule, date) {
|
|
45
|
+
if (schedule.months && !schedule.months.includes(date.getUTCMonth() + 1)) return false;
|
|
46
|
+
const dom = schedule.days?.includes(date.getUTCDate());
|
|
47
|
+
const dow = schedule.weekdays?.includes(date.getUTCDay());
|
|
48
|
+
if (schedule.days && schedule.weekdays) return Boolean(dom || dow);
|
|
49
|
+
if (schedule.days) return Boolean(dom);
|
|
50
|
+
if (schedule.weekdays) return Boolean(dow);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The first time after `after`, to the minute, the schedule runs — the copy
|
|
55
|
+
* of `schedule.ts`'s `nextRun` this file carries, since it ships alone.
|
|
56
|
+
*/
|
|
57
|
+
function nextRun(schedule, after) {
|
|
58
|
+
const from = Math.floor(after.getTime() / 6e4) * 6e4 + 6e4;
|
|
59
|
+
if (schedule.kind === "every") {
|
|
60
|
+
const interval = schedule.seconds * 1e3;
|
|
61
|
+
const offset = schedule.offset * 1e3;
|
|
62
|
+
return new Date(offset + Math.ceil((from - offset) / interval) * interval);
|
|
63
|
+
}
|
|
64
|
+
const start = new Date(from);
|
|
65
|
+
const day = Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
|
|
66
|
+
for (let i = 0; i < CRON_HORIZON_DAYS; i++) {
|
|
67
|
+
const date = new Date(day + i * 864e5);
|
|
68
|
+
if (!cronDay(schedule, date)) continue;
|
|
69
|
+
for (const hour of schedule.hours) for (const minute of schedule.minutes) {
|
|
70
|
+
const at = date.getTime() + hour * 36e5 + minute * 6e4;
|
|
71
|
+
if (at >= from) return new Date(at);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/** One run's folder: `2026-10-10/02-00-00Z`. */
|
|
77
|
+
function runFolder(at) {
|
|
78
|
+
const iso = at.toISOString();
|
|
79
|
+
return `${iso.slice(0, 10)}/${iso.slice(11, 19).replace(/:/g, "-")}Z`;
|
|
80
|
+
}
|
|
81
|
+
/** `BACKUPS_URL`, read the way `@geekmidas/storage/s3-url` writes it. */
|
|
82
|
+
function destination(url) {
|
|
83
|
+
const parsed = new URL(url);
|
|
84
|
+
if (parsed.protocol !== "s3:" || !parsed.hostname) throw new BackupsEnvInvalid("BACKUPS_URL", "is not an s3:// URL with a bucket");
|
|
85
|
+
const endpoint = parsed.searchParams.get("endpoint") ?? void 0;
|
|
86
|
+
return {
|
|
87
|
+
bucket: parsed.hostname,
|
|
88
|
+
region: parsed.searchParams.get("region") ?? "us-east-1",
|
|
89
|
+
...endpoint ? { endpoint } : {},
|
|
90
|
+
forcePathStyle: parsed.searchParams.get("forcePathStyle") === "true",
|
|
91
|
+
accessKeyId: decodeURIComponent(parsed.username),
|
|
92
|
+
secretAccessKey: decodeURIComponent(parsed.password)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const sha256 = (data) => createHash("sha256").update(data).digest("hex");
|
|
96
|
+
const hmac = (key, data) => createHmac("sha256", key).update(data).digest();
|
|
97
|
+
/** RFC 3986, as SigV4 wants it: everything but unreserved characters. */
|
|
98
|
+
function encode(value) {
|
|
99
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
100
|
+
}
|
|
101
|
+
/** One request to S3, signed with SigV4; the response's status and body. */
|
|
102
|
+
async function s3Send(to, request, now = /* @__PURE__ */ new Date()) {
|
|
103
|
+
const body = request.body ?? Buffer.alloc(0);
|
|
104
|
+
const path = request.key.split("/").map(encode).join("/");
|
|
105
|
+
let url;
|
|
106
|
+
if (to.endpoint) {
|
|
107
|
+
const base = to.endpoint.replace(/\/+$/, "");
|
|
108
|
+
url = to.forcePathStyle ? new URL(`${base}/${to.bucket}/${path}`) : new URL(`${base.replace("://", `://${to.bucket}.`)}/${path}`);
|
|
109
|
+
} else url = new URL(`https://${to.bucket}.s3.${to.region}.amazonaws.com/${path}`);
|
|
110
|
+
const query = Object.entries(request.query ?? {}).map(([k, v]) => [encode(k), encode(v)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
|
|
111
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
112
|
+
const day = amzDate.slice(0, 8);
|
|
113
|
+
const payload = sha256(body);
|
|
114
|
+
const headers = {
|
|
115
|
+
host: url.host,
|
|
116
|
+
"x-amz-content-sha256": payload,
|
|
117
|
+
"x-amz-date": amzDate,
|
|
118
|
+
...Object.fromEntries(Object.entries(request.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]))
|
|
119
|
+
};
|
|
120
|
+
const names = Object.keys(headers).sort();
|
|
121
|
+
const canonical = [
|
|
122
|
+
request.method,
|
|
123
|
+
url.pathname,
|
|
124
|
+
query,
|
|
125
|
+
names.map((n) => `${n}:${headers[n].trim()}\n`).join(""),
|
|
126
|
+
names.join(";"),
|
|
127
|
+
payload
|
|
128
|
+
].join("\n");
|
|
129
|
+
const scope = `${day}/${to.region}/s3/aws4_request`;
|
|
130
|
+
const toSign = [
|
|
131
|
+
"AWS4-HMAC-SHA256",
|
|
132
|
+
amzDate,
|
|
133
|
+
scope,
|
|
134
|
+
sha256(canonical)
|
|
135
|
+
].join("\n");
|
|
136
|
+
const key = hmac(hmac(hmac(hmac(`AWS4${to.secretAccessKey}`, day), to.region), "s3"), "aws4_request");
|
|
137
|
+
const signature = createHmac("sha256", key).update(toSign).digest("hex");
|
|
138
|
+
headers.authorization = `AWS4-HMAC-SHA256 Credential=${to.accessKeyId}/${scope}, SignedHeaders=${names.join(";")}, Signature=${signature}`;
|
|
139
|
+
headers["content-length"] = String(body.length);
|
|
140
|
+
const send = url.protocol === "https:" ? https.request : http.request;
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const req = send(url, {
|
|
143
|
+
method: request.method,
|
|
144
|
+
headers,
|
|
145
|
+
path: `${url.pathname}${query ? `?${query}` : ""}`
|
|
146
|
+
}, (res) => {
|
|
147
|
+
const chunks = [];
|
|
148
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
149
|
+
res.on("end", () => resolve({
|
|
150
|
+
status: res.statusCode ?? 0,
|
|
151
|
+
body: Buffer.concat(chunks).toString("utf-8"),
|
|
152
|
+
headers: Object.fromEntries(Object.entries(res.headers).map(([k, v]) => [k, String(v ?? "")]))
|
|
153
|
+
}));
|
|
154
|
+
res.on("error", reject);
|
|
155
|
+
});
|
|
156
|
+
req.on("error", reject);
|
|
157
|
+
req.setTimeout(3e5, () => req.destroy(new RequestTimedOut(request.key)));
|
|
158
|
+
req.end(body);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/** S3 did not answer a request within five minutes. */
|
|
162
|
+
var RequestTimedOut = class extends Error {
|
|
163
|
+
key;
|
|
164
|
+
constructor(key) {
|
|
165
|
+
super(`S3 did not answer within five minutes putting ${key}.`);
|
|
166
|
+
this.key = key;
|
|
167
|
+
this.name = "RequestTimedOut";
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
/** A variable the container is started with that is missing or wrong. */
|
|
171
|
+
var BackupsEnvInvalid = class extends Error {
|
|
172
|
+
variable;
|
|
173
|
+
reason;
|
|
174
|
+
constructor(variable, reason) {
|
|
175
|
+
super(`${variable} ${reason}. It is written by gkm into the stack's backups.env; deploy the stage again.`);
|
|
176
|
+
this.variable = variable;
|
|
177
|
+
this.reason = reason;
|
|
178
|
+
this.name = "BackupsEnvInvalid";
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
/** `pg_dump` exited with an error: the dump is not uploaded. */
|
|
182
|
+
var DumpFailed = class extends Error {
|
|
183
|
+
database;
|
|
184
|
+
code;
|
|
185
|
+
stderr;
|
|
186
|
+
constructor(database, code, stderr) {
|
|
187
|
+
super(`pg_dump ${database} exited ${code}: ${stderr.trim().slice(-500)}`);
|
|
188
|
+
this.database = database;
|
|
189
|
+
this.code = code;
|
|
190
|
+
this.stderr = stderr;
|
|
191
|
+
this.name = "DumpFailed";
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
/** A request S3 refused, or that never got an answer. */
|
|
195
|
+
var UploadFailed = class extends Error {
|
|
196
|
+
key;
|
|
197
|
+
status;
|
|
198
|
+
said;
|
|
199
|
+
constructor(key, status, said) {
|
|
200
|
+
super(`S3 answered HTTP ${status} putting ${key}: ${said.replace(/\s+/g, " ").slice(0, 500)}`);
|
|
201
|
+
this.key = key;
|
|
202
|
+
this.status = status;
|
|
203
|
+
this.said = said;
|
|
204
|
+
this.name = "UploadFailed";
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/** `s3Send`, again after 1, 4 and 9 seconds when the network or S3 fails. */
|
|
208
|
+
async function sendWithRetry(to, request) {
|
|
209
|
+
let last;
|
|
210
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
211
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, attempt * attempt * 1e3));
|
|
212
|
+
try {
|
|
213
|
+
const res = await s3Send(to, request);
|
|
214
|
+
if (res.status >= 200 && res.status < 300) return res;
|
|
215
|
+
last = new UploadFailed(request.key, res.status, res.body);
|
|
216
|
+
if (res.status < 500) break;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
last = error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
throw last;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Upload a stream to `key`: one PUT when it fits in a part, else a multipart
|
|
225
|
+
* upload — every action of which is `s3:PutObject`, all the key may do. A
|
|
226
|
+
* failed upload is left for the bucket's lifecycle to abort.
|
|
227
|
+
*/
|
|
228
|
+
async function upload(to, key, stream, finished = Promise.resolve()) {
|
|
229
|
+
let buffered = [];
|
|
230
|
+
let size = 0;
|
|
231
|
+
let total = 0;
|
|
232
|
+
let uploadId;
|
|
233
|
+
const parts = [];
|
|
234
|
+
const flush = async (body) => {
|
|
235
|
+
if (!uploadId) {
|
|
236
|
+
const created = await sendWithRetry(to, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
key,
|
|
239
|
+
query: { uploads: "" },
|
|
240
|
+
headers: { "content-type": "application/gzip" }
|
|
241
|
+
});
|
|
242
|
+
uploadId = /<UploadId>([^<]+)<\/UploadId>/.exec(created.body)?.[1];
|
|
243
|
+
if (!uploadId) throw new UploadFailed(key, created.status, created.body);
|
|
244
|
+
}
|
|
245
|
+
const part = await sendWithRetry(to, {
|
|
246
|
+
method: "PUT",
|
|
247
|
+
key,
|
|
248
|
+
query: {
|
|
249
|
+
partNumber: String(parts.length + 1),
|
|
250
|
+
uploadId
|
|
251
|
+
},
|
|
252
|
+
body
|
|
253
|
+
});
|
|
254
|
+
parts.push(part.headers.etag ?? "");
|
|
255
|
+
};
|
|
256
|
+
for await (const chunk of stream) {
|
|
257
|
+
buffered.push(chunk);
|
|
258
|
+
size += chunk.length;
|
|
259
|
+
total += chunk.length;
|
|
260
|
+
if (size >= PART_BYTES) {
|
|
261
|
+
const body = Buffer.concat(buffered);
|
|
262
|
+
buffered = [];
|
|
263
|
+
size = 0;
|
|
264
|
+
await flush(body);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
await finished;
|
|
268
|
+
const rest = Buffer.concat(buffered);
|
|
269
|
+
if (!uploadId) {
|
|
270
|
+
await sendWithRetry(to, {
|
|
271
|
+
method: "PUT",
|
|
272
|
+
key,
|
|
273
|
+
body: rest,
|
|
274
|
+
headers: { "content-type": "application/gzip" }
|
|
275
|
+
});
|
|
276
|
+
return total;
|
|
277
|
+
}
|
|
278
|
+
if (rest.length > 0) await flush(rest);
|
|
279
|
+
const complete = parts.map((etag, i) => `<Part><PartNumber>${i + 1}</PartNumber><ETag>${etag}</ETag></Part>`).join("");
|
|
280
|
+
const done = await sendWithRetry(to, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
key,
|
|
283
|
+
query: { uploadId },
|
|
284
|
+
body: Buffer.from(`<CompleteMultipartUpload>${complete}</CompleteMultipartUpload>`),
|
|
285
|
+
headers: { "content-type": "application/xml" }
|
|
286
|
+
});
|
|
287
|
+
if (/<Error>/.test(done.body)) throw new UploadFailed(key, 200, done.body);
|
|
288
|
+
return total;
|
|
289
|
+
}
|
|
290
|
+
/** Who else is told each line — a test, reading what a run said. */
|
|
291
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
292
|
+
/** Be handed each line the runner logs. Returns what stops it. */
|
|
293
|
+
function onReport(listener) {
|
|
294
|
+
listeners.add(listener);
|
|
295
|
+
return () => listeners.delete(listener);
|
|
296
|
+
}
|
|
297
|
+
/** One line of the run's result: JSON on stdout, and OTLP when telemetry is on. */
|
|
298
|
+
async function report(level, message, fields) {
|
|
299
|
+
const at = /* @__PURE__ */ new Date();
|
|
300
|
+
const line = {
|
|
301
|
+
time: at.toISOString(),
|
|
302
|
+
level,
|
|
303
|
+
msg: message,
|
|
304
|
+
...fields
|
|
305
|
+
};
|
|
306
|
+
(level === "error" ? process.stderr : process.stdout).write(`${JSON.stringify(line)}\n`);
|
|
307
|
+
for (const listener of listeners) listener(line);
|
|
308
|
+
if (process.pid !== 1 && process.platform === "linux") try {
|
|
309
|
+
appendFileSync(`/proc/1/fd/${level === "error" ? 2 : 1}`, `${JSON.stringify(line)}\n`);
|
|
310
|
+
} catch {}
|
|
311
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
312
|
+
if (!endpoint) return;
|
|
313
|
+
const headers = { "content-type": "application/json" };
|
|
314
|
+
for (const pair of (process.env.OTEL_EXPORTER_OTLP_HEADERS ?? "").split(",")) {
|
|
315
|
+
const at = pair.indexOf("=");
|
|
316
|
+
if (at > 0) headers[decodeURIComponent(pair.slice(0, at).trim())] = decodeURIComponent(pair.slice(at + 1).trim());
|
|
317
|
+
}
|
|
318
|
+
const value = (v) => typeof v === "number" ? { doubleValue: v } : typeof v === "boolean" ? { boolValue: v } : { stringValue: typeof v === "string" ? v : JSON.stringify(v) };
|
|
319
|
+
const body = JSON.stringify({ resourceLogs: [{
|
|
320
|
+
resource: { attributes: [{
|
|
321
|
+
key: "service.name",
|
|
322
|
+
value: { stringValue: process.env.OTEL_SERVICE_NAME ?? "backups" }
|
|
323
|
+
}] },
|
|
324
|
+
scopeLogs: [{
|
|
325
|
+
scope: { name: "gkm-backups" },
|
|
326
|
+
logRecords: [{
|
|
327
|
+
timeUnixNano: `${at.getTime()}000000`,
|
|
328
|
+
severityText: level.toUpperCase(),
|
|
329
|
+
severityNumber: level === "error" ? 17 : 9,
|
|
330
|
+
body: { stringValue: message },
|
|
331
|
+
attributes: Object.entries(fields).map(([key, v]) => ({
|
|
332
|
+
key,
|
|
333
|
+
value: value(v)
|
|
334
|
+
}))
|
|
335
|
+
}]
|
|
336
|
+
}]
|
|
337
|
+
}] });
|
|
338
|
+
try {
|
|
339
|
+
await fetch(`${endpoint.replace(/\/+$/, "")}/v1/logs`, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers,
|
|
342
|
+
body,
|
|
343
|
+
signal: AbortSignal.timeout(1e4)
|
|
344
|
+
});
|
|
345
|
+
} catch {}
|
|
346
|
+
}
|
|
347
|
+
/** `pg_dump` of one database, gzipped, as a stream — and how it ended. */
|
|
348
|
+
function dump(database) {
|
|
349
|
+
const child = spawn("pg_dump", [
|
|
350
|
+
"--format=plain",
|
|
351
|
+
"--create",
|
|
352
|
+
"--clean",
|
|
353
|
+
"--if-exists",
|
|
354
|
+
"--dbname",
|
|
355
|
+
database
|
|
356
|
+
], { stdio: [
|
|
357
|
+
"ignore",
|
|
358
|
+
"pipe",
|
|
359
|
+
"pipe"
|
|
360
|
+
] });
|
|
361
|
+
let stderr = "";
|
|
362
|
+
child.stderr.on("data", (chunk) => {
|
|
363
|
+
stderr += chunk.toString();
|
|
364
|
+
});
|
|
365
|
+
const gzip = createGzip();
|
|
366
|
+
child.stdout.pipe(gzip);
|
|
367
|
+
return {
|
|
368
|
+
stream: gzip,
|
|
369
|
+
done: new Promise((resolve, reject) => {
|
|
370
|
+
child.on("error", reject);
|
|
371
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new DumpFailed(database, code, stderr)));
|
|
372
|
+
})
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function readEnv(name) {
|
|
376
|
+
const value = process.env[name];
|
|
377
|
+
if (!value) throw new BackupsEnvInvalid(name, "is not set");
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
/** A lock, so `backup:now` and the schedule never dump at once. */
|
|
381
|
+
async function withLock(run) {
|
|
382
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
383
|
+
const lock = `${STATE_DIR}/lock`;
|
|
384
|
+
for (;;) try {
|
|
385
|
+
mkdirSync(lock);
|
|
386
|
+
break;
|
|
387
|
+
} catch {
|
|
388
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
return await run();
|
|
392
|
+
} finally {
|
|
393
|
+
rmdirSync(lock);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** One backup of every database, into one run folder. Resolves whether it all went. */
|
|
397
|
+
async function backupOnce(at = /* @__PURE__ */ new Date()) {
|
|
398
|
+
return withLock(async () => {
|
|
399
|
+
const to = destination(readEnv("BACKUPS_URL"));
|
|
400
|
+
const prefix = readEnv("BACKUPS_PREFIX").replace(/\/+$/, "");
|
|
401
|
+
const databases = JSON.parse(readEnv("BACKUPS_DATABASES"));
|
|
402
|
+
let startedAt = at;
|
|
403
|
+
const last = (() => {
|
|
404
|
+
try {
|
|
405
|
+
return readFileSync(`${STATE_DIR}/last-folder`, "utf-8");
|
|
406
|
+
} catch {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
})();
|
|
410
|
+
while (runFolder(startedAt) === last) {
|
|
411
|
+
await new Promise((r) => setTimeout(r, 1e3 - Date.now() % 1e3 + 5));
|
|
412
|
+
startedAt = /* @__PURE__ */ new Date();
|
|
413
|
+
}
|
|
414
|
+
writeFileSync(`${STATE_DIR}/last-folder`, runFolder(startedAt));
|
|
415
|
+
const folder = `${prefix}/${runFolder(startedAt)}`;
|
|
416
|
+
const started = Date.now();
|
|
417
|
+
const sizes = {};
|
|
418
|
+
const failed = {};
|
|
419
|
+
for (const database of databases) {
|
|
420
|
+
const key = `${folder}/${database.file}.sql.gz`;
|
|
421
|
+
try {
|
|
422
|
+
const { stream, done } = dump(database.name);
|
|
423
|
+
done.catch(() => {});
|
|
424
|
+
sizes[database.file] = await upload(to, key, stream, done);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
failed[database.file] = error instanceof Error ? error.message : String(error);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const ok = Object.keys(failed).length === 0;
|
|
430
|
+
const fields = {
|
|
431
|
+
folder,
|
|
432
|
+
databases: databases.map((d) => d.file),
|
|
433
|
+
bytes: sizes,
|
|
434
|
+
durationMs: Date.now() - started,
|
|
435
|
+
...ok ? {} : { failed }
|
|
436
|
+
};
|
|
437
|
+
if (ok) {
|
|
438
|
+
writeFileSync(`${STATE_DIR}/last-success`, String(Date.now()));
|
|
439
|
+
await report("info", "backup finished", {
|
|
440
|
+
ok,
|
|
441
|
+
...fields
|
|
442
|
+
});
|
|
443
|
+
} else await report("error", "backup failed", {
|
|
444
|
+
ok,
|
|
445
|
+
...fields
|
|
446
|
+
});
|
|
447
|
+
return ok;
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
/** Whether the last good run — or, before one, the start — is recent enough. */
|
|
451
|
+
function healthy(now = Date.now()) {
|
|
452
|
+
const gap = Number(readEnv("BACKUPS_MAX_GAP_SECONDS"));
|
|
453
|
+
const read = (file) => {
|
|
454
|
+
try {
|
|
455
|
+
return Number(readFileSync(`${STATE_DIR}/${file}`, "utf-8"));
|
|
456
|
+
} catch {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const since = read("last-success") ?? read("started");
|
|
461
|
+
if (since === void 0) return false;
|
|
462
|
+
return now - since <= (2 * gap + STARTUP_GRACE_SECONDS) * 1e3;
|
|
463
|
+
}
|
|
464
|
+
/** Run on schedule, forever. */
|
|
465
|
+
async function serve() {
|
|
466
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
467
|
+
writeFileSync(`${STATE_DIR}/started`, String(Date.now()));
|
|
468
|
+
const schedule = JSON.parse(readEnv("BACKUPS_SCHEDULE"));
|
|
469
|
+
let stopping = false;
|
|
470
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => {
|
|
471
|
+
stopping = true;
|
|
472
|
+
process.exit(0);
|
|
473
|
+
});
|
|
474
|
+
await report("info", "backups scheduled", { next: nextRun(schedule, /* @__PURE__ */ new Date())?.toISOString() ?? null });
|
|
475
|
+
while (!stopping) {
|
|
476
|
+
const next = nextRun(schedule, /* @__PURE__ */ new Date());
|
|
477
|
+
if (!next) return;
|
|
478
|
+
while (Date.now() < next.getTime()) await new Promise((r) => setTimeout(r, Math.min(next.getTime() - Date.now(), 36e5)));
|
|
479
|
+
await backupOnce(next).catch((error) => report("error", "backup failed", {
|
|
480
|
+
ok: false,
|
|
481
|
+
error: error instanceof Error ? error.message : String(error)
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/** What `node backup.mjs <command>` runs; resolves with its exit code. */
|
|
486
|
+
async function main(command) {
|
|
487
|
+
if (command === "serve") {
|
|
488
|
+
await serve();
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
if (command === "run") return await backupOnce() ? 0 : 1;
|
|
492
|
+
if (command === "health") return healthy() ? 0 : 1;
|
|
493
|
+
await report("error", "usage: backup.mjs serve | run | health", { command: command ?? null });
|
|
494
|
+
return 2;
|
|
495
|
+
}
|
|
496
|
+
if (process.argv[1] && /backup\.mjs$|runner\.(?:ts|mjs)$/.test(process.argv[1])) main(process.argv[2]).then((code) => process.exit(code), async (error) => {
|
|
497
|
+
await report("error", "backup failed", {
|
|
498
|
+
ok: false,
|
|
499
|
+
error: error instanceof Error ? error.message : String(error)
|
|
500
|
+
});
|
|
501
|
+
process.exit(1);
|
|
502
|
+
});
|
|
503
|
+
//#endregion
|
|
504
|
+
export { backupOnce, destination, healthy, main, nextRun, onReport, runFolder, s3Send, serve, upload };
|
|
505
|
+
|
|
506
|
+
//# sourceMappingURL=runner.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.mjs","names":[],"sources":["../../src/backups/runner.ts"],"sourcesContent":["/**\n * The backups container's program: `pg_dump` each of the stack's databases\n * on schedule and stream it, gzipped, to the stage's backups prefix.\n *\n * It ships alone — copied into an image built `FROM postgres:<major>-alpine`\n * with Node added — so it imports nothing but Node's own modules: S3 is\n * spoken to with a SigV4 signature made here, not the AWS SDK.\n *\n * node backup.mjs serve run on schedule, forever (the container's command)\n * node backup.mjs run one backup now (`gkm backup:now`)\n * node backup.mjs health exit 1 when the last good run is too old\n *\n * Its key may only put objects under the prefix: it never reads, lists or\n * deletes, so nothing here does either. A dump is uploaded in parts as it is\n * made, so no database is ever held on disk or in memory whole.\n *\n * Environment:\n *\n * - `BACKUPS_URL` — `s3://KEY:SECRET@bucket?region=…`, the stage's backups key\n * - `BACKUPS_PREFIX` — `gkm/<project>/<stage>/backups`\n * - `BACKUPS_DATABASES` — `[{\"file\":\"auth-database\",\"name\":\"auth_database_production\"}]`\n * - `BACKUPS_SCHEDULE` — `{\"kind\":\"every\",\"seconds\":86400,\"offset\":7200}`, or\n * a cron expanded: `{\"kind\":\"cron\",\"minutes\":[0],\"hours\":[0,6,12,18],…}`\n * - `BACKUPS_MAX_GAP_SECONDS` — the longest the schedule goes between runs\n * - `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` — read by `pg_dump` itself\n * - `OTEL_EXPORTER_OTLP_ENDPOINT`/`_HEADERS` — where each run's result is\n * also sent as an OTLP log, when the stage's telemetry is on\n */\n\nimport { spawn } from 'node:child_process';\nimport { createHash, createHmac } from 'node:crypto';\nimport {\n\tappendFileSync,\n\tmkdirSync,\n\treadFileSync,\n\trmdirSync,\n\twriteFileSync,\n} from 'node:fs';\nimport http from 'node:http';\nimport https from 'node:https';\nimport { createGzip } from 'node:zlib';\n\n/** Where the runner keeps what its health check reads. */\nconst STATE_DIR = process.env.BACKUPS_STATE_DIR ?? '/tmp/gkm-backups';\n/** How long a container may go without a good run before it has had one. */\nconst STARTUP_GRACE_SECONDS = 15 * 60;\n/** S3's smallest part, but the last; and the size each part is cut at. */\nconst PART_BYTES = Number(process.env.BACKUPS_PART_BYTES) || 16 * 1024 * 1024;\n\nexport interface IntervalSchedule {\n\tkind: 'every';\n\tseconds: number;\n\toffset: number;\n}\n\nexport interface CronSchedule {\n\tkind: 'cron';\n\tminutes: number[];\n\thours: number[];\n\tdays: number[] | null;\n\tmonths: number[] | null;\n\tweekdays: number[] | null;\n}\n\nexport type Schedule = IntervalSchedule | CronSchedule;\n\n/** Four years: how far ahead a cron schedule is looked for its next run. */\nconst CRON_HORIZON_DAYS = 4 * 366;\n\nfunction cronDay(schedule: CronSchedule, date: Date): boolean {\n\tif (schedule.months && !schedule.months.includes(date.getUTCMonth() + 1)) {\n\t\treturn false;\n\t}\n\tconst dom = schedule.days?.includes(date.getUTCDate());\n\tconst dow = schedule.weekdays?.includes(date.getUTCDay());\n\tif (schedule.days && schedule.weekdays) return Boolean(dom || dow);\n\tif (schedule.days) return Boolean(dom);\n\tif (schedule.weekdays) return Boolean(dow);\n\treturn true;\n}\n\n/**\n * The first time after `after`, to the minute, the schedule runs — the copy\n * of `schedule.ts`'s `nextRun` this file carries, since it ships alone.\n */\nexport function nextRun(schedule: Schedule, after: Date): Date | null {\n\tconst from = Math.floor(after.getTime() / 60_000) * 60_000 + 60_000;\n\tif (schedule.kind === 'every') {\n\t\tconst interval = schedule.seconds * 1000;\n\t\tconst offset = schedule.offset * 1000;\n\t\treturn new Date(offset + Math.ceil((from - offset) / interval) * interval);\n\t}\n\tconst start = new Date(from);\n\tconst day = Date.UTC(\n\t\tstart.getUTCFullYear(),\n\t\tstart.getUTCMonth(),\n\t\tstart.getUTCDate(),\n\t);\n\tfor (let i = 0; i < CRON_HORIZON_DAYS; i++) {\n\t\tconst date = new Date(day + i * 86_400_000);\n\t\tif (!cronDay(schedule, date)) continue;\n\t\tfor (const hour of schedule.hours) {\n\t\t\tfor (const minute of schedule.minutes) {\n\t\t\t\tconst at = date.getTime() + hour * 3_600_000 + minute * 60_000;\n\t\t\t\tif (at >= from) return new Date(at);\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/** One run's folder: `2026-10-10/02-00-00Z`. */\nexport function runFolder(at: Date): string {\n\tconst iso = at.toISOString();\n\treturn `${iso.slice(0, 10)}/${iso.slice(11, 19).replace(/:/g, '-')}Z`;\n}\n\n// ============================================================================\n// S3, signed here\n// ============================================================================\n\n/** Where and as whom the backups are put. */\nexport interface Destination {\n\tbucket: string;\n\tregion: string;\n\tendpoint?: string;\n\tforcePathStyle: boolean;\n\taccessKeyId: string;\n\tsecretAccessKey: string;\n}\n\n/** `BACKUPS_URL`, read the way `@geekmidas/storage/s3-url` writes it. */\nexport function destination(url: string): Destination {\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== 's3:' || !parsed.hostname) {\n\t\tthrow new BackupsEnvInvalid(\n\t\t\t'BACKUPS_URL',\n\t\t\t'is not an s3:// URL with a bucket',\n\t\t);\n\t}\n\tconst endpoint = parsed.searchParams.get('endpoint') ?? undefined;\n\treturn {\n\t\tbucket: parsed.hostname,\n\t\tregion: parsed.searchParams.get('region') ?? 'us-east-1',\n\t\t...(endpoint ? { endpoint } : {}),\n\t\tforcePathStyle: parsed.searchParams.get('forcePathStyle') === 'true',\n\t\taccessKeyId: decodeURIComponent(parsed.username),\n\t\tsecretAccessKey: decodeURIComponent(parsed.password),\n\t};\n}\n\nconst sha256 = (data: string | Buffer) =>\n\tcreateHash('sha256').update(data).digest('hex');\nconst hmac = (key: string | Buffer, data: string) =>\n\tcreateHmac('sha256', key).update(data).digest();\n\n/** RFC 3986, as SigV4 wants it: everything but unreserved characters. */\nfunction encode(value: string): string {\n\treturn encodeURIComponent(value).replace(\n\t\t/[!'()*]/g,\n\t\t(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n\t);\n}\n\ninterface S3Request {\n\tmethod: 'PUT' | 'POST';\n\tkey: string;\n\tquery?: Record<string, string>;\n\tbody?: Buffer;\n\theaders?: Record<string, string>;\n}\n\n/** One request to S3, signed with SigV4; the response's status and body. */\nexport async function s3Send(\n\tto: Destination,\n\trequest: S3Request,\n\tnow: Date = new Date(),\n): Promise<{ status: number; body: string; headers: Record<string, string> }> {\n\tconst body = request.body ?? Buffer.alloc(0);\n\tconst path = request.key.split('/').map(encode).join('/');\n\tlet url: URL;\n\tif (to.endpoint) {\n\t\tconst base = to.endpoint.replace(/\\/+$/, '');\n\t\turl = to.forcePathStyle\n\t\t\t? new URL(`${base}/${to.bucket}/${path}`)\n\t\t\t: new URL(`${base.replace('://', `://${to.bucket}.`)}/${path}`);\n\t} else {\n\t\turl = new URL(`https://${to.bucket}.s3.${to.region}.amazonaws.com/${path}`);\n\t}\n\tconst query = Object.entries(request.query ?? {})\n\t\t.map(([k, v]) => [encode(k), encode(v)] as const)\n\t\t.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n\t\t.map(([k, v]) => `${k}=${v}`)\n\t\t.join('&');\n\n\tconst amzDate = now.toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n\tconst day = amzDate.slice(0, 8);\n\tconst payload = sha256(body);\n\tconst headers: Record<string, string> = {\n\t\thost: url.host,\n\t\t'x-amz-content-sha256': payload,\n\t\t'x-amz-date': amzDate,\n\t\t...Object.fromEntries(\n\t\t\tObject.entries(request.headers ?? {}).map(([k, v]) => [\n\t\t\t\tk.toLowerCase(),\n\t\t\t\tv,\n\t\t\t]),\n\t\t),\n\t};\n\tconst names = Object.keys(headers).sort();\n\tconst canonical = [\n\t\trequest.method,\n\t\turl.pathname,\n\t\tquery,\n\t\tnames.map((n) => `${n}:${headers[n]!.trim()}\\n`).join(''),\n\t\tnames.join(';'),\n\t\tpayload,\n\t].join('\\n');\n\tconst scope = `${day}/${to.region}/s3/aws4_request`;\n\tconst toSign = ['AWS4-HMAC-SHA256', amzDate, scope, sha256(canonical)].join(\n\t\t'\\n',\n\t);\n\tconst key = hmac(\n\t\thmac(hmac(hmac(`AWS4${to.secretAccessKey}`, day), to.region), 's3'),\n\t\t'aws4_request',\n\t);\n\tconst signature = createHmac('sha256', key).update(toSign).digest('hex');\n\theaders.authorization =\n\t\t`AWS4-HMAC-SHA256 Credential=${to.accessKeyId}/${scope}, ` +\n\t\t`SignedHeaders=${names.join(';')}, Signature=${signature}`;\n\theaders['content-length'] = String(body.length);\n\n\t// Looked up when called, not bound at import: a test's HTTP interceptor\n\t// patches the module's own `request`.\n\tconst send = url.protocol === 'https:' ? https.request : http.request;\n\treturn new Promise((resolve, reject) => {\n\t\tconst req = send(\n\t\t\turl,\n\t\t\t{\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders,\n\t\t\t\tpath: `${url.pathname}${query ? `?${query}` : ''}`,\n\t\t\t},\n\t\t\t(res) => {\n\t\t\t\tconst chunks: Buffer[] = [];\n\t\t\t\tres.on('data', (chunk: Buffer) => chunks.push(chunk));\n\t\t\t\tres.on('end', () =>\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tstatus: res.statusCode ?? 0,\n\t\t\t\t\t\tbody: Buffer.concat(chunks).toString('utf-8'),\n\t\t\t\t\t\theaders: Object.fromEntries(\n\t\t\t\t\t\t\tObject.entries(res.headers).map(([k, v]) => [k, String(v ?? '')]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tres.on('error', reject);\n\t\t\t},\n\t\t);\n\t\treq.on('error', reject);\n\t\treq.setTimeout(5 * 60_000, () =>\n\t\t\treq.destroy(new RequestTimedOut(request.key)),\n\t\t);\n\t\treq.end(body);\n\t});\n}\n\n/** S3 did not answer a request within five minutes. */\nclass RequestTimedOut extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(`S3 did not answer within five minutes putting ${key}.`);\n\t\tthis.name = 'RequestTimedOut';\n\t}\n}\n\n/** A variable the container is started with that is missing or wrong. */\nclass BackupsEnvInvalid extends Error {\n\tconstructor(\n\t\treadonly variable: string,\n\t\treadonly reason: string,\n\t) {\n\t\tsuper(\n\t\t\t`${variable} ${reason}. It is written by gkm into the stack's backups.env; deploy the stage again.`,\n\t\t);\n\t\tthis.name = 'BackupsEnvInvalid';\n\t}\n}\n\n/** `pg_dump` exited with an error: the dump is not uploaded. */\nclass DumpFailed extends Error {\n\tconstructor(\n\t\treadonly database: string,\n\t\treadonly code: number | null,\n\t\treadonly stderr: string,\n\t) {\n\t\tsuper(`pg_dump ${database} exited ${code}: ${stderr.trim().slice(-500)}`);\n\t\tthis.name = 'DumpFailed';\n\t}\n}\n\n/** A request S3 refused, or that never got an answer. */\nclass UploadFailed extends Error {\n\tconstructor(\n\t\treadonly key: string,\n\t\treadonly status: number,\n\t\treadonly said: string,\n\t) {\n\t\tsuper(\n\t\t\t`S3 answered HTTP ${status} putting ${key}: ${said.replace(/\\s+/g, ' ').slice(0, 500)}`,\n\t\t);\n\t\tthis.name = 'UploadFailed';\n\t}\n}\n\n/** `s3Send`, again after 1, 4 and 9 seconds when the network or S3 fails. */\nasync function sendWithRetry(\n\tto: Destination,\n\trequest: S3Request,\n): Promise<{ status: number; body: string; headers: Record<string, string> }> {\n\tlet last: unknown;\n\tfor (let attempt = 0; attempt < 4; attempt++) {\n\t\tif (attempt > 0) {\n\t\t\tawait new Promise((r) => setTimeout(r, attempt * attempt * 1000));\n\t\t}\n\t\ttry {\n\t\t\tconst res = await s3Send(to, request);\n\t\t\tif (res.status >= 200 && res.status < 300) return res;\n\t\t\tlast = new UploadFailed(request.key, res.status, res.body);\n\t\t\t// A refusal is the key's or the request's; asking again changes nothing.\n\t\t\tif (res.status < 500) break;\n\t\t} catch (error) {\n\t\t\tlast = error;\n\t\t}\n\t}\n\tthrow last;\n}\n\n/**\n * Upload a stream to `key`: one PUT when it fits in a part, else a multipart\n * upload — every action of which is `s3:PutObject`, all the key may do. A\n * failed upload is left for the bucket's lifecycle to abort.\n */\nexport async function upload(\n\tto: Destination,\n\tkey: string,\n\tstream: AsyncIterable<Buffer>,\n\t/**\n\t * How what made the stream ended. Awaited before the last request: a dump\n\t * that failed half way ends its stream early, and must never be put as\n\t * though it were whole.\n\t */\n\tfinished: Promise<void> = Promise.resolve(),\n): Promise<number> {\n\tlet buffered: Buffer[] = [];\n\tlet size = 0;\n\tlet total = 0;\n\tlet uploadId: string | undefined;\n\tconst parts: string[] = [];\n\n\tconst flush = async (body: Buffer) => {\n\t\tif (!uploadId) {\n\t\t\tconst created = await sendWithRetry(to, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tkey,\n\t\t\t\tquery: { uploads: '' },\n\t\t\t\theaders: { 'content-type': 'application/gzip' },\n\t\t\t});\n\t\t\tuploadId = /<UploadId>([^<]+)<\\/UploadId>/.exec(created.body)?.[1];\n\t\t\tif (!uploadId) throw new UploadFailed(key, created.status, created.body);\n\t\t}\n\t\tconst part = await sendWithRetry(to, {\n\t\t\tmethod: 'PUT',\n\t\t\tkey,\n\t\t\tquery: { partNumber: String(parts.length + 1), uploadId },\n\t\t\tbody,\n\t\t});\n\t\tparts.push(part.headers.etag ?? '');\n\t};\n\n\tfor await (const chunk of stream) {\n\t\tbuffered.push(chunk);\n\t\tsize += chunk.length;\n\t\ttotal += chunk.length;\n\t\tif (size >= PART_BYTES) {\n\t\t\tconst body = Buffer.concat(buffered);\n\t\t\tbuffered = [];\n\t\t\tsize = 0;\n\t\t\tawait flush(body);\n\t\t}\n\t}\n\tawait finished;\n\tconst rest = Buffer.concat(buffered);\n\tif (!uploadId) {\n\t\tawait sendWithRetry(to, {\n\t\t\tmethod: 'PUT',\n\t\t\tkey,\n\t\t\tbody: rest,\n\t\t\theaders: { 'content-type': 'application/gzip' },\n\t\t});\n\t\treturn total;\n\t}\n\tif (rest.length > 0) await flush(rest);\n\tconst complete = parts\n\t\t.map(\n\t\t\t(etag, i) =>\n\t\t\t\t`<Part><PartNumber>${i + 1}</PartNumber><ETag>${etag}</ETag></Part>`,\n\t\t)\n\t\t.join('');\n\tconst done = await sendWithRetry(to, {\n\t\tmethod: 'POST',\n\t\tkey,\n\t\tquery: { uploadId: uploadId! },\n\t\tbody: Buffer.from(\n\t\t\t`<CompleteMultipartUpload>${complete}</CompleteMultipartUpload>`,\n\t\t),\n\t\theaders: { 'content-type': 'application/xml' },\n\t});\n\t// S3 can answer 200 and put an error in the body.\n\tif (/<Error>/.test(done.body)) throw new UploadFailed(key, 200, done.body);\n\treturn total;\n}\n\n// ============================================================================\n// A run\n// ============================================================================\n\nexport interface BackupDatabase {\n\tfile: string;\n\tname: string;\n}\n\n/** Who else is told each line — a test, reading what a run said. */\nconst listeners = new Set<(line: Record<string, unknown>) => void>();\n\n/** Be handed each line the runner logs. Returns what stops it. */\nexport function onReport(\n\tlistener: (line: Record<string, unknown>) => void,\n): () => void {\n\tlisteners.add(listener);\n\treturn () => listeners.delete(listener);\n}\n\n/** One line of the run's result: JSON on stdout, and OTLP when telemetry is on. */\nasync function report(\n\tlevel: 'info' | 'error',\n\tmessage: string,\n\tfields: Record<string, unknown>,\n): Promise<void> {\n\tconst at = new Date();\n\tconst line = { time: at.toISOString(), level, msg: message, ...fields };\n\tconst out = level === 'error' ? process.stderr : process.stdout;\n\tout.write(`${JSON.stringify(line)}\\n`);\n\tfor (const listener of listeners) listener(line);\n\t// A run `docker exec` started (`gkm backup:now`) is in the container's\n\t// log too, beside the scheduled ones: written to the main process's own.\n\tif (process.pid !== 1 && process.platform === 'linux') {\n\t\ttry {\n\t\t\tappendFileSync(\n\t\t\t\t`/proc/1/fd/${level === 'error' ? 2 : 1}`,\n\t\t\t\t`${JSON.stringify(line)}\\n`,\n\t\t\t);\n\t\t} catch {\n\t\t\t// Not in the container, or not allowed: the caller has the line.\n\t\t}\n\t}\n\n\tconst endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;\n\tif (!endpoint) return;\n\tconst headers: Record<string, string> = {\n\t\t'content-type': 'application/json',\n\t};\n\tfor (const pair of (process.env.OTEL_EXPORTER_OTLP_HEADERS ?? '').split(\n\t\t',',\n\t)) {\n\t\tconst at = pair.indexOf('=');\n\t\tif (at > 0) {\n\t\t\theaders[decodeURIComponent(pair.slice(0, at).trim())] =\n\t\t\t\tdecodeURIComponent(pair.slice(at + 1).trim());\n\t\t}\n\t}\n\tconst value = (v: unknown) =>\n\t\ttypeof v === 'number'\n\t\t\t? { doubleValue: v }\n\t\t\t: typeof v === 'boolean'\n\t\t\t\t? { boolValue: v }\n\t\t\t\t: { stringValue: typeof v === 'string' ? v : JSON.stringify(v) };\n\tconst body = JSON.stringify({\n\t\tresourceLogs: [\n\t\t\t{\n\t\t\t\tresource: {\n\t\t\t\t\tattributes: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkey: 'service.name',\n\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\tstringValue: process.env.OTEL_SERVICE_NAME ?? 'backups',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\tscopeLogs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tscope: { name: 'gkm-backups' },\n\t\t\t\t\t\tlogRecords: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttimeUnixNano: `${at.getTime()}000000`,\n\t\t\t\t\t\t\t\tseverityText: level.toUpperCase(),\n\t\t\t\t\t\t\t\tseverityNumber: level === 'error' ? 17 : 9,\n\t\t\t\t\t\t\t\tbody: { stringValue: message },\n\t\t\t\t\t\t\t\tattributes: Object.entries(fields).map(([key, v]) => ({\n\t\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\t\tvalue: value(v),\n\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\ttry {\n\t\tawait fetch(`${endpoint.replace(/\\/+$/, '')}/v1/logs`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders,\n\t\t\tbody,\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t} catch {\n\t\t// Telemetry that cannot be delivered never fails a backup.\n\t}\n}\n\n/** `pg_dump` of one database, gzipped, as a stream — and how it ended. */\nfunction dump(database: string): {\n\tstream: AsyncIterable<Buffer>;\n\tdone: Promise<void>;\n} {\n\tconst child = spawn(\n\t\t'pg_dump',\n\t\t[\n\t\t\t'--format=plain',\n\t\t\t// A restore recreates the database as it was, owners and grants\n\t\t\t// included, over whatever is there.\n\t\t\t'--create',\n\t\t\t'--clean',\n\t\t\t'--if-exists',\n\t\t\t'--dbname',\n\t\t\tdatabase,\n\t\t],\n\t\t{ stdio: ['ignore', 'pipe', 'pipe'] },\n\t);\n\tlet stderr = '';\n\tchild.stderr.on('data', (chunk: Buffer) => {\n\t\tstderr += chunk.toString();\n\t});\n\tconst gzip = createGzip();\n\tchild.stdout.pipe(gzip);\n\tconst done = new Promise<void>((resolve, reject) => {\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) =>\n\t\t\tcode === 0 ? resolve() : reject(new DumpFailed(database, code, stderr)),\n\t\t);\n\t});\n\treturn { stream: gzip, done };\n}\n\nfunction readEnv(name: string): string {\n\tconst value = process.env[name];\n\tif (!value) throw new BackupsEnvInvalid(name, 'is not set');\n\treturn value;\n}\n\n/** A lock, so `backup:now` and the schedule never dump at once. */\nasync function withLock<T>(run: () => Promise<T>): Promise<T> {\n\tmkdirSync(STATE_DIR, { recursive: true });\n\tconst lock = `${STATE_DIR}/lock`;\n\tfor (;;) {\n\t\ttry {\n\t\t\tmkdirSync(lock);\n\t\t\tbreak;\n\t\t} catch {\n\t\t\tawait new Promise((r) => setTimeout(r, 2000));\n\t\t}\n\t}\n\ttry {\n\t\treturn await run();\n\t} finally {\n\t\trmdirSync(lock);\n\t}\n}\n\n/** One backup of every database, into one run folder. Resolves whether it all went. */\nexport async function backupOnce(at: Date = new Date()): Promise<boolean> {\n\treturn withLock(async () => {\n\t\tconst to = destination(readEnv('BACKUPS_URL'));\n\t\tconst prefix = readEnv('BACKUPS_PREFIX').replace(/\\/+$/, '');\n\t\tconst databases = JSON.parse(\n\t\t\treadEnv('BACKUPS_DATABASES'),\n\t\t) as BackupDatabase[];\n\t\t// A run's folder is its second: one that would share the last run's\n\t\t// — `backup:now` twice in a second — waits for the next, and is never\n\t\t// written over the other.\n\t\tlet startedAt = at;\n\t\tconst last = (() => {\n\t\t\ttry {\n\t\t\t\treturn readFileSync(`${STATE_DIR}/last-folder`, 'utf-8');\n\t\t\t} catch {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t})();\n\t\twhile (runFolder(startedAt) === last) {\n\t\t\tawait new Promise((r) => setTimeout(r, 1000 - (Date.now() % 1000) + 5));\n\t\t\tstartedAt = new Date();\n\t\t}\n\t\twriteFileSync(`${STATE_DIR}/last-folder`, runFolder(startedAt));\n\t\tconst folder = `${prefix}/${runFolder(startedAt)}`;\n\t\tconst started = Date.now();\n\t\tconst sizes: Record<string, number> = {};\n\t\tconst failed: Record<string, string> = {};\n\n\t\tfor (const database of databases) {\n\t\t\tconst key = `${folder}/${database.file}.sql.gz`;\n\t\t\ttry {\n\t\t\t\tconst { stream, done } = dump(database.name);\n\t\t\t\t// Handled by the upload, which waits for it before its last\n\t\t\t\t// request; never left to reject on its own.\n\t\t\t\tdone.catch(() => {});\n\t\t\t\tsizes[database.file] = await upload(to, key, stream, done);\n\t\t\t} catch (error) {\n\t\t\t\tfailed[database.file] =\n\t\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\t}\n\t\t}\n\n\t\tconst ok = Object.keys(failed).length === 0;\n\t\tconst fields = {\n\t\t\tfolder,\n\t\t\tdatabases: databases.map((d) => d.file),\n\t\t\tbytes: sizes,\n\t\t\tdurationMs: Date.now() - started,\n\t\t\t...(ok ? {} : { failed }),\n\t\t};\n\t\tif (ok) {\n\t\t\twriteFileSync(`${STATE_DIR}/last-success`, String(Date.now()));\n\t\t\tawait report('info', 'backup finished', { ok, ...fields });\n\t\t} else {\n\t\t\tawait report('error', 'backup failed', { ok, ...fields });\n\t\t}\n\t\treturn ok;\n\t});\n}\n\n/** Whether the last good run — or, before one, the start — is recent enough. */\nexport function healthy(now = Date.now()): boolean {\n\tconst gap = Number(readEnv('BACKUPS_MAX_GAP_SECONDS'));\n\tconst read = (file: string) => {\n\t\ttry {\n\t\t\treturn Number(readFileSync(`${STATE_DIR}/${file}`, 'utf-8'));\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t};\n\tconst since = read('last-success') ?? read('started');\n\tif (since === undefined) return false;\n\treturn now - since <= (2 * gap + STARTUP_GRACE_SECONDS) * 1000;\n}\n\n/** Run on schedule, forever. */\nexport async function serve(): Promise<void> {\n\tmkdirSync(STATE_DIR, { recursive: true });\n\twriteFileSync(`${STATE_DIR}/started`, String(Date.now()));\n\tconst schedule = JSON.parse(readEnv('BACKUPS_SCHEDULE')) as Schedule;\n\tlet stopping = false;\n\tfor (const signal of ['SIGTERM', 'SIGINT'] as const) {\n\t\tprocess.once(signal, () => {\n\t\t\tstopping = true;\n\t\t\tprocess.exit(0);\n\t\t});\n\t}\n\tawait report('info', 'backups scheduled', {\n\t\tnext: nextRun(schedule, new Date())?.toISOString() ?? null,\n\t});\n\twhile (!stopping) {\n\t\tconst next = nextRun(schedule, new Date());\n\t\tif (!next) return;\n\t\t// Woken at least hourly, so a clock that jumps never oversleeps a run.\n\t\twhile (Date.now() < next.getTime()) {\n\t\t\tawait new Promise((r) =>\n\t\t\t\tsetTimeout(r, Math.min(next.getTime() - Date.now(), 3_600_000)),\n\t\t\t);\n\t\t}\n\t\tawait backupOnce(next).catch((error: unknown) =>\n\t\t\treport('error', 'backup failed', {\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t}),\n\t\t);\n\t}\n}\n\n/** What `node backup.mjs <command>` runs; resolves with its exit code. */\nexport async function main(command: string | undefined): Promise<number> {\n\tif (command === 'serve') {\n\t\tawait serve();\n\t\treturn 0;\n\t}\n\tif (command === 'run') return (await backupOnce()) ? 0 : 1;\n\tif (command === 'health') return healthy() ? 0 : 1;\n\tawait report('error', 'usage: backup.mjs serve | run | health', {\n\t\tcommand: command ?? null,\n\t});\n\treturn 2;\n}\n\n// Run as a program, not when imported by a test.\nif (\n\tprocess.argv[1] &&\n\t/backup\\.mjs$|runner\\.(?:ts|mjs)$/.test(process.argv[1])\n) {\n\tmain(process.argv[2]).then(\n\t\t(code) => process.exit(code),\n\t\tasync (error: unknown) => {\n\t\t\tawait report('error', 'backup failed', {\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t\tprocess.exit(1);\n\t\t},\n\t);\n}\n"],"mappings":"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"}
|