@stacksjs/ts-cloud 0.7.56 → 0.7.58

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.
Files changed (467) hide show
  1. package/dist/alerts/alerts.test.d.ts +1 -0
  2. package/dist/alerts/delivery.d.ts +45 -0
  3. package/dist/alerts/evaluator.d.ts +12 -0
  4. package/dist/alerts/health.d.ts +34 -0
  5. package/dist/alerts/index.d.ts +6 -0
  6. package/dist/alerts/model.d.ts +170 -0
  7. package/dist/alerts/store.d.ts +90 -0
  8. package/dist/alerts/telemetry.d.ts +3 -0
  9. package/dist/api/client.d.ts +197 -0
  10. package/dist/api/handler.d.ts +18 -0
  11. package/dist/api/handler.test.d.ts +1 -0
  12. package/dist/api/index.d.ts +6 -0
  13. package/dist/api/openapi.d.ts +2 -0
  14. package/dist/api/service.d.ts +28 -0
  15. package/dist/api/types.d.ts +119 -0
  16. package/dist/auth/encryption.d.ts +3 -0
  17. package/dist/auth/index.d.ts +8 -0
  18. package/dist/auth/mailer.d.ts +9 -0
  19. package/dist/auth/oidc.d.ts +36 -0
  20. package/dist/auth/oidc.test.d.ts +1 -0
  21. package/dist/auth/store.d.ts +121 -0
  22. package/dist/auth/store.test.d.ts +1 -0
  23. package/dist/auth/totp.d.ts +11 -0
  24. package/dist/auth/totp.test.d.ts +1 -0
  25. package/dist/auth/types.d.ts +138 -0
  26. package/dist/automation/index.d.ts +2 -0
  27. package/dist/automation/store.d.ts +46 -0
  28. package/dist/automation/store.test.d.ts +1 -0
  29. package/dist/automation/types.d.ts +66 -0
  30. package/dist/aws/acm.d.ts +0 -4
  31. package/dist/aws/application-autoscaling.d.ts +0 -5
  32. package/dist/aws/backup.d.ts +42 -0
  33. package/dist/aws/bedrock.d.ts +0 -5
  34. package/dist/aws/client.d.ts +3 -6
  35. package/dist/aws/cloudformation.d.ts +0 -4
  36. package/dist/aws/cloudfront-media.test.d.ts +1 -0
  37. package/dist/aws/cloudfront-origin.test.d.ts +1 -0
  38. package/dist/aws/cloudfront.d.ts +87 -3
  39. package/dist/aws/cloudwatch-logs.d.ts +2 -4
  40. package/dist/aws/cloudwatch.d.ts +1 -1
  41. package/dist/aws/comprehend.d.ts +0 -5
  42. package/dist/aws/connect.d.ts +0 -4
  43. package/dist/aws/cost-explorer-cache.d.ts +1 -15
  44. package/dist/aws/cost-explorer.d.ts +18 -9
  45. package/dist/aws/cost-explorer.test.d.ts +1 -0
  46. package/dist/aws/dynamodb.d.ts +0 -4
  47. package/dist/aws/ec2.d.ts +0 -4
  48. package/dist/aws/ecr.d.ts +0 -4
  49. package/dist/aws/ecs.d.ts +17 -7
  50. package/dist/aws/efs.d.ts +0 -4
  51. package/dist/aws/elasticache.d.ts +0 -4
  52. package/dist/aws/elbv2.d.ts +0 -7
  53. package/dist/aws/email.d.ts +0 -10
  54. package/dist/aws/eventbridge.d.ts +0 -4
  55. package/dist/aws/iam.d.ts +1 -5
  56. package/dist/aws/imap-server.d.ts +0 -7
  57. package/dist/aws/index.d.ts +4 -3
  58. package/dist/aws/index.js +31 -31
  59. package/dist/aws/kendra.d.ts +0 -5
  60. package/dist/aws/lambda.d.ts +3 -5
  61. package/dist/aws/opensearch.d.ts +0 -4
  62. package/dist/aws/personalize.d.ts +0 -5
  63. package/dist/aws/polly.d.ts +0 -5
  64. package/dist/aws/rds.d.ts +59 -4
  65. package/dist/aws/rekognition.d.ts +0 -5
  66. package/dist/aws/route53-domains.d.ts +0 -4
  67. package/dist/aws/route53.d.ts +0 -4
  68. package/dist/aws/s3.d.ts +10 -6
  69. package/dist/aws/scheduler.d.ts +0 -4
  70. package/dist/aws/secrets-manager.d.ts +0 -4
  71. package/dist/aws/setup-sms.d.ts +0 -14
  72. package/dist/aws/sms.d.ts +0 -10
  73. package/dist/aws/smtp-server.d.ts +0 -11
  74. package/dist/aws/sns.d.ts +0 -4
  75. package/dist/aws/sqs.d.ts +0 -4
  76. package/dist/aws/ssm.d.ts +0 -4
  77. package/dist/aws/sts.d.ts +0 -4
  78. package/dist/aws/support.d.ts +0 -4
  79. package/dist/aws/textract.d.ts +0 -5
  80. package/dist/aws/transcribe.d.ts +0 -4
  81. package/dist/aws/translate.d.ts +0 -5
  82. package/dist/aws/voice.d.ts +0 -11
  83. package/dist/aws/xray.d.ts +1 -0
  84. package/dist/backups/aws-database-source.d.ts +19 -0
  85. package/dist/backups/aws-database-source.test.d.ts +1 -0
  86. package/dist/backups/aws-infrastructure-source.d.ts +43 -0
  87. package/dist/backups/aws-infrastructure-source.test.d.ts +1 -0
  88. package/dist/backups/control-plane-source.d.ts +16 -0
  89. package/dist/backups/control-plane-source.test.d.ts +1 -0
  90. package/dist/backups/docker-volume-source.d.ts +31 -0
  91. package/dist/backups/docker-volume-source.test.d.ts +1 -0
  92. package/dist/backups/filesystem-source.d.ts +34 -0
  93. package/dist/backups/filesystem-source.test.d.ts +1 -0
  94. package/dist/backups/index.d.ts +10 -0
  95. package/dist/backups/logical-database-source.d.ts +16 -0
  96. package/dist/backups/logical-database-source.test.d.ts +1 -0
  97. package/dist/backups/model.d.ts +121 -0
  98. package/dist/backups/s3-destination.d.ts +85 -0
  99. package/dist/backups/s3-destination.test.d.ts +1 -0
  100. package/dist/backups/service.d.ts +80 -0
  101. package/dist/backups/service.test.d.ts +1 -0
  102. package/dist/backups/store.d.ts +30 -0
  103. package/dist/backups/store.test.d.ts +1 -0
  104. package/dist/bin/cli.js +3360 -1802
  105. package/dist/{chunk-tjjgajbh.js → chunk-01d86gt1.js} +301 -9
  106. package/dist/{chunk-73vcqg6z.js → chunk-0z43x8re.js} +7 -7
  107. package/dist/{chunk-tnztxpcb.js → chunk-32e7ya18.js} +61 -49
  108. package/dist/{chunk-qpj3edwz.js → chunk-4cjrg98a.js} +3 -7
  109. package/dist/{chunk-hsk6fe6x.js → chunk-50jpda9q.js} +2 -2
  110. package/dist/{chunk-93hjhs78.js → chunk-703nkybg.js} +1 -1
  111. package/dist/{chunk-d5vqwj8s.js → chunk-9m6b4m74.js} +755 -720
  112. package/dist/chunk-e05phx5f.js +10 -0
  113. package/dist/{chunk-f29w04w5.js → chunk-exbcsab2.js} +10 -4
  114. package/dist/{chunk-eq08r166.js → chunk-f55nkpvb.js} +2 -2
  115. package/dist/{chunk-3bfv1z4v.js → chunk-fv1bj3yt.js} +63 -56
  116. package/dist/chunk-fw3q88wk.js +48641 -0
  117. package/dist/{chunk-3knnr7wh.js → chunk-he9a874b.js} +170 -2
  118. package/dist/{chunk-zn0nxxa8.js → chunk-hpv68b4a.js} +1070 -1070
  119. package/dist/chunk-k114qwg9.js +12 -0
  120. package/dist/{chunk-r16e02bc.js → chunk-vdk53yyq.js} +3900 -4101
  121. package/dist/{chunk-jp2a8yad.js → chunk-vvkd46k1.js} +15 -13
  122. package/dist/{chunk-vd87cpvn.js → chunk-wj3s95p9.js} +62 -46
  123. package/dist/{chunk-mj1tmhte.js → chunk-x9s0cqfx.js} +1 -1
  124. package/dist/{chunk-3rsfns7x.js → chunk-ybcz6sxc.js} +2048 -387
  125. package/dist/{chunk-arsh1g5h.js → chunk-zqtpg06c.js} +14 -11
  126. package/dist/compose/index.d.ts +6 -0
  127. package/dist/compose/parser.d.ts +13 -0
  128. package/dist/compose/parser.test.d.ts +1 -0
  129. package/dist/compose/runtime.d.ts +10 -0
  130. package/dist/compose/service.d.ts +18 -0
  131. package/dist/compose/store.d.ts +59 -0
  132. package/dist/compose/store.test.d.ts +1 -0
  133. package/dist/compose/templates.d.ts +17 -0
  134. package/dist/compose/types.d.ts +164 -0
  135. package/dist/configuration/backends.d.ts +66 -0
  136. package/dist/configuration/backends.test.d.ts +1 -0
  137. package/dist/configuration/dotenv.d.ts +14 -0
  138. package/dist/configuration/dotenv.test.d.ts +1 -0
  139. package/dist/configuration/index.d.ts +6 -0
  140. package/dist/configuration/migrate.d.ts +10 -0
  141. package/dist/configuration/migrate.test.d.ts +1 -0
  142. package/dist/configuration/model.d.ts +50 -0
  143. package/dist/configuration/service.d.ts +144 -0
  144. package/dist/configuration/service.test.d.ts +1 -0
  145. package/dist/configuration/store.d.ts +33 -0
  146. package/dist/configuration/store.test.d.ts +1 -0
  147. package/dist/control-plane/authorization.d.ts +24 -0
  148. package/dist/control-plane/authorization.test.d.ts +1 -0
  149. package/dist/control-plane/index.d.ts +8 -0
  150. package/dist/control-plane/migrations.d.ts +7 -0
  151. package/dist/control-plane/search.d.ts +25 -0
  152. package/dist/control-plane/store.d.ts +117 -0
  153. package/dist/control-plane/store.test.d.ts +1 -0
  154. package/dist/control-plane/types.d.ts +381 -0
  155. package/dist/cost/reporting.d.ts +24 -0
  156. package/dist/cost/reporting.test.d.ts +1 -0
  157. package/dist/cost/resource-inventory.d.ts +32 -0
  158. package/dist/cost/resource-inventory.test.d.ts +1 -0
  159. package/dist/cost/resource-optimization.d.ts +59 -0
  160. package/dist/cost/resource-optimization.test.d.ts +1 -0
  161. package/dist/data-services/adapters.d.ts +39 -0
  162. package/dist/data-services/aws-transports.d.ts +29 -0
  163. package/dist/data-services/aws-transports.test.d.ts +1 -0
  164. package/dist/data-services/capabilities.d.ts +2 -0
  165. package/dist/data-services/data-services.test.d.ts +1 -0
  166. package/dist/data-services/docker-transport.d.ts +55 -0
  167. package/dist/data-services/docker-transport.test.d.ts +1 -0
  168. package/dist/data-services/index.d.ts +8 -0
  169. package/dist/data-services/model.d.ts +79 -0
  170. package/dist/data-services/secrets.d.ts +13 -0
  171. package/dist/data-services/secrets.test.d.ts +1 -0
  172. package/dist/data-services/service.d.ts +51 -0
  173. package/dist/data-services/store.d.ts +17 -0
  174. package/dist/deploy/container-image.d.ts +28 -0
  175. package/dist/deploy/container-image.test.d.ts +1 -0
  176. package/dist/deploy/dashboard-audit.test.d.ts +1 -0
  177. package/dist/deploy/dashboard-auth.d.ts +4 -23
  178. package/dist/deploy/dashboard-backups.integration.test.d.ts +1 -0
  179. package/dist/deploy/dashboard-configuration.integration.test.d.ts +1 -0
  180. package/dist/deploy/dashboard-control-plane.d.ts +37 -0
  181. package/dist/deploy/dashboard-control-plane.test.d.ts +1 -0
  182. package/dist/deploy/dashboard-data-services.integration.test.d.ts +1 -0
  183. package/dist/deploy/dashboard-environment-scope.test.d.ts +1 -0
  184. package/dist/deploy/dashboard-guard.d.ts +12 -0
  185. package/dist/deploy/dashboard-guard.test.d.ts +1 -0
  186. package/dist/deploy/dashboard-identities.d.ts +15 -0
  187. package/dist/deploy/dashboard-identities.test.d.ts +1 -0
  188. package/dist/deploy/dashboard-jobs.integration.test.d.ts +1 -0
  189. package/dist/deploy/dashboard-login-page.d.ts +5 -5
  190. package/dist/deploy/dashboard-login-page.test.d.ts +1 -0
  191. package/dist/deploy/dashboard-oidc.integration.test.d.ts +1 -0
  192. package/dist/deploy/dashboard-onboarding.integration.test.d.ts +1 -0
  193. package/dist/deploy/dashboard-policy.d.ts +4 -2
  194. package/dist/deploy/dashboard-queue-worker.integration.test.d.ts +1 -0
  195. package/dist/deploy/dashboard-release.integration.test.d.ts +1 -0
  196. package/dist/deploy/dashboard-route-manifest.d.ts +14 -0
  197. package/dist/deploy/dashboard-route-manifest.test.d.ts +1 -0
  198. package/dist/deploy/dashboard-security.integration.test.d.ts +1 -0
  199. package/dist/deploy/dashboard-session.d.ts +3 -14
  200. package/dist/deploy/dashboard-source.integration.test.d.ts +1 -0
  201. package/dist/deploy/dashboard-users.d.ts +3 -0
  202. package/dist/deploy/fullstack-container.d.ts +87 -0
  203. package/dist/deploy/fullstack-container.test.d.ts +1 -0
  204. package/dist/deploy/index.d.ts +7 -2
  205. package/dist/deploy/index.js +65 -25
  206. package/dist/deploy/local-dashboard-server.d.ts +30 -0
  207. package/dist/deploy/quick-deploy.d.ts +1 -1
  208. package/dist/deploy/serverless-operations.d.ts +1 -0
  209. package/dist/deploy/site-target.d.ts +16 -0
  210. package/dist/deploy/static-api-origin.d.ts +107 -0
  211. package/dist/deploy/static-api-origin.test.d.ts +1 -0
  212. package/dist/deploy/static-site-helper.d.ts +0 -16
  213. package/dist/deploy/telemetry-access.d.ts +5 -0
  214. package/dist/deploy/telemetry-access.test.d.ts +1 -0
  215. package/dist/deploy/telemetry-collection.d.ts +44 -0
  216. package/dist/deploy/telemetry-collection.test.d.ts +1 -0
  217. package/dist/dns/index.js +3 -3
  218. package/dist/dns/route53-adapter.d.ts +1 -1
  219. package/dist/drivers/hetzner/cloud-init.d.ts +1 -1
  220. package/dist/drivers/index.d.ts +2 -2
  221. package/dist/drivers/index.js +6 -6
  222. package/dist/drivers/shared/notifications.d.ts +1 -1
  223. package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
  224. package/dist/fleet/fleet.test.d.ts +1 -0
  225. package/dist/fleet/index.d.ts +144 -0
  226. package/dist/fleet/queue.d.ts +3 -0
  227. package/dist/fleet/ssh-driver.d.ts +34 -0
  228. package/dist/index.d.ts +28 -4
  229. package/dist/index.js +1132 -424
  230. package/dist/jobs/adapters.d.ts +23 -0
  231. package/dist/jobs/index.d.ts +7 -0
  232. package/dist/jobs/jobs.test.d.ts +1 -0
  233. package/dist/jobs/model.d.ts +100 -0
  234. package/dist/jobs/provider-adapters.d.ts +49 -0
  235. package/dist/jobs/reconcile.d.ts +19 -0
  236. package/dist/jobs/schedule.d.ts +9 -0
  237. package/dist/jobs/service.d.ts +30 -0
  238. package/dist/jobs/store.d.ts +48 -0
  239. package/dist/maintenance/drivers.d.ts +117 -0
  240. package/dist/maintenance/index.d.ts +6 -0
  241. package/dist/maintenance/maintenance.test.d.ts +1 -0
  242. package/dist/maintenance/manifest.d.ts +15 -0
  243. package/dist/maintenance/queue.d.ts +9 -0
  244. package/dist/maintenance/service.d.ts +31 -0
  245. package/dist/maintenance/store.d.ts +84 -0
  246. package/dist/maintenance/types.d.ts +192 -0
  247. package/dist/media.d.ts +62 -0
  248. package/dist/media.test.d.ts +1 -0
  249. package/dist/object-storage/index.d.ts +0 -24
  250. package/dist/onboarding/archive.d.ts +12 -0
  251. package/dist/onboarding/artifact-store.d.ts +22 -0
  252. package/dist/onboarding/detection.d.ts +8 -0
  253. package/dist/onboarding/index.d.ts +9 -0
  254. package/dist/onboarding/migrations.d.ts +3 -0
  255. package/dist/onboarding/onboarding.test.d.ts +1 -0
  256. package/dist/onboarding/plan.d.ts +3 -0
  257. package/dist/onboarding/registry.d.ts +41 -0
  258. package/dist/onboarding/service.d.ts +16 -0
  259. package/dist/onboarding/store.d.ts +30 -0
  260. package/dist/onboarding/types.d.ts +204 -0
  261. package/dist/placement/builds.d.ts +20 -0
  262. package/dist/placement/drivers.d.ts +54 -0
  263. package/dist/placement/index.d.ts +6 -0
  264. package/dist/placement/placement.test.d.ts +1 -0
  265. package/dist/placement/queue.d.ts +4 -0
  266. package/dist/placement/service.d.ts +34 -0
  267. package/dist/placement/store.d.ts +49 -0
  268. package/dist/placement/types.d.ts +109 -0
  269. package/dist/preview/index.d.ts +3 -0
  270. package/dist/preview/service.d.ts +45 -0
  271. package/dist/preview/service.test.d.ts +1 -0
  272. package/dist/preview/store.d.ts +47 -0
  273. package/dist/preview/store.test.d.ts +1 -0
  274. package/dist/preview/types.d.ts +99 -0
  275. package/dist/push/apns.d.ts +0 -26
  276. package/dist/push/fcm.d.ts +0 -23
  277. package/dist/push/index.d.ts +2 -2
  278. package/dist/push/index.js +1 -1
  279. package/dist/queue/deployment-handlers.d.ts +27 -0
  280. package/dist/queue/deployment-handlers.test.d.ts +1 -0
  281. package/dist/queue/index.d.ts +4 -0
  282. package/dist/queue/queue.d.ts +57 -0
  283. package/dist/queue/queue.test.d.ts +1 -0
  284. package/dist/queue/types.d.ts +93 -0
  285. package/dist/queue/worker.d.ts +32 -0
  286. package/dist/queue/worker.test.d.ts +1 -0
  287. package/dist/regions/drivers.d.ts +139 -0
  288. package/dist/regions/index.d.ts +5 -0
  289. package/dist/regions/queue.d.ts +4 -0
  290. package/dist/regions/regions.test.d.ts +1 -0
  291. package/dist/regions/service.d.ts +23 -0
  292. package/dist/regions/store.d.ts +41 -0
  293. package/dist/regions/types.d.ts +132 -0
  294. package/dist/release/index.d.ts +6 -0
  295. package/dist/release/queue.d.ts +15 -0
  296. package/dist/release/queue.test.d.ts +1 -0
  297. package/dist/release/runtime.d.ts +35 -0
  298. package/dist/release/runtime.test.d.ts +1 -0
  299. package/dist/release/service.d.ts +28 -0
  300. package/dist/release/store.d.ts +88 -0
  301. package/dist/release/store.test.d.ts +1 -0
  302. package/dist/release/strategy.d.ts +8 -0
  303. package/dist/release/types.d.ts +103 -0
  304. package/dist/runtime/adapters/aws.d.ts +5 -0
  305. package/dist/runtime/adapters/docker.d.ts +53 -0
  306. package/dist/runtime/adapters/systemd.d.ts +26 -0
  307. package/dist/runtime/index.d.ts +9 -0
  308. package/dist/runtime/inventory.d.ts +6 -0
  309. package/dist/runtime/model.d.ts +130 -0
  310. package/dist/runtime/normalize.d.ts +5 -0
  311. package/dist/runtime/operations.d.ts +113 -0
  312. package/dist/runtime/service.d.ts +32 -0
  313. package/dist/runtime/streams.d.ts +49 -0
  314. package/dist/security/artifacts.d.ts +105 -0
  315. package/dist/security/artifacts.test.d.ts +1 -0
  316. package/dist/security/container-release.d.ts +35 -0
  317. package/dist/security/container-release.test.d.ts +1 -0
  318. package/dist/security/index.d.ts +8 -0
  319. package/dist/security/integration.d.ts +41 -0
  320. package/dist/security/integration.test.d.ts +1 -0
  321. package/dist/security/posture-store.d.ts +80 -0
  322. package/dist/security/posture-store.test.d.ts +1 -0
  323. package/dist/security/pre-deploy-scanner.d.ts +0 -4
  324. package/dist/security/scanners.d.ts +81 -0
  325. package/dist/security/scanners.test.d.ts +1 -0
  326. package/dist/security/types.d.ts +150 -0
  327. package/dist/source/git-workspace.d.ts +26 -0
  328. package/dist/source/git-workspace.test.d.ts +1 -0
  329. package/dist/source/index.d.ts +6 -0
  330. package/dist/source/providers.d.ts +199 -0
  331. package/dist/source/providers.test.d.ts +1 -0
  332. package/dist/source/service.d.ts +18 -0
  333. package/dist/source/store.d.ts +149 -0
  334. package/dist/source/store.test.d.ts +1 -0
  335. package/dist/source/types.d.ts +191 -0
  336. package/dist/source/webhooks.d.ts +33 -0
  337. package/dist/source/webhooks.test.d.ts +1 -0
  338. package/dist/ssl/acme-client.d.ts +0 -6
  339. package/dist/storage/compose.d.ts +4 -0
  340. package/dist/storage/compose.test.d.ts +1 -0
  341. package/dist/storage/drivers.d.ts +107 -0
  342. package/dist/storage/drivers.test.d.ts +1 -0
  343. package/dist/storage/index.d.ts +5 -0
  344. package/dist/storage/model.d.ts +112 -0
  345. package/dist/storage/service.d.ts +95 -0
  346. package/dist/storage/storage.test.d.ts +1 -0
  347. package/dist/storage/store.d.ts +40 -0
  348. package/dist/telemetry/correlation.test.d.ts +1 -0
  349. package/dist/telemetry/index.d.ts +4 -0
  350. package/dist/telemetry/model.d.ts +145 -0
  351. package/dist/telemetry/policy.d.ts +8 -0
  352. package/dist/telemetry/policy.test.d.ts +1 -0
  353. package/dist/telemetry/redaction.d.ts +9 -0
  354. package/dist/telemetry/store.d.ts +34 -0
  355. package/dist/telemetry/store.test.d.ts +1 -0
  356. package/dist/ui/404.html +1506 -17
  357. package/dist/ui/accept-invitation.html +1163 -0
  358. package/dist/ui/{server/backups.html → access-denied.html} +344 -55
  359. package/dist/ui/account/automation.html +1669 -0
  360. package/dist/ui/account/security.html +1646 -0
  361. package/dist/ui/applications/compose.html +1590 -0
  362. package/dist/ui/applications/new.html +1868 -0
  363. package/dist/ui/data/backups.html +1757 -0
  364. package/dist/ui/data/services.html +1723 -0
  365. package/dist/ui/data/volumes.html +1639 -0
  366. package/dist/ui/index.html +392 -29
  367. package/dist/ui/integrations.html +1865 -0
  368. package/dist/ui/operations/alerts.html +1610 -0
  369. package/dist/ui/operations/configuration.html +1746 -0
  370. package/dist/ui/operations/jobs.html +1709 -0
  371. package/dist/ui/operations/maintenance.html +1540 -0
  372. package/dist/ui/operations/observability.html +1742 -0
  373. package/dist/ui/{serverless/scheduler.html → operations/previews.html} +447 -46
  374. package/dist/ui/operations/queue.html +1615 -0
  375. package/dist/ui/{server/workers.html → operations/regions.html} +413 -49
  376. package/dist/ui/operations/releases.html +1596 -0
  377. package/dist/ui/operations/workloads.html +1775 -0
  378. package/dist/ui/security.html +1767 -0
  379. package/dist/ui/server/actions.html +446 -57
  380. package/dist/ui/server/activity.html +346 -1
  381. package/dist/ui/server/capacity.html +1542 -0
  382. package/dist/ui/server/database.html +443 -36
  383. package/dist/ui/server/deployments.html +439 -53
  384. package/dist/ui/server/diagnostics.html +346 -1
  385. package/dist/ui/server/firewall.html +476 -50
  386. package/dist/ui/server/fleet.html +1577 -0
  387. package/dist/ui/server/logs.html +389 -25
  388. package/dist/ui/server/metrics.html +346 -1
  389. package/dist/ui/server/security.html +346 -1
  390. package/dist/ui/server/services.html +421 -46
  391. package/dist/ui/server/sites.html +476 -95
  392. package/dist/ui/server/ssh-keys.html +476 -46
  393. package/dist/ui/server/team.html +700 -77
  394. package/dist/ui/server/terminal.html +444 -54
  395. package/dist/ui/serverless/alarms.html +430 -42
  396. package/dist/ui/serverless/assets.html +421 -46
  397. package/dist/ui/serverless/cost.html +346 -1
  398. package/dist/ui/serverless/data.html +422 -47
  399. package/dist/ui/serverless/deployments.html +421 -46
  400. package/dist/ui/serverless/firewall.html +346 -1
  401. package/dist/ui/serverless/functions.html +423 -48
  402. package/dist/ui/serverless/logs.html +389 -25
  403. package/dist/ui/serverless/metrics.html +346 -1
  404. package/dist/ui/serverless/queues.html +448 -62
  405. package/dist/ui/serverless/secrets.html +448 -42
  406. package/dist/ui/serverless/traces.html +350 -5
  407. package/dist/ui/serverless.html +434 -58
  408. package/dist/ui-src/pages/404.stx +18 -0
  409. package/dist/ui-src/pages/accept-invitation.stx +50 -0
  410. package/dist/ui-src/pages/access-denied.stx +18 -0
  411. package/dist/ui-src/pages/account/automation.stx +187 -0
  412. package/dist/ui-src/pages/account/security.stx +249 -0
  413. package/dist/ui-src/pages/applications/compose.stx +38 -0
  414. package/dist/ui-src/pages/applications/new.stx +76 -0
  415. package/dist/ui-src/pages/data/backups.stx +214 -0
  416. package/dist/ui-src/pages/data/services.stx +257 -0
  417. package/dist/ui-src/pages/data/volumes.stx +40 -0
  418. package/dist/ui-src/pages/index.stx +24 -10
  419. package/dist/ui-src/pages/integrations.stx +186 -0
  420. package/dist/ui-src/pages/operations/alerts.stx +40 -0
  421. package/dist/ui-src/pages/operations/configuration.stx +164 -0
  422. package/dist/ui-src/pages/operations/jobs.stx +238 -0
  423. package/dist/ui-src/pages/operations/maintenance.stx +15 -0
  424. package/dist/ui-src/pages/operations/observability.stx +76 -0
  425. package/dist/ui-src/pages/operations/previews.stx +62 -0
  426. package/dist/ui-src/pages/operations/queue.stx +158 -0
  427. package/dist/ui-src/pages/operations/regions.stx +12 -0
  428. package/dist/ui-src/pages/operations/releases.stx +39 -0
  429. package/dist/ui-src/pages/operations/workloads.stx +151 -0
  430. package/dist/ui-src/pages/partials/head.stx +148 -15
  431. package/dist/ui-src/pages/partials/nav.stx +491 -74
  432. package/dist/ui-src/pages/partials/op-confirm.stx +6 -6
  433. package/dist/ui-src/pages/security.stx +264 -0
  434. package/dist/ui-src/pages/server/actions.stx +17 -26
  435. package/dist/ui-src/pages/server/capacity.stx +16 -0
  436. package/dist/ui-src/pages/server/database.stx +31 -16
  437. package/dist/ui-src/pages/server/deployments.stx +28 -30
  438. package/dist/ui-src/pages/server/firewall.stx +25 -30
  439. package/dist/ui-src/pages/server/fleet.stx +10 -0
  440. package/dist/ui-src/pages/server/logs.stx +11 -9
  441. package/dist/ui-src/pages/server/metrics.stx +8 -16
  442. package/dist/ui-src/pages/server/services.stx +19 -28
  443. package/dist/ui-src/pages/server/sites.stx +75 -72
  444. package/dist/ui-src/pages/server/ssh-keys.stx +24 -30
  445. package/dist/ui-src/pages/server/team.stx +286 -149
  446. package/dist/ui-src/pages/server/terminal.stx +6 -28
  447. package/dist/ui-src/pages/serverless/alarms.stx +17 -31
  448. package/dist/ui-src/pages/serverless/assets.stx +13 -22
  449. package/dist/ui-src/pages/serverless/cost.stx +15 -3
  450. package/dist/ui-src/pages/serverless/data.stx +12 -25
  451. package/dist/ui-src/pages/serverless/deployments.stx +14 -23
  452. package/dist/ui-src/pages/serverless/firewall.stx +5 -1
  453. package/dist/ui-src/pages/serverless/functions.stx +20 -26
  454. package/dist/ui-src/pages/serverless/logs.stx +11 -10
  455. package/dist/ui-src/pages/serverless/metrics.stx +18 -17
  456. package/dist/ui-src/pages/serverless/queues.stx +34 -42
  457. package/dist/ui-src/pages/serverless/secrets.stx +31 -34
  458. package/dist/ui-src/pages/serverless/traces.stx +2 -6
  459. package/dist/ui-src/pages/serverless.stx +40 -74
  460. package/package.json +11 -11
  461. package/dist/chunk-b17vf1g1.js +0 -13787
  462. package/dist/chunk-b82pbxyp.js +0 -1572
  463. package/dist/chunk-pqfzdg68.js +0 -12
  464. package/dist/chunk-z58fqnjm.js +0 -10
  465. package/dist/ui-src/pages/server/backups.stx +0 -91
  466. package/dist/ui-src/pages/server/workers.stx +0 -99
  467. 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-r16e02bc.js";
