@stacksjs/ts-cloud 0.7.55 → 0.7.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alerts/alerts.test.d.ts +1 -0
- package/dist/alerts/delivery.d.ts +45 -0
- package/dist/alerts/evaluator.d.ts +12 -0
- package/dist/alerts/health.d.ts +34 -0
- package/dist/alerts/index.d.ts +6 -0
- package/dist/alerts/model.d.ts +170 -0
- package/dist/alerts/store.d.ts +90 -0
- package/dist/alerts/telemetry.d.ts +3 -0
- package/dist/api/client.d.ts +197 -0
- package/dist/api/handler.d.ts +18 -0
- package/dist/api/handler.test.d.ts +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/openapi.d.ts +2 -0
- package/dist/api/service.d.ts +28 -0
- package/dist/api/types.d.ts +119 -0
- package/dist/auth/encryption.d.ts +3 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/mailer.d.ts +9 -0
- package/dist/auth/oidc.d.ts +36 -0
- package/dist/auth/oidc.test.d.ts +1 -0
- package/dist/auth/store.d.ts +121 -0
- package/dist/auth/store.test.d.ts +1 -0
- package/dist/auth/totp.d.ts +11 -0
- package/dist/auth/totp.test.d.ts +1 -0
- package/dist/auth/types.d.ts +138 -0
- package/dist/automation/index.d.ts +2 -0
- package/dist/automation/store.d.ts +46 -0
- package/dist/automation/store.test.d.ts +1 -0
- package/dist/automation/types.d.ts +66 -0
- package/dist/aws/acm.d.ts +0 -4
- package/dist/aws/application-autoscaling.d.ts +0 -5
- package/dist/aws/backup.d.ts +42 -0
- package/dist/aws/bedrock.d.ts +0 -5
- package/dist/aws/client.d.ts +3 -6
- package/dist/aws/cloudformation.d.ts +0 -4
- package/dist/aws/cloudfront-media.test.d.ts +1 -0
- package/dist/aws/cloudfront-origin.test.d.ts +1 -0
- package/dist/aws/cloudfront.d.ts +87 -3
- package/dist/aws/cloudwatch-logs.d.ts +2 -4
- package/dist/aws/cloudwatch.d.ts +1 -1
- package/dist/aws/comprehend.d.ts +0 -5
- package/dist/aws/connect.d.ts +0 -4
- package/dist/aws/cost-explorer-cache.d.ts +1 -15
- package/dist/aws/cost-explorer.d.ts +18 -9
- package/dist/aws/cost-explorer.test.d.ts +1 -0
- package/dist/aws/dynamodb.d.ts +0 -4
- package/dist/aws/ec2.d.ts +0 -4
- package/dist/aws/ecr.d.ts +0 -4
- package/dist/aws/ecs.d.ts +17 -7
- package/dist/aws/efs.d.ts +0 -4
- package/dist/aws/elasticache.d.ts +0 -4
- package/dist/aws/elbv2.d.ts +0 -7
- package/dist/aws/email.d.ts +0 -10
- package/dist/aws/eventbridge.d.ts +0 -4
- package/dist/aws/iam.d.ts +1 -5
- package/dist/aws/imap-server.d.ts +0 -7
- package/dist/aws/index.d.ts +4 -3
- package/dist/aws/index.js +31 -31
- package/dist/aws/kendra.d.ts +0 -5
- package/dist/aws/lambda.d.ts +3 -5
- package/dist/aws/opensearch.d.ts +0 -4
- package/dist/aws/personalize.d.ts +0 -5
- package/dist/aws/polly.d.ts +0 -5
- package/dist/aws/rds.d.ts +59 -4
- package/dist/aws/rekognition.d.ts +0 -5
- package/dist/aws/route53-domains.d.ts +0 -4
- package/dist/aws/route53.d.ts +0 -4
- package/dist/aws/s3.d.ts +10 -6
- package/dist/aws/scheduler.d.ts +0 -4
- package/dist/aws/secrets-manager.d.ts +0 -4
- package/dist/aws/setup-sms.d.ts +0 -14
- package/dist/aws/sms.d.ts +0 -10
- package/dist/aws/smtp-server.d.ts +0 -11
- package/dist/aws/sns.d.ts +0 -4
- package/dist/aws/sqs.d.ts +0 -4
- package/dist/aws/ssm.d.ts +0 -4
- package/dist/aws/sts.d.ts +0 -4
- package/dist/aws/support.d.ts +0 -4
- package/dist/aws/textract.d.ts +0 -5
- package/dist/aws/transcribe.d.ts +0 -4
- package/dist/aws/translate.d.ts +0 -5
- package/dist/aws/voice.d.ts +0 -11
- package/dist/aws/xray.d.ts +1 -0
- package/dist/backups/aws-database-source.d.ts +19 -0
- package/dist/backups/aws-database-source.test.d.ts +1 -0
- package/dist/backups/aws-infrastructure-source.d.ts +43 -0
- package/dist/backups/aws-infrastructure-source.test.d.ts +1 -0
- package/dist/backups/control-plane-source.d.ts +16 -0
- package/dist/backups/control-plane-source.test.d.ts +1 -0
- package/dist/backups/docker-volume-source.d.ts +31 -0
- package/dist/backups/docker-volume-source.test.d.ts +1 -0
- package/dist/backups/filesystem-source.d.ts +34 -0
- package/dist/backups/filesystem-source.test.d.ts +1 -0
- package/dist/backups/index.d.ts +10 -0
- package/dist/backups/logical-database-source.d.ts +16 -0
- package/dist/backups/logical-database-source.test.d.ts +1 -0
- package/dist/backups/model.d.ts +121 -0
- package/dist/backups/s3-destination.d.ts +85 -0
- package/dist/backups/s3-destination.test.d.ts +1 -0
- package/dist/backups/service.d.ts +80 -0
- package/dist/backups/service.test.d.ts +1 -0
- package/dist/backups/store.d.ts +30 -0
- package/dist/backups/store.test.d.ts +1 -0
- package/dist/bin/cli.js +3360 -1802
- package/dist/{chunk-tjjgajbh.js → chunk-01d86gt1.js} +301 -9
- package/dist/{chunk-73vcqg6z.js → chunk-0z43x8re.js} +7 -7
- package/dist/{chunk-tnztxpcb.js → chunk-32e7ya18.js} +61 -49
- package/dist/{chunk-qpj3edwz.js → chunk-4cjrg98a.js} +3 -7
- package/dist/{chunk-hsk6fe6x.js → chunk-50jpda9q.js} +2 -2
- package/dist/{chunk-93hjhs78.js → chunk-703nkybg.js} +1 -1
- package/dist/{chunk-d5vqwj8s.js → chunk-9m6b4m74.js} +755 -720
- package/dist/chunk-e05phx5f.js +10 -0
- package/dist/{chunk-f29w04w5.js → chunk-exbcsab2.js} +10 -4
- package/dist/{chunk-eq08r166.js → chunk-f55nkpvb.js} +2 -2
- package/dist/{chunk-3bfv1z4v.js → chunk-fv1bj3yt.js} +63 -56
- package/dist/chunk-fw3q88wk.js +48641 -0
- package/dist/{chunk-3knnr7wh.js → chunk-he9a874b.js} +170 -2
- package/dist/{chunk-zn0nxxa8.js → chunk-hpv68b4a.js} +1070 -1070
- package/dist/chunk-k114qwg9.js +12 -0
- package/dist/{chunk-r16e02bc.js → chunk-vdk53yyq.js} +3900 -4101
- package/dist/{chunk-jp2a8yad.js → chunk-vvkd46k1.js} +15 -13
- package/dist/{chunk-vd87cpvn.js → chunk-wj3s95p9.js} +62 -46
- package/dist/{chunk-mj1tmhte.js → chunk-x9s0cqfx.js} +1 -1
- package/dist/{chunk-3rsfns7x.js → chunk-ybcz6sxc.js} +2048 -387
- package/dist/{chunk-arsh1g5h.js → chunk-zqtpg06c.js} +14 -11
- package/dist/compose/index.d.ts +6 -0
- package/dist/compose/parser.d.ts +13 -0
- package/dist/compose/parser.test.d.ts +1 -0
- package/dist/compose/runtime.d.ts +10 -0
- package/dist/compose/service.d.ts +18 -0
- package/dist/compose/store.d.ts +59 -0
- package/dist/compose/store.test.d.ts +1 -0
- package/dist/compose/templates.d.ts +17 -0
- package/dist/compose/types.d.ts +164 -0
- package/dist/configuration/backends.d.ts +66 -0
- package/dist/configuration/backends.test.d.ts +1 -0
- package/dist/configuration/dotenv.d.ts +14 -0
- package/dist/configuration/dotenv.test.d.ts +1 -0
- package/dist/configuration/index.d.ts +6 -0
- package/dist/configuration/migrate.d.ts +10 -0
- package/dist/configuration/migrate.test.d.ts +1 -0
- package/dist/configuration/model.d.ts +50 -0
- package/dist/configuration/service.d.ts +144 -0
- package/dist/configuration/service.test.d.ts +1 -0
- package/dist/configuration/store.d.ts +33 -0
- package/dist/configuration/store.test.d.ts +1 -0
- package/dist/control-plane/authorization.d.ts +24 -0
- package/dist/control-plane/authorization.test.d.ts +1 -0
- package/dist/control-plane/index.d.ts +8 -0
- package/dist/control-plane/migrations.d.ts +7 -0
- package/dist/control-plane/search.d.ts +25 -0
- package/dist/control-plane/store.d.ts +117 -0
- package/dist/control-plane/store.test.d.ts +1 -0
- package/dist/control-plane/types.d.ts +381 -0
- package/dist/cost/reporting.d.ts +24 -0
- package/dist/cost/reporting.test.d.ts +1 -0
- package/dist/cost/resource-inventory.d.ts +32 -0
- package/dist/cost/resource-inventory.test.d.ts +1 -0
- package/dist/cost/resource-optimization.d.ts +59 -0
- package/dist/cost/resource-optimization.test.d.ts +1 -0
- package/dist/data-services/adapters.d.ts +39 -0
- package/dist/data-services/aws-transports.d.ts +29 -0
- package/dist/data-services/aws-transports.test.d.ts +1 -0
- package/dist/data-services/capabilities.d.ts +2 -0
- package/dist/data-services/data-services.test.d.ts +1 -0
- package/dist/data-services/docker-transport.d.ts +55 -0
- package/dist/data-services/docker-transport.test.d.ts +1 -0
- package/dist/data-services/index.d.ts +8 -0
- package/dist/data-services/model.d.ts +79 -0
- package/dist/data-services/secrets.d.ts +13 -0
- package/dist/data-services/secrets.test.d.ts +1 -0
- package/dist/data-services/service.d.ts +51 -0
- package/dist/data-services/store.d.ts +17 -0
- package/dist/deploy/container-image.d.ts +28 -0
- package/dist/deploy/container-image.test.d.ts +1 -0
- package/dist/deploy/dashboard-audit.test.d.ts +1 -0
- package/dist/deploy/dashboard-auth.d.ts +4 -23
- package/dist/deploy/dashboard-backups.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-configuration.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-control-plane.d.ts +37 -0
- package/dist/deploy/dashboard-control-plane.test.d.ts +1 -0
- package/dist/deploy/dashboard-data-services.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-environment-scope.test.d.ts +1 -0
- package/dist/deploy/dashboard-guard.d.ts +12 -0
- package/dist/deploy/dashboard-guard.test.d.ts +1 -0
- package/dist/deploy/dashboard-identities.d.ts +15 -0
- package/dist/deploy/dashboard-identities.test.d.ts +1 -0
- package/dist/deploy/dashboard-jobs.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-login-page.d.ts +5 -5
- package/dist/deploy/dashboard-login-page.test.d.ts +1 -0
- package/dist/deploy/dashboard-oidc.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-onboarding.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-policy.d.ts +4 -2
- package/dist/deploy/dashboard-queue-worker.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-release.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-route-manifest.d.ts +14 -0
- package/dist/deploy/dashboard-route-manifest.test.d.ts +1 -0
- package/dist/deploy/dashboard-security.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-session.d.ts +3 -14
- package/dist/deploy/dashboard-source.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-users.d.ts +3 -0
- package/dist/deploy/fullstack-container.d.ts +87 -0
- package/dist/deploy/fullstack-container.test.d.ts +1 -0
- package/dist/deploy/index.d.ts +7 -2
- package/dist/deploy/index.js +65 -25
- package/dist/deploy/local-dashboard-server.d.ts +30 -0
- package/dist/deploy/quick-deploy.d.ts +1 -1
- package/dist/deploy/serverless-operations.d.ts +1 -0
- package/dist/deploy/site-target.d.ts +16 -0
- package/dist/deploy/static-api-origin.d.ts +107 -0
- package/dist/deploy/static-api-origin.test.d.ts +1 -0
- package/dist/deploy/static-site-helper.d.ts +0 -16
- package/dist/deploy/telemetry-access.d.ts +5 -0
- package/dist/deploy/telemetry-access.test.d.ts +1 -0
- package/dist/deploy/telemetry-collection.d.ts +44 -0
- package/dist/deploy/telemetry-collection.test.d.ts +1 -0
- package/dist/dns/index.js +3 -3
- package/dist/dns/route53-adapter.d.ts +1 -1
- package/dist/drivers/hetzner/cloud-init.d.ts +1 -1
- package/dist/drivers/index.d.ts +2 -2
- package/dist/drivers/index.js +6 -6
- package/dist/drivers/shared/notifications.d.ts +1 -1
- package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
- package/dist/fleet/fleet.test.d.ts +1 -0
- package/dist/fleet/index.d.ts +144 -0
- package/dist/fleet/queue.d.ts +3 -0
- package/dist/fleet/ssh-driver.d.ts +34 -0
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1132 -424
- package/dist/jobs/adapters.d.ts +23 -0
- package/dist/jobs/index.d.ts +7 -0
- package/dist/jobs/jobs.test.d.ts +1 -0
- package/dist/jobs/model.d.ts +100 -0
- package/dist/jobs/provider-adapters.d.ts +49 -0
- package/dist/jobs/reconcile.d.ts +19 -0
- package/dist/jobs/schedule.d.ts +9 -0
- package/dist/jobs/service.d.ts +30 -0
- package/dist/jobs/store.d.ts +48 -0
- package/dist/maintenance/drivers.d.ts +117 -0
- package/dist/maintenance/index.d.ts +6 -0
- package/dist/maintenance/maintenance.test.d.ts +1 -0
- package/dist/maintenance/manifest.d.ts +15 -0
- package/dist/maintenance/queue.d.ts +9 -0
- package/dist/maintenance/service.d.ts +31 -0
- package/dist/maintenance/store.d.ts +84 -0
- package/dist/maintenance/types.d.ts +192 -0
- package/dist/media.d.ts +62 -0
- package/dist/media.test.d.ts +1 -0
- package/dist/object-storage/index.d.ts +0 -24
- package/dist/onboarding/archive.d.ts +12 -0
- package/dist/onboarding/artifact-store.d.ts +22 -0
- package/dist/onboarding/detection.d.ts +8 -0
- package/dist/onboarding/index.d.ts +9 -0
- package/dist/onboarding/migrations.d.ts +3 -0
- package/dist/onboarding/onboarding.test.d.ts +1 -0
- package/dist/onboarding/plan.d.ts +3 -0
- package/dist/onboarding/registry.d.ts +41 -0
- package/dist/onboarding/service.d.ts +16 -0
- package/dist/onboarding/store.d.ts +30 -0
- package/dist/onboarding/types.d.ts +204 -0
- package/dist/placement/builds.d.ts +20 -0
- package/dist/placement/drivers.d.ts +54 -0
- package/dist/placement/index.d.ts +6 -0
- package/dist/placement/placement.test.d.ts +1 -0
- package/dist/placement/queue.d.ts +4 -0
- package/dist/placement/service.d.ts +34 -0
- package/dist/placement/store.d.ts +49 -0
- package/dist/placement/types.d.ts +109 -0
- package/dist/preview/index.d.ts +3 -0
- package/dist/preview/service.d.ts +45 -0
- package/dist/preview/service.test.d.ts +1 -0
- package/dist/preview/store.d.ts +47 -0
- package/dist/preview/store.test.d.ts +1 -0
- package/dist/preview/types.d.ts +99 -0
- package/dist/push/apns.d.ts +0 -26
- package/dist/push/fcm.d.ts +0 -23
- package/dist/push/index.d.ts +2 -2
- package/dist/push/index.js +1 -1
- package/dist/queue/deployment-handlers.d.ts +27 -0
- package/dist/queue/deployment-handlers.test.d.ts +1 -0
- package/dist/queue/index.d.ts +4 -0
- package/dist/queue/queue.d.ts +57 -0
- package/dist/queue/queue.test.d.ts +1 -0
- package/dist/queue/types.d.ts +93 -0
- package/dist/queue/worker.d.ts +32 -0
- package/dist/queue/worker.test.d.ts +1 -0
- package/dist/regions/drivers.d.ts +139 -0
- package/dist/regions/index.d.ts +5 -0
- package/dist/regions/queue.d.ts +4 -0
- package/dist/regions/regions.test.d.ts +1 -0
- package/dist/regions/service.d.ts +23 -0
- package/dist/regions/store.d.ts +41 -0
- package/dist/regions/types.d.ts +132 -0
- package/dist/release/index.d.ts +6 -0
- package/dist/release/queue.d.ts +15 -0
- package/dist/release/queue.test.d.ts +1 -0
- package/dist/release/runtime.d.ts +35 -0
- package/dist/release/runtime.test.d.ts +1 -0
- package/dist/release/service.d.ts +28 -0
- package/dist/release/store.d.ts +88 -0
- package/dist/release/store.test.d.ts +1 -0
- package/dist/release/strategy.d.ts +8 -0
- package/dist/release/types.d.ts +103 -0
- package/dist/runtime/adapters/aws.d.ts +5 -0
- package/dist/runtime/adapters/docker.d.ts +53 -0
- package/dist/runtime/adapters/systemd.d.ts +26 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/inventory.d.ts +6 -0
- package/dist/runtime/model.d.ts +130 -0
- package/dist/runtime/normalize.d.ts +5 -0
- package/dist/runtime/operations.d.ts +113 -0
- package/dist/runtime/service.d.ts +32 -0
- package/dist/runtime/streams.d.ts +49 -0
- package/dist/security/artifacts.d.ts +105 -0
- package/dist/security/artifacts.test.d.ts +1 -0
- package/dist/security/container-release.d.ts +35 -0
- package/dist/security/container-release.test.d.ts +1 -0
- package/dist/security/index.d.ts +8 -0
- package/dist/security/integration.d.ts +41 -0
- package/dist/security/integration.test.d.ts +1 -0
- package/dist/security/posture-store.d.ts +80 -0
- package/dist/security/posture-store.test.d.ts +1 -0
- package/dist/security/pre-deploy-scanner.d.ts +0 -4
- package/dist/security/scanners.d.ts +81 -0
- package/dist/security/scanners.test.d.ts +1 -0
- package/dist/security/types.d.ts +150 -0
- package/dist/source/git-workspace.d.ts +26 -0
- package/dist/source/git-workspace.test.d.ts +1 -0
- package/dist/source/index.d.ts +6 -0
- package/dist/source/providers.d.ts +199 -0
- package/dist/source/providers.test.d.ts +1 -0
- package/dist/source/service.d.ts +18 -0
- package/dist/source/store.d.ts +149 -0
- package/dist/source/store.test.d.ts +1 -0
- package/dist/source/types.d.ts +191 -0
- package/dist/source/webhooks.d.ts +33 -0
- package/dist/source/webhooks.test.d.ts +1 -0
- package/dist/ssl/acme-client.d.ts +0 -6
- package/dist/storage/compose.d.ts +4 -0
- package/dist/storage/compose.test.d.ts +1 -0
- package/dist/storage/drivers.d.ts +107 -0
- package/dist/storage/drivers.test.d.ts +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/model.d.ts +112 -0
- package/dist/storage/service.d.ts +95 -0
- package/dist/storage/storage.test.d.ts +1 -0
- package/dist/storage/store.d.ts +40 -0
- package/dist/telemetry/correlation.test.d.ts +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/model.d.ts +145 -0
- package/dist/telemetry/policy.d.ts +8 -0
- package/dist/telemetry/policy.test.d.ts +1 -0
- package/dist/telemetry/redaction.d.ts +9 -0
- package/dist/telemetry/store.d.ts +34 -0
- package/dist/telemetry/store.test.d.ts +1 -0
- package/dist/ui/404.html +1506 -17
- package/dist/ui/accept-invitation.html +1163 -0
- package/dist/ui/{server/backups.html → access-denied.html} +344 -55
- package/dist/ui/account/automation.html +1669 -0
- package/dist/ui/account/security.html +1646 -0
- package/dist/ui/applications/compose.html +1590 -0
- package/dist/ui/applications/new.html +1868 -0
- package/dist/ui/data/backups.html +1757 -0
- package/dist/ui/data/services.html +1723 -0
- package/dist/ui/data/volumes.html +1639 -0
- package/dist/ui/index.html +392 -29
- package/dist/ui/integrations.html +1865 -0
- package/dist/ui/operations/alerts.html +1610 -0
- package/dist/ui/operations/configuration.html +1746 -0
- package/dist/ui/operations/jobs.html +1709 -0
- package/dist/ui/operations/maintenance.html +1540 -0
- package/dist/ui/operations/observability.html +1742 -0
- package/dist/ui/{serverless/scheduler.html → operations/previews.html} +447 -46
- package/dist/ui/operations/queue.html +1615 -0
- package/dist/ui/{server/workers.html → operations/regions.html} +413 -49
- package/dist/ui/operations/releases.html +1596 -0
- package/dist/ui/operations/workloads.html +1775 -0
- package/dist/ui/security.html +1767 -0
- package/dist/ui/server/actions.html +446 -57
- package/dist/ui/server/activity.html +346 -1
- package/dist/ui/server/capacity.html +1542 -0
- package/dist/ui/server/database.html +443 -36
- package/dist/ui/server/deployments.html +439 -53
- package/dist/ui/server/diagnostics.html +346 -1
- package/dist/ui/server/firewall.html +476 -50
- package/dist/ui/server/fleet.html +1577 -0
- package/dist/ui/server/logs.html +389 -25
- package/dist/ui/server/metrics.html +346 -1
- package/dist/ui/server/security.html +346 -1
- package/dist/ui/server/services.html +421 -46
- package/dist/ui/server/sites.html +476 -95
- package/dist/ui/server/ssh-keys.html +476 -46
- package/dist/ui/server/team.html +700 -77
- package/dist/ui/server/terminal.html +444 -54
- package/dist/ui/serverless/alarms.html +430 -42
- package/dist/ui/serverless/assets.html +421 -46
- package/dist/ui/serverless/cost.html +346 -1
- package/dist/ui/serverless/data.html +422 -47
- package/dist/ui/serverless/deployments.html +421 -46
- package/dist/ui/serverless/firewall.html +346 -1
- package/dist/ui/serverless/functions.html +423 -48
- package/dist/ui/serverless/logs.html +389 -25
- package/dist/ui/serverless/metrics.html +346 -1
- package/dist/ui/serverless/queues.html +448 -62
- package/dist/ui/serverless/secrets.html +448 -42
- package/dist/ui/serverless/traces.html +350 -5
- package/dist/ui/serverless.html +434 -58
- package/dist/ui-src/pages/404.stx +18 -0
- package/dist/ui-src/pages/accept-invitation.stx +50 -0
- package/dist/ui-src/pages/access-denied.stx +18 -0
- package/dist/ui-src/pages/account/automation.stx +187 -0
- package/dist/ui-src/pages/account/security.stx +249 -0
- package/dist/ui-src/pages/applications/compose.stx +38 -0
- package/dist/ui-src/pages/applications/new.stx +76 -0
- package/dist/ui-src/pages/data/backups.stx +214 -0
- package/dist/ui-src/pages/data/services.stx +257 -0
- package/dist/ui-src/pages/data/volumes.stx +40 -0
- package/dist/ui-src/pages/index.stx +24 -10
- package/dist/ui-src/pages/integrations.stx +186 -0
- package/dist/ui-src/pages/operations/alerts.stx +40 -0
- package/dist/ui-src/pages/operations/configuration.stx +164 -0
- package/dist/ui-src/pages/operations/jobs.stx +238 -0
- package/dist/ui-src/pages/operations/maintenance.stx +15 -0
- package/dist/ui-src/pages/operations/observability.stx +76 -0
- package/dist/ui-src/pages/operations/previews.stx +62 -0
- package/dist/ui-src/pages/operations/queue.stx +158 -0
- package/dist/ui-src/pages/operations/regions.stx +12 -0
- package/dist/ui-src/pages/operations/releases.stx +39 -0
- package/dist/ui-src/pages/operations/workloads.stx +151 -0
- package/dist/ui-src/pages/partials/head.stx +148 -15
- package/dist/ui-src/pages/partials/nav.stx +491 -74
- package/dist/ui-src/pages/partials/op-confirm.stx +6 -6
- package/dist/ui-src/pages/security.stx +264 -0
- package/dist/ui-src/pages/server/actions.stx +17 -26
- package/dist/ui-src/pages/server/capacity.stx +16 -0
- package/dist/ui-src/pages/server/database.stx +31 -16
- package/dist/ui-src/pages/server/deployments.stx +28 -30
- package/dist/ui-src/pages/server/firewall.stx +25 -30
- package/dist/ui-src/pages/server/fleet.stx +10 -0
- package/dist/ui-src/pages/server/logs.stx +11 -9
- package/dist/ui-src/pages/server/metrics.stx +8 -16
- package/dist/ui-src/pages/server/services.stx +19 -28
- package/dist/ui-src/pages/server/sites.stx +75 -72
- package/dist/ui-src/pages/server/ssh-keys.stx +24 -30
- package/dist/ui-src/pages/server/team.stx +286 -149
- package/dist/ui-src/pages/server/terminal.stx +6 -28
- package/dist/ui-src/pages/serverless/alarms.stx +17 -31
- package/dist/ui-src/pages/serverless/assets.stx +13 -22
- package/dist/ui-src/pages/serverless/cost.stx +15 -3
- package/dist/ui-src/pages/serverless/data.stx +12 -25
- package/dist/ui-src/pages/serverless/deployments.stx +14 -23
- package/dist/ui-src/pages/serverless/firewall.stx +5 -1
- package/dist/ui-src/pages/serverless/functions.stx +20 -26
- package/dist/ui-src/pages/serverless/logs.stx +11 -10
- package/dist/ui-src/pages/serverless/metrics.stx +18 -17
- package/dist/ui-src/pages/serverless/queues.stx +34 -42
- package/dist/ui-src/pages/serverless/secrets.stx +31 -34
- package/dist/ui-src/pages/serverless/traces.stx +2 -6
- package/dist/ui-src/pages/serverless.stx +40 -74
- package/package.json +11 -11
- package/dist/chunk-b17vf1g1.js +0 -13787
- package/dist/chunk-b82pbxyp.js +0 -1572
- package/dist/chunk-pqfzdg68.js +0 -12
- package/dist/chunk-z58fqnjm.js +0 -10
- package/dist/ui-src/pages/server/backups.stx +0 -91
- package/dist/ui-src/pages/server/workers.stx +0 -99
- package/dist/ui-src/pages/serverless/scheduler.stx +0 -89
|
@@ -6,17 +6,17 @@ import {
|
|
|
6
6
|
resolveCloudProvider,
|
|
7
7
|
resolveManagementDashboardSites,
|
|
8
8
|
resolveProjectStackName
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-vdk53yyq.js";
|
|
10
10
|
import {
|
|
11
11
|
EC2Client,
|
|
12
12
|
SSMClient
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-703nkybg.js";
|
|
14
14
|
import {
|
|
15
15
|
CloudFormationClient
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-4cjrg98a.js";
|
|
17
17
|
import {
|
|
18
18
|
S3Client
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-wj3s95p9.js";
|
|
20
20
|
import {
|
|
21
21
|
__require
|
|
22
22
|
} from "./chunk-v0bahtg2.js";
|
|
@@ -124,10 +124,7 @@ function buildServicesProvisionScript(services = {}, _options = {}) {
|
|
|
124
124
|
const plan = planServices(services);
|
|
125
125
|
if (plan.packages.length === 0)
|
|
126
126
|
return [];
|
|
127
|
-
return [
|
|
128
|
-
...buildPantryInstallScript(plan.packages),
|
|
129
|
-
...buildPantryServiceScript(plan.services)
|
|
130
|
-
];
|
|
127
|
+
return [...buildPantryInstallScript(plan.packages), ...buildPantryServiceScript(plan.services)];
|
|
131
128
|
}
|
|
132
129
|
function buildDatabaseSetupScript(database, services = {}) {
|
|
133
130
|
if (!database?.name)
|
|
@@ -226,6 +223,233 @@ function buildManagedDbEnv(database) {
|
|
|
226
223
|
return env;
|
|
227
224
|
}
|
|
228
225
|
|
|
226
|
+
// src/drivers/shared/backups.ts
|
|
227
|
+
var BACKUP_OUTPUT_DIR = "/var/backups/ts-cloud";
|
|
228
|
+
var BACKUP_CONFIG_PATH = "/etc/ts-cloud/backups.config.ts";
|
|
229
|
+
var BACKUP_CRON_PATH = "/etc/cron.d/ts-cloud-backups";
|
|
230
|
+
var BACKUP_RUNNER_PATH = "/usr/local/bin/ts-cloud-backup.sh";
|
|
231
|
+
function backupEntryFor(database) {
|
|
232
|
+
if (!database.name)
|
|
233
|
+
return null;
|
|
234
|
+
const host = database.host || "127.0.0.1";
|
|
235
|
+
const isPostgres = database.engine === "postgres";
|
|
236
|
+
const type = isPostgres ? "postgresql" : "mysql";
|
|
237
|
+
const port = database.port ?? (isPostgres ? 5432 : 3306);
|
|
238
|
+
const ts = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
239
|
+
return [
|
|
240
|
+
" {",
|
|
241
|
+
` type: '${type}',`,
|
|
242
|
+
` name: '${ts(database.name)}',`,
|
|
243
|
+
" connection: {",
|
|
244
|
+
` hostname: '${ts(host)}',`,
|
|
245
|
+
` port: ${port},`,
|
|
246
|
+
` database: '${ts(database.name)}',`,
|
|
247
|
+
` username: '${ts(database.username || database.name)}',`,
|
|
248
|
+
` password: '${ts(database.password || "")}',`,
|
|
249
|
+
" ssl: false,",
|
|
250
|
+
" },",
|
|
251
|
+
" includeSchema: true,",
|
|
252
|
+
" includeData: true,",
|
|
253
|
+
" },"
|
|
254
|
+
].join(`
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
function buildBackupsConfigTs(database, backups) {
|
|
258
|
+
const entry = database ? backupEntryFor(database) : null;
|
|
259
|
+
const destinations = backups.bucket ? [
|
|
260
|
+
" destinations: [",
|
|
261
|
+
" {",
|
|
262
|
+
" type: 's3',",
|
|
263
|
+
` bucket: '${backups.bucket}',`,
|
|
264
|
+
" prefix: 'db-backups',",
|
|
265
|
+
...backups.endpoint ? [` endpoint: '${backups.endpoint}',`] : [],
|
|
266
|
+
" optional: false,",
|
|
267
|
+
" },",
|
|
268
|
+
" ],"
|
|
269
|
+
] : [];
|
|
270
|
+
return [
|
|
271
|
+
"import type { BackupConfig } from 'ts-backups'",
|
|
272
|
+
"",
|
|
273
|
+
"const config: BackupConfig = {",
|
|
274
|
+
" verbose: true,",
|
|
275
|
+
` outputPath: '${BACKUP_OUTPUT_DIR}',`,
|
|
276
|
+
" retention: {",
|
|
277
|
+
` count: ${backups.retentionCount ?? 5},`,
|
|
278
|
+
` maxAge: ${backups.retentionDays ?? 30},`,
|
|
279
|
+
" },",
|
|
280
|
+
" databases: [",
|
|
281
|
+
...entry ? [entry] : [],
|
|
282
|
+
" ],",
|
|
283
|
+
...destinations,
|
|
284
|
+
"}",
|
|
285
|
+
"",
|
|
286
|
+
"export default config",
|
|
287
|
+
""
|
|
288
|
+
].join(`
|
|
289
|
+
`);
|
|
290
|
+
}
|
|
291
|
+
function buildBackupProvisionScript(options) {
|
|
292
|
+
const { database, backups } = options;
|
|
293
|
+
if (!backups.enabled)
|
|
294
|
+
return [];
|
|
295
|
+
const schedule = backups.schedule || "0 2 * * *";
|
|
296
|
+
const configTs = buildBackupsConfigTs(database, backups);
|
|
297
|
+
return [
|
|
298
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
299
|
+
`mkdir -p /etc/ts-cloud ${BACKUP_OUTPUT_DIR}`,
|
|
300
|
+
"command -v bun >/dev/null 2>&1 || (curl -fsSL https://bun.sh/install | bash && ln -sf /root/.bun/bin/bun /usr/local/bin/bun)",
|
|
301
|
+
"bun add -g ts-backups || true",
|
|
302
|
+
`cat > ${BACKUP_CONFIG_PATH} <<'TS_CLOUD_BACKUP_CFG_EOF'`,
|
|
303
|
+
configTs.replace(/\n$/, ""),
|
|
304
|
+
"TS_CLOUD_BACKUP_CFG_EOF",
|
|
305
|
+
`cat > ${BACKUP_RUNNER_PATH} <<'TS_CLOUD_BACKUP_RUN_EOF'`,
|
|
306
|
+
"#!/bin/bash",
|
|
307
|
+
"set -uo pipefail",
|
|
308
|
+
'export PATH="/root/.bun/bin:/usr/local/bin:$PATH"',
|
|
309
|
+
'notify() { [ -x /usr/local/bin/ts-cloud-notify ] && /usr/local/bin/ts-cloud-notify "$1" || true; }',
|
|
310
|
+
"cd /etc/ts-cloud",
|
|
311
|
+
'if ! ts-backups backup --config /etc/ts-cloud/backups.config.ts; then notify "❌ ts-cloud backup failed"; exit 1; fi',
|
|
312
|
+
"TS_CLOUD_BACKUP_RUN_EOF",
|
|
313
|
+
`chmod +x ${BACKUP_RUNNER_PATH}`,
|
|
314
|
+
`cat > ${BACKUP_CRON_PATH} <<'TS_CLOUD_BACKUP_CRON_EOF'`,
|
|
315
|
+
`${schedule} root ${BACKUP_RUNNER_PATH} >> /var/log/ts-cloud-backup.log 2>&1`,
|
|
316
|
+
"TS_CLOUD_BACKUP_CRON_EOF",
|
|
317
|
+
`chmod 644 ${BACKUP_CRON_PATH}`
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
function engineSocket(engine) {
|
|
321
|
+
return engine === "mariadb" ? "/var/lib/pantry/mariadb/mariadbd.sock" : "/var/lib/pantry/mysql/mysqld.sock";
|
|
322
|
+
}
|
|
323
|
+
function buildBackupRestoreScript(database, options = {}) {
|
|
324
|
+
if (!database?.name)
|
|
325
|
+
return [];
|
|
326
|
+
const name = database.name;
|
|
327
|
+
const isPg = database.engine === "postgres";
|
|
328
|
+
const locate = options.from ? `TS_CLOUD_DUMP="${options.from}"` : `TS_CLOUD_DUMP="$(find ${BACKUP_OUTPUT_DIR} -type f -name '*${name}*.sql' -o -type f -name '*${name}*.sql.gz' 2>/dev/null | xargs -r ls -1t 2>/dev/null | head -1)"`;
|
|
329
|
+
const client = isPg ? `${pgAdminCommand(database)} -d "${name}"` : `mysql --socket=${engineSocket(database.engine)} -u root "${name}"`;
|
|
330
|
+
return [
|
|
331
|
+
"set -uo pipefail",
|
|
332
|
+
'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',
|
|
333
|
+
locate,
|
|
334
|
+
'[ -n "$TS_CLOUD_DUMP" ] && [ -f "$TS_CLOUD_DUMP" ] || { echo "no backup dump found to restore" >&2; exit 1; }',
|
|
335
|
+
'echo "[ts-cloud] restoring ' + name + ' from $TS_CLOUD_DUMP"',
|
|
336
|
+
`case "$TS_CLOUD_DUMP" in *.gz) gunzip -c "$TS_CLOUD_DUMP" ;; *) cat "$TS_CLOUD_DUMP" ;; esac | ${client}`,
|
|
337
|
+
'echo "restore complete"'
|
|
338
|
+
];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/drivers/shared/maintenance.ts
|
|
342
|
+
function buildAutoUpdatesScript(enabled2 = true) {
|
|
343
|
+
if (!enabled2)
|
|
344
|
+
return [];
|
|
345
|
+
return [
|
|
346
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
347
|
+
"apt-get install -y unattended-upgrades",
|
|
348
|
+
"cat > /etc/apt/apt.conf.d/20auto-upgrades <<'TS_CLOUD_AUTOUPD_EOF'",
|
|
349
|
+
'APT::Periodic::Update-Package-Lists "1";',
|
|
350
|
+
'APT::Periodic::Unattended-Upgrade "1";',
|
|
351
|
+
'APT::Periodic::Download-Upgradeable-Packages "1";',
|
|
352
|
+
'APT::Periodic::AutocleanInterval "7";',
|
|
353
|
+
"TS_CLOUD_AUTOUPD_EOF",
|
|
354
|
+
"systemctl enable unattended-upgrades 2>/dev/null || true",
|
|
355
|
+
"systemctl restart unattended-upgrades 2>/dev/null || true"
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/drivers/shared/monitoring.ts
|
|
360
|
+
var METRICS_PATH = "/var/lib/ts-cloud/metrics.json";
|
|
361
|
+
var ALERT_STATE_PATH = "/var/lib/ts-cloud/alert-state";
|
|
362
|
+
var DEFAULT_CPU_LOAD_PER_CORE = 2;
|
|
363
|
+
var DEFAULT_MEM_PERCENT = 90;
|
|
364
|
+
var DEFAULT_DISK_PERCENT = 90;
|
|
365
|
+
var SERVICE_PROBES = [
|
|
366
|
+
["nginx", 80],
|
|
367
|
+
["phpFpm", 9074],
|
|
368
|
+
["mysql", 3306],
|
|
369
|
+
["postgres", 5432],
|
|
370
|
+
["redis", 6379],
|
|
371
|
+
["meilisearch", 7700]
|
|
372
|
+
];
|
|
373
|
+
function resolveMonitoring(monitoring = true) {
|
|
374
|
+
const obj = typeof monitoring === "object" ? monitoring : {};
|
|
375
|
+
const enabled2 = typeof monitoring === "boolean" ? monitoring : monitoring.enabled !== false;
|
|
376
|
+
return {
|
|
377
|
+
enabled: enabled2,
|
|
378
|
+
cpuLoadPerCore: obj.alerts?.cpuLoadPerCore ?? DEFAULT_CPU_LOAD_PER_CORE,
|
|
379
|
+
memPercent: obj.alerts?.memPercent ?? DEFAULT_MEM_PERCENT,
|
|
380
|
+
diskPercent: obj.alerts?.diskPercent ?? DEFAULT_DISK_PERCENT
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function buildMonitoringScript(monitoring = true) {
|
|
384
|
+
const { enabled: enabled2, cpuLoadPerCore, memPercent, diskPercent } = resolveMonitoring(monitoring);
|
|
385
|
+
if (!enabled2)
|
|
386
|
+
return [];
|
|
387
|
+
const probeLines = SERVICE_PROBES.map(([name, port]) => `SVC_${name.toUpperCase()}=$(probe ${port})`);
|
|
388
|
+
const servicesJson = SERVICE_PROBES.map(([name]) => `"${name}":"$SVC_${name.toUpperCase()}"`).join(",");
|
|
389
|
+
return [
|
|
390
|
+
"mkdir -p /var/lib/ts-cloud",
|
|
391
|
+
"cat > /usr/local/bin/ts-cloud-metrics.sh <<'TS_CLOUD_METRICS_EOF'",
|
|
392
|
+
"#!/bin/bash",
|
|
393
|
+
"set -uo pipefail",
|
|
394
|
+
"LOAD=$(cut -d' ' -f1 /proc/loadavg)",
|
|
395
|
+
"CPUS=$(nproc)",
|
|
396
|
+
"MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')",
|
|
397
|
+
"MEM_USED=$(free -m | awk '/^Mem:/{print $3}')",
|
|
398
|
+
"SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}')",
|
|
399
|
+
"SWAP_USED=$(free -m | awk '/^Swap:/{print $3}')",
|
|
400
|
+
`DISK_PCT=$(df -P / | awk 'NR==2{gsub("%","",$5); print $5}')`,
|
|
401
|
+
"UPTIME_SEC=$(cut -d' ' -f1 /proc/uptime | cut -d. -f1)",
|
|
402
|
+
`RX_BYTES=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{rx+=$3} END{print rx+0}' /proc/net/dev)`,
|
|
403
|
+
`TX_BYTES=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{tx+=$11} END{print tx+0}' /proc/net/dev)`,
|
|
404
|
+
"probe(){ (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null && echo up || echo down; }",
|
|
405
|
+
...probeLines,
|
|
406
|
+
"LOAD=${LOAD:-0}; CPUS=${CPUS:-1}; MEM_TOTAL=${MEM_TOTAL:-0}; MEM_USED=${MEM_USED:-0}",
|
|
407
|
+
"SWAP_TOTAL=${SWAP_TOTAL:-0}; SWAP_USED=${SWAP_USED:-0}; DISK_PCT=${DISK_PCT:-0}",
|
|
408
|
+
"UPTIME_SEC=${UPTIME_SEC:-0}; RX_BYTES=${RX_BYTES:-0}; TX_BYTES=${TX_BYTES:-0}",
|
|
409
|
+
"MEM_PCT=$(( MEM_TOTAL > 0 ? MEM_USED * 100 / MEM_TOTAL : 0 ))",
|
|
410
|
+
`cat > ${METRICS_PATH}.tmp <<JSON`,
|
|
411
|
+
'{"load":$LOAD,"cpus":$CPUS,"memTotalMb":$MEM_TOTAL,"memUsedMb":$MEM_USED,"memUsedPct":$MEM_PCT,"swapTotalMb":$SWAP_TOTAL,"swapUsedMb":$SWAP_USED,"diskUsedPct":$DISK_PCT,"uptimeSec":$UPTIME_SEC,"network":{"rxBytes":$RX_BYTES,"txBytes":$TX_BYTES},"services":{' + servicesJson + "}}",
|
|
412
|
+
"JSON",
|
|
413
|
+
`mv -f ${METRICS_PATH}.tmp ${METRICS_PATH}`,
|
|
414
|
+
'ALERTS=""',
|
|
415
|
+
`if awk -v l="$LOAD" -v c="$CPUS" -v t=${cpuLoadPerCore} 'BEGIN{exit !(c>0 && l/c > t)}'; then ALERTS="$ALERTS load=$LOAD/${cpuLoadPerCore}xCPU"; fi`,
|
|
416
|
+
`if [ "\${MEM_PCT:-0}" -ge ${memPercent} ]; then ALERTS="$ALERTS mem=\${MEM_PCT}%"; fi`,
|
|
417
|
+
`if [ "\${DISK_PCT:-0}" -ge ${diskPercent} ]; then ALERTS="$ALERTS disk=\${DISK_PCT}%"; fi`,
|
|
418
|
+
`PREV=$(cat ${ALERT_STATE_PATH} 2>/dev/null || echo ok)`,
|
|
419
|
+
'if [ -n "$ALERTS" ]; then',
|
|
420
|
+
' if [ "$PREV" != alert ] && [ -x /usr/local/bin/ts-cloud-notify ]; then /usr/local/bin/ts-cloud-notify "⚠️ $(hostname): resource alert —$ALERTS" || true; fi',
|
|
421
|
+
` echo alert > ${ALERT_STATE_PATH}`,
|
|
422
|
+
"else",
|
|
423
|
+
' if [ "$PREV" = alert ] && [ -x /usr/local/bin/ts-cloud-notify ]; then /usr/local/bin/ts-cloud-notify "✅ $(hostname): resource usage back to normal" || true; fi',
|
|
424
|
+
` echo ok > ${ALERT_STATE_PATH}`,
|
|
425
|
+
"fi",
|
|
426
|
+
"TS_CLOUD_METRICS_EOF",
|
|
427
|
+
"chmod +x /usr/local/bin/ts-cloud-metrics.sh",
|
|
428
|
+
"cat > /etc/systemd/system/ts-cloud-metrics.service <<'TS_CLOUD_METRICS_SVC_EOF'",
|
|
429
|
+
"[Unit]",
|
|
430
|
+
"Description=ts-cloud metrics collector",
|
|
431
|
+
"",
|
|
432
|
+
"[Service]",
|
|
433
|
+
"Type=oneshot",
|
|
434
|
+
"ExecStart=/usr/local/bin/ts-cloud-metrics.sh",
|
|
435
|
+
"TS_CLOUD_METRICS_SVC_EOF",
|
|
436
|
+
"cat > /etc/systemd/system/ts-cloud-metrics.timer <<'TS_CLOUD_METRICS_TMR_EOF'",
|
|
437
|
+
"[Unit]",
|
|
438
|
+
"Description=Run ts-cloud metrics collector every minute",
|
|
439
|
+
"",
|
|
440
|
+
"[Timer]",
|
|
441
|
+
"OnBootSec=60",
|
|
442
|
+
"OnUnitActiveSec=60",
|
|
443
|
+
"",
|
|
444
|
+
"[Install]",
|
|
445
|
+
"WantedBy=timers.target",
|
|
446
|
+
"TS_CLOUD_METRICS_TMR_EOF",
|
|
447
|
+
"systemctl daemon-reload",
|
|
448
|
+
"systemctl enable ts-cloud-metrics.timer",
|
|
449
|
+
"systemctl start ts-cloud-metrics.timer"
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
|
|
229
453
|
// src/drivers/shared/php-provision.ts
|
|
230
454
|
var PRODUCTION_PHP_INI = {
|
|
231
455
|
"opcache.enable": "1",
|
|
@@ -435,14 +659,7 @@ function buildNginxVhost(options) {
|
|
|
435
659
|
`)}
|
|
436
660
|
`;
|
|
437
661
|
}
|
|
438
|
-
const lines = [
|
|
439
|
-
"server {",
|
|
440
|
-
" listen 80;",
|
|
441
|
-
" listen [::]:80;",
|
|
442
|
-
` server_name ${serverNames};`,
|
|
443
|
-
...body,
|
|
444
|
-
"}"
|
|
445
|
-
];
|
|
662
|
+
const lines = ["server {", " listen 80;", " listen [::]:80;", ` server_name ${serverNames};`, ...body, "}"];
|
|
446
663
|
return `${lines.join(`
|
|
447
664
|
`)}
|
|
448
665
|
`;
|
|
@@ -565,6 +782,93 @@ function buildNginxServiceScript(projectDir = "/opt/pantry") {
|
|
|
565
782
|
];
|
|
566
783
|
}
|
|
567
784
|
|
|
785
|
+
// src/drivers/shared/notifications.ts
|
|
786
|
+
function wantsEvent(config, event) {
|
|
787
|
+
return !config.events || config.events.includes(event);
|
|
788
|
+
}
|
|
789
|
+
async function sendNotifications(config, event, message, options = {}) {
|
|
790
|
+
if (!config)
|
|
791
|
+
return [];
|
|
792
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
793
|
+
const attempted = [];
|
|
794
|
+
const tasks = [];
|
|
795
|
+
const post = (url, body) => fetchImpl(url, {
|
|
796
|
+
method: "POST",
|
|
797
|
+
headers: { "Content-Type": "application/json" },
|
|
798
|
+
body: JSON.stringify(body)
|
|
799
|
+
}).catch(() => {
|
|
800
|
+
return;
|
|
801
|
+
});
|
|
802
|
+
if (config.slack?.webhookUrl && wantsEvent(config, event)) {
|
|
803
|
+
attempted.push("slack");
|
|
804
|
+
tasks.push(post(config.slack.webhookUrl, { text: message }));
|
|
805
|
+
}
|
|
806
|
+
if (config.discord?.webhookUrl && wantsEvent(config, event)) {
|
|
807
|
+
attempted.push("discord");
|
|
808
|
+
tasks.push(post(config.discord.webhookUrl, { content: message }));
|
|
809
|
+
}
|
|
810
|
+
if (config.telegram?.botToken && config.telegram.chatId && wantsEvent(config, event)) {
|
|
811
|
+
attempted.push("telegram");
|
|
812
|
+
const url = `https://api.telegram.org/bot${config.telegram.botToken}/sendMessage`;
|
|
813
|
+
tasks.push(post(url, { chat_id: config.telegram.chatId, text: message }));
|
|
814
|
+
}
|
|
815
|
+
if (config.webhook?.url && wantsEvent(config, event)) {
|
|
816
|
+
attempted.push("webhook");
|
|
817
|
+
if ((config.webhook.method || "POST") === "GET") {
|
|
818
|
+
const sep = config.webhook.url.includes("?") ? "&" : "?";
|
|
819
|
+
const url = `${config.webhook.url}${sep}event=${encodeURIComponent(event)}&message=${encodeURIComponent(message)}`;
|
|
820
|
+
tasks.push(fetchImpl(url, { method: "GET" }).catch(() => {
|
|
821
|
+
return;
|
|
822
|
+
}));
|
|
823
|
+
} else {
|
|
824
|
+
tasks.push(post(config.webhook.url, { event, message }));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (config.email?.to && wantsEvent(config, event)) {
|
|
828
|
+
attempted.push("email");
|
|
829
|
+
tasks.push(sendEmailNotification(config.email, event, message));
|
|
830
|
+
}
|
|
831
|
+
await Promise.all(tasks);
|
|
832
|
+
return attempted;
|
|
833
|
+
}
|
|
834
|
+
async function sendEmailNotification(email, event, message) {
|
|
835
|
+
try {
|
|
836
|
+
const mod = await import("./chunk-k114qwg9.js");
|
|
837
|
+
await mod.email.send({
|
|
838
|
+
to: email.to,
|
|
839
|
+
from: email.from,
|
|
840
|
+
subject: `[ts-cloud] ${event}`,
|
|
841
|
+
text: message
|
|
842
|
+
});
|
|
843
|
+
} catch {}
|
|
844
|
+
}
|
|
845
|
+
function resolveNotifications(project, site) {
|
|
846
|
+
return site ?? project;
|
|
847
|
+
}
|
|
848
|
+
function buildNotifierScript(config) {
|
|
849
|
+
if (!config)
|
|
850
|
+
return [];
|
|
851
|
+
const curls = [];
|
|
852
|
+
if (config.slack?.webhookUrl)
|
|
853
|
+
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"text\\":\\"$MSG\\"}" ${config.slack.webhookUrl} || true`);
|
|
854
|
+
if (config.discord?.webhookUrl)
|
|
855
|
+
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"content\\":\\"$MSG\\"}" ${config.discord.webhookUrl} || true`);
|
|
856
|
+
if (config.telegram?.botToken && config.telegram.chatId)
|
|
857
|
+
curls.push(`curl -fsS -X POST "https://api.telegram.org/bot${config.telegram.botToken}/sendMessage" --data-urlencode "chat_id=${config.telegram.chatId}" --data-urlencode "text=$MSG" || true`);
|
|
858
|
+
if (config.webhook?.url)
|
|
859
|
+
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"message\\":\\"$MSG\\"}" ${config.webhook.url} || true`);
|
|
860
|
+
if (curls.length === 0)
|
|
861
|
+
return [];
|
|
862
|
+
return [
|
|
863
|
+
"cat > /usr/local/bin/ts-cloud-notify <<'TS_CLOUD_NOTIFY_EOF'",
|
|
864
|
+
"#!/bin/bash",
|
|
865
|
+
'MSG="$1"',
|
|
866
|
+
...curls,
|
|
867
|
+
"TS_CLOUD_NOTIFY_EOF",
|
|
868
|
+
"chmod +x /usr/local/bin/ts-cloud-notify"
|
|
869
|
+
];
|
|
870
|
+
}
|
|
871
|
+
|
|
568
872
|
// src/deploy/site-target.ts
|
|
569
873
|
function siteInstallBase(slug, siteName) {
|
|
570
874
|
return `/var/www/${slug}-${siteName}`;
|
|
@@ -681,6 +985,10 @@ function validateDeploymentConfig(config) {
|
|
|
681
985
|
}
|
|
682
986
|
return { errors, warnings };
|
|
683
987
|
}
|
|
988
|
+
function shipsARelease(site) {
|
|
989
|
+
const kind = resolveSiteKind(site);
|
|
990
|
+
return kind !== "bucket" && kind !== "redirect";
|
|
991
|
+
}
|
|
684
992
|
|
|
685
993
|
// src/drivers/shared/rpx-gateway.ts
|
|
686
994
|
var DEFAULT_RPX_CERTS_DIR = "/etc/rpx/certs";
|
|
@@ -740,7 +1048,13 @@ function buildRpxConfigInternal(sites, options, appBoxes) {
|
|
|
740
1048
|
const id = deriveRouteId(site.domain, path);
|
|
741
1049
|
const auth = resolveRouteAuth(site);
|
|
742
1050
|
if (kind === "redirect") {
|
|
743
|
-
proxies.push({
|
|
1051
|
+
proxies.push({
|
|
1052
|
+
to: site.domain,
|
|
1053
|
+
path,
|
|
1054
|
+
redirect: normalizeSiteRedirect(site.redirect),
|
|
1055
|
+
id,
|
|
1056
|
+
...auth ? { auth } : {}
|
|
1057
|
+
});
|
|
744
1058
|
domains.add(site.domain);
|
|
745
1059
|
continue;
|
|
746
1060
|
}
|
|
@@ -800,7 +1114,8 @@ function buildRpxConfigInternal(sites, options, appBoxes) {
|
|
|
800
1114
|
enabled: true,
|
|
801
1115
|
allowedSuffixes: [...domains],
|
|
802
1116
|
email: options.proxy.onDemandTlsEmail,
|
|
803
|
-
certsDir
|
|
1117
|
+
certsDir,
|
|
1118
|
+
staging: options.proxy.onDemandTlsStaging ?? false
|
|
804
1119
|
};
|
|
805
1120
|
}
|
|
806
1121
|
if (options.proxy.onDemandTls) {
|
|
@@ -859,6 +1174,7 @@ let email
|
|
|
859
1174
|
let certsDir = ${JSON.stringify(defaultCertsDir)}
|
|
860
1175
|
let acmeChallengeWebroot
|
|
861
1176
|
let guard
|
|
1177
|
+
let anyProduction = false
|
|
862
1178
|
let files = []
|
|
863
1179
|
try { files = readdirSync(dir).filter(n => n.endsWith('.json')).sort() } catch {}
|
|
864
1180
|
for (const f of files) {
|
|
@@ -878,6 +1194,11 @@ for (const f of files) {
|
|
|
878
1194
|
}
|
|
879
1195
|
for (const s of frag.onDemandTls?.allowedSuffixes ?? []) suffixes.add(s)
|
|
880
1196
|
email ??= frag.onDemandTls?.email
|
|
1197
|
+
// One gateway, one ACME directory: production wins any disagreement between
|
|
1198
|
+
// co-tenants, and a fragment predating the flag counts as production. A
|
|
1199
|
+
// staging cert chains to an untrusted root, so defaulting the other way
|
|
1200
|
+
// breaks every client of every tenant that wanted a real cert.
|
|
1201
|
+
if (frag.onDemandTls && frag.onDemandTls.staging !== true) anyProduction = true
|
|
881
1202
|
if (frag.productionCerts?.certsDir) certsDir = frag.productionCerts.certsDir
|
|
882
1203
|
acmeChallengeWebroot ??= frag.acmeChallengeWebroot
|
|
883
1204
|
if (frag.originGuard) {
|
|
@@ -897,7 +1218,7 @@ const config = {
|
|
|
897
1218
|
// journal and a production TLS failure looks like "nothing happens". Silence
|
|
898
1219
|
// it by setting RPX_VERBOSE=false on the systemd unit.
|
|
899
1220
|
verbose: process.env.RPX_VERBOSE !== 'false',
|
|
900
|
-
...(suffixes.size > 0 ? { onDemandTls: { enabled: true, allowedSuffixes: [...suffixes], email, certsDir } } : {}),
|
|
1221
|
+
...(suffixes.size > 0 ? { onDemandTls: { enabled: true, allowedSuffixes: [...suffixes], email, certsDir, staging: !anyProduction } } : {}),
|
|
901
1222
|
...(acmeChallengeWebroot ? { acmeChallengeWebroot } : {}),
|
|
902
1223
|
...(guard ? { originGuard: { header: guard.header, value: guard.value, hosts: [...guardHosts] } } : {}),
|
|
903
1224
|
}
|
|
@@ -1042,165 +1363,34 @@ function buildRpxProvisionScript(options) {
|
|
|
1042
1363
|
`Environment=BUN_INSTALL=/root/.bun`,
|
|
1043
1364
|
...poolEnv,
|
|
1044
1365
|
"MemoryAccounting=true",
|
|
1045
|
-
`MemoryHigh=${memoryHigh}`,
|
|
1046
|
-
`MemoryMax=${memoryMax}`,
|
|
1047
|
-
"OOMPolicy=stop",
|
|
1048
|
-
"Restart=always",
|
|
1049
|
-
"RestartSec=5",
|
|
1050
|
-
"LimitNOFILE=1048576",
|
|
1051
|
-
"AmbientCapabilities=CAP_NET_BIND_SERVICE",
|
|
1052
|
-
"",
|
|
1053
|
-
"[Install]",
|
|
1054
|
-
"WantedBy=multi-user.target"
|
|
1055
|
-
].join(`
|
|
1056
|
-
`), "TS_CLOUD_RPX_UNIT_EOF"),
|
|
1057
|
-
"systemctl daemon-reload",
|
|
1058
|
-
"systemctl disable --now bun-gateway.service 2>/dev/null || true",
|
|
1059
|
-
"systemctl disable --now ts-cloud-nginx.service 2>/dev/null || true",
|
|
1060
|
-
`systemctl enable ${RPX_SERVICE_NAME}`,
|
|
1061
|
-
`systemctl restart ${RPX_SERVICE_NAME}`,
|
|
1062
|
-
...buildCertManagementCommands(options)
|
|
1063
|
-
];
|
|
1064
|
-
}
|
|
1065
|
-
function buildRpxFragmentRefreshScript(options) {
|
|
1066
|
-
const slug = (options.slug || "app").replace(/[^a-z0-9._-]+/gi, "-");
|
|
1067
|
-
const fragment = JSON.stringify({ slug, ...options.config }, null, 2);
|
|
1068
|
-
return [
|
|
1069
|
-
"set -euo pipefail",
|
|
1070
|
-
`mkdir -p ${RPX_SITES_DIR}`,
|
|
1071
|
-
...writeFileHeredoc(`${RPX_SITES_DIR}/${slug}.json`, fragment, "TS_CLOUD_RPX_FRAGMENT_EOF", "0600"),
|
|
1072
|
-
`systemctl restart ${RPX_SERVICE_NAME}`
|
|
1073
|
-
];
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// src/drivers/shared/ufw.ts
|
|
1077
|
-
var UFW_BASE_PORTS = [80, 443];
|
|
1078
|
-
function buildUfwScript(firewall = {}) {
|
|
1079
|
-
if (firewall.enabled === false)
|
|
1080
|
-
return [];
|
|
1081
|
-
const ports = [...new Set([...UFW_BASE_PORTS, ...firewall.allowedPorts || []])].filter((p) => Number.isInteger(p) && p >= 1 && p <= 65535).sort((a, b) => a - b);
|
|
1082
|
-
const lines = [
|
|
1083
|
-
"export DEBIAN_FRONTEND=noninteractive",
|
|
1084
|
-
"apt-get install -y ufw",
|
|
1085
|
-
"ufw default deny incoming",
|
|
1086
|
-
"ufw default allow outgoing",
|
|
1087
|
-
"ufw allow OpenSSH"
|
|
1088
|
-
];
|
|
1089
|
-
for (const port of ports)
|
|
1090
|
-
lines.push(`ufw allow ${port}/tcp`);
|
|
1091
|
-
lines.push("ufw --force enable");
|
|
1092
|
-
return lines;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// src/drivers/shared/maintenance.ts
|
|
1096
|
-
function buildAutoUpdatesScript(enabled2 = true) {
|
|
1097
|
-
if (!enabled2)
|
|
1098
|
-
return [];
|
|
1099
|
-
return [
|
|
1100
|
-
"export DEBIAN_FRONTEND=noninteractive",
|
|
1101
|
-
"apt-get install -y unattended-upgrades",
|
|
1102
|
-
"cat > /etc/apt/apt.conf.d/20auto-upgrades <<'TS_CLOUD_AUTOUPD_EOF'",
|
|
1103
|
-
'APT::Periodic::Update-Package-Lists "1";',
|
|
1104
|
-
'APT::Periodic::Unattended-Upgrade "1";',
|
|
1105
|
-
'APT::Periodic::Download-Upgradeable-Packages "1";',
|
|
1106
|
-
'APT::Periodic::AutocleanInterval "7";',
|
|
1107
|
-
"TS_CLOUD_AUTOUPD_EOF",
|
|
1108
|
-
"systemctl enable unattended-upgrades 2>/dev/null || true",
|
|
1109
|
-
"systemctl restart unattended-upgrades 2>/dev/null || true"
|
|
1110
|
-
];
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// src/drivers/shared/monitoring.ts
|
|
1114
|
-
var METRICS_PATH = "/var/lib/ts-cloud/metrics.json";
|
|
1115
|
-
var ALERT_STATE_PATH = "/var/lib/ts-cloud/alert-state";
|
|
1116
|
-
var DEFAULT_CPU_LOAD_PER_CORE = 2;
|
|
1117
|
-
var DEFAULT_MEM_PERCENT = 90;
|
|
1118
|
-
var DEFAULT_DISK_PERCENT = 90;
|
|
1119
|
-
var SERVICE_PROBES = [
|
|
1120
|
-
["nginx", 80],
|
|
1121
|
-
["phpFpm", 9074],
|
|
1122
|
-
["mysql", 3306],
|
|
1123
|
-
["postgres", 5432],
|
|
1124
|
-
["redis", 6379],
|
|
1125
|
-
["meilisearch", 7700]
|
|
1126
|
-
];
|
|
1127
|
-
function resolveMonitoring(monitoring = true) {
|
|
1128
|
-
const obj = typeof monitoring === "object" ? monitoring : {};
|
|
1129
|
-
const enabled2 = typeof monitoring === "boolean" ? monitoring : monitoring.enabled !== false;
|
|
1130
|
-
return {
|
|
1131
|
-
enabled: enabled2,
|
|
1132
|
-
cpuLoadPerCore: obj.alerts?.cpuLoadPerCore ?? DEFAULT_CPU_LOAD_PER_CORE,
|
|
1133
|
-
memPercent: obj.alerts?.memPercent ?? DEFAULT_MEM_PERCENT,
|
|
1134
|
-
diskPercent: obj.alerts?.diskPercent ?? DEFAULT_DISK_PERCENT
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
function buildMonitoringScript(monitoring = true) {
|
|
1138
|
-
const { enabled: enabled2, cpuLoadPerCore, memPercent, diskPercent } = resolveMonitoring(monitoring);
|
|
1139
|
-
if (!enabled2)
|
|
1140
|
-
return [];
|
|
1141
|
-
const probeLines = SERVICE_PROBES.map(([name, port]) => `SVC_${name.toUpperCase()}=$(probe ${port})`);
|
|
1142
|
-
const servicesJson = SERVICE_PROBES.map(([name]) => `"${name}":"$SVC_${name.toUpperCase()}"`).join(",");
|
|
1143
|
-
return [
|
|
1144
|
-
"mkdir -p /var/lib/ts-cloud",
|
|
1145
|
-
"cat > /usr/local/bin/ts-cloud-metrics.sh <<'TS_CLOUD_METRICS_EOF'",
|
|
1146
|
-
"#!/bin/bash",
|
|
1147
|
-
"set -uo pipefail",
|
|
1148
|
-
"LOAD=$(cut -d' ' -f1 /proc/loadavg)",
|
|
1149
|
-
"CPUS=$(nproc)",
|
|
1150
|
-
"MEM_TOTAL=$(free -m | awk '/^Mem:/{print $2}')",
|
|
1151
|
-
"MEM_USED=$(free -m | awk '/^Mem:/{print $3}')",
|
|
1152
|
-
"SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}')",
|
|
1153
|
-
"SWAP_USED=$(free -m | awk '/^Swap:/{print $3}')",
|
|
1154
|
-
`DISK_PCT=$(df -P / | awk 'NR==2{gsub("%","",$5); print $5}')`,
|
|
1155
|
-
"UPTIME_SEC=$(cut -d' ' -f1 /proc/uptime | cut -d. -f1)",
|
|
1156
|
-
`RX_BYTES=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{rx+=$3} END{print rx+0}' /proc/net/dev)`,
|
|
1157
|
-
`TX_BYTES=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{tx+=$11} END{print tx+0}' /proc/net/dev)`,
|
|
1158
|
-
"probe(){ (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null && echo up || echo down; }",
|
|
1159
|
-
...probeLines,
|
|
1160
|
-
"LOAD=${LOAD:-0}; CPUS=${CPUS:-1}; MEM_TOTAL=${MEM_TOTAL:-0}; MEM_USED=${MEM_USED:-0}",
|
|
1161
|
-
"SWAP_TOTAL=${SWAP_TOTAL:-0}; SWAP_USED=${SWAP_USED:-0}; DISK_PCT=${DISK_PCT:-0}",
|
|
1162
|
-
"UPTIME_SEC=${UPTIME_SEC:-0}; RX_BYTES=${RX_BYTES:-0}; TX_BYTES=${TX_BYTES:-0}",
|
|
1163
|
-
"MEM_PCT=$(( MEM_TOTAL > 0 ? MEM_USED * 100 / MEM_TOTAL : 0 ))",
|
|
1164
|
-
`cat > ${METRICS_PATH}.tmp <<JSON`,
|
|
1165
|
-
'{"load":$LOAD,"cpus":$CPUS,"memTotalMb":$MEM_TOTAL,"memUsedMb":$MEM_USED,"memUsedPct":$MEM_PCT,"swapTotalMb":$SWAP_TOTAL,"swapUsedMb":$SWAP_USED,"diskUsedPct":$DISK_PCT,"uptimeSec":$UPTIME_SEC,"network":{"rxBytes":$RX_BYTES,"txBytes":$TX_BYTES},"services":{' + servicesJson + "}}",
|
|
1166
|
-
"JSON",
|
|
1167
|
-
`mv -f ${METRICS_PATH}.tmp ${METRICS_PATH}`,
|
|
1168
|
-
'ALERTS=""',
|
|
1169
|
-
`if awk -v l="$LOAD" -v c="$CPUS" -v t=${cpuLoadPerCore} 'BEGIN{exit !(c>0 && l/c > t)}'; then ALERTS="$ALERTS load=$LOAD/${cpuLoadPerCore}xCPU"; fi`,
|
|
1170
|
-
`if [ "\${MEM_PCT:-0}" -ge ${memPercent} ]; then ALERTS="$ALERTS mem=\${MEM_PCT}%"; fi`,
|
|
1171
|
-
`if [ "\${DISK_PCT:-0}" -ge ${diskPercent} ]; then ALERTS="$ALERTS disk=\${DISK_PCT}%"; fi`,
|
|
1172
|
-
`PREV=$(cat ${ALERT_STATE_PATH} 2>/dev/null || echo ok)`,
|
|
1173
|
-
'if [ -n "$ALERTS" ]; then',
|
|
1174
|
-
' if [ "$PREV" != alert ] && [ -x /usr/local/bin/ts-cloud-notify ]; then /usr/local/bin/ts-cloud-notify "⚠️ $(hostname): resource alert —$ALERTS" || true; fi',
|
|
1175
|
-
` echo alert > ${ALERT_STATE_PATH}`,
|
|
1176
|
-
"else",
|
|
1177
|
-
' if [ "$PREV" = alert ] && [ -x /usr/local/bin/ts-cloud-notify ]; then /usr/local/bin/ts-cloud-notify "✅ $(hostname): resource usage back to normal" || true; fi',
|
|
1178
|
-
` echo ok > ${ALERT_STATE_PATH}`,
|
|
1179
|
-
"fi",
|
|
1180
|
-
"TS_CLOUD_METRICS_EOF",
|
|
1181
|
-
"chmod +x /usr/local/bin/ts-cloud-metrics.sh",
|
|
1182
|
-
"cat > /etc/systemd/system/ts-cloud-metrics.service <<'TS_CLOUD_METRICS_SVC_EOF'",
|
|
1183
|
-
"[Unit]",
|
|
1184
|
-
"Description=ts-cloud metrics collector",
|
|
1185
|
-
"",
|
|
1186
|
-
"[Service]",
|
|
1187
|
-
"Type=oneshot",
|
|
1188
|
-
"ExecStart=/usr/local/bin/ts-cloud-metrics.sh",
|
|
1189
|
-
"TS_CLOUD_METRICS_SVC_EOF",
|
|
1190
|
-
"cat > /etc/systemd/system/ts-cloud-metrics.timer <<'TS_CLOUD_METRICS_TMR_EOF'",
|
|
1191
|
-
"[Unit]",
|
|
1192
|
-
"Description=Run ts-cloud metrics collector every minute",
|
|
1193
|
-
"",
|
|
1194
|
-
"[Timer]",
|
|
1195
|
-
"OnBootSec=60",
|
|
1196
|
-
"OnUnitActiveSec=60",
|
|
1197
|
-
"",
|
|
1198
|
-
"[Install]",
|
|
1199
|
-
"WantedBy=timers.target",
|
|
1200
|
-
"TS_CLOUD_METRICS_TMR_EOF",
|
|
1366
|
+
`MemoryHigh=${memoryHigh}`,
|
|
1367
|
+
`MemoryMax=${memoryMax}`,
|
|
1368
|
+
"OOMPolicy=stop",
|
|
1369
|
+
"Restart=always",
|
|
1370
|
+
"RestartSec=5",
|
|
1371
|
+
"LimitNOFILE=1048576",
|
|
1372
|
+
"AmbientCapabilities=CAP_NET_BIND_SERVICE",
|
|
1373
|
+
"",
|
|
1374
|
+
"[Install]",
|
|
1375
|
+
"WantedBy=multi-user.target"
|
|
1376
|
+
].join(`
|
|
1377
|
+
`), "TS_CLOUD_RPX_UNIT_EOF"),
|
|
1201
1378
|
"systemctl daemon-reload",
|
|
1202
|
-
"systemctl
|
|
1203
|
-
"systemctl
|
|
1379
|
+
"systemctl disable --now bun-gateway.service 2>/dev/null || true",
|
|
1380
|
+
"systemctl disable --now ts-cloud-nginx.service 2>/dev/null || true",
|
|
1381
|
+
`systemctl enable ${RPX_SERVICE_NAME}`,
|
|
1382
|
+
`systemctl restart ${RPX_SERVICE_NAME}`,
|
|
1383
|
+
...buildCertManagementCommands(options)
|
|
1384
|
+
];
|
|
1385
|
+
}
|
|
1386
|
+
function buildRpxFragmentRefreshScript(options) {
|
|
1387
|
+
const slug = (options.slug || "app").replace(/[^a-z0-9._-]+/gi, "-");
|
|
1388
|
+
const fragment = JSON.stringify({ slug, ...options.config }, null, 2);
|
|
1389
|
+
return [
|
|
1390
|
+
"set -euo pipefail",
|
|
1391
|
+
`mkdir -p ${RPX_SITES_DIR}`,
|
|
1392
|
+
...writeFileHeredoc(`${RPX_SITES_DIR}/${slug}.json`, fragment, "TS_CLOUD_RPX_FRAGMENT_EOF", "0600"),
|
|
1393
|
+
`systemctl restart ${RPX_SERVICE_NAME}`
|
|
1204
1394
|
];
|
|
1205
1395
|
}
|
|
1206
1396
|
|
|
@@ -1217,11 +1407,7 @@ function buildAuthorizedKeysScript(keys = [], options = {}) {
|
|
|
1217
1407
|
`sed -i '/^${escapeSed(BLOCK_BEGIN)}$/,/^${escapeSed(BLOCK_END)}$/d' ${path}`
|
|
1218
1408
|
];
|
|
1219
1409
|
if (keys.length > 0) {
|
|
1220
|
-
const block = [
|
|
1221
|
-
BLOCK_BEGIN,
|
|
1222
|
-
...keys.map((k) => `${k.publicKey.trim()} ${k.name}`),
|
|
1223
|
-
BLOCK_END
|
|
1224
|
-
].join(`
|
|
1410
|
+
const block = [BLOCK_BEGIN, ...keys.map((k) => `${k.publicKey.trim()} ${k.name}`), BLOCK_END].join(`
|
|
1225
1411
|
`);
|
|
1226
1412
|
lines.push(`cat >> ${path} <<'TS_CLOUD_KEYS_EOF'`, block, "TS_CLOUD_KEYS_EOF", `chmod 600 ${path}`);
|
|
1227
1413
|
}
|
|
@@ -1231,202 +1417,23 @@ function escapeSed(value) {
|
|
|
1231
1417
|
return value.replace(/[.*[\]\\/^$]/g, "\\$&");
|
|
1232
1418
|
}
|
|
1233
1419
|
|
|
1234
|
-
// src/drivers/shared/
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
async function sendNotifications(config, event, message, options = {}) {
|
|
1239
|
-
if (!config)
|
|
1240
|
-
return [];
|
|
1241
|
-
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
1242
|
-
const attempted = [];
|
|
1243
|
-
const tasks = [];
|
|
1244
|
-
const post = (url, body) => fetchImpl(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }).catch(() => {
|
|
1245
|
-
return;
|
|
1246
|
-
});
|
|
1247
|
-
if (config.slack?.webhookUrl && wantsEvent(config, event)) {
|
|
1248
|
-
attempted.push("slack");
|
|
1249
|
-
tasks.push(post(config.slack.webhookUrl, { text: message }));
|
|
1250
|
-
}
|
|
1251
|
-
if (config.discord?.webhookUrl && wantsEvent(config, event)) {
|
|
1252
|
-
attempted.push("discord");
|
|
1253
|
-
tasks.push(post(config.discord.webhookUrl, { content: message }));
|
|
1254
|
-
}
|
|
1255
|
-
if (config.telegram?.botToken && config.telegram.chatId && wantsEvent(config, event)) {
|
|
1256
|
-
attempted.push("telegram");
|
|
1257
|
-
const url = `https://api.telegram.org/bot${config.telegram.botToken}/sendMessage`;
|
|
1258
|
-
tasks.push(post(url, { chat_id: config.telegram.chatId, text: message }));
|
|
1259
|
-
}
|
|
1260
|
-
if (config.webhook?.url && wantsEvent(config, event)) {
|
|
1261
|
-
attempted.push("webhook");
|
|
1262
|
-
if ((config.webhook.method || "POST") === "GET") {
|
|
1263
|
-
const sep = config.webhook.url.includes("?") ? "&" : "?";
|
|
1264
|
-
const url = `${config.webhook.url}${sep}event=${encodeURIComponent(event)}&message=${encodeURIComponent(message)}`;
|
|
1265
|
-
tasks.push(fetchImpl(url, { method: "GET" }).catch(() => {
|
|
1266
|
-
return;
|
|
1267
|
-
}));
|
|
1268
|
-
} else {
|
|
1269
|
-
tasks.push(post(config.webhook.url, { event, message }));
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (config.email?.to && wantsEvent(config, event)) {
|
|
1273
|
-
attempted.push("email");
|
|
1274
|
-
tasks.push(sendEmailNotification(config.email, event, message));
|
|
1275
|
-
}
|
|
1276
|
-
await Promise.all(tasks);
|
|
1277
|
-
return attempted;
|
|
1278
|
-
}
|
|
1279
|
-
async function sendEmailNotification(email, event, message) {
|
|
1280
|
-
try {
|
|
1281
|
-
const mod = await import("./chunk-pqfzdg68.js");
|
|
1282
|
-
await mod.email.send({
|
|
1283
|
-
to: email.to,
|
|
1284
|
-
from: email.from,
|
|
1285
|
-
subject: `[ts-cloud] ${event}`,
|
|
1286
|
-
text: message
|
|
1287
|
-
});
|
|
1288
|
-
} catch {}
|
|
1289
|
-
}
|
|
1290
|
-
function resolveNotifications(project, site) {
|
|
1291
|
-
return site ?? project;
|
|
1292
|
-
}
|
|
1293
|
-
function buildNotifierScript(config) {
|
|
1294
|
-
if (!config)
|
|
1295
|
-
return [];
|
|
1296
|
-
const curls = [];
|
|
1297
|
-
if (config.slack?.webhookUrl)
|
|
1298
|
-
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"text\\":\\"$MSG\\"}" ${config.slack.webhookUrl} || true`);
|
|
1299
|
-
if (config.discord?.webhookUrl)
|
|
1300
|
-
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"content\\":\\"$MSG\\"}" ${config.discord.webhookUrl} || true`);
|
|
1301
|
-
if (config.telegram?.botToken && config.telegram.chatId)
|
|
1302
|
-
curls.push(`curl -fsS -X POST "https://api.telegram.org/bot${config.telegram.botToken}/sendMessage" --data-urlencode "chat_id=${config.telegram.chatId}" --data-urlencode "text=$MSG" || true`);
|
|
1303
|
-
if (config.webhook?.url)
|
|
1304
|
-
curls.push(`curl -fsS -X POST -H 'Content-Type: application/json' -d "{\\"message\\":\\"$MSG\\"}" ${config.webhook.url} || true`);
|
|
1305
|
-
if (curls.length === 0)
|
|
1306
|
-
return [];
|
|
1307
|
-
return [
|
|
1308
|
-
"cat > /usr/local/bin/ts-cloud-notify <<'TS_CLOUD_NOTIFY_EOF'",
|
|
1309
|
-
"#!/bin/bash",
|
|
1310
|
-
'MSG="$1"',
|
|
1311
|
-
...curls,
|
|
1312
|
-
"TS_CLOUD_NOTIFY_EOF",
|
|
1313
|
-
"chmod +x /usr/local/bin/ts-cloud-notify"
|
|
1314
|
-
];
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
// src/drivers/shared/backups.ts
|
|
1318
|
-
var BACKUP_OUTPUT_DIR = "/var/backups/ts-cloud";
|
|
1319
|
-
var BACKUP_CONFIG_PATH = "/etc/ts-cloud/backups.config.ts";
|
|
1320
|
-
var BACKUP_CRON_PATH = "/etc/cron.d/ts-cloud-backups";
|
|
1321
|
-
var BACKUP_RUNNER_PATH = "/usr/local/bin/ts-cloud-backup.sh";
|
|
1322
|
-
function backupEntryFor(database) {
|
|
1323
|
-
if (!database.name)
|
|
1324
|
-
return null;
|
|
1325
|
-
const host = database.host || "127.0.0.1";
|
|
1326
|
-
const isPostgres = database.engine === "postgres";
|
|
1327
|
-
const type = isPostgres ? "postgresql" : "mysql";
|
|
1328
|
-
const port = database.port ?? (isPostgres ? 5432 : 3306);
|
|
1329
|
-
const ts = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1330
|
-
return [
|
|
1331
|
-
" {",
|
|
1332
|
-
` type: '${type}',`,
|
|
1333
|
-
` name: '${ts(database.name)}',`,
|
|
1334
|
-
" connection: {",
|
|
1335
|
-
` hostname: '${ts(host)}',`,
|
|
1336
|
-
` port: ${port},`,
|
|
1337
|
-
` database: '${ts(database.name)}',`,
|
|
1338
|
-
` username: '${ts(database.username || database.name)}',`,
|
|
1339
|
-
` password: '${ts(database.password || "")}',`,
|
|
1340
|
-
" ssl: false,",
|
|
1341
|
-
" },",
|
|
1342
|
-
" includeSchema: true,",
|
|
1343
|
-
" includeData: true,",
|
|
1344
|
-
" },"
|
|
1345
|
-
].join(`
|
|
1346
|
-
`);
|
|
1347
|
-
}
|
|
1348
|
-
function buildBackupsConfigTs(database, backups) {
|
|
1349
|
-
const entry = database ? backupEntryFor(database) : null;
|
|
1350
|
-
const destinations = backups.bucket ? [
|
|
1351
|
-
" destinations: [",
|
|
1352
|
-
" {",
|
|
1353
|
-
" type: 's3',",
|
|
1354
|
-
` bucket: '${backups.bucket}',`,
|
|
1355
|
-
" prefix: 'db-backups',",
|
|
1356
|
-
...backups.endpoint ? [` endpoint: '${backups.endpoint}',`] : [],
|
|
1357
|
-
" optional: false,",
|
|
1358
|
-
" },",
|
|
1359
|
-
" ],"
|
|
1360
|
-
] : [];
|
|
1361
|
-
return [
|
|
1362
|
-
"import type { BackupConfig } from 'ts-backups'",
|
|
1363
|
-
"",
|
|
1364
|
-
"const config: BackupConfig = {",
|
|
1365
|
-
" verbose: true,",
|
|
1366
|
-
` outputPath: '${BACKUP_OUTPUT_DIR}',`,
|
|
1367
|
-
" retention: {",
|
|
1368
|
-
` count: ${backups.retentionCount ?? 5},`,
|
|
1369
|
-
` maxAge: ${backups.retentionDays ?? 30},`,
|
|
1370
|
-
" },",
|
|
1371
|
-
" databases: [",
|
|
1372
|
-
...entry ? [entry] : [],
|
|
1373
|
-
" ],",
|
|
1374
|
-
...destinations,
|
|
1375
|
-
"}",
|
|
1376
|
-
"",
|
|
1377
|
-
"export default config",
|
|
1378
|
-
""
|
|
1379
|
-
].join(`
|
|
1380
|
-
`);
|
|
1381
|
-
}
|
|
1382
|
-
function buildBackupProvisionScript(options) {
|
|
1383
|
-
const { database, backups } = options;
|
|
1384
|
-
if (!backups.enabled)
|
|
1420
|
+
// src/drivers/shared/ufw.ts
|
|
1421
|
+
var UFW_BASE_PORTS = [80, 443];
|
|
1422
|
+
function buildUfwScript(firewall = {}) {
|
|
1423
|
+
if (firewall.enabled === false)
|
|
1385
1424
|
return [];
|
|
1386
|
-
const
|
|
1387
|
-
const
|
|
1388
|
-
return [
|
|
1425
|
+
const ports = [...new Set([...UFW_BASE_PORTS, ...firewall.allowedPorts || []])].filter((p) => Number.isInteger(p) && p >= 1 && p <= 65535).sort((a, b) => a - b);
|
|
1426
|
+
const lines = [
|
|
1389
1427
|
"export DEBIAN_FRONTEND=noninteractive",
|
|
1390
|
-
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
|
|
1394
|
-
configTs.replace(/\n$/, ""),
|
|
1395
|
-
"TS_CLOUD_BACKUP_CFG_EOF",
|
|
1396
|
-
`cat > ${BACKUP_RUNNER_PATH} <<'TS_CLOUD_BACKUP_RUN_EOF'`,
|
|
1397
|
-
"#!/bin/bash",
|
|
1398
|
-
"set -uo pipefail",
|
|
1399
|
-
'export PATH="/root/.bun/bin:/usr/local/bin:$PATH"',
|
|
1400
|
-
'notify() { [ -x /usr/local/bin/ts-cloud-notify ] && /usr/local/bin/ts-cloud-notify "$1" || true; }',
|
|
1401
|
-
"cd /etc/ts-cloud",
|
|
1402
|
-
'if ! ts-backups backup --config /etc/ts-cloud/backups.config.ts; then notify "❌ ts-cloud backup failed"; exit 1; fi',
|
|
1403
|
-
"TS_CLOUD_BACKUP_RUN_EOF",
|
|
1404
|
-
`chmod +x ${BACKUP_RUNNER_PATH}`,
|
|
1405
|
-
`cat > ${BACKUP_CRON_PATH} <<'TS_CLOUD_BACKUP_CRON_EOF'`,
|
|
1406
|
-
`${schedule} root ${BACKUP_RUNNER_PATH} >> /var/log/ts-cloud-backup.log 2>&1`,
|
|
1407
|
-
"TS_CLOUD_BACKUP_CRON_EOF",
|
|
1408
|
-
`chmod 644 ${BACKUP_CRON_PATH}`
|
|
1409
|
-
];
|
|
1410
|
-
}
|
|
1411
|
-
function engineSocket(engine) {
|
|
1412
|
-
return engine === "mariadb" ? "/var/lib/pantry/mariadb/mariadbd.sock" : "/var/lib/pantry/mysql/mysqld.sock";
|
|
1413
|
-
}
|
|
1414
|
-
function buildBackupRestoreScript(database, options = {}) {
|
|
1415
|
-
if (!database?.name)
|
|
1416
|
-
return [];
|
|
1417
|
-
const name = database.name;
|
|
1418
|
-
const isPg = database.engine === "postgres";
|
|
1419
|
-
const locate = options.from ? `TS_CLOUD_DUMP="${options.from}"` : `TS_CLOUD_DUMP="$(find ${BACKUP_OUTPUT_DIR} -type f -name '*${name}*.sql' -o -type f -name '*${name}*.sql.gz' 2>/dev/null | xargs -r ls -1t 2>/dev/null | head -1)"`;
|
|
1420
|
-
const client = isPg ? `${pgAdminCommand(database)} -d "${name}"` : `mysql --socket=${engineSocket(database.engine)} -u root "${name}"`;
|
|
1421
|
-
return [
|
|
1422
|
-
"set -uo pipefail",
|
|
1423
|
-
'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',
|
|
1424
|
-
locate,
|
|
1425
|
-
'[ -n "$TS_CLOUD_DUMP" ] && [ -f "$TS_CLOUD_DUMP" ] || { echo "no backup dump found to restore" >&2; exit 1; }',
|
|
1426
|
-
'echo "[ts-cloud] restoring ' + name + ' from $TS_CLOUD_DUMP"',
|
|
1427
|
-
`case "$TS_CLOUD_DUMP" in *.gz) gunzip -c "$TS_CLOUD_DUMP" ;; *) cat "$TS_CLOUD_DUMP" ;; esac | ${client}`,
|
|
1428
|
-
'echo "restore complete"'
|
|
1428
|
+
"apt-get install -y ufw",
|
|
1429
|
+
"ufw default deny incoming",
|
|
1430
|
+
"ufw default allow outgoing",
|
|
1431
|
+
"ufw allow OpenSSH"
|
|
1429
1432
|
];
|
|
1433
|
+
for (const port of ports)
|
|
1434
|
+
lines.push(`ufw allow ${port}/tcp`);
|
|
1435
|
+
lines.push("ufw --force enable");
|
|
1436
|
+
return lines;
|
|
1430
1437
|
}
|
|
1431
1438
|
|
|
1432
1439
|
// src/drivers/shared/compute-provision.ts
|
|
@@ -1720,7 +1727,12 @@ class AwsDriver {
|
|
|
1720
1727
|
const existing = await this.findComputeTargets({ slug, environment, role: "app" });
|
|
1721
1728
|
if (existing.length > 0) {
|
|
1722
1729
|
const first = existing[0];
|
|
1723
|
-
return {
|
|
1730
|
+
return {
|
|
1731
|
+
appInstanceId: first.id,
|
|
1732
|
+
appPublicIp: first.publicIp,
|
|
1733
|
+
sshUser: "ubuntu",
|
|
1734
|
+
deployStoragePath: "/var/ts-cloud/staging"
|
|
1735
|
+
};
|
|
1724
1736
|
}
|
|
1725
1737
|
let imageId = resolveAwsImageId(config);
|
|
1726
1738
|
if (!imageId) {
|
|
@@ -1740,16 +1752,30 @@ class AwsDriver {
|
|
|
1740
1752
|
if (!subnet?.MapPublicIpOnLaunch)
|
|
1741
1753
|
console.warn("ts-cloud: no public subnet found; the instance may not get a public IP (deploys/SSL need one).");
|
|
1742
1754
|
const sgName = `${slug}-${environment}-app-sg`;
|
|
1743
|
-
const found = await ec2.describeSecurityGroups({
|
|
1755
|
+
const found = await ec2.describeSecurityGroups({
|
|
1756
|
+
Filters: [
|
|
1757
|
+
{ Name: "group-name", Values: [sgName] },
|
|
1758
|
+
{ Name: "vpc-id", Values: [vpc.VpcId] }
|
|
1759
|
+
]
|
|
1760
|
+
});
|
|
1744
1761
|
let groupId = found.SecurityGroups?.[0]?.GroupId;
|
|
1745
1762
|
if (!groupId) {
|
|
1746
|
-
const created = await ec2.createSecurityGroup({
|
|
1763
|
+
const created = await ec2.createSecurityGroup({
|
|
1764
|
+
GroupName: sgName,
|
|
1765
|
+
Description: `ts-cloud ${slug}/${environment} app`,
|
|
1766
|
+
VpcId: vpc.VpcId
|
|
1767
|
+
});
|
|
1747
1768
|
groupId = created.GroupId;
|
|
1748
1769
|
}
|
|
1749
1770
|
const rules = awsComputeIngressRules(config);
|
|
1750
1771
|
await ec2.authorizeSecurityGroupIngress({
|
|
1751
1772
|
GroupId: groupId,
|
|
1752
|
-
IpPermissions: rules.map((r) => ({
|
|
1773
|
+
IpPermissions: rules.map((r) => ({
|
|
1774
|
+
IpProtocol: r.protocol,
|
|
1775
|
+
FromPort: r.port,
|
|
1776
|
+
ToPort: r.port,
|
|
1777
|
+
IpRanges: [{ CidrIp: r.cidr }]
|
|
1778
|
+
}))
|
|
1753
1779
|
}).catch((e) => {
|
|
1754
1780
|
if (!/InvalidPermission\.Duplicate/.test(e instanceof Error ? e.message : ""))
|
|
1755
1781
|
throw e;
|
|
@@ -1765,15 +1791,17 @@ class AwsDriver {
|
|
|
1765
1791
|
SubnetId: subnetId,
|
|
1766
1792
|
UserData: userData,
|
|
1767
1793
|
IamInstanceProfile: compute.server?.iamInstanceProfile ? { Name: compute.server.iamInstanceProfile } : undefined,
|
|
1768
|
-
TagSpecifications: [
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1794
|
+
TagSpecifications: [
|
|
1795
|
+
{
|
|
1796
|
+
ResourceType: "instance",
|
|
1797
|
+
Tags: [
|
|
1798
|
+
{ Key: "Name", Value: `${slug}-${environment}-app` },
|
|
1799
|
+
{ Key: "Project", Value: slug },
|
|
1800
|
+
{ Key: "Environment", Value: environment },
|
|
1801
|
+
{ Key: "Role", Value: "app" }
|
|
1802
|
+
]
|
|
1803
|
+
}
|
|
1804
|
+
]
|
|
1777
1805
|
});
|
|
1778
1806
|
const instanceId = run.Instances?.[0]?.InstanceId;
|
|
1779
1807
|
if (!instanceId)
|
|
@@ -1989,10 +2017,53 @@ class AwsDriver {
|
|
|
1989
2017
|
};
|
|
1990
2018
|
}
|
|
1991
2019
|
}
|
|
1992
|
-
|
|
1993
|
-
// src/drivers/hetzner/driver.ts
|
|
1994
|
-
import {
|
|
1995
|
-
import {
|
|
2020
|
+
|
|
2021
|
+
// src/drivers/hetzner/driver.ts
|
|
2022
|
+
import { execSync } from "node:child_process";
|
|
2023
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
2024
|
+
|
|
2025
|
+
// src/drivers/shared/fleet.ts
|
|
2026
|
+
function resolveFleetTopology(compute = {}) {
|
|
2027
|
+
const appServers = Math.max(1, compute.appServers ?? compute.instances ?? 1);
|
|
2028
|
+
const dedicatedServices = !!compute.servicesServer || appServers > 1;
|
|
2029
|
+
const loadBalancer = appServers > 1 || !!compute.server?.loadBalancer;
|
|
2030
|
+
return {
|
|
2031
|
+
appServers,
|
|
2032
|
+
loadBalancer,
|
|
2033
|
+
dedicatedServices,
|
|
2034
|
+
servicesOnApp: !dedicatedServices
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
function buildFleetServicesEnv(servicesPrivateIp, database) {
|
|
2038
|
+
const env = {
|
|
2039
|
+
REDIS_HOST: servicesPrivateIp,
|
|
2040
|
+
MEILISEARCH_HOST: `http://${servicesPrivateIp}:7700`
|
|
2041
|
+
};
|
|
2042
|
+
if (database?.name) {
|
|
2043
|
+
env.DB_CONNECTION = database.engine === "postgres" ? "pgsql" : "mysql";
|
|
2044
|
+
env.DB_HOST = servicesPrivateIp;
|
|
2045
|
+
env.DB_PORT = String(database.port ?? (database.engine === "postgres" ? 5432 : 3306));
|
|
2046
|
+
env.DB_DATABASE = database.name;
|
|
2047
|
+
if (database.username)
|
|
2048
|
+
env.DB_USERNAME = database.username;
|
|
2049
|
+
if (database.password)
|
|
2050
|
+
env.DB_PASSWORD = database.password;
|
|
2051
|
+
}
|
|
2052
|
+
return env;
|
|
2053
|
+
}
|
|
2054
|
+
function buildFleetServicesBoxProvision(config) {
|
|
2055
|
+
const compute = config.infrastructure?.compute ?? {};
|
|
2056
|
+
const appDatabase = resolveAppDatabase(config);
|
|
2057
|
+
return [
|
|
2058
|
+
...buildServicesProvisionScript(compute.managedServices ?? { mysql: true, redis: true }, { bindPrivate: true }),
|
|
2059
|
+
...buildDatabaseSetupScript(appDatabase, compute.managedServices ?? { mysql: true }),
|
|
2060
|
+
...buildAutoUpdatesScript(true),
|
|
2061
|
+
...buildMonitoringScript(true),
|
|
2062
|
+
...buildAuthorizedKeysScript(compute.sshKeys),
|
|
2063
|
+
...buildNotifierScript(config.notifications),
|
|
2064
|
+
...compute.backups?.enabled ? [...buildBackupProvisionScript({ database: appDatabase, backups: compute.backups })] : []
|
|
2065
|
+
];
|
|
2066
|
+
}
|
|
1996
2067
|
|
|
1997
2068
|
// src/drivers/hetzner/config.ts
|
|
1998
2069
|
import { homedir } from "node:os";
|
|
@@ -2159,7 +2230,13 @@ class HetznerClient {
|
|
|
2159
2230
|
network_zone: options.network ? undefined : options.networkZone ?? "eu-central",
|
|
2160
2231
|
network: options.network,
|
|
2161
2232
|
labels: options.labels,
|
|
2162
|
-
targets: [
|
|
2233
|
+
targets: [
|
|
2234
|
+
{
|
|
2235
|
+
type: "label_selector",
|
|
2236
|
+
label_selector: { selector: options.labelSelector },
|
|
2237
|
+
use_private_ip: !!options.network
|
|
2238
|
+
}
|
|
2239
|
+
],
|
|
2163
2240
|
services: options.services.map((s) => ({
|
|
2164
2241
|
protocol: s.protocol ?? "tcp",
|
|
2165
2242
|
listen_port: s.listenPort,
|
|
@@ -2279,49 +2356,6 @@ runcmd:
|
|
|
2279
2356
|
`;
|
|
2280
2357
|
}
|
|
2281
2358
|
|
|
2282
|
-
// src/drivers/shared/fleet.ts
|
|
2283
|
-
function resolveFleetTopology(compute = {}) {
|
|
2284
|
-
const appServers = Math.max(1, compute.appServers ?? compute.instances ?? 1);
|
|
2285
|
-
const dedicatedServices = !!compute.servicesServer || appServers > 1;
|
|
2286
|
-
const loadBalancer = appServers > 1 || !!compute.server?.loadBalancer;
|
|
2287
|
-
return {
|
|
2288
|
-
appServers,
|
|
2289
|
-
loadBalancer,
|
|
2290
|
-
dedicatedServices,
|
|
2291
|
-
servicesOnApp: !dedicatedServices
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
function buildFleetServicesEnv(servicesPrivateIp, database) {
|
|
2295
|
-
const env2 = {
|
|
2296
|
-
REDIS_HOST: servicesPrivateIp,
|
|
2297
|
-
MEILISEARCH_HOST: `http://${servicesPrivateIp}:7700`
|
|
2298
|
-
};
|
|
2299
|
-
if (database?.name) {
|
|
2300
|
-
env2.DB_CONNECTION = database.engine === "postgres" ? "pgsql" : "mysql";
|
|
2301
|
-
env2.DB_HOST = servicesPrivateIp;
|
|
2302
|
-
env2.DB_PORT = String(database.port ?? (database.engine === "postgres" ? 5432 : 3306));
|
|
2303
|
-
env2.DB_DATABASE = database.name;
|
|
2304
|
-
if (database.username)
|
|
2305
|
-
env2.DB_USERNAME = database.username;
|
|
2306
|
-
if (database.password)
|
|
2307
|
-
env2.DB_PASSWORD = database.password;
|
|
2308
|
-
}
|
|
2309
|
-
return env2;
|
|
2310
|
-
}
|
|
2311
|
-
function buildFleetServicesBoxProvision(config) {
|
|
2312
|
-
const compute = config.infrastructure?.compute ?? {};
|
|
2313
|
-
const appDatabase = resolveAppDatabase(config);
|
|
2314
|
-
return [
|
|
2315
|
-
...buildServicesProvisionScript(compute.managedServices ?? { mysql: true, redis: true }, { bindPrivate: true }),
|
|
2316
|
-
...buildDatabaseSetupScript(appDatabase, compute.managedServices ?? { mysql: true }),
|
|
2317
|
-
...buildAutoUpdatesScript(true),
|
|
2318
|
-
...buildMonitoringScript(true),
|
|
2319
|
-
...buildAuthorizedKeysScript(compute.sshKeys),
|
|
2320
|
-
...buildNotifierScript(config.notifications),
|
|
2321
|
-
...compute.backups?.enabled ? [...buildBackupProvisionScript({ database: appDatabase, backups: compute.backups })] : []
|
|
2322
|
-
];
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
2359
|
// src/drivers/hetzner/firewall-rules.ts
|
|
2326
2360
|
function buildHetznerFirewallRules(config) {
|
|
2327
2361
|
const openPorts = new Set([80, 443, ...config.sitePorts, ...config.allowedPorts ?? []]);
|
|
@@ -2616,7 +2650,13 @@ class HetznerDriver {
|
|
|
2616
2650
|
optimizeForProduction: compute.php?.optimizeForProduction,
|
|
2617
2651
|
ini: compute.php?.ini
|
|
2618
2652
|
});
|
|
2619
|
-
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({
|
|
2653
|
+
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({
|
|
2654
|
+
runtime: "php",
|
|
2655
|
+
phpProvision: appPhp,
|
|
2656
|
+
servicesProvision: appProvision,
|
|
2657
|
+
baked,
|
|
2658
|
+
swapGb: compute.swapGb
|
|
2659
|
+
}));
|
|
2620
2660
|
const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
|
|
2621
2661
|
const appServerIds = existingApp.map((s) => s.id);
|
|
2622
2662
|
if (existingApp.length > topology.appServers) {
|
|
@@ -2931,7 +2971,11 @@ class HetznerDriver {
|
|
|
2931
2971
|
} catch {}
|
|
2932
2972
|
}
|
|
2933
2973
|
const firewalls = await this.client.listFirewalls().catch(() => []);
|
|
2934
|
-
for (const name of [
|
|
2974
|
+
for (const name of [
|
|
2975
|
+
`${slug}-${environment}-app-fw`,
|
|
2976
|
+
`${slug}-${environment}-services-fw`,
|
|
2977
|
+
`${slug}-${environment}-lb-fw`
|
|
2978
|
+
]) {
|
|
2935
2979
|
const fw = firewalls.find((f) => f.name === name);
|
|
2936
2980
|
if (!fw)
|
|
2937
2981
|
continue;
|
|
@@ -3031,7 +3075,9 @@ class HetznerDriver {
|
|
|
3031
3075
|
return exact.map(toTarget);
|
|
3032
3076
|
if (role === "app" || role === "lb" || role === "services") {
|
|
3033
3077
|
const state = await readDriverState(options.stackName ?? `${options.slug}-${options.environment}`);
|
|
3034
|
-
const pinnedIds = [
|
|
3078
|
+
const pinnedIds = [
|
|
3079
|
+
...new Set(role === "app" ? [state?.serverId, ...state?.appServerIds ?? []] : role === "lb" ? [state?.lbServerId] : [state?.servicesServerId])
|
|
3080
|
+
].filter((id) => typeof id === "number");
|
|
3035
3081
|
if (pinnedIds.length > 0) {
|
|
3036
3082
|
const pinned = [];
|
|
3037
3083
|
for (const id of pinnedIds) {
|
|
@@ -3197,7 +3243,7 @@ class HetznerDriver {
|
|
|
3197
3243
|
let lastErr;
|
|
3198
3244
|
while (Date.now() - start < sshTimeoutMs) {
|
|
3199
3245
|
try {
|
|
3200
|
-
execSync(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `
|
|
3246
|
+
execSync(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `'${a.replace(/'/g, "\\\"")}"`).join(" ")} true`, {
|
|
3201
3247
|
stdio: "pipe",
|
|
3202
3248
|
maxBuffer: SSH_MAX_BUFFER
|
|
3203
3249
|
});
|
|
@@ -3388,7 +3434,11 @@ async function ensureSshKey(client, options) {
|
|
|
3388
3434
|
const existing = (await client.listSshKeys()).find((k) => normalizeSshPublicKey(k.public_key) === body);
|
|
3389
3435
|
if (existing)
|
|
3390
3436
|
return { id: existing.id, name: existing.name, created: false };
|
|
3391
|
-
const created = await client.createSshKey({
|
|
3437
|
+
const created = await client.createSshKey({
|
|
3438
|
+
name: options.name,
|
|
3439
|
+
publicKey: options.publicKey.trim(),
|
|
3440
|
+
labels: options.labels
|
|
3441
|
+
});
|
|
3392
3442
|
return { id: created.id, name: created.name, created: true };
|
|
3393
3443
|
}
|
|
3394
3444
|
async function ensureFirewall(client, options) {
|
|
@@ -3429,10 +3479,7 @@ async function sshExec(host, command, options = {}) {
|
|
|
3429
3479
|
stdout: "pipe",
|
|
3430
3480
|
stderr: "pipe"
|
|
3431
3481
|
});
|
|
3432
|
-
const [stdout, stderr] = await Promise.all([
|
|
3433
|
-
new Response(proc.stdout).text(),
|
|
3434
|
-
new Response(proc.stderr).text()
|
|
3435
|
-
]);
|
|
3482
|
+
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
3436
3483
|
const code = await proc.exited;
|
|
3437
3484
|
return { code, stdout, stderr };
|
|
3438
3485
|
}
|
|
@@ -3483,12 +3530,7 @@ async function waitForCloudInit(host, options = {}) {
|
|
|
3483
3530
|
var UBUNTU_2404_AMI_PARAM = "/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id";
|
|
3484
3531
|
function buildBoxUserData(spec) {
|
|
3485
3532
|
const key = spec.sshPublicKey.trim();
|
|
3486
|
-
const lines = [
|
|
3487
|
-
"#cloud-config",
|
|
3488
|
-
"disable_root: false",
|
|
3489
|
-
"ssh_authorized_keys:",
|
|
3490
|
-
` - ${key}`
|
|
3491
|
-
];
|
|
3533
|
+
const lines = ["#cloud-config", "disable_root: false", "ssh_authorized_keys:", ` - ${key}`];
|
|
3492
3534
|
if (spec.bootstrapScript) {
|
|
3493
3535
|
const path = "/var/lib/cloud/box-bootstrap.sh";
|
|
3494
3536
|
const indented = spec.bootstrapScript.split(`
|
|
@@ -3537,7 +3579,13 @@ class HetznerBoxProvisioner {
|
|
|
3537
3579
|
userData: buildBoxUserData(spec),
|
|
3538
3580
|
labels: spec.labels
|
|
3539
3581
|
});
|
|
3540
|
-
return {
|
|
3582
|
+
return {
|
|
3583
|
+
provider: "hetzner",
|
|
3584
|
+
id: String(server.id),
|
|
3585
|
+
name: server.name,
|
|
3586
|
+
publicIp: serverPublicIpv4(server),
|
|
3587
|
+
created
|
|
3588
|
+
};
|
|
3541
3589
|
}
|
|
3542
3590
|
async destroyBox(name) {
|
|
3543
3591
|
const destroyed = [];
|
|
@@ -3634,12 +3682,15 @@ class AwsBoxProvisioner {
|
|
|
3634
3682
|
const existing = await this.findInstance(spec.name, ["pending", "running"]);
|
|
3635
3683
|
if (existing?.InstanceId) {
|
|
3636
3684
|
const instance2 = await this.waitForPublicIp(existing.InstanceId);
|
|
3637
|
-
return {
|
|
3685
|
+
return {
|
|
3686
|
+
provider: "aws",
|
|
3687
|
+
id: instance2.InstanceId,
|
|
3688
|
+
name: spec.name,
|
|
3689
|
+
publicIp: instance2.PublicIpAddress,
|
|
3690
|
+
created: false
|
|
3691
|
+
};
|
|
3638
3692
|
}
|
|
3639
|
-
const [groupId, imageId] = await Promise.all([
|
|
3640
|
-
this.ensureSecurityGroup(spec),
|
|
3641
|
-
this.resolveImage(spec)
|
|
3642
|
-
]);
|
|
3693
|
+
const [groupId, imageId] = await Promise.all([this.ensureSecurityGroup(spec), this.resolveImage(spec)]);
|
|
3643
3694
|
const tags = [
|
|
3644
3695
|
{ Key: "Name", Value: spec.name },
|
|
3645
3696
|
...Object.entries(spec.labels ?? {}).map(([Key, Value]) => ({ Key, Value }))
|
|
@@ -3743,9 +3794,7 @@ function isFileSharedPath(p) {
|
|
|
3743
3794
|
return /\.[a-z0-9]+$/i.test(name);
|
|
3744
3795
|
}
|
|
3745
3796
|
function buildEnsureReleaseLayout(paths, sharedPaths = DEFAULT_SHARED_PATHS) {
|
|
3746
|
-
const lines = [
|
|
3747
|
-
`mkdir -p ${paths.releases} ${paths.shared}`
|
|
3748
|
-
];
|
|
3797
|
+
const lines = [`mkdir -p ${paths.releases} ${paths.shared}`];
|
|
3749
3798
|
for (const p of sharedPaths) {
|
|
3750
3799
|
if (isFileSharedPath(p)) {
|
|
3751
3800
|
lines.push(`mkdir -p "$(dirname ${paths.shared}/${p})"`, `touch ${paths.shared}/${p}`);
|
|
@@ -3767,10 +3816,7 @@ function buildLinkSharedPaths(paths, sharedPaths = DEFAULT_SHARED_PATHS) {
|
|
|
3767
3816
|
return lines;
|
|
3768
3817
|
}
|
|
3769
3818
|
function buildActivateRelease(paths) {
|
|
3770
|
-
return [
|
|
3771
|
-
`ln -sfn ${paths.release} ${paths.current}.tmp`,
|
|
3772
|
-
`mv -Tf ${paths.current}.tmp ${paths.current}`
|
|
3773
|
-
];
|
|
3819
|
+
return [`ln -sfn ${paths.release} ${paths.current}.tmp`, `mv -Tf ${paths.current}.tmp ${paths.current}`];
|
|
3774
3820
|
}
|
|
3775
3821
|
function buildRollbackScript(paths, options = {}) {
|
|
3776
3822
|
const flip = options.to ? [
|
|
@@ -3978,9 +4024,7 @@ function releaseTarballTmpPath(slug, siteName, releaseId) {
|
|
|
3978
4024
|
return `/tmp/${parts}-release.tar.gz`;
|
|
3979
4025
|
}
|
|
3980
4026
|
function buildAwsArtifactFetch(bucket, key, region, destPath) {
|
|
3981
|
-
return [
|
|
3982
|
-
`aws s3 cp "s3://${bucket}/${key}" ${destPath} --region ${region}`
|
|
3983
|
-
];
|
|
4027
|
+
return [`aws s3 cp "s3://${bucket}/${key}" ${destPath} --region ${region}`];
|
|
3984
4028
|
}
|
|
3985
4029
|
function buildLocalArtifactFetch(localPath, destPath) {
|
|
3986
4030
|
return [
|
|
@@ -4224,18 +4268,204 @@ function buildManagementDashboardArtifact(site, options) {
|
|
|
4224
4268
|
logger.info(`Management dashboard: building UI (${site.build})`);
|
|
4225
4269
|
execSync2(site.build, { cwd, stdio: "inherit" });
|
|
4226
4270
|
}
|
|
4227
|
-
const root = isAbsolute(site.root) ? site.root : join4(cwd, site.root);
|
|
4228
|
-
if (!existsSync2(root)) {
|
|
4229
|
-
logger.warn(`Management dashboard: build output not found at ${root} — skipping dashboard artifact.`);
|
|
4230
|
-
return null;
|
|
4271
|
+
const root = isAbsolute(site.root) ? site.root : join4(cwd, site.root);
|
|
4272
|
+
if (!existsSync2(root)) {
|
|
4273
|
+
logger.warn(`Management dashboard: build output not found at ${root} — skipping dashboard artifact.`);
|
|
4274
|
+
return null;
|
|
4275
|
+
}
|
|
4276
|
+
const tarball = join4(tmpdir(), `${options.slug}-${options.siteName ?? MANAGEMENT_DASHBOARD_SITE}-${options.sha}.tar.gz`);
|
|
4277
|
+
execSync2(`tar czf "${tarball}" -C "${root}" .`, { stdio: "inherit" });
|
|
4278
|
+
return tarball;
|
|
4279
|
+
} catch (error) {
|
|
4280
|
+
logger.warn(`Management dashboard: failed to build artifact — ${error?.message ?? error}. Skipping dashboard deploy.`);
|
|
4281
|
+
return null;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
|
|
4285
|
+
// src/drivers/shared/app-frameworks.ts
|
|
4286
|
+
var PHP_SITE_TYPES2 = new Set(["laravel", "php", "statamic", "wordpress"]);
|
|
4287
|
+
function resolveSiteFramework(site) {
|
|
4288
|
+
if (site.framework)
|
|
4289
|
+
return site.framework;
|
|
4290
|
+
if (site.type && PHP_SITE_TYPES2.has(site.type))
|
|
4291
|
+
return "laravel";
|
|
4292
|
+
return "stacks";
|
|
4293
|
+
}
|
|
4294
|
+
var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
|
|
4295
|
+
var BUN_BIN = "/usr/local/bin/bun";
|
|
4296
|
+
var STACKS_CLI = "storage/framework/core/buddy/src/cli.ts";
|
|
4297
|
+
var stacksDriver = {
|
|
4298
|
+
id: "stacks",
|
|
4299
|
+
wrapExec: (command) => command,
|
|
4300
|
+
execEnv: { PATH: "/usr/local/bin:/usr/bin:/bin", BUN_INSTALL: "/root/.bun" },
|
|
4301
|
+
schedulerMode: "daemon",
|
|
4302
|
+
schedulerCommand: () => `${BUN_BIN} ${STACKS_CLI} schedule:run`,
|
|
4303
|
+
queueWorkerCommand: (worker, { current }) => {
|
|
4304
|
+
const flags = [
|
|
4305
|
+
`--queue=${worker.queue || "default"}`,
|
|
4306
|
+
`--sleep=${worker.sleep ?? 3}`,
|
|
4307
|
+
`--tries=${worker.tries ?? 3}`,
|
|
4308
|
+
`--timeout=${worker.timeout ?? 60}`
|
|
4309
|
+
];
|
|
4310
|
+
return `${BUN_BIN} ${current}/${STACKS_CLI} queue:work ${flags.join(" ")}`;
|
|
4311
|
+
}
|
|
4312
|
+
};
|
|
4313
|
+
var laravelDriver = {
|
|
4314
|
+
id: "laravel",
|
|
4315
|
+
wrapExec: (command) => `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${command}'`,
|
|
4316
|
+
schedulerMode: "cron",
|
|
4317
|
+
schedulerCommand: ({ current }) => `cd ${current} && ${PANTRY_ENV_EVAL} && php artisan schedule:run`,
|
|
4318
|
+
queueWorkerCommand: (worker, { current }) => {
|
|
4319
|
+
const artisan = `${current}/artisan`;
|
|
4320
|
+
if (worker.horizon)
|
|
4321
|
+
return `php ${artisan} horizon`;
|
|
4322
|
+
const flags = [
|
|
4323
|
+
worker.connection || "default",
|
|
4324
|
+
`--queue=${worker.queue || "default"}`,
|
|
4325
|
+
`--sleep=${worker.sleep ?? 3}`,
|
|
4326
|
+
`--tries=${worker.tries ?? 3}`,
|
|
4327
|
+
`--timeout=${worker.timeout ?? 60}`,
|
|
4328
|
+
`--memory=${worker.memory ?? 128}`
|
|
4329
|
+
];
|
|
4330
|
+
if (worker.maxJobs)
|
|
4331
|
+
flags.push(`--max-jobs=${worker.maxJobs}`);
|
|
4332
|
+
if (worker.maxTime)
|
|
4333
|
+
flags.push(`--max-time=${worker.maxTime}`);
|
|
4334
|
+
return `php ${artisan} queue:work ${flags.join(" ")}`;
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
var DRIVERS = {
|
|
4338
|
+
stacks: stacksDriver,
|
|
4339
|
+
laravel: laravelDriver
|
|
4340
|
+
};
|
|
4341
|
+
function getAppFrameworkDriver(framework) {
|
|
4342
|
+
return DRIVERS[framework] ?? stacksDriver;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
// src/drivers/shared/app-services.ts
|
|
4346
|
+
function reEscape(value) {
|
|
4347
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4348
|
+
}
|
|
4349
|
+
function cronQuote(value) {
|
|
4350
|
+
return `'${value.split("'").join("'\\''")}'`;
|
|
4351
|
+
}
|
|
4352
|
+
function slugify(value) {
|
|
4353
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unnamed";
|
|
4354
|
+
}
|
|
4355
|
+
function queueUnitName(slug, siteName, index) {
|
|
4356
|
+
return `${slug}-${siteName}-queue-${index}`;
|
|
4357
|
+
}
|
|
4358
|
+
function daemonUnitName(slug, siteName, daemon, index) {
|
|
4359
|
+
return `${slug}-${siteName}-daemon-${daemon.name ? slugify(daemon.name) : slugify(daemon.command).slice(0, 32) || String(index)}`;
|
|
4360
|
+
}
|
|
4361
|
+
function systemdUnit(opts) {
|
|
4362
|
+
const lines = [
|
|
4363
|
+
"[Unit]",
|
|
4364
|
+
`Description=${opts.description}`,
|
|
4365
|
+
"After=network.target",
|
|
4366
|
+
"",
|
|
4367
|
+
"[Service]",
|
|
4368
|
+
"Type=simple",
|
|
4369
|
+
`WorkingDirectory=${opts.workingDir}`,
|
|
4370
|
+
...Object.entries(opts.environment ?? {}).map(([k, v]) => `Environment="${k}=${v}"`),
|
|
4371
|
+
`ExecStart=${opts.execStart}`,
|
|
4372
|
+
`Restart=${opts.restart || "always"}`,
|
|
4373
|
+
"RestartSec=5"
|
|
4374
|
+
];
|
|
4375
|
+
if (opts.user)
|
|
4376
|
+
lines.push(`User=${opts.user}`);
|
|
4377
|
+
if (typeof opts.stopWaitSecs === "number")
|
|
4378
|
+
lines.push(`TimeoutStopSec=${opts.stopWaitSecs}`);
|
|
4379
|
+
lines.push("", "[Install]", "WantedBy=multi-user.target");
|
|
4380
|
+
return `${lines.join(`
|
|
4381
|
+
`)}
|
|
4382
|
+
`;
|
|
4383
|
+
}
|
|
4384
|
+
function writeUnitScript(name, body) {
|
|
4385
|
+
return [
|
|
4386
|
+
`cat > /etc/systemd/system/${name}.service <<'TS_CLOUD_UNIT_EOF'`,
|
|
4387
|
+
body.replace(/\n$/, ""),
|
|
4388
|
+
"TS_CLOUD_UNIT_EOF"
|
|
4389
|
+
];
|
|
4390
|
+
}
|
|
4391
|
+
function schedulerCronPath(slug, siteName) {
|
|
4392
|
+
return `/etc/cron.d/${slug}-${siteName}-scheduler`;
|
|
4393
|
+
}
|
|
4394
|
+
function buildSiteServicesScript(options) {
|
|
4395
|
+
const { slug, siteName, site } = options;
|
|
4396
|
+
const base = options.appBase ?? `/var/www/${siteName}`;
|
|
4397
|
+
const current = `${base}/current`;
|
|
4398
|
+
const driver = getAppFrameworkDriver(resolveSiteFramework(site));
|
|
4399
|
+
const ctx = { current };
|
|
4400
|
+
const out = [];
|
|
4401
|
+
const desiredUnits = [];
|
|
4402
|
+
const queues = site.queues || [];
|
|
4403
|
+
queues.forEach((worker, qIndex) => {
|
|
4404
|
+
const processes = Math.max(1, worker.processes ?? 1);
|
|
4405
|
+
for (let p = 0;p < processes; p++) {
|
|
4406
|
+
const name = queueUnitName(slug, siteName, desiredUnits.length);
|
|
4407
|
+
desiredUnits.push(name);
|
|
4408
|
+
out.push(...writeUnitScript(name, systemdUnit({
|
|
4409
|
+
description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
|
|
4410
|
+
workingDir: current,
|
|
4411
|
+
execStart: driver.wrapExec(driver.queueWorkerCommand(worker, ctx)),
|
|
4412
|
+
environment: driver.execEnv,
|
|
4413
|
+
stopWaitSecs: worker.stopWaitSecs ?? 90
|
|
4414
|
+
})));
|
|
4415
|
+
}
|
|
4416
|
+
});
|
|
4417
|
+
const daemons = site.daemons || [];
|
|
4418
|
+
daemons.forEach((daemon, dIndex) => {
|
|
4419
|
+
const processes = Math.max(1, daemon.processes ?? 1);
|
|
4420
|
+
for (let p = 0;p < processes; p++) {
|
|
4421
|
+
const name = `${daemonUnitName(slug, siteName, daemon, dIndex)}-${p}`;
|
|
4422
|
+
desiredUnits.push(name);
|
|
4423
|
+
out.push(...writeUnitScript(name, systemdUnit({
|
|
4424
|
+
description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
|
|
4425
|
+
workingDir: daemon.directory || current,
|
|
4426
|
+
execStart: driver.wrapExec(daemon.command),
|
|
4427
|
+
environment: driver.execEnv,
|
|
4428
|
+
restart: daemon.restart,
|
|
4429
|
+
user: daemon.user
|
|
4430
|
+
})));
|
|
4431
|
+
}
|
|
4432
|
+
});
|
|
4433
|
+
const scheduler = site.scheduler;
|
|
4434
|
+
const schedulerEnabled = scheduler === true || typeof scheduler === "object" && scheduler !== null;
|
|
4435
|
+
const schedulerUnit = `${slug}-${siteName}-scheduler`;
|
|
4436
|
+
if (schedulerEnabled && driver.schedulerMode === "daemon") {
|
|
4437
|
+
desiredUnits.push(schedulerUnit);
|
|
4438
|
+
out.push(...writeUnitScript(schedulerUnit, systemdUnit({
|
|
4439
|
+
description: `${siteName} scheduler (managed by ts-cloud)`,
|
|
4440
|
+
workingDir: current,
|
|
4441
|
+
execStart: driver.wrapExec(driver.schedulerCommand(ctx)),
|
|
4442
|
+
environment: driver.execEnv
|
|
4443
|
+
})));
|
|
4444
|
+
}
|
|
4445
|
+
const desiredList = desiredUnits.map((n) => `${n}.service`).join(" ");
|
|
4446
|
+
out.push("systemctl daemon-reload", `TS_CLOUD_DESIRED="${desiredList}"`, `for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E '^${reEscape(slug)}-${reEscape(siteName)}-((queue|daemon)-.*|scheduler)\\.service$' || true); do`, ' case " $TS_CLOUD_DESIRED " in', ' *" $unit "*) ;;', ' *) systemctl stop "$unit" 2>/dev/null || true; systemctl disable "$unit" 2>/dev/null || true; rm -f "/etc/systemd/system/$unit" ;;', " esac", "done", "systemctl daemon-reload");
|
|
4447
|
+
for (const name of desiredUnits) {
|
|
4448
|
+
out.push(`systemctl enable ${name}.service`, `systemctl restart ${name}.service`);
|
|
4449
|
+
}
|
|
4450
|
+
const cronPath = schedulerCronPath(slug, siteName);
|
|
4451
|
+
if (schedulerEnabled && driver.schedulerMode === "cron") {
|
|
4452
|
+
const heartbeat = typeof scheduler === "object" && scheduler !== null ? scheduler : undefined;
|
|
4453
|
+
let command = `${driver.schedulerCommand(ctx)} >> /dev/null 2>&1`;
|
|
4454
|
+
if (heartbeat?.heartbeatUrl) {
|
|
4455
|
+
const method = heartbeat.heartbeatMethod || "GET";
|
|
4456
|
+
const methodFlag = method === "GET" ? "" : `-X ${method} `;
|
|
4457
|
+
command += ` && curl -fsS -m 10 ${methodFlag}${cronQuote(heartbeat.heartbeatUrl)} >/dev/null 2>&1`;
|
|
4231
4458
|
}
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4237
|
-
return null;
|
|
4459
|
+
const cron = `* * * * * root ${command}
|
|
4460
|
+
`.replace(/%/g, "\\%");
|
|
4461
|
+
out.push(`cat > ${cronPath} <<'TS_CLOUD_CRON_EOF'`, cron.replace(/\n$/, ""), "TS_CLOUD_CRON_EOF", `chmod 644 ${cronPath}`);
|
|
4462
|
+
} else {
|
|
4463
|
+
out.push(`rm -f ${cronPath}`);
|
|
4238
4464
|
}
|
|
4465
|
+
return out;
|
|
4466
|
+
}
|
|
4467
|
+
function siteHasServices(site) {
|
|
4468
|
+
return !!(site.queues?.length || site.daemons?.length || site.scheduler);
|
|
4239
4469
|
}
|
|
4240
4470
|
|
|
4241
4471
|
// src/drivers/shared/certbot.ts
|
|
@@ -4276,12 +4506,7 @@ function buildDnsCredentialsScript(dns) {
|
|
|
4276
4506
|
const file = dnsCredentialsPath(dns.provider);
|
|
4277
4507
|
const lines = Object.entries(dns.credentials).map(([k, v]) => `${k} = ${v}`).join(`
|
|
4278
4508
|
`);
|
|
4279
|
-
return [
|
|
4280
|
-
`cat > ${file} <<'TS_CLOUD_DNSCREDS_EOF'`,
|
|
4281
|
-
lines,
|
|
4282
|
-
"TS_CLOUD_DNSCREDS_EOF",
|
|
4283
|
-
`chmod 600 ${file}`
|
|
4284
|
-
];
|
|
4509
|
+
return [`cat > ${file} <<'TS_CLOUD_DNSCREDS_EOF'`, lines, "TS_CLOUD_DNSCREDS_EOF", `chmod 600 ${file}`];
|
|
4285
4510
|
}
|
|
4286
4511
|
function buildCertbotIssueScript(options) {
|
|
4287
4512
|
const dns = options.dns;
|
|
@@ -4334,10 +4559,7 @@ function buildGitCheckoutScript(options) {
|
|
|
4334
4559
|
const { repository, releaseDir, commit } = options;
|
|
4335
4560
|
const branch = repository.branch || DEFAULT_DEPLOY_BRANCH;
|
|
4336
4561
|
const url = repository.url;
|
|
4337
|
-
const lines = [
|
|
4338
|
-
`rm -rf ${releaseDir}`,
|
|
4339
|
-
`mkdir -p ${releaseDir}`
|
|
4340
|
-
];
|
|
4562
|
+
const lines = [`rm -rf ${releaseDir}`, `mkdir -p ${releaseDir}`];
|
|
4341
4563
|
if (repository.strategy === "tag") {
|
|
4342
4564
|
if (repository.tag) {
|
|
4343
4565
|
lines.push(`git clone -q --depth 1 --branch ${shellQuote(repository.tag)} ${shellQuote(url)} ${releaseDir}`);
|
|
@@ -4385,17 +4607,10 @@ function defaultDeployScriptFor(type) {
|
|
|
4385
4607
|
];
|
|
4386
4608
|
case "php":
|
|
4387
4609
|
case "wordpress":
|
|
4388
|
-
return [
|
|
4389
|
-
MACRO_CREATE_RELEASE,
|
|
4390
|
-
`${COMPOSER_INSTALL} || true`,
|
|
4391
|
-
MACRO_ACTIVATE_RELEASE
|
|
4392
|
-
];
|
|
4610
|
+
return [MACRO_CREATE_RELEASE, `${COMPOSER_INSTALL} || true`, MACRO_ACTIVATE_RELEASE];
|
|
4393
4611
|
case "static":
|
|
4394
4612
|
case "spa":
|
|
4395
|
-
return [
|
|
4396
|
-
MACRO_CREATE_RELEASE,
|
|
4397
|
-
MACRO_ACTIVATE_RELEASE
|
|
4398
|
-
];
|
|
4613
|
+
return [MACRO_CREATE_RELEASE, MACRO_ACTIVATE_RELEASE];
|
|
4399
4614
|
}
|
|
4400
4615
|
}
|
|
4401
4616
|
function substituteBins(line, phpBin) {
|
|
@@ -4403,12 +4618,7 @@ function substituteBins(line, phpBin) {
|
|
|
4403
4618
|
}
|
|
4404
4619
|
function writeSharedEnv(sharedEnvPath, env2) {
|
|
4405
4620
|
const body = formatEnvFile(env2);
|
|
4406
|
-
return [
|
|
4407
|
-
`cat > ${sharedEnvPath} <<'TS_CLOUD_ENV_EOF'`,
|
|
4408
|
-
body,
|
|
4409
|
-
"TS_CLOUD_ENV_EOF",
|
|
4410
|
-
`chmod 600 ${sharedEnvPath}`
|
|
4411
|
-
];
|
|
4621
|
+
return [`cat > ${sharedEnvPath} <<'TS_CLOUD_ENV_EOF'`, body, "TS_CLOUD_ENV_EOF", `chmod 600 ${sharedEnvPath}`];
|
|
4412
4622
|
}
|
|
4413
4623
|
function writeFileHeredoc2(path, body, marker) {
|
|
4414
4624
|
return [`cat > ${path} <<'${marker}'`, body.replace(/\n$/, ""), marker, `chmod 600 ${path}`];
|
|
@@ -4485,192 +4695,6 @@ function buildLaravelDeployScript(options) {
|
|
|
4485
4695
|
return out;
|
|
4486
4696
|
}
|
|
4487
4697
|
|
|
4488
|
-
// src/drivers/shared/app-frameworks.ts
|
|
4489
|
-
var PHP_SITE_TYPES2 = new Set(["laravel", "php", "statamic", "wordpress"]);
|
|
4490
|
-
function resolveSiteFramework(site) {
|
|
4491
|
-
if (site.framework)
|
|
4492
|
-
return site.framework;
|
|
4493
|
-
if (site.type && PHP_SITE_TYPES2.has(site.type))
|
|
4494
|
-
return "laravel";
|
|
4495
|
-
return "stacks";
|
|
4496
|
-
}
|
|
4497
|
-
var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
|
|
4498
|
-
var BUN_BIN = "/usr/local/bin/bun";
|
|
4499
|
-
var STACKS_CLI = "storage/framework/core/buddy/src/cli.ts";
|
|
4500
|
-
var stacksDriver = {
|
|
4501
|
-
id: "stacks",
|
|
4502
|
-
wrapExec: (command) => command,
|
|
4503
|
-
execEnv: { PATH: "/usr/local/bin:/usr/bin:/bin", BUN_INSTALL: "/root/.bun" },
|
|
4504
|
-
schedulerMode: "daemon",
|
|
4505
|
-
schedulerCommand: () => `${BUN_BIN} ${STACKS_CLI} schedule:run`,
|
|
4506
|
-
queueWorkerCommand: (worker, { current }) => {
|
|
4507
|
-
const flags = [
|
|
4508
|
-
`--queue=${worker.queue || "default"}`,
|
|
4509
|
-
`--sleep=${worker.sleep ?? 3}`,
|
|
4510
|
-
`--tries=${worker.tries ?? 3}`,
|
|
4511
|
-
`--timeout=${worker.timeout ?? 60}`
|
|
4512
|
-
];
|
|
4513
|
-
return `${BUN_BIN} ${current}/${STACKS_CLI} queue:work ${flags.join(" ")}`;
|
|
4514
|
-
}
|
|
4515
|
-
};
|
|
4516
|
-
var laravelDriver = {
|
|
4517
|
-
id: "laravel",
|
|
4518
|
-
wrapExec: (command) => `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${command}'`,
|
|
4519
|
-
schedulerMode: "cron",
|
|
4520
|
-
schedulerCommand: ({ current }) => `cd ${current} && ${PANTRY_ENV_EVAL} && php artisan schedule:run`,
|
|
4521
|
-
queueWorkerCommand: (worker, { current }) => {
|
|
4522
|
-
const artisan = `${current}/artisan`;
|
|
4523
|
-
if (worker.horizon)
|
|
4524
|
-
return `php ${artisan} horizon`;
|
|
4525
|
-
const flags = [
|
|
4526
|
-
worker.connection || "default",
|
|
4527
|
-
`--queue=${worker.queue || "default"}`,
|
|
4528
|
-
`--sleep=${worker.sleep ?? 3}`,
|
|
4529
|
-
`--tries=${worker.tries ?? 3}`,
|
|
4530
|
-
`--timeout=${worker.timeout ?? 60}`,
|
|
4531
|
-
`--memory=${worker.memory ?? 128}`
|
|
4532
|
-
];
|
|
4533
|
-
if (worker.maxJobs)
|
|
4534
|
-
flags.push(`--max-jobs=${worker.maxJobs}`);
|
|
4535
|
-
if (worker.maxTime)
|
|
4536
|
-
flags.push(`--max-time=${worker.maxTime}`);
|
|
4537
|
-
return `php ${artisan} queue:work ${flags.join(" ")}`;
|
|
4538
|
-
}
|
|
4539
|
-
};
|
|
4540
|
-
var DRIVERS = {
|
|
4541
|
-
stacks: stacksDriver,
|
|
4542
|
-
laravel: laravelDriver
|
|
4543
|
-
};
|
|
4544
|
-
function getAppFrameworkDriver(framework) {
|
|
4545
|
-
return DRIVERS[framework] ?? stacksDriver;
|
|
4546
|
-
}
|
|
4547
|
-
|
|
4548
|
-
// src/drivers/shared/app-services.ts
|
|
4549
|
-
function reEscape(value) {
|
|
4550
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4551
|
-
}
|
|
4552
|
-
function cronQuote(value) {
|
|
4553
|
-
return `'${value.split("'").join("'\\''")}'`;
|
|
4554
|
-
}
|
|
4555
|
-
function slugify(value) {
|
|
4556
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unnamed";
|
|
4557
|
-
}
|
|
4558
|
-
function queueUnitName(slug, siteName, index) {
|
|
4559
|
-
return `${slug}-${siteName}-queue-${index}`;
|
|
4560
|
-
}
|
|
4561
|
-
function daemonUnitName(slug, siteName, daemon, index) {
|
|
4562
|
-
return `${slug}-${siteName}-daemon-${daemon.name ? slugify(daemon.name) : slugify(daemon.command).slice(0, 32) || String(index)}`;
|
|
4563
|
-
}
|
|
4564
|
-
function systemdUnit(opts) {
|
|
4565
|
-
const lines = [
|
|
4566
|
-
"[Unit]",
|
|
4567
|
-
`Description=${opts.description}`,
|
|
4568
|
-
"After=network.target",
|
|
4569
|
-
"",
|
|
4570
|
-
"[Service]",
|
|
4571
|
-
"Type=simple",
|
|
4572
|
-
`WorkingDirectory=${opts.workingDir}`,
|
|
4573
|
-
...Object.entries(opts.environment ?? {}).map(([k, v]) => `Environment="${k}=${v}"`),
|
|
4574
|
-
`ExecStart=${opts.execStart}`,
|
|
4575
|
-
`Restart=${opts.restart || "always"}`,
|
|
4576
|
-
"RestartSec=5"
|
|
4577
|
-
];
|
|
4578
|
-
if (opts.user)
|
|
4579
|
-
lines.push(`User=${opts.user}`);
|
|
4580
|
-
if (typeof opts.stopWaitSecs === "number")
|
|
4581
|
-
lines.push(`TimeoutStopSec=${opts.stopWaitSecs}`);
|
|
4582
|
-
lines.push("", "[Install]", "WantedBy=multi-user.target");
|
|
4583
|
-
return `${lines.join(`
|
|
4584
|
-
`)}
|
|
4585
|
-
`;
|
|
4586
|
-
}
|
|
4587
|
-
function writeUnitScript(name, body) {
|
|
4588
|
-
return [
|
|
4589
|
-
`cat > /etc/systemd/system/${name}.service <<'TS_CLOUD_UNIT_EOF'`,
|
|
4590
|
-
body.replace(/\n$/, ""),
|
|
4591
|
-
"TS_CLOUD_UNIT_EOF"
|
|
4592
|
-
];
|
|
4593
|
-
}
|
|
4594
|
-
function schedulerCronPath(slug, siteName) {
|
|
4595
|
-
return `/etc/cron.d/${slug}-${siteName}-scheduler`;
|
|
4596
|
-
}
|
|
4597
|
-
function buildSiteServicesScript(options) {
|
|
4598
|
-
const { slug, siteName, site } = options;
|
|
4599
|
-
const base = options.appBase ?? `/var/www/${siteName}`;
|
|
4600
|
-
const current = `${base}/current`;
|
|
4601
|
-
const driver = getAppFrameworkDriver(resolveSiteFramework(site));
|
|
4602
|
-
const ctx = { current };
|
|
4603
|
-
const out = [];
|
|
4604
|
-
const desiredUnits = [];
|
|
4605
|
-
const queues = site.queues || [];
|
|
4606
|
-
queues.forEach((worker, qIndex) => {
|
|
4607
|
-
const processes = Math.max(1, worker.processes ?? 1);
|
|
4608
|
-
for (let p = 0;p < processes; p++) {
|
|
4609
|
-
const name = queueUnitName(slug, siteName, desiredUnits.length);
|
|
4610
|
-
desiredUnits.push(name);
|
|
4611
|
-
out.push(...writeUnitScript(name, systemdUnit({
|
|
4612
|
-
description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
|
|
4613
|
-
workingDir: current,
|
|
4614
|
-
execStart: driver.wrapExec(driver.queueWorkerCommand(worker, ctx)),
|
|
4615
|
-
environment: driver.execEnv,
|
|
4616
|
-
stopWaitSecs: worker.stopWaitSecs ?? 90
|
|
4617
|
-
})));
|
|
4618
|
-
}
|
|
4619
|
-
});
|
|
4620
|
-
const daemons = site.daemons || [];
|
|
4621
|
-
daemons.forEach((daemon, dIndex) => {
|
|
4622
|
-
const processes = Math.max(1, daemon.processes ?? 1);
|
|
4623
|
-
for (let p = 0;p < processes; p++) {
|
|
4624
|
-
const name = `${daemonUnitName(slug, siteName, daemon, dIndex)}-${p}`;
|
|
4625
|
-
desiredUnits.push(name);
|
|
4626
|
-
out.push(...writeUnitScript(name, systemdUnit({
|
|
4627
|
-
description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
|
|
4628
|
-
workingDir: daemon.directory || current,
|
|
4629
|
-
execStart: driver.wrapExec(daemon.command),
|
|
4630
|
-
environment: driver.execEnv,
|
|
4631
|
-
restart: daemon.restart,
|
|
4632
|
-
user: daemon.user
|
|
4633
|
-
})));
|
|
4634
|
-
}
|
|
4635
|
-
});
|
|
4636
|
-
const scheduler = site.scheduler;
|
|
4637
|
-
const schedulerEnabled = scheduler === true || typeof scheduler === "object" && scheduler !== null;
|
|
4638
|
-
const schedulerUnit = `${slug}-${siteName}-scheduler`;
|
|
4639
|
-
if (schedulerEnabled && driver.schedulerMode === "daemon") {
|
|
4640
|
-
desiredUnits.push(schedulerUnit);
|
|
4641
|
-
out.push(...writeUnitScript(schedulerUnit, systemdUnit({
|
|
4642
|
-
description: `${siteName} scheduler (managed by ts-cloud)`,
|
|
4643
|
-
workingDir: current,
|
|
4644
|
-
execStart: driver.wrapExec(driver.schedulerCommand(ctx)),
|
|
4645
|
-
environment: driver.execEnv
|
|
4646
|
-
})));
|
|
4647
|
-
}
|
|
4648
|
-
const desiredList = desiredUnits.map((n) => `${n}.service`).join(" ");
|
|
4649
|
-
out.push("systemctl daemon-reload", `TS_CLOUD_DESIRED="${desiredList}"`, `for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E '^${reEscape(slug)}-${reEscape(siteName)}-((queue|daemon)-.*|scheduler)\\.service$' || true); do`, ' case " $TS_CLOUD_DESIRED " in', ' *" $unit "*) ;;', ' *) systemctl stop "$unit" 2>/dev/null || true; systemctl disable "$unit" 2>/dev/null || true; rm -f "/etc/systemd/system/$unit" ;;', " esac", "done", "systemctl daemon-reload");
|
|
4650
|
-
for (const name of desiredUnits) {
|
|
4651
|
-
out.push(`systemctl enable ${name}.service`, `systemctl restart ${name}.service`);
|
|
4652
|
-
}
|
|
4653
|
-
const cronPath = schedulerCronPath(slug, siteName);
|
|
4654
|
-
if (schedulerEnabled && driver.schedulerMode === "cron") {
|
|
4655
|
-
const heartbeat = typeof scheduler === "object" && scheduler !== null ? scheduler : undefined;
|
|
4656
|
-
let command = `${driver.schedulerCommand(ctx)} >> /dev/null 2>&1`;
|
|
4657
|
-
if (heartbeat?.heartbeatUrl) {
|
|
4658
|
-
const method = heartbeat.heartbeatMethod || "GET";
|
|
4659
|
-
const methodFlag = method === "GET" ? "" : `-X ${method} `;
|
|
4660
|
-
command += ` && curl -fsS -m 10 ${methodFlag}${cronQuote(heartbeat.heartbeatUrl)} >/dev/null 2>&1`;
|
|
4661
|
-
}
|
|
4662
|
-
const cron = `* * * * * root ${command}
|
|
4663
|
-
`.replace(/%/g, "\\%");
|
|
4664
|
-
out.push(`cat > ${cronPath} <<'TS_CLOUD_CRON_EOF'`, cron.replace(/\n$/, ""), "TS_CLOUD_CRON_EOF", `chmod 644 ${cronPath}`);
|
|
4665
|
-
} else {
|
|
4666
|
-
out.push(`rm -f ${cronPath}`);
|
|
4667
|
-
}
|
|
4668
|
-
return out;
|
|
4669
|
-
}
|
|
4670
|
-
function siteHasServices(site) {
|
|
4671
|
-
return !!(site.queues?.length || site.daemons?.length || site.scheduler);
|
|
4672
|
-
}
|
|
4673
|
-
|
|
4674
4698
|
// src/drivers/shared/php-fpm-pool.ts
|
|
4675
4699
|
var PHP_FPM_POOL_DIR = "/var/lib/pantry/php-fpm/pool.d";
|
|
4676
4700
|
var POOL_PORT_BASE = 9100;
|
|
@@ -4742,16 +4766,7 @@ var noopLogger2 = {
|
|
|
4742
4766
|
success: () => {}
|
|
4743
4767
|
};
|
|
4744
4768
|
async function deploySiteRelease(driver, options, logger = noopLogger2) {
|
|
4745
|
-
const {
|
|
4746
|
-
config,
|
|
4747
|
-
environment,
|
|
4748
|
-
siteName,
|
|
4749
|
-
site,
|
|
4750
|
-
slug,
|
|
4751
|
-
sha,
|
|
4752
|
-
runtime,
|
|
4753
|
-
localTarballPath
|
|
4754
|
-
} = options;
|
|
4769
|
+
const { config, environment, siteName, site, slug, sha, runtime, localTarballPath } = options;
|
|
4755
4770
|
const remoteKey = `releases/${siteName}/${sha}.tar.gz`;
|
|
4756
4771
|
const stackName = resolveProjectStackName(config, environment);
|
|
4757
4772
|
const outputs = await driver.getComputeOutputs({ config, environment });
|
|
@@ -4807,7 +4822,15 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
|
|
|
4807
4822
|
logger.step(`Deploying PHP site '${siteName}' to ${targets.length} target(s)...`);
|
|
4808
4823
|
const phpResult = await driver.runRemoteDeploy({
|
|
4809
4824
|
targets,
|
|
4810
|
-
commands: [
|
|
4825
|
+
commands: [
|
|
4826
|
+
...buildSiteOwnerGuard(appBase2, slug),
|
|
4827
|
+
...deployScript,
|
|
4828
|
+
...poolScript,
|
|
4829
|
+
...vhostScript,
|
|
4830
|
+
...sslScript,
|
|
4831
|
+
...servicesScript2,
|
|
4832
|
+
...healthCheckScript
|
|
4833
|
+
],
|
|
4811
4834
|
comment: `ts-cloud deploy ${slug}/${siteName}@${sha}`,
|
|
4812
4835
|
tags: { Project: slug, Environment: environment, Role: "app" }
|
|
4813
4836
|
});
|
|
@@ -4970,7 +4993,13 @@ async function deployAllComputeSites(options) {
|
|
|
4970
4993
|
let dashboardTarball = null;
|
|
4971
4994
|
const dashboardTarballBySite = new Map;
|
|
4972
4995
|
if (injectedDashboardSites.length > 0) {
|
|
4973
|
-
dashboardTarball = buildManagementDashboardArtifact(config.sites?.[injectedDashboardSites[0]], {
|
|
4996
|
+
dashboardTarball = buildManagementDashboardArtifact(config.sites?.[injectedDashboardSites[0]], {
|
|
4997
|
+
cwd,
|
|
4998
|
+
slug,
|
|
4999
|
+
sha,
|
|
5000
|
+
siteName: injectedDashboardSites[0],
|
|
5001
|
+
logger: { info: logger.info, warn: logger.warn }
|
|
5002
|
+
});
|
|
4974
5003
|
if (!dashboardTarball) {
|
|
4975
5004
|
logger.warn("Management dashboard: no artifact available — skipping dashboard site(s) for this deploy.");
|
|
4976
5005
|
for (const name of injectedDashboardSites) {
|
|
@@ -5182,7 +5211,11 @@ function cacheBehavior(pathPattern, kind) {
|
|
|
5182
5211
|
TrustedKeyGroups: { Enabled: false, Quantity: 0 },
|
|
5183
5212
|
LambdaFunctionAssociations: { Quantity: 0 },
|
|
5184
5213
|
FunctionAssociations: { Quantity: 0 },
|
|
5185
|
-
AllowedMethods: kind === "dynamic" ? {
|
|
5214
|
+
AllowedMethods: kind === "dynamic" ? {
|
|
5215
|
+
Quantity: 7,
|
|
5216
|
+
Items: ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"],
|
|
5217
|
+
CachedMethods: { Quantity: 2, Items: ["GET", "HEAD"] }
|
|
5218
|
+
} : { Quantity: 3, Items: ["GET", "HEAD", "OPTIONS"], CachedMethods: { Quantity: 2, Items: ["GET", "HEAD"] } }
|
|
5186
5219
|
};
|
|
5187
5220
|
if (pathPattern !== null)
|
|
5188
5221
|
base.PathPattern = pathPattern;
|
|
@@ -5207,24 +5240,26 @@ function buildCloudFrontOriginConfig(options) {
|
|
|
5207
5240
|
DefaultRootObject: "",
|
|
5208
5241
|
Origins: {
|
|
5209
5242
|
Quantity: 1,
|
|
5210
|
-
Items: [
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5243
|
+
Items: [
|
|
5244
|
+
{
|
|
5245
|
+
Id: ORIGIN_ID,
|
|
5246
|
+
DomainName: options.originDomain,
|
|
5247
|
+
OriginPath: "",
|
|
5248
|
+
CustomHeaders: customHeaders,
|
|
5249
|
+
CustomOriginConfig: {
|
|
5250
|
+
HTTPPort: 80,
|
|
5251
|
+
HTTPSPort: 443,
|
|
5252
|
+
OriginProtocolPolicy: "https-only",
|
|
5253
|
+
OriginSslProtocols: { Quantity: 1, Items: ["TLSv1.2"] },
|
|
5254
|
+
OriginReadTimeout: 30,
|
|
5255
|
+
OriginKeepaliveTimeout: 5
|
|
5256
|
+
},
|
|
5257
|
+
ConnectionAttempts: 3,
|
|
5258
|
+
ConnectionTimeout: 10,
|
|
5259
|
+
OriginShield: options.originShield ? { Enabled: true, OriginShieldRegion: options.originShieldRegion } : { Enabled: false },
|
|
5260
|
+
OriginAccessControlId: ""
|
|
5261
|
+
}
|
|
5262
|
+
]
|
|
5228
5263
|
},
|
|
5229
5264
|
OriginGroups: { Quantity: 0 },
|
|
5230
5265
|
DefaultCacheBehavior: cacheBehavior(null, "static"),
|
|
@@ -5246,4 +5281,4 @@ function buildCloudFrontOriginConfig(options) {
|
|
|
5246
5281
|
CustomErrorResponses: { Quantity: 0 }
|
|
5247
5282
|
};
|
|
5248
5283
|
}
|
|
5249
|
-
export { siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig,
|
|
5284
|
+
export { PANTRY_PROJECT_DIR, pgAdminCommand, BACKUP_RUNNER_PATH, buildBackupRestoreScript, siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, shipsARelease, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, buildUbuntuBootstrapScript, AwsDriver, resolveHetznerLocation, HetznerClient, resolveHetznerApiToken2 as resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, releaseTarballTmpPath, buildAwsArtifactFetch, buildLocalArtifactFetch, buildHostCleanupScript, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, renewRpxCertificates, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
|