@geekmidas/cli 10.0.0-alpha.10 → 10.0.0-alpha.101
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-Br97xgs_.mjs +1031 -0
- package/dist/EndpointGenerator-Br97xgs_.mjs.map +1 -0
- package/dist/EndpointGenerator-By5_tYE9.cjs +1090 -0
- package/dist/EndpointGenerator-By5_tYE9.cjs.map +1 -0
- package/dist/GoDaddyProvider-BOuLf_Py.cjs +388 -0
- package/dist/GoDaddyProvider-BOuLf_Py.cjs.map +1 -0
- package/dist/GoDaddyProvider-u9MF-Dqk.mjs +388 -0
- package/dist/GoDaddyProvider-u9MF-Dqk.mjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-BNpS9aRJ.mjs} +29 -25
- package/dist/HostingerProvider-BNpS9aRJ.mjs.map +1 -0
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-DR1iws1P.cjs} +29 -25
- package/dist/HostingerProvider-DR1iws1P.cjs.map +1 -0
- package/dist/LegacyStateStore-DWmHGNgH.cjs +124 -0
- package/dist/LegacyStateStore-DWmHGNgH.cjs.map +1 -0
- package/dist/LegacyStateStore-vhkx7Bbq.mjs +124 -0
- package/dist/LegacyStateStore-vhkx7Bbq.mjs.map +1 -0
- package/dist/LocalStateStore-BVKDADoG.cjs +178 -0
- package/dist/LocalStateStore-BVKDADoG.cjs.map +1 -0
- package/dist/LocalStateStore-FVsg0UoM.mjs +167 -0
- package/dist/LocalStateStore-FVsg0UoM.mjs.map +1 -0
- package/dist/OpenApiTsGenerator-BgSX_Z-e.cjs +541 -0
- package/dist/OpenApiTsGenerator-BgSX_Z-e.cjs.map +1 -0
- package/dist/OpenApiTsGenerator-C8aTX--y.mjs +536 -0
- package/dist/OpenApiTsGenerator-C8aTX--y.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-Bllus2NR.cjs} +21 -22
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-QXxjkZO1.mjs} +13 -13
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/S3StateStore-B8seZUsw.mjs +223 -0
- package/dist/S3StateStore-B8seZUsw.mjs.map +1 -0
- package/dist/S3StateStore-B_2gE9Un.cjs +222 -0
- package/dist/S3StateStore-B_2gE9Un.cjs.map +1 -0
- package/dist/SSMStateStore-CTDEI5Bi.cjs +204 -0
- package/dist/SSMStateStore-CTDEI5Bi.cjs.map +1 -0
- package/dist/SSMStateStore-DGTbBomw.mjs +205 -0
- package/dist/SSMStateStore-DGTbBomw.mjs.map +1 -0
- package/dist/SubscriberGenerator-1-IsjIwF.mjs +449 -0
- package/dist/SubscriberGenerator-1-IsjIwF.mjs.map +1 -0
- package/dist/SubscriberGenerator-BlIOdbi-.cjs +490 -0
- package/dist/SubscriberGenerator-BlIOdbi-.cjs.map +1 -0
- package/dist/add-BB2tLVc2.cjs +720 -0
- package/dist/add-BB2tLVc2.cjs.map +1 -0
- package/dist/add-CIbhGE5v.mjs +718 -0
- package/dist/add-CIbhGE5v.mjs.map +1 -0
- package/dist/appPorts-C9z9Pzn9.mjs +223 -0
- package/dist/appPorts-C9z9Pzn9.mjs.map +1 -0
- package/dist/appPorts-Cu79hJF6.cjs +264 -0
- package/dist/appPorts-Cu79hJF6.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-Bgj5Vi_L.mjs → backup-provisioner-C8DnCmxK.mjs} +11 -8
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.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-BY1OmV1e.cjs → bundler-Ba6BER3w.cjs} +38 -35
- package/dist/bundler-Ba6BER3w.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-CHjRYVKh.mjs} +36 -32
- package/dist/bundler-CHjRYVKh.mjs.map +1 -0
- package/dist/cli-CtlqYRlN.cjs +71 -0
- package/dist/cli-CtlqYRlN.cjs.map +1 -0
- package/dist/cli-DOlkhrsh.mjs +71 -0
- package/dist/cli-DOlkhrsh.mjs.map +1 -0
- package/dist/compose-FDP_TaFt.mjs +3154 -0
- package/dist/compose-FDP_TaFt.mjs.map +1 -0
- package/dist/compose-uAWLy-nR.cjs +3267 -0
- package/dist/compose-uAWLy-nR.cjs.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-DDda4Mmm.cjs +272 -0
- package/dist/credentialSchemas-DDda4Mmm.cjs.map +1 -0
- package/dist/credentialSchemas-DvitraL-.mjs +237 -0
- package/dist/credentialSchemas-DvitraL-.mjs.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-DUUc3jV9.cjs +2390 -0
- package/dist/deploy-DUUc3jV9.cjs.map +1 -0
- package/dist/deploy-DgZxTnGS.mjs +2317 -0
- package/dist/deploy-DgZxTnGS.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-BfhVW_kT.cjs +600 -0
- package/dist/discover-BfhVW_kT.cjs.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/discover-kziEht4W.mjs +527 -0
- package/dist/discover-kziEht4W.mjs.map +1 -0
- package/dist/dns-DDDCfj75.mjs +926 -0
- package/dist/dns-DDDCfj75.mjs.map +1 -0
- package/dist/dns-DnE_02tB.cjs +1057 -0
- package/dist/dns-DnE_02tB.cjs.map +1 -0
- package/dist/dokploy-BrfdeVEu.cjs +38 -0
- package/dist/dokploy-BrfdeVEu.cjs.map +1 -0
- package/dist/dokploy-DBlGSdnb.mjs +38 -0
- package/dist/dokploy-DBlGSdnb.mjs.map +1 -0
- package/dist/drivers-BLU1fNs_.cjs +200 -0
- package/dist/drivers-BLU1fNs_.cjs.map +1 -0
- package/dist/drivers-C08LL4E8.mjs +177 -0
- package/dist/drivers-C08LL4E8.mjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-C1e8qD08.mjs} +13 -26
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-Dmc1nvdS.cjs} +31 -57
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-CeW3xpE1.cjs +90 -0
- package/dist/encryption-CeW3xpE1.cjs.map +1 -0
- package/dist/encryption-DC8zsh2s.mjs +89 -0
- package/dist/encryption-DC8zsh2s.mjs.map +1 -0
- package/dist/engine-BXxvcxVq.cjs +3195 -0
- package/dist/engine-BXxvcxVq.cjs.map +1 -0
- package/dist/engine-z0UClzE9.mjs +3082 -0
- package/dist/engine-z0UClzE9.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-DJgO5z37.mjs +829 -0
- package/dist/github-DJgO5z37.mjs.map +1 -0
- package/dist/github-DWoFgxtl.cjs +829 -0
- package/dist/github-DWoFgxtl.cjs.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-C6e-ULLx.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 +10022 -12718
- 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 +10648 -13356
- 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-B1OjPwUY.cjs +181 -0
- package/dist/migrate-B1OjPwUY.cjs.map +1 -0
- package/dist/migrate-CWSCkzu3.cjs +271 -0
- package/dist/migrate-CWSCkzu3.cjs.map +1 -0
- package/dist/migrate-DIVxoju6.mjs +266 -0
- package/dist/migrate-DIVxoju6.mjs.map +1 -0
- package/dist/migrate-DoI2hWNH.mjs +181 -0
- package/dist/migrate-DoI2hWNH.mjs.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-BtxiI7Qb.cjs +403 -0
- package/dist/ready-BtxiI7Qb.cjs.map +1 -0
- package/dist/ready-CzHNHhI6.mjs +374 -0
- package/dist/ready-CzHNHhI6.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-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/resolve-WAwYMN6q.cjs +316 -0
- package/dist/resolve-WAwYMN6q.cjs.map +1 -0
- package/dist/resolve-pmXIdi9j.mjs +263 -0
- package/dist/resolve-pmXIdi9j.mjs.map +1 -0
- package/dist/rollback-CLtwbByg.mjs +136 -0
- package/dist/rollback-CLtwbByg.mjs.map +1 -0
- package/dist/rollback-Cz8F9xSB.cjs +165 -0
- package/dist/rollback-Cz8F9xSB.cjs.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-C6VJQVtG.cjs → schemas-BCwKKA42.mjs} +958 -1842
- package/dist/schemas-BCwKKA42.mjs.map +1 -0
- package/dist/{workspace-zoqRbihZ.mjs → schemas-BQ_v0lEX.cjs} +1048 -1658
- 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-BHKUAExH.mjs +8148 -0
- package/dist/seeds-BHKUAExH.mjs.map +1 -0
- package/dist/seeds-Dk4WHhjU.cjs +9115 -0
- package/dist/seeds-Dk4WHhjU.cjs.map +1 -0
- package/dist/sniffer-BjBZ01PE.cjs +1998 -0
- package/dist/sniffer-BjBZ01PE.cjs.map +1 -0
- package/dist/sniffer-CaXl51vO.mjs +1825 -0
- package/dist/sniffer-CaXl51vO.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/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-Bs_lNQhL.mjs +85 -0
- package/dist/terminal-Bs_lNQhL.mjs.map +1 -0
- package/dist/terminal-CsQz4oT0.cjs +85 -0
- package/dist/terminal-CsQz4oT0.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-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs +0 -42
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs +0 -43
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs +0 -66
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs +0 -66
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-BZ1F0WuA.cjs +0 -462
- package/dist/config-BZ1F0WuA.cjs.map +0 -1
- package/dist/config-DVVph9yz.mjs +0 -402
- package/dist/config-DVVph9yz.mjs.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.mts.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-Xamqe_NJ.cjs +0 -2681
- package/dist/credentials-Xamqe_NJ.cjs.map +0 -1
- package/dist/credentials-dP8vRVRv.mjs +0 -2549
- package/dist/credentials-dP8vRVRv.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-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.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/index-C6e-ULLx.d.mts.map +0 -1
- package/dist/index-CR5urhWX.d.cts.map +0 -1
- package/dist/openapi-SjsgARie.cjs +0 -2075
- package/dist/openapi-SjsgARie.cjs.map +0 -1
- package/dist/openapi-Xx0A7Wg0.mjs +0 -2021
- package/dist/openapi-Xx0A7Wg0.mjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.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/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-BA8ciTPU.mjs +0 -11
- package/dist/setup-BsZvZP6f.cjs +0 -11
- package/dist/setup-C8KOx8_8.mjs +0 -443
- package/dist/setup-C8KOx8_8.mjs.map +0 -1
- package/dist/setup-hC6hI86f.cjs +0 -473
- package/dist/setup-hC6hI86f.cjs.map +0 -1
- package/dist/storage-BJmzYxt8.cjs +0 -363
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs +0 -297
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-6pkp2RRj.d.mts +0 -2220
- package/dist/types-6pkp2RRj.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-BzrG_2SI.mjs +0 -8
- package/dist/workspace-C6VJQVtG.cjs.map +0 -1
- package/dist/workspace-XXtNhqJl.cjs +0 -9
- package/dist/workspace-zoqRbihZ.mjs.map +0 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
2
|
+
let _aws_sdk_client_iam = require("@aws-sdk/client-iam");
|
|
3
|
+
//#region src/target/dokploy/backup-provisioner.ts
|
|
4
|
+
/**
|
|
5
|
+
* Backup Destination Provisioner
|
|
6
|
+
*
|
|
7
|
+
* Creates AWS resources (S3 bucket, IAM user, access keys) and configures
|
|
8
|
+
* Dokploy backup destinations for database backups.
|
|
9
|
+
*/
|
|
6
10
|
/**
|
|
7
11
|
* Generate a random suffix for unique resource names
|
|
8
12
|
*/
|
|
@@ -33,8 +37,8 @@ async function createAwsClients(region, profile, endpoint) {
|
|
|
33
37
|
};
|
|
34
38
|
}
|
|
35
39
|
return {
|
|
36
|
-
s3: new
|
|
37
|
-
iam: new
|
|
40
|
+
s3: new _aws_sdk_client_s3.S3Client(config),
|
|
41
|
+
iam: new _aws_sdk_client_iam.IAMClient(config)
|
|
38
42
|
};
|
|
39
43
|
}
|
|
40
44
|
/**
|
|
@@ -42,7 +46,7 @@ async function createAwsClients(region, profile, endpoint) {
|
|
|
42
46
|
*/
|
|
43
47
|
async function bucketExists(s3, bucketName) {
|
|
44
48
|
try {
|
|
45
|
-
await s3.send(new
|
|
49
|
+
await s3.send(new _aws_sdk_client_s3.HeadBucketCommand({ Bucket: bucketName }));
|
|
46
50
|
return true;
|
|
47
51
|
} catch (error) {
|
|
48
52
|
if (error.name === "NotFound") return false;
|
|
@@ -55,7 +59,7 @@ async function bucketExists(s3, bucketName) {
|
|
|
55
59
|
*/
|
|
56
60
|
async function userExists(iam, userName) {
|
|
57
61
|
try {
|
|
58
|
-
await iam.send(new
|
|
62
|
+
await iam.send(new _aws_sdk_client_iam.GetUserCommand({ UserName: userName }));
|
|
59
63
|
return true;
|
|
60
64
|
} catch (error) {
|
|
61
65
|
const errorName = error.name;
|
|
@@ -81,22 +85,20 @@ async function provisionBackupDestination(options) {
|
|
|
81
85
|
const aws = await createAwsClients(config.region, config.profile, awsEndpoint);
|
|
82
86
|
const sanitizedProject = sanitizeName(projectName);
|
|
83
87
|
const bucketName = existingState?.bucketName ?? `${sanitizedProject}-${stage}-backups-${randomSuffix()}`;
|
|
84
|
-
|
|
85
|
-
if (!bucketAlreadyExists) {
|
|
88
|
+
if (!await bucketExists(aws.s3, bucketName)) {
|
|
86
89
|
logger.log(` Creating S3 bucket: ${bucketName}`);
|
|
87
90
|
const createBucketParams = { Bucket: bucketName };
|
|
88
91
|
if (config.region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: config.region };
|
|
89
|
-
await aws.s3.send(new
|
|
90
|
-
await aws.s3.send(new
|
|
92
|
+
await aws.s3.send(new _aws_sdk_client_s3.CreateBucketCommand(createBucketParams));
|
|
93
|
+
await aws.s3.send(new _aws_sdk_client_s3.PutBucketVersioningCommand({
|
|
91
94
|
Bucket: bucketName,
|
|
92
95
|
VersioningConfiguration: { Status: "Enabled" }
|
|
93
96
|
}));
|
|
94
97
|
} else logger.log(` Using existing S3 bucket: ${bucketName}`);
|
|
95
98
|
const iamUserName = existingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;
|
|
96
|
-
|
|
97
|
-
if (!iamUserAlreadyExists) {
|
|
99
|
+
if (!await userExists(aws.iam, iamUserName)) {
|
|
98
100
|
logger.log(` Creating IAM user: ${iamUserName}`);
|
|
99
|
-
await aws.iam.send(new
|
|
101
|
+
await aws.iam.send(new _aws_sdk_client_iam.CreateUserCommand({ UserName: iamUserName }));
|
|
100
102
|
} else logger.log(` Using existing IAM user: ${iamUserName}`);
|
|
101
103
|
const policyDocument = {
|
|
102
104
|
Version: "2012-10-17",
|
|
@@ -113,7 +115,7 @@ async function provisionBackupDestination(options) {
|
|
|
113
115
|
}]
|
|
114
116
|
};
|
|
115
117
|
logger.log(" Updating IAM policy");
|
|
116
|
-
await aws.iam.send(new
|
|
118
|
+
await aws.iam.send(new _aws_sdk_client_iam.PutUserPolicyCommand({
|
|
117
119
|
UserName: iamUserName,
|
|
118
120
|
PolicyName: "DokployBackupAccess",
|
|
119
121
|
PolicyDocument: JSON.stringify(policyDocument)
|
|
@@ -126,7 +128,7 @@ async function provisionBackupDestination(options) {
|
|
|
126
128
|
secretAccessKey = existingState.iamSecretAccessKey;
|
|
127
129
|
} else {
|
|
128
130
|
logger.log(" Creating IAM access key");
|
|
129
|
-
const accessKeyResult = await aws.iam.send(new
|
|
131
|
+
const accessKeyResult = await aws.iam.send(new _aws_sdk_client_iam.CreateAccessKeyCommand({ UserName: iamUserName }));
|
|
130
132
|
if (!accessKeyResult.AccessKey) throw new Error("Failed to create IAM access key");
|
|
131
133
|
accessKeyId = accessKeyResult.AccessKey.AccessKeyId;
|
|
132
134
|
secretAccessKey = accessKeyResult.AccessKey.SecretAccessKey;
|
|
@@ -158,7 +160,7 @@ async function provisionBackupDestination(options) {
|
|
|
158
160
|
createdAt: existingState?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
159
161
|
};
|
|
160
162
|
}
|
|
161
|
-
|
|
162
163
|
//#endregion
|
|
163
164
|
exports.provisionBackupDestination = provisionBackupDestination;
|
|
164
|
-
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=backup-provisioner-oDEsPj02.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-provisioner-oDEsPj02.cjs","names":["S3Client","IAMClient","HeadBucketCommand","GetUserCommand","CreateBucketCommand","PutBucketVersioningCommand","CreateUserCommand","PutUserPolicyCommand","CreateAccessKeyCommand"],"sources":["../src/target/dokploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupState } from '../../deploy/state.js';\nimport type { DokployBackupsConfig } from '../../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: DokployBackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("../rolldown-runtime-C0BPl7ul.cjs");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
let node_child_process = require("node:child_process");
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
6
|
+
let node_http = require("node:http");
|
|
7
|
+
node_http = require_rolldown_runtime.__toESM(node_http, 1);
|
|
8
|
+
let node_zlib = require("node:zlib");
|
|
9
|
+
let node_https = require("node:https");
|
|
10
|
+
node_https = require_rolldown_runtime.__toESM(node_https, 1);
|
|
11
|
+
//#region src/backups/runner.ts
|
|
12
|
+
/**
|
|
13
|
+
* The backups container's program: `pg_dump` each of the stack's databases
|
|
14
|
+
* on schedule and stream it, gzipped, to the stage's backups prefix.
|
|
15
|
+
*
|
|
16
|
+
* It ships alone — copied into an image built `FROM postgres:<major>-alpine`
|
|
17
|
+
* with Node added — so it imports nothing but Node's own modules: S3 is
|
|
18
|
+
* spoken to with a SigV4 signature made here, not the AWS SDK.
|
|
19
|
+
*
|
|
20
|
+
* node backup.mjs serve run on schedule, forever (the container's command)
|
|
21
|
+
* node backup.mjs run one backup now (`gkm backup:now`)
|
|
22
|
+
* node backup.mjs health exit 1 when the last good run is too old
|
|
23
|
+
*
|
|
24
|
+
* Its key may only put objects under the prefix: it never reads, lists or
|
|
25
|
+
* deletes, so nothing here does either. A dump is uploaded in parts as it is
|
|
26
|
+
* made, so no database is ever held on disk or in memory whole.
|
|
27
|
+
*
|
|
28
|
+
* Environment:
|
|
29
|
+
*
|
|
30
|
+
* - `BACKUPS_URL` — `s3://KEY:SECRET@bucket?region=…`, the stage's backups key
|
|
31
|
+
* - `BACKUPS_PREFIX` — `gkm/<project>/<stage>/backups`
|
|
32
|
+
* - `BACKUPS_DATABASES` — `[{"file":"auth-database","name":"auth_database_production"}]`
|
|
33
|
+
* - `BACKUPS_SCHEDULE` — `{"kind":"every","seconds":86400,"offset":7200}`, or
|
|
34
|
+
* a cron expanded: `{"kind":"cron","minutes":[0],"hours":[0,6,12,18],…}`
|
|
35
|
+
* - `BACKUPS_MAX_GAP_SECONDS` — the longest the schedule goes between runs
|
|
36
|
+
* - `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` — read by `pg_dump` itself
|
|
37
|
+
* - `OTEL_EXPORTER_OTLP_ENDPOINT`/`_HEADERS` — where each run's result is
|
|
38
|
+
* also sent as an OTLP log, when the stage's telemetry is on
|
|
39
|
+
*/
|
|
40
|
+
/** Where the runner keeps what its health check reads. */
|
|
41
|
+
const STATE_DIR = process.env.BACKUPS_STATE_DIR ?? "/tmp/gkm-backups";
|
|
42
|
+
/** How long a container may go without a good run before it has had one. */
|
|
43
|
+
const STARTUP_GRACE_SECONDS = 900;
|
|
44
|
+
/** S3's smallest part, but the last; and the size each part is cut at. */
|
|
45
|
+
const PART_BYTES = Number(process.env.BACKUPS_PART_BYTES) || 16777216;
|
|
46
|
+
/** Four years: how far ahead a cron schedule is looked for its next run. */
|
|
47
|
+
const CRON_HORIZON_DAYS = 1464;
|
|
48
|
+
function cronDay(schedule, date) {
|
|
49
|
+
if (schedule.months && !schedule.months.includes(date.getUTCMonth() + 1)) return false;
|
|
50
|
+
const dom = schedule.days?.includes(date.getUTCDate());
|
|
51
|
+
const dow = schedule.weekdays?.includes(date.getUTCDay());
|
|
52
|
+
if (schedule.days && schedule.weekdays) return Boolean(dom || dow);
|
|
53
|
+
if (schedule.days) return Boolean(dom);
|
|
54
|
+
if (schedule.weekdays) return Boolean(dow);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The first time after `after`, to the minute, the schedule runs — the copy
|
|
59
|
+
* of `schedule.ts`'s `nextRun` this file carries, since it ships alone.
|
|
60
|
+
*/
|
|
61
|
+
function nextRun(schedule, after) {
|
|
62
|
+
const from = Math.floor(after.getTime() / 6e4) * 6e4 + 6e4;
|
|
63
|
+
if (schedule.kind === "every") {
|
|
64
|
+
const interval = schedule.seconds * 1e3;
|
|
65
|
+
const offset = schedule.offset * 1e3;
|
|
66
|
+
return new Date(offset + Math.ceil((from - offset) / interval) * interval);
|
|
67
|
+
}
|
|
68
|
+
const start = new Date(from);
|
|
69
|
+
const day = Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
|
|
70
|
+
for (let i = 0; i < CRON_HORIZON_DAYS; i++) {
|
|
71
|
+
const date = new Date(day + i * 864e5);
|
|
72
|
+
if (!cronDay(schedule, date)) continue;
|
|
73
|
+
for (const hour of schedule.hours) for (const minute of schedule.minutes) {
|
|
74
|
+
const at = date.getTime() + hour * 36e5 + minute * 6e4;
|
|
75
|
+
if (at >= from) return new Date(at);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/** One run's folder: `2026-10-10/02-00-00Z`. */
|
|
81
|
+
function runFolder(at) {
|
|
82
|
+
const iso = at.toISOString();
|
|
83
|
+
return `${iso.slice(0, 10)}/${iso.slice(11, 19).replace(/:/g, "-")}Z`;
|
|
84
|
+
}
|
|
85
|
+
/** `BACKUPS_URL`, read the way `@geekmidas/storage/s3-url` writes it. */
|
|
86
|
+
function destination(url) {
|
|
87
|
+
const parsed = new URL(url);
|
|
88
|
+
if (parsed.protocol !== "s3:" || !parsed.hostname) throw new BackupsEnvInvalid("BACKUPS_URL", "is not an s3:// URL with a bucket");
|
|
89
|
+
const endpoint = parsed.searchParams.get("endpoint") ?? void 0;
|
|
90
|
+
return {
|
|
91
|
+
bucket: parsed.hostname,
|
|
92
|
+
region: parsed.searchParams.get("region") ?? "us-east-1",
|
|
93
|
+
...endpoint ? { endpoint } : {},
|
|
94
|
+
forcePathStyle: parsed.searchParams.get("forcePathStyle") === "true",
|
|
95
|
+
accessKeyId: decodeURIComponent(parsed.username),
|
|
96
|
+
secretAccessKey: decodeURIComponent(parsed.password)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const sha256 = (data) => (0, node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
100
|
+
const hmac = (key, data) => (0, node_crypto.createHmac)("sha256", key).update(data).digest();
|
|
101
|
+
/** RFC 3986, as SigV4 wants it: everything but unreserved characters. */
|
|
102
|
+
function encode(value) {
|
|
103
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
104
|
+
}
|
|
105
|
+
/** One request to S3, signed with SigV4; the response's status and body. */
|
|
106
|
+
async function s3Send(to, request, now = /* @__PURE__ */ new Date()) {
|
|
107
|
+
const body = request.body ?? Buffer.alloc(0);
|
|
108
|
+
const path = request.key.split("/").map(encode).join("/");
|
|
109
|
+
let url;
|
|
110
|
+
if (to.endpoint) {
|
|
111
|
+
const base = to.endpoint.replace(/\/+$/, "");
|
|
112
|
+
url = to.forcePathStyle ? new URL(`${base}/${to.bucket}/${path}`) : new URL(`${base.replace("://", `://${to.bucket}.`)}/${path}`);
|
|
113
|
+
} else url = new URL(`https://${to.bucket}.s3.${to.region}.amazonaws.com/${path}`);
|
|
114
|
+
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("&");
|
|
115
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
116
|
+
const day = amzDate.slice(0, 8);
|
|
117
|
+
const payload = sha256(body);
|
|
118
|
+
const headers = {
|
|
119
|
+
host: url.host,
|
|
120
|
+
"x-amz-content-sha256": payload,
|
|
121
|
+
"x-amz-date": amzDate,
|
|
122
|
+
...Object.fromEntries(Object.entries(request.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]))
|
|
123
|
+
};
|
|
124
|
+
const names = Object.keys(headers).sort();
|
|
125
|
+
const canonical = [
|
|
126
|
+
request.method,
|
|
127
|
+
url.pathname,
|
|
128
|
+
query,
|
|
129
|
+
names.map((n) => `${n}:${headers[n].trim()}\n`).join(""),
|
|
130
|
+
names.join(";"),
|
|
131
|
+
payload
|
|
132
|
+
].join("\n");
|
|
133
|
+
const scope = `${day}/${to.region}/s3/aws4_request`;
|
|
134
|
+
const toSign = [
|
|
135
|
+
"AWS4-HMAC-SHA256",
|
|
136
|
+
amzDate,
|
|
137
|
+
scope,
|
|
138
|
+
sha256(canonical)
|
|
139
|
+
].join("\n");
|
|
140
|
+
const key = hmac(hmac(hmac(hmac(`AWS4${to.secretAccessKey}`, day), to.region), "s3"), "aws4_request");
|
|
141
|
+
const signature = (0, node_crypto.createHmac)("sha256", key).update(toSign).digest("hex");
|
|
142
|
+
headers.authorization = `AWS4-HMAC-SHA256 Credential=${to.accessKeyId}/${scope}, SignedHeaders=${names.join(";")}, Signature=${signature}`;
|
|
143
|
+
headers["content-length"] = String(body.length);
|
|
144
|
+
const send = url.protocol === "https:" ? node_https.default.request : node_http.default.request;
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const req = send(url, {
|
|
147
|
+
method: request.method,
|
|
148
|
+
headers,
|
|
149
|
+
path: `${url.pathname}${query ? `?${query}` : ""}`
|
|
150
|
+
}, (res) => {
|
|
151
|
+
const chunks = [];
|
|
152
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
153
|
+
res.on("end", () => resolve({
|
|
154
|
+
status: res.statusCode ?? 0,
|
|
155
|
+
body: Buffer.concat(chunks).toString("utf-8"),
|
|
156
|
+
headers: Object.fromEntries(Object.entries(res.headers).map(([k, v]) => [k, String(v ?? "")]))
|
|
157
|
+
}));
|
|
158
|
+
res.on("error", reject);
|
|
159
|
+
});
|
|
160
|
+
req.on("error", reject);
|
|
161
|
+
req.setTimeout(3e5, () => req.destroy(new RequestTimedOut(request.key)));
|
|
162
|
+
req.end(body);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** S3 did not answer a request within five minutes. */
|
|
166
|
+
var RequestTimedOut = class extends Error {
|
|
167
|
+
key;
|
|
168
|
+
constructor(key) {
|
|
169
|
+
super(`S3 did not answer within five minutes putting ${key}.`);
|
|
170
|
+
this.key = key;
|
|
171
|
+
this.name = "RequestTimedOut";
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
/** A variable the container is started with that is missing or wrong. */
|
|
175
|
+
var BackupsEnvInvalid = class extends Error {
|
|
176
|
+
variable;
|
|
177
|
+
reason;
|
|
178
|
+
constructor(variable, reason) {
|
|
179
|
+
super(`${variable} ${reason}. It is written by gkm into the stack's backups.env; deploy the stage again.`);
|
|
180
|
+
this.variable = variable;
|
|
181
|
+
this.reason = reason;
|
|
182
|
+
this.name = "BackupsEnvInvalid";
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
/** `pg_dump` exited with an error: the dump is not uploaded. */
|
|
186
|
+
var DumpFailed = class extends Error {
|
|
187
|
+
database;
|
|
188
|
+
code;
|
|
189
|
+
stderr;
|
|
190
|
+
constructor(database, code, stderr) {
|
|
191
|
+
super(`pg_dump ${database} exited ${code}: ${stderr.trim().slice(-500)}`);
|
|
192
|
+
this.database = database;
|
|
193
|
+
this.code = code;
|
|
194
|
+
this.stderr = stderr;
|
|
195
|
+
this.name = "DumpFailed";
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
/** A request S3 refused, or that never got an answer. */
|
|
199
|
+
var UploadFailed = class extends Error {
|
|
200
|
+
key;
|
|
201
|
+
status;
|
|
202
|
+
said;
|
|
203
|
+
constructor(key, status, said) {
|
|
204
|
+
super(`S3 answered HTTP ${status} putting ${key}: ${said.replace(/\s+/g, " ").slice(0, 500)}`);
|
|
205
|
+
this.key = key;
|
|
206
|
+
this.status = status;
|
|
207
|
+
this.said = said;
|
|
208
|
+
this.name = "UploadFailed";
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/** `s3Send`, again after 1, 4 and 9 seconds when the network or S3 fails. */
|
|
212
|
+
async function sendWithRetry(to, request) {
|
|
213
|
+
let last;
|
|
214
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
215
|
+
if (attempt > 0) await new Promise((r) => setTimeout(r, attempt * attempt * 1e3));
|
|
216
|
+
try {
|
|
217
|
+
const res = await s3Send(to, request);
|
|
218
|
+
if (res.status >= 200 && res.status < 300) return res;
|
|
219
|
+
last = new UploadFailed(request.key, res.status, res.body);
|
|
220
|
+
if (res.status < 500) break;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
last = error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
throw last;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Upload a stream to `key`: one PUT when it fits in a part, else a multipart
|
|
229
|
+
* upload — every action of which is `s3:PutObject`, all the key may do. A
|
|
230
|
+
* failed upload is left for the bucket's lifecycle to abort.
|
|
231
|
+
*/
|
|
232
|
+
async function upload(to, key, stream, finished = Promise.resolve()) {
|
|
233
|
+
let buffered = [];
|
|
234
|
+
let size = 0;
|
|
235
|
+
let total = 0;
|
|
236
|
+
let uploadId;
|
|
237
|
+
const parts = [];
|
|
238
|
+
const flush = async (body) => {
|
|
239
|
+
if (!uploadId) {
|
|
240
|
+
const created = await sendWithRetry(to, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
key,
|
|
243
|
+
query: { uploads: "" },
|
|
244
|
+
headers: { "content-type": "application/gzip" }
|
|
245
|
+
});
|
|
246
|
+
uploadId = /<UploadId>([^<]+)<\/UploadId>/.exec(created.body)?.[1];
|
|
247
|
+
if (!uploadId) throw new UploadFailed(key, created.status, created.body);
|
|
248
|
+
}
|
|
249
|
+
const part = await sendWithRetry(to, {
|
|
250
|
+
method: "PUT",
|
|
251
|
+
key,
|
|
252
|
+
query: {
|
|
253
|
+
partNumber: String(parts.length + 1),
|
|
254
|
+
uploadId
|
|
255
|
+
},
|
|
256
|
+
body
|
|
257
|
+
});
|
|
258
|
+
parts.push(part.headers.etag ?? "");
|
|
259
|
+
};
|
|
260
|
+
for await (const chunk of stream) {
|
|
261
|
+
buffered.push(chunk);
|
|
262
|
+
size += chunk.length;
|
|
263
|
+
total += chunk.length;
|
|
264
|
+
if (size >= PART_BYTES) {
|
|
265
|
+
const body = Buffer.concat(buffered);
|
|
266
|
+
buffered = [];
|
|
267
|
+
size = 0;
|
|
268
|
+
await flush(body);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await finished;
|
|
272
|
+
const rest = Buffer.concat(buffered);
|
|
273
|
+
if (!uploadId) {
|
|
274
|
+
await sendWithRetry(to, {
|
|
275
|
+
method: "PUT",
|
|
276
|
+
key,
|
|
277
|
+
body: rest,
|
|
278
|
+
headers: { "content-type": "application/gzip" }
|
|
279
|
+
});
|
|
280
|
+
return total;
|
|
281
|
+
}
|
|
282
|
+
if (rest.length > 0) await flush(rest);
|
|
283
|
+
const complete = parts.map((etag, i) => `<Part><PartNumber>${i + 1}</PartNumber><ETag>${etag}</ETag></Part>`).join("");
|
|
284
|
+
const done = await sendWithRetry(to, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
key,
|
|
287
|
+
query: { uploadId },
|
|
288
|
+
body: Buffer.from(`<CompleteMultipartUpload>${complete}</CompleteMultipartUpload>`),
|
|
289
|
+
headers: { "content-type": "application/xml" }
|
|
290
|
+
});
|
|
291
|
+
if (/<Error>/.test(done.body)) throw new UploadFailed(key, 200, done.body);
|
|
292
|
+
return total;
|
|
293
|
+
}
|
|
294
|
+
/** Who else is told each line — a test, reading what a run said. */
|
|
295
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
296
|
+
/** Be handed each line the runner logs. Returns what stops it. */
|
|
297
|
+
function onReport(listener) {
|
|
298
|
+
listeners.add(listener);
|
|
299
|
+
return () => listeners.delete(listener);
|
|
300
|
+
}
|
|
301
|
+
/** One line of the run's result: JSON on stdout, and OTLP when telemetry is on. */
|
|
302
|
+
async function report(level, message, fields) {
|
|
303
|
+
const at = /* @__PURE__ */ new Date();
|
|
304
|
+
const line = {
|
|
305
|
+
time: at.toISOString(),
|
|
306
|
+
level,
|
|
307
|
+
msg: message,
|
|
308
|
+
...fields
|
|
309
|
+
};
|
|
310
|
+
(level === "error" ? process.stderr : process.stdout).write(`${JSON.stringify(line)}\n`);
|
|
311
|
+
for (const listener of listeners) listener(line);
|
|
312
|
+
if (process.pid !== 1 && process.platform === "linux") try {
|
|
313
|
+
(0, node_fs.appendFileSync)(`/proc/1/fd/${level === "error" ? 2 : 1}`, `${JSON.stringify(line)}\n`);
|
|
314
|
+
} catch {}
|
|
315
|
+
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
316
|
+
if (!endpoint) return;
|
|
317
|
+
const headers = { "content-type": "application/json" };
|
|
318
|
+
for (const pair of (process.env.OTEL_EXPORTER_OTLP_HEADERS ?? "").split(",")) {
|
|
319
|
+
const at = pair.indexOf("=");
|
|
320
|
+
if (at > 0) headers[decodeURIComponent(pair.slice(0, at).trim())] = decodeURIComponent(pair.slice(at + 1).trim());
|
|
321
|
+
}
|
|
322
|
+
const value = (v) => typeof v === "number" ? { doubleValue: v } : typeof v === "boolean" ? { boolValue: v } : { stringValue: typeof v === "string" ? v : JSON.stringify(v) };
|
|
323
|
+
const body = JSON.stringify({ resourceLogs: [{
|
|
324
|
+
resource: { attributes: [{
|
|
325
|
+
key: "service.name",
|
|
326
|
+
value: { stringValue: process.env.OTEL_SERVICE_NAME ?? "backups" }
|
|
327
|
+
}] },
|
|
328
|
+
scopeLogs: [{
|
|
329
|
+
scope: { name: "gkm-backups" },
|
|
330
|
+
logRecords: [{
|
|
331
|
+
timeUnixNano: `${at.getTime()}000000`,
|
|
332
|
+
severityText: level.toUpperCase(),
|
|
333
|
+
severityNumber: level === "error" ? 17 : 9,
|
|
334
|
+
body: { stringValue: message },
|
|
335
|
+
attributes: Object.entries(fields).map(([key, v]) => ({
|
|
336
|
+
key,
|
|
337
|
+
value: value(v)
|
|
338
|
+
}))
|
|
339
|
+
}]
|
|
340
|
+
}]
|
|
341
|
+
}] });
|
|
342
|
+
try {
|
|
343
|
+
await fetch(`${endpoint.replace(/\/+$/, "")}/v1/logs`, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers,
|
|
346
|
+
body,
|
|
347
|
+
signal: AbortSignal.timeout(1e4)
|
|
348
|
+
});
|
|
349
|
+
} catch {}
|
|
350
|
+
}
|
|
351
|
+
/** `pg_dump` of one database, gzipped, as a stream — and how it ended. */
|
|
352
|
+
function dump(database) {
|
|
353
|
+
const child = (0, node_child_process.spawn)("pg_dump", [
|
|
354
|
+
"--format=plain",
|
|
355
|
+
"--create",
|
|
356
|
+
"--clean",
|
|
357
|
+
"--if-exists",
|
|
358
|
+
"--dbname",
|
|
359
|
+
database
|
|
360
|
+
], { stdio: [
|
|
361
|
+
"ignore",
|
|
362
|
+
"pipe",
|
|
363
|
+
"pipe"
|
|
364
|
+
] });
|
|
365
|
+
let stderr = "";
|
|
366
|
+
child.stderr.on("data", (chunk) => {
|
|
367
|
+
stderr += chunk.toString();
|
|
368
|
+
});
|
|
369
|
+
const gzip = (0, node_zlib.createGzip)();
|
|
370
|
+
child.stdout.pipe(gzip);
|
|
371
|
+
return {
|
|
372
|
+
stream: gzip,
|
|
373
|
+
done: new Promise((resolve, reject) => {
|
|
374
|
+
child.on("error", reject);
|
|
375
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new DumpFailed(database, code, stderr)));
|
|
376
|
+
})
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function readEnv(name) {
|
|
380
|
+
const value = process.env[name];
|
|
381
|
+
if (!value) throw new BackupsEnvInvalid(name, "is not set");
|
|
382
|
+
return value;
|
|
383
|
+
}
|
|
384
|
+
/** A lock, so `backup:now` and the schedule never dump at once. */
|
|
385
|
+
async function withLock(run) {
|
|
386
|
+
(0, node_fs.mkdirSync)(STATE_DIR, { recursive: true });
|
|
387
|
+
const lock = `${STATE_DIR}/lock`;
|
|
388
|
+
for (;;) try {
|
|
389
|
+
(0, node_fs.mkdirSync)(lock);
|
|
390
|
+
break;
|
|
391
|
+
} catch {
|
|
392
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
return await run();
|
|
396
|
+
} finally {
|
|
397
|
+
(0, node_fs.rmdirSync)(lock);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/** One backup of every database, into one run folder. Resolves whether it all went. */
|
|
401
|
+
async function backupOnce(at = /* @__PURE__ */ new Date()) {
|
|
402
|
+
return withLock(async () => {
|
|
403
|
+
const to = destination(readEnv("BACKUPS_URL"));
|
|
404
|
+
const prefix = readEnv("BACKUPS_PREFIX").replace(/\/+$/, "");
|
|
405
|
+
const databases = JSON.parse(readEnv("BACKUPS_DATABASES"));
|
|
406
|
+
let startedAt = at;
|
|
407
|
+
const last = (() => {
|
|
408
|
+
try {
|
|
409
|
+
return (0, node_fs.readFileSync)(`${STATE_DIR}/last-folder`, "utf-8");
|
|
410
|
+
} catch {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
})();
|
|
414
|
+
while (runFolder(startedAt) === last) {
|
|
415
|
+
await new Promise((r) => setTimeout(r, 1e3 - Date.now() % 1e3 + 5));
|
|
416
|
+
startedAt = /* @__PURE__ */ new Date();
|
|
417
|
+
}
|
|
418
|
+
(0, node_fs.writeFileSync)(`${STATE_DIR}/last-folder`, runFolder(startedAt));
|
|
419
|
+
const folder = `${prefix}/${runFolder(startedAt)}`;
|
|
420
|
+
const started = Date.now();
|
|
421
|
+
const sizes = {};
|
|
422
|
+
const failed = {};
|
|
423
|
+
for (const database of databases) {
|
|
424
|
+
const key = `${folder}/${database.file}.sql.gz`;
|
|
425
|
+
try {
|
|
426
|
+
const { stream, done } = dump(database.name);
|
|
427
|
+
done.catch(() => {});
|
|
428
|
+
sizes[database.file] = await upload(to, key, stream, done);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
failed[database.file] = error instanceof Error ? error.message : String(error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const ok = Object.keys(failed).length === 0;
|
|
434
|
+
const fields = {
|
|
435
|
+
folder,
|
|
436
|
+
databases: databases.map((d) => d.file),
|
|
437
|
+
bytes: sizes,
|
|
438
|
+
durationMs: Date.now() - started,
|
|
439
|
+
...ok ? {} : { failed }
|
|
440
|
+
};
|
|
441
|
+
if (ok) {
|
|
442
|
+
(0, node_fs.writeFileSync)(`${STATE_DIR}/last-success`, String(Date.now()));
|
|
443
|
+
await report("info", "backup finished", {
|
|
444
|
+
ok,
|
|
445
|
+
...fields
|
|
446
|
+
});
|
|
447
|
+
} else await report("error", "backup failed", {
|
|
448
|
+
ok,
|
|
449
|
+
...fields
|
|
450
|
+
});
|
|
451
|
+
return ok;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
/** Whether the last good run — or, before one, the start — is recent enough. */
|
|
455
|
+
function healthy(now = Date.now()) {
|
|
456
|
+
const gap = Number(readEnv("BACKUPS_MAX_GAP_SECONDS"));
|
|
457
|
+
const read = (file) => {
|
|
458
|
+
try {
|
|
459
|
+
return Number((0, node_fs.readFileSync)(`${STATE_DIR}/${file}`, "utf-8"));
|
|
460
|
+
} catch {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const since = read("last-success") ?? read("started");
|
|
465
|
+
if (since === void 0) return false;
|
|
466
|
+
return now - since <= (2 * gap + STARTUP_GRACE_SECONDS) * 1e3;
|
|
467
|
+
}
|
|
468
|
+
/** Run on schedule, forever. */
|
|
469
|
+
async function serve() {
|
|
470
|
+
(0, node_fs.mkdirSync)(STATE_DIR, { recursive: true });
|
|
471
|
+
(0, node_fs.writeFileSync)(`${STATE_DIR}/started`, String(Date.now()));
|
|
472
|
+
const schedule = JSON.parse(readEnv("BACKUPS_SCHEDULE"));
|
|
473
|
+
let stopping = false;
|
|
474
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.once(signal, () => {
|
|
475
|
+
stopping = true;
|
|
476
|
+
process.exit(0);
|
|
477
|
+
});
|
|
478
|
+
await report("info", "backups scheduled", { next: nextRun(schedule, /* @__PURE__ */ new Date())?.toISOString() ?? null });
|
|
479
|
+
while (!stopping) {
|
|
480
|
+
const next = nextRun(schedule, /* @__PURE__ */ new Date());
|
|
481
|
+
if (!next) return;
|
|
482
|
+
while (Date.now() < next.getTime()) await new Promise((r) => setTimeout(r, Math.min(next.getTime() - Date.now(), 36e5)));
|
|
483
|
+
await backupOnce(next).catch((error) => report("error", "backup failed", {
|
|
484
|
+
ok: false,
|
|
485
|
+
error: error instanceof Error ? error.message : String(error)
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/** What `node backup.mjs <command>` runs; resolves with its exit code. */
|
|
490
|
+
async function main(command) {
|
|
491
|
+
if (command === "serve") {
|
|
492
|
+
await serve();
|
|
493
|
+
return 0;
|
|
494
|
+
}
|
|
495
|
+
if (command === "run") return await backupOnce() ? 0 : 1;
|
|
496
|
+
if (command === "health") return healthy() ? 0 : 1;
|
|
497
|
+
await report("error", "usage: backup.mjs serve | run | health", { command: command ?? null });
|
|
498
|
+
return 2;
|
|
499
|
+
}
|
|
500
|
+
if (process.argv[1] && /backup\.mjs$|runner\.(?:ts|mjs)$/.test(process.argv[1])) main(process.argv[2]).then((code) => process.exit(code), async (error) => {
|
|
501
|
+
await report("error", "backup failed", {
|
|
502
|
+
ok: false,
|
|
503
|
+
error: error instanceof Error ? error.message : String(error)
|
|
504
|
+
});
|
|
505
|
+
process.exit(1);
|
|
506
|
+
});
|
|
507
|
+
//#endregion
|
|
508
|
+
exports.backupOnce = backupOnce;
|
|
509
|
+
exports.destination = destination;
|
|
510
|
+
exports.healthy = healthy;
|
|
511
|
+
exports.main = main;
|
|
512
|
+
exports.nextRun = nextRun;
|
|
513
|
+
exports.onReport = onReport;
|
|
514
|
+
exports.runFolder = runFolder;
|
|
515
|
+
exports.s3Send = s3Send;
|
|
516
|
+
exports.serve = serve;
|
|
517
|
+
exports.upload = upload;
|
|
518
|
+
|
|
519
|
+
//# sourceMappingURL=runner.cjs.map
|