9
+ } from "./chunk-vdk53yyq.js";
10
10
  import {
11
11
  EC2Client,
12
12
  SSMClient
13
- } from "./chunk-93hjhs78.js";
13
+ } from "./chunk-703nkybg.js";
14
14
  import {
15
15
  CloudFormationClient
16
- } from "./chunk-qpj3edwz.js";
16
+ } from "./chunk-4cjrg98a.js";
17
17
  import {
18
18
  S3Client
19
- } from "./chunk-vd87cpvn.js";
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({ to: site.domain, path, redirect: normalizeSiteRedirect(site.redirect), id, ...auth ? { auth } : {} });
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 enable ts-cloud-metrics.timer",
1203
- "systemctl start ts-cloud-metrics.timer"
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/notifications.ts
1235
- function wantsEvent(config, event) {
1236
- return !config.events || config.events.includes(event);
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 schedule = backups.schedule || "0 2 * * *";
1387
- const configTs = buildBackupsConfigTs(database, backups);
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
- `mkdir -p /etc/ts-cloud ${BACKUP_OUTPUT_DIR}`,
1391
- "command -v bun >/dev/null 2>&1 || (curl -fsSL https://bun.sh/install | bash && ln -sf /root/.bun/bin/bun /usr/local/bin/bun)",
1392
- "bun add -g ts-backups || true",
1393
- `cat > ${BACKUP_CONFIG_PATH} <<'TS_CLOUD_BACKUP_CFG_EOF'`,
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 { appInstanceId: first.id, appPublicIp: first.publicIp, sshUser: "ubuntu", deployStoragePath: "/var/ts-cloud/staging" };
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({ Filters: [{ Name: "group-name", Values: [sgName] }, { Name: "vpc-id", Values: [vpc.VpcId] }] });
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({ GroupName: sgName, Description: `ts-cloud ${slug}/${environment} app`, VpcId: vpc.VpcId });
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) => ({ IpProtocol: r.protocol, FromPort: r.port, ToPort: r.port, IpRanges: [{ CidrIp: r.cidr }] }))
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
- ResourceType: "instance",
1770
- Tags: [
1771
- { Key: "Name", Value: `${slug}-${environment}-app` },
1772
- { Key: "Project", Value: slug },
1773
- { Key: "Environment", Value: environment },
1774
- { Key: "Role", Value: "app" }
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 { existsSync, readFileSync as readFileSync2 } from "node:fs";
1995
- import { execSync } from "node:child_process";
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: [{ type: "label_selector", label_selector: { selector: options.labelSelector }, use_private_ip: !!options.network }],
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({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked, swapGb: compute.swapGb }));
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 [`${slug}-${environment}-app-fw`, `${slug}-${environment}-services-fw`, `${slug}-${environment}-lb-fw`]) {
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 = [...new Set(role === "app" ? [state?.serverId, ...state?.appServerIds ?? []] : role === "lb" ? [state?.lbServerId] : [state?.servicesServerId])].filter((id) => typeof id === "number");
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) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} true`, {
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({ name: options.name, publicKey: options.publicKey.trim(), labels: options.labels });
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 { provider: "hetzner", id: String(server.id), name: server.name, publicIp: serverPublicIpv4(server), created };
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 { provider: "aws", id: instance2.InstanceId, name: spec.name, publicIp: instance2.PublicIpAddress, created: false };
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 tarball = join4(tmpdir(), `${options.slug}-${options.siteName ?? MANAGEMENT_DASHBOARD_SITE}-${options.sha}.tar.gz`);
4233
- execSync2(`tar czf "${tarball}" -C "${root}" .`, { stdio: "inherit" });
4234
- return tarball;
4235
- } catch (error) {
4236
- logger.warn(`Management dashboard: failed to build artifact — ${error?.message ?? error}. Skipping dashboard deploy.`);
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: [...buildSiteOwnerGuard(appBase2, slug), ...deployScript, ...poolScript, ...vhostScript, ...sslScript, ...servicesScript2, ...healthCheckScript],
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]], { cwd, slug, sha, siteName: injectedDashboardSites[0], logger: { info: logger.info, warn: logger.warn } });
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" ? { Quantity: 7, Items: ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"], CachedMethods: { Quantity: 2, Items: ["GET", "HEAD"] } } : { Quantity: 3, Items: ["GET", "HEAD", "OPTIONS"], CachedMethods: { Quantity: 2, Items: ["GET", "HEAD"] } }
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
- Id: ORIGIN_ID,
5212
- DomainName: options.originDomain,
5213
- OriginPath: "",
5214
- CustomHeaders: customHeaders,
5215
- CustomOriginConfig: {
5216
- HTTPPort: 80,
5217
- HTTPSPort: 443,
5218
- OriginProtocolPolicy: "https-only",
5219
- OriginSslProtocols: { Quantity: 1, Items: ["TLSv1.2"] },
5220
- OriginReadTimeout: 30,
5221
- OriginKeepaliveTimeout: 5
5222
- },
5223
- ConnectionAttempts: 3,
5224
- ConnectionTimeout: 10,
5225
- OriginShield: options.originShield ? { Enabled: true, OriginShieldRegion: options.originShieldRegion } : { Enabled: false },
5226
- OriginAccessControlId: ""
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, PANTRY_PROJECT_DIR, pgAdminCommand, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, buildRpxFragmentRefreshScript, BACKUP_RUNNER_PATH, buildBackupRestoreScript, 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 };
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 };