@ankhorage/infra 4.1.12 → 5.0.0

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 (521) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +21 -32
  3. package/dist/cli/bin.d.ts +1 -8
  4. package/dist/cli/bin.d.ts.map +1 -1
  5. package/dist/cli/bin.js +2 -34
  6. package/dist/cli/bin.js.map +1 -1
  7. package/dist/cli/commands/destroy.d.ts +11 -0
  8. package/dist/cli/commands/destroy.d.ts.map +1 -0
  9. package/dist/cli/commands/destroy.js +51 -0
  10. package/dist/cli/commands/destroy.js.map +1 -0
  11. package/dist/cli/commands/down.d.ts +11 -0
  12. package/dist/cli/commands/down.d.ts.map +1 -0
  13. package/dist/cli/commands/down.js +27 -0
  14. package/dist/cli/commands/down.js.map +1 -0
  15. package/dist/cli/commands/generate.d.ts +11 -0
  16. package/dist/cli/commands/generate.d.ts.map +1 -0
  17. package/dist/cli/commands/generate.js +29 -0
  18. package/dist/cli/commands/generate.js.map +1 -0
  19. package/dist/cli/commands/outputs.d.ts +11 -0
  20. package/dist/cli/commands/outputs.d.ts.map +1 -0
  21. package/dist/cli/commands/outputs.js +25 -0
  22. package/dist/cli/commands/outputs.js.map +1 -0
  23. package/dist/cli/commands/plan.d.ts +11 -0
  24. package/dist/cli/commands/plan.d.ts.map +1 -0
  25. package/dist/cli/commands/plan.js +19 -0
  26. package/dist/cli/commands/plan.js.map +1 -0
  27. package/dist/cli/commands/status.d.ts +11 -0
  28. package/dist/cli/commands/status.d.ts.map +1 -0
  29. package/dist/cli/commands/status.js +19 -0
  30. package/dist/cli/commands/status.js.map +1 -0
  31. package/dist/cli/commands/up.d.ts +11 -0
  32. package/dist/cli/commands/up.d.ts.map +1 -0
  33. package/dist/cli/commands/up.js +28 -0
  34. package/dist/cli/commands/up.js.map +1 -0
  35. package/dist/cli/commands/validate.d.ts +11 -0
  36. package/dist/cli/commands/validate.d.ts.map +1 -0
  37. package/dist/cli/commands/validate.js +19 -0
  38. package/dist/cli/commands/validate.js.map +1 -0
  39. package/dist/cli/constants.d.ts +66 -0
  40. package/dist/cli/constants.d.ts.map +1 -0
  41. package/dist/cli/constants.js +19 -0
  42. package/dist/cli/constants.js.map +1 -0
  43. package/dist/cli/createDefaultInfraCommandContext.d.ts +4 -0
  44. package/dist/cli/createDefaultInfraCommandContext.d.ts.map +1 -0
  45. package/dist/{commandContext.js → cli/createDefaultInfraCommandContext.js} +4 -3
  46. package/dist/cli/createDefaultInfraCommandContext.js.map +1 -0
  47. package/dist/cli/createInfraRuntimeProvider.d.ts +5 -0
  48. package/dist/cli/createInfraRuntimeProvider.d.ts.map +1 -0
  49. package/dist/cli/createInfraRuntimeProvider.js +22 -0
  50. package/dist/cli/createInfraRuntimeProvider.js.map +1 -0
  51. package/dist/cli/createProviderCommandDescriptors.d.ts +4 -0
  52. package/dist/cli/createProviderCommandDescriptors.d.ts.map +1 -0
  53. package/dist/cli/createProviderCommandDescriptors.js +6 -0
  54. package/dist/cli/createProviderCommandDescriptors.js.map +1 -0
  55. package/dist/cli/findInfraCommand.d.ts +4 -0
  56. package/dist/cli/findInfraCommand.d.ts.map +1 -0
  57. package/dist/cli/findInfraCommand.js +6 -0
  58. package/dist/cli/findInfraCommand.js.map +1 -0
  59. package/dist/cli/index.d.ts +3 -8
  60. package/dist/cli/index.d.ts.map +1 -1
  61. package/dist/cli/index.js +2 -24
  62. package/dist/cli/index.js.map +1 -1
  63. package/dist/cli/renderInfraRootHelp.d.ts +3 -0
  64. package/dist/cli/renderInfraRootHelp.d.ts.map +1 -0
  65. package/dist/cli/renderInfraRootHelp.js +21 -0
  66. package/dist/cli/renderInfraRootHelp.js.map +1 -0
  67. package/dist/cli/renderUnknownInfraCommand.d.ts +3 -0
  68. package/dist/cli/renderUnknownInfraCommand.d.ts.map +1 -0
  69. package/dist/cli/renderUnknownInfraCommand.js +5 -0
  70. package/dist/cli/renderUnknownInfraCommand.js.map +1 -0
  71. package/dist/cli/runInfraCliAsync.d.ts +10 -0
  72. package/dist/cli/runInfraCliAsync.d.ts.map +1 -0
  73. package/dist/cli/runInfraCliAsync.js +34 -0
  74. package/dist/cli/runInfraCliAsync.js.map +1 -0
  75. package/dist/cli/runInfraCommandAsync.d.ts +4 -0
  76. package/dist/cli/runInfraCommandAsync.d.ts.map +1 -0
  77. package/dist/cli/runInfraCommandAsync.js +100 -0
  78. package/dist/cli/runInfraCommandAsync.js.map +1 -0
  79. package/dist/cli/utils/prepareInfraCommandOperationAsync.d.ts +4 -0
  80. package/dist/cli/utils/prepareInfraCommandOperationAsync.d.ts.map +1 -0
  81. package/dist/cli/utils/prepareInfraCommandOperationAsync.js +45 -0
  82. package/dist/cli/utils/prepareInfraCommandOperationAsync.js.map +1 -0
  83. package/dist/cli/utils/requireInfraSuccess.d.ts +7 -0
  84. package/dist/cli/utils/requireInfraSuccess.d.ts.map +1 -0
  85. package/dist/cli/utils/requireInfraSuccess.js +17 -0
  86. package/dist/cli/utils/requireInfraSuccess.js.map +1 -0
  87. package/dist/cli/utils/resolveInfraPersistence.d.ts +4 -0
  88. package/dist/cli/utils/resolveInfraPersistence.d.ts.map +1 -0
  89. package/dist/cli/utils/resolveInfraPersistence.js +17 -0
  90. package/dist/cli/utils/resolveInfraPersistence.js.map +1 -0
  91. package/dist/cli/utils/writeInfraEnvironmentOutputs.d.ts +5 -0
  92. package/dist/cli/utils/writeInfraEnvironmentOutputs.d.ts.map +1 -0
  93. package/dist/cli/utils/writeInfraEnvironmentOutputs.js +16 -0
  94. package/dist/cli/utils/writeInfraEnvironmentOutputs.js.map +1 -0
  95. package/dist/cli/utils/writeInfraStructured.d.ts +4 -0
  96. package/dist/cli/utils/writeInfraStructured.d.ts.map +1 -0
  97. package/dist/cli/utils/writeInfraStructured.js +8 -0
  98. package/dist/cli/utils/writeInfraStructured.js.map +1 -0
  99. package/dist/constants.d.ts +10 -0
  100. package/dist/constants.d.ts.map +1 -0
  101. package/dist/{packageMetadata.js → constants.js} +6 -3
  102. package/dist/constants.js.map +1 -0
  103. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.d.ts +4 -0
  104. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.d.ts.map +1 -0
  105. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.js +35 -0
  106. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.js.map +1 -0
  107. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraSecretPort.d.ts +4 -0
  108. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraSecretPort.d.ts.map +1 -0
  109. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraSecretPort.js +23 -0
  110. package/dist/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraSecretPort.js.map +1 -0
  111. package/dist/features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.d.ts +4 -0
  112. package/dist/features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.d.ts.map +1 -0
  113. package/dist/features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.js +9 -0
  114. package/dist/features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.js.map +1 -0
  115. package/dist/features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.d.ts +5 -0
  116. package/dist/features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.d.ts.map +1 -0
  117. package/dist/features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js +28 -0
  118. package/dist/features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js.map +1 -0
  119. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.d.ts +4 -0
  120. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.d.ts.map +1 -0
  121. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js +7 -0
  122. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js.map +1 -0
  123. package/dist/features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.d.ts +7 -0
  124. package/dist/features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.d.ts.map +1 -0
  125. package/dist/{project.js → features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js} +10 -34
  126. package/dist/features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.js.map +1 -0
  127. package/dist/features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.d.ts +5 -0
  128. package/dist/features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.d.ts.map +1 -0
  129. package/dist/features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js +28 -0
  130. package/dist/features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js.map +1 -0
  131. package/dist/features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.d.ts +4 -0
  132. package/dist/features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.d.ts.map +1 -0
  133. package/dist/features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js +16 -0
  134. package/dist/features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js.map +1 -0
  135. package/dist/features/environment-lifecycle/application/ports/outbound/infraAdapterPackage.d.ts +9 -0
  136. package/dist/features/environment-lifecycle/application/ports/outbound/infraAdapterPackage.d.ts.map +1 -0
  137. package/dist/features/environment-lifecycle/application/ports/outbound/infraAdapterPackage.js +2 -0
  138. package/dist/features/environment-lifecycle/application/ports/outbound/infraAdapterPackage.js.map +1 -0
  139. package/dist/features/environment-lifecycle/application/use-cases/collectInfraWorkloadsAsync.d.ts +4 -0
  140. package/dist/features/environment-lifecycle/application/use-cases/collectInfraWorkloadsAsync.d.ts.map +1 -0
  141. package/dist/features/environment-lifecycle/application/use-cases/collectInfraWorkloadsAsync.js +15 -0
  142. package/dist/features/environment-lifecycle/application/use-cases/collectInfraWorkloadsAsync.js.map +1 -0
  143. package/dist/features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.d.ts +4 -0
  144. package/dist/features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.d.ts.map +1 -0
  145. package/dist/features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.js +84 -0
  146. package/dist/features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.js.map +1 -0
  147. package/dist/features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.d.ts +4 -0
  148. package/dist/features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.d.ts.map +1 -0
  149. package/dist/features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.js +55 -0
  150. package/dist/features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.js.map +1 -0
  151. package/dist/features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.d.ts +4 -0
  152. package/dist/features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.d.ts.map +1 -0
  153. package/dist/features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.js +38 -0
  154. package/dist/features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.js.map +1 -0
  155. package/dist/features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.d.ts +4 -0
  156. package/dist/features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.d.ts.map +1 -0
  157. package/dist/features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.js +34 -0
  158. package/dist/features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.js.map +1 -0
  159. package/dist/features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.d.ts +4 -0
  160. package/dist/features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.d.ts.map +1 -0
  161. package/dist/features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.js +57 -0
  162. package/dist/features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.js.map +1 -0
  163. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraOperationAsync.d.ts +5 -0
  164. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraOperationAsync.d.ts.map +1 -0
  165. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraOperationAsync.js +109 -0
  166. package/dist/features/environment-lifecycle/application/use-cases/prepareInfraOperationAsync.js.map +1 -0
  167. package/dist/features/environment-lifecycle/application/use-cases/resolveInfraAdaptersAsync.d.ts +6 -0
  168. package/dist/features/environment-lifecycle/application/use-cases/resolveInfraAdaptersAsync.d.ts.map +1 -0
  169. package/dist/features/environment-lifecycle/application/use-cases/resolveInfraAdaptersAsync.js +181 -0
  170. package/dist/features/environment-lifecycle/application/use-cases/resolveInfraAdaptersAsync.js.map +1 -0
  171. package/dist/features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.d.ts +4 -0
  172. package/dist/features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.d.ts.map +1 -0
  173. package/dist/features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.js +68 -0
  174. package/dist/features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.js.map +1 -0
  175. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.d.ts +4 -0
  176. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.d.ts.map +1 -0
  177. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js +98 -0
  178. package/dist/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js.map +1 -0
  179. package/dist/features/environment-lifecycle/application/use-cases/validateInfraAdaptersAsync.d.ts +5 -0
  180. package/dist/features/environment-lifecycle/application/use-cases/validateInfraAdaptersAsync.d.ts.map +1 -0
  181. package/dist/features/environment-lifecycle/application/use-cases/validateInfraAdaptersAsync.js +17 -0
  182. package/dist/features/environment-lifecycle/application/use-cases/validateInfraAdaptersAsync.js.map +1 -0
  183. package/dist/features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.d.ts +4 -0
  184. package/dist/features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.d.ts.map +1 -0
  185. package/dist/features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.js +13 -0
  186. package/dist/features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.js.map +1 -0
  187. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.d.ts +4 -0
  188. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.d.ts.map +1 -0
  189. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.js +43 -0
  190. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.js.map +1 -0
  191. package/dist/features/environment-lifecycle/domain/orderInfraPlanActions.d.ts +4 -0
  192. package/dist/features/environment-lifecycle/domain/orderInfraPlanActions.d.ts.map +1 -0
  193. package/dist/features/environment-lifecycle/domain/orderInfraPlanActions.js +54 -0
  194. package/dist/features/environment-lifecycle/domain/orderInfraPlanActions.js.map +1 -0
  195. package/dist/features/environment-lifecycle/domain/resolveInfraEnvironment.d.ts +5 -0
  196. package/dist/features/environment-lifecycle/domain/resolveInfraEnvironment.d.ts.map +1 -0
  197. package/dist/features/environment-lifecycle/domain/resolveInfraEnvironment.js +39 -0
  198. package/dist/features/environment-lifecycle/domain/resolveInfraEnvironment.js.map +1 -0
  199. package/dist/features/environment-lifecycle/utils/appendInfraFailure.d.ts +7 -0
  200. package/dist/features/environment-lifecycle/utils/appendInfraFailure.d.ts.map +1 -0
  201. package/dist/features/environment-lifecycle/utils/appendInfraFailure.js +5 -0
  202. package/dist/features/environment-lifecycle/utils/appendInfraFailure.js.map +1 -0
  203. package/dist/features/environment-lifecycle/utils/createInfraExecutionContext.d.ts +5 -0
  204. package/dist/features/environment-lifecycle/utils/createInfraExecutionContext.d.ts.map +1 -0
  205. package/dist/features/environment-lifecycle/utils/createInfraExecutionContext.js +13 -0
  206. package/dist/features/environment-lifecycle/utils/createInfraExecutionContext.js.map +1 -0
  207. package/dist/features/environment-lifecycle/utils/createInfraFailure.d.ts +4 -0
  208. package/dist/features/environment-lifecycle/utils/createInfraFailure.d.ts.map +1 -0
  209. package/dist/features/environment-lifecycle/utils/createInfraFailure.js +5 -0
  210. package/dist/features/environment-lifecycle/utils/createInfraFailure.js.map +1 -0
  211. package/dist/features/environment-lifecycle/utils/createInfraLedger.d.ts +12 -0
  212. package/dist/features/environment-lifecycle/utils/createInfraLedger.d.ts.map +1 -0
  213. package/dist/features/environment-lifecycle/utils/createInfraLedger.js +14 -0
  214. package/dist/features/environment-lifecycle/utils/createInfraLedger.js.map +1 -0
  215. package/dist/features/environment-lifecycle/utils/infraIdentityKey.d.ts +4 -0
  216. package/dist/features/environment-lifecycle/utils/infraIdentityKey.d.ts.map +1 -0
  217. package/dist/features/environment-lifecycle/utils/infraIdentityKey.js +5 -0
  218. package/dist/features/environment-lifecycle/utils/infraIdentityKey.js.map +1 -0
  219. package/dist/features/environment-lifecycle/utils/isInfraStoredState.d.ts +4 -0
  220. package/dist/features/environment-lifecycle/utils/isInfraStoredState.d.ts.map +1 -0
  221. package/dist/features/environment-lifecycle/utils/isInfraStoredState.js +103 -0
  222. package/dist/features/environment-lifecycle/utils/isInfraStoredState.js.map +1 -0
  223. package/dist/features/environment-lifecycle/utils/mergeInfraOutputs.d.ts +4 -0
  224. package/dist/features/environment-lifecycle/utils/mergeInfraOutputs.d.ts.map +1 -0
  225. package/dist/features/environment-lifecycle/utils/mergeInfraOutputs.js +10 -0
  226. package/dist/features/environment-lifecycle/utils/mergeInfraOutputs.js.map +1 -0
  227. package/dist/features/environment-lifecycle/utils/mergeInfraResources.d.ts +4 -0
  228. package/dist/features/environment-lifecycle/utils/mergeInfraResources.d.ts.map +1 -0
  229. package/dist/features/environment-lifecycle/utils/mergeInfraResources.js +10 -0
  230. package/dist/features/environment-lifecycle/utils/mergeInfraResources.js.map +1 -0
  231. package/dist/features/environment-lifecycle/utils/normalizeInfraEnvironmentKey.d.ts +3 -0
  232. package/dist/features/environment-lifecycle/utils/normalizeInfraEnvironmentKey.d.ts.map +1 -0
  233. package/dist/features/environment-lifecycle/utils/normalizeInfraEnvironmentKey.js +8 -0
  234. package/dist/features/environment-lifecycle/utils/normalizeInfraEnvironmentKey.js.map +1 -0
  235. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.d.ts +4 -0
  236. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.d.ts.map +1 -0
  237. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.js +11 -0
  238. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.js.map +1 -0
  239. package/dist/features/environment-lifecycle/utils/resolveProjectFile.d.ts +3 -0
  240. package/dist/features/environment-lifecycle/utils/resolveProjectFile.d.ts.map +1 -0
  241. package/dist/features/environment-lifecycle/utils/resolveProjectFile.js +19 -0
  242. package/dist/features/environment-lifecycle/utils/resolveProjectFile.js.map +1 -0
  243. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterOutputs.d.ts +4 -0
  244. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterOutputs.d.ts.map +1 -0
  245. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterOutputs.js +6 -0
  246. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterOutputs.js.map +1 -0
  247. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterResources.d.ts +4 -0
  248. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterResources.d.ts.map +1 -0
  249. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterResources.js +6 -0
  250. package/dist/features/environment-lifecycle/utils/retainUnchangedAdapterResources.js.map +1 -0
  251. package/dist/index.d.ts +16 -6
  252. package/dist/index.d.ts.map +1 -1
  253. package/dist/index.js +14 -15
  254. package/dist/index.js.map +1 -1
  255. package/dist/metadata/index.d.ts +1 -1
  256. package/dist/metadata/index.d.ts.map +1 -1
  257. package/dist/metadata/index.js +1 -1
  258. package/dist/metadata/index.js.map +1 -1
  259. package/dist/project/index.d.ts +7 -5
  260. package/dist/project/index.d.ts.map +1 -1
  261. package/dist/project/index.js +6 -5
  262. package/dist/project/index.js.map +1 -1
  263. package/dist/readme-usage.js +17 -26
  264. package/dist/readme-usage.js.map +1 -1
  265. package/dist/testSupport.d.ts +1 -1
  266. package/dist/testSupport.d.ts.map +1 -1
  267. package/dist/testSupport.js +11 -1
  268. package/dist/testSupport.js.map +1 -1
  269. package/dist/types/infraCli.d.ts +91 -0
  270. package/dist/types/infraCli.d.ts.map +1 -0
  271. package/dist/types/infraCli.js +2 -0
  272. package/dist/types/infraCli.js.map +1 -0
  273. package/dist/types/infraOrchestration.d.ts +84 -0
  274. package/dist/types/infraOrchestration.d.ts.map +1 -0
  275. package/dist/types/infraOrchestration.js +2 -0
  276. package/dist/types/infraOrchestration.js.map +1 -0
  277. package/dist/{project.d.ts → types/infraProject.d.ts} +6 -6
  278. package/dist/types/infraProject.d.ts.map +1 -0
  279. package/dist/types/infraProject.js +2 -0
  280. package/dist/types/infraProject.js.map +1 -0
  281. package/package.json +20 -10
  282. package/src/adapterPackages.integration.test.ts +79 -0
  283. package/src/cli/bin.ts +2 -61
  284. package/src/cli/cli.test.ts +35 -126
  285. package/src/cli/commands/destroy.ts +57 -0
  286. package/src/cli/commands/down.ts +28 -0
  287. package/src/cli/commands/generate.ts +34 -0
  288. package/src/cli/commands/outputs.ts +25 -0
  289. package/src/cli/commands/plan.ts +20 -0
  290. package/src/cli/commands/status.ts +20 -0
  291. package/src/cli/commands/up.ts +29 -0
  292. package/src/cli/commands/validate.ts +20 -0
  293. package/src/cli/constants.ts +19 -0
  294. package/src/cli/createDefaultInfraCommandContext.ts +17 -0
  295. package/src/cli/createInfraRuntimeProvider.ts +35 -0
  296. package/src/cli/createProviderCommandDescriptors.ts +8 -0
  297. package/src/cli/findInfraCommand.ts +7 -0
  298. package/src/cli/index.test.ts +12 -20
  299. package/src/cli/index.ts +7 -49
  300. package/src/cli/renderInfraRootHelp.ts +23 -0
  301. package/src/cli/renderUnknownInfraCommand.ts +4 -0
  302. package/src/cli/runInfraCliAsync.ts +51 -0
  303. package/src/cli/runInfraCommandAsync.ts +114 -0
  304. package/src/cli/utils/prepareInfraCommandOperationAsync.ts +64 -0
  305. package/src/cli/utils/requireInfraSuccess.ts +24 -0
  306. package/src/cli/utils/resolveInfraPersistence.ts +22 -0
  307. package/src/cli/utils/writeInfraEnvironmentOutputs.ts +24 -0
  308. package/src/cli/utils/writeInfraStructured.ts +11 -0
  309. package/src/commands.test.ts +198 -196
  310. package/src/{packageMetadata.ts → constants.ts} +5 -2
  311. package/src/environmentLifecycle.test.ts +193 -0
  312. package/src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraCredentialPort.ts +52 -0
  313. package/src/features/environment-lifecycle/adapters/outbound/createEnvironmentInfraSecretPort.ts +32 -0
  314. package/src/features/environment-lifecycle/adapters/outbound/createNodeInfraAdapterPackageResolver.ts +10 -0
  315. package/src/features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.ts +34 -0
  316. package/src/features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.ts +12 -0
  317. package/src/{project.ts → features/environment-lifecycle/adapters/outbound/resolveInfraProjectAsync.ts} +10 -45
  318. package/src/features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.ts +34 -0
  319. package/src/features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.ts +21 -0
  320. package/src/features/environment-lifecycle/application/ports/outbound/infraAdapterPackage.ts +10 -0
  321. package/src/features/environment-lifecycle/application/use-cases/collectInfraWorkloadsAsync.ts +25 -0
  322. package/src/features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.ts +119 -0
  323. package/src/features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.ts +65 -0
  324. package/src/features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.ts +51 -0
  325. package/src/features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.ts +41 -0
  326. package/src/features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.ts +67 -0
  327. package/src/features/environment-lifecycle/application/use-cases/prepareInfraOperationAsync.ts +137 -0
  328. package/src/features/environment-lifecycle/application/use-cases/resolveInfraAdaptersAsync.ts +257 -0
  329. package/src/features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.ts +80 -0
  330. package/src/features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.ts +126 -0
  331. package/src/features/environment-lifecycle/application/use-cases/validateInfraAdaptersAsync.ts +25 -0
  332. package/src/features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.ts +21 -0
  333. package/src/features/environment-lifecycle/composition/createInfraCommandServices.ts +47 -0
  334. package/src/features/environment-lifecycle/domain/orderInfraPlanActions.ts +64 -0
  335. package/src/features/environment-lifecycle/domain/resolveInfraEnvironment.ts +62 -0
  336. package/src/features/environment-lifecycle/utils/appendInfraFailure.ts +9 -0
  337. package/src/features/environment-lifecycle/utils/createInfraExecutionContext.ts +24 -0
  338. package/src/features/environment-lifecycle/utils/createInfraFailure.ts +6 -0
  339. package/src/features/environment-lifecycle/utils/createInfraLedger.ts +30 -0
  340. package/src/features/environment-lifecycle/utils/infraIdentityKey.ts +6 -0
  341. package/src/features/environment-lifecycle/utils/isInfraStoredState.ts +141 -0
  342. package/src/features/environment-lifecycle/utils/mergeInfraOutputs.ts +14 -0
  343. package/src/features/environment-lifecycle/utils/mergeInfraResources.ts +14 -0
  344. package/src/features/environment-lifecycle/utils/normalizeInfraEnvironmentKey.ts +7 -0
  345. package/src/features/environment-lifecycle/utils/resolveInfraStatePath.ts +12 -0
  346. package/src/features/environment-lifecycle/utils/resolveProjectFile.ts +22 -0
  347. package/src/features/environment-lifecycle/utils/retainUnchangedAdapterOutputs.ts +15 -0
  348. package/src/features/environment-lifecycle/utils/retainUnchangedAdapterResources.ts +15 -0
  349. package/src/index.test.ts +22 -315
  350. package/src/index.ts +41 -53
  351. package/src/infraComposition.integration.test.ts +128 -0
  352. package/src/infraOperations.test.ts +386 -0
  353. package/src/infraUp.test.ts +260 -0
  354. package/src/metadata/index.ts +1 -1
  355. package/src/package.test.ts +4 -2
  356. package/src/project/index.test.ts +8 -188
  357. package/src/project/index.ts +7 -22
  358. package/src/project.test.ts +39 -189
  359. package/src/projectContractsParser.test.ts +2 -2
  360. package/src/projectInfrastructure.test.ts +121 -167
  361. package/src/readme-usage.ts +17 -26
  362. package/src/testSupport.ts +12 -2
  363. package/src/types/infraCli.ts +115 -0
  364. package/src/types/infraOrchestration.ts +126 -0
  365. package/src/types/infraProject.ts +15 -0
  366. package/dist/adapters/minikube/auth/index.d.ts +0 -8
  367. package/dist/adapters/minikube/auth/index.d.ts.map +0 -1
  368. package/dist/adapters/minikube/auth/index.js +0 -29
  369. package/dist/adapters/minikube/auth/index.js.map +0 -1
  370. package/dist/adapters/minikube/auth/oauthRuntime.d.ts +0 -4
  371. package/dist/adapters/minikube/auth/oauthRuntime.d.ts.map +0 -1
  372. package/dist/adapters/minikube/auth/oauthRuntime.js +0 -138
  373. package/dist/adapters/minikube/auth/oauthRuntime.js.map +0 -1
  374. package/dist/adapters/minikube/auth/supabase/index.d.ts +0 -7
  375. package/dist/adapters/minikube/auth/supabase/index.d.ts.map +0 -1
  376. package/dist/adapters/minikube/auth/supabase/index.js +0 -232
  377. package/dist/adapters/minikube/auth/supabase/index.js.map +0 -1
  378. package/dist/adapters/minikube/auth/supabase/profile.d.ts +0 -20
  379. package/dist/adapters/minikube/auth/supabase/profile.d.ts.map +0 -1
  380. package/dist/adapters/minikube/auth/supabase/profile.js +0 -452
  381. package/dist/adapters/minikube/auth/supabase/profile.js.map +0 -1
  382. package/dist/adapters/minikube/auth/targetAwareOAuthRuntime.d.ts +0 -7
  383. package/dist/adapters/minikube/auth/targetAwareOAuthRuntime.d.ts.map +0 -1
  384. package/dist/adapters/minikube/auth/targetAwareOAuthRuntime.js +0 -104
  385. package/dist/adapters/minikube/auth/targetAwareOAuthRuntime.js.map +0 -1
  386. package/dist/adapters/minikube/authz/cerbos/index.d.ts +0 -13
  387. package/dist/adapters/minikube/authz/cerbos/index.d.ts.map +0 -1
  388. package/dist/adapters/minikube/authz/cerbos/index.js +0 -382
  389. package/dist/adapters/minikube/authz/cerbos/index.js.map +0 -1
  390. package/dist/adapters/minikube/authz/index.d.ts +0 -9
  391. package/dist/adapters/minikube/authz/index.d.ts.map +0 -1
  392. package/dist/adapters/minikube/authz/index.js +0 -23
  393. package/dist/adapters/minikube/authz/index.js.map +0 -1
  394. package/dist/adapters/minikube/base/index.d.ts +0 -14
  395. package/dist/adapters/minikube/base/index.d.ts.map +0 -1
  396. package/dist/adapters/minikube/base/index.js +0 -3433
  397. package/dist/adapters/minikube/base/index.js.map +0 -1
  398. package/dist/adapters/minikube/base/supabaseMigrations.d.ts +0 -2
  399. package/dist/adapters/minikube/base/supabaseMigrations.d.ts.map +0 -1
  400. package/dist/adapters/minikube/base/supabaseMigrations.js +0 -6
  401. package/dist/adapters/minikube/base/supabaseMigrations.js.map +0 -1
  402. package/dist/adapters/minikube/contracts.d.ts +0 -34
  403. package/dist/adapters/minikube/contracts.d.ts.map +0 -1
  404. package/dist/adapters/minikube/contracts.js +0 -10
  405. package/dist/adapters/minikube/contracts.js.map +0 -1
  406. package/dist/adapters/minikube/index.d.ts +0 -3
  407. package/dist/adapters/minikube/index.d.ts.map +0 -1
  408. package/dist/adapters/minikube/index.js +0 -136
  409. package/dist/adapters/minikube/index.js.map +0 -1
  410. package/dist/adapters/minikube/secrets/index.d.ts +0 -7
  411. package/dist/adapters/minikube/secrets/index.d.ts.map +0 -1
  412. package/dist/adapters/minikube/secrets/index.js +0 -13
  413. package/dist/adapters/minikube/secrets/index.js.map +0 -1
  414. package/dist/adapters/minikube/secrets/supabase-vault/index.d.ts +0 -8
  415. package/dist/adapters/minikube/secrets/supabase-vault/index.d.ts.map +0 -1
  416. package/dist/adapters/minikube/secrets/supabase-vault/index.js +0 -118
  417. package/dist/adapters/minikube/secrets/supabase-vault/index.js.map +0 -1
  418. package/dist/adapters/minikube/storage/index.d.ts +0 -7
  419. package/dist/adapters/minikube/storage/index.d.ts.map +0 -1
  420. package/dist/adapters/minikube/storage/index.js +0 -77
  421. package/dist/adapters/minikube/storage/index.js.map +0 -1
  422. package/dist/adapters/minikube/storage/supabase/index.d.ts +0 -7
  423. package/dist/adapters/minikube/storage/supabase/index.d.ts.map +0 -1
  424. package/dist/adapters/minikube/storage/supabase/index.js +0 -82
  425. package/dist/adapters/minikube/storage/supabase/index.js.map +0 -1
  426. package/dist/authRedirects.d.ts +0 -19
  427. package/dist/authRedirects.d.ts.map +0 -1
  428. package/dist/authRedirects.js +0 -80
  429. package/dist/authRedirects.js.map +0 -1
  430. package/dist/authTargetRedirects.d.ts +0 -12
  431. package/dist/authTargetRedirects.d.ts.map +0 -1
  432. package/dist/authTargetRedirects.js +0 -32
  433. package/dist/authTargetRedirects.js.map +0 -1
  434. package/dist/commandContext.d.ts +0 -12
  435. package/dist/commandContext.d.ts.map +0 -1
  436. package/dist/commandContext.js.map +0 -1
  437. package/dist/commands.d.ts +0 -95
  438. package/dist/commands.d.ts.map +0 -1
  439. package/dist/commands.js +0 -256
  440. package/dist/commands.js.map +0 -1
  441. package/dist/infraValidation.d.ts +0 -4
  442. package/dist/infraValidation.d.ts.map +0 -1
  443. package/dist/infraValidation.js +0 -63
  444. package/dist/infraValidation.js.map +0 -1
  445. package/dist/packageMetadata.d.ts +0 -10
  446. package/dist/packageMetadata.d.ts.map +0 -1
  447. package/dist/packageMetadata.js.map +0 -1
  448. package/dist/project.d.ts.map +0 -1
  449. package/dist/project.js.map +0 -1
  450. package/dist/projectDatabase.d.ts +0 -5
  451. package/dist/projectDatabase.d.ts.map +0 -1
  452. package/dist/projectDatabase.js +0 -22
  453. package/dist/projectDatabase.js.map +0 -1
  454. package/dist/projectEnvironment.d.ts +0 -6
  455. package/dist/projectEnvironment.d.ts.map +0 -1
  456. package/dist/projectEnvironment.js +0 -45
  457. package/dist/projectEnvironment.js.map +0 -1
  458. package/dist/projectInfrastructure.d.ts +0 -38
  459. package/dist/projectInfrastructure.d.ts.map +0 -1
  460. package/dist/projectInfrastructure.js +0 -145
  461. package/dist/projectInfrastructure.js.map +0 -1
  462. package/dist/runtime.d.ts +0 -54
  463. package/dist/runtime.d.ts.map +0 -1
  464. package/dist/runtime.js +0 -151
  465. package/dist/runtime.js.map +0 -1
  466. package/dist/secretStore.d.ts +0 -19
  467. package/dist/secretStore.d.ts.map +0 -1
  468. package/dist/secretStore.js +0 -22
  469. package/dist/secretStore.js.map +0 -1
  470. package/dist/types.d.ts +0 -70
  471. package/dist/types.d.ts.map +0 -1
  472. package/dist/types.js +0 -2
  473. package/dist/types.js.map +0 -1
  474. package/src/adapters/minikube/README.md +0 -115
  475. package/src/adapters/minikube/appRuntimeServing.test.ts +0 -28
  476. package/src/adapters/minikube/auth/index.ts +0 -40
  477. package/src/adapters/minikube/auth/oauthRuntime.ts +0 -163
  478. package/src/adapters/minikube/auth/supabase/index.test.ts +0 -227
  479. package/src/adapters/minikube/auth/supabase/index.ts +0 -293
  480. package/src/adapters/minikube/auth/supabase/oauthRuntime.test.ts +0 -154
  481. package/src/adapters/minikube/auth/supabase/profile.integration.test.ts +0 -1028
  482. package/src/adapters/minikube/auth/supabase/profile.ts +0 -526
  483. package/src/adapters/minikube/auth/supabase/providerAssignments.test.ts +0 -36
  484. package/src/adapters/minikube/auth/targetAwareOAuthRuntime.test.ts +0 -83
  485. package/src/adapters/minikube/auth/targetAwareOAuthRuntime.ts +0 -136
  486. package/src/adapters/minikube/authz/cerbos/index.ts +0 -486
  487. package/src/adapters/minikube/authz/index.ts +0 -32
  488. package/src/adapters/minikube/base/appImageBuild.test.ts +0 -352
  489. package/src/adapters/minikube/base/index.test.ts +0 -693
  490. package/src/adapters/minikube/base/index.ts +0 -3763
  491. package/src/adapters/minikube/base/portForwardRuntime.test.ts +0 -624
  492. package/src/adapters/minikube/base/supabaseMigrations.test.ts +0 -168
  493. package/src/adapters/minikube/base/supabaseMigrations.ts +0 -5
  494. package/src/adapters/minikube/base/twoAppIsolation.integration.test.ts +0 -898
  495. package/src/adapters/minikube/base/upgradeReconciliation.test.ts +0 -135
  496. package/src/adapters/minikube/contracts.ts +0 -46
  497. package/src/adapters/minikube/index.ts +0 -172
  498. package/src/adapters/minikube/secrets/index.test.ts +0 -147
  499. package/src/adapters/minikube/secrets/index.ts +0 -19
  500. package/src/adapters/minikube/secrets/supabase-vault/index.ts +0 -139
  501. package/src/adapters/minikube/storage/index.ts +0 -91
  502. package/src/adapters/minikube/storage/supabase/index.ts +0 -108
  503. package/src/apiInfrastructure.test.ts +0 -69
  504. package/src/authRedirects.test.ts +0 -63
  505. package/src/authRedirects.ts +0 -113
  506. package/src/authTargetRedirects.test.ts +0 -62
  507. package/src/authTargetRedirects.ts +0 -46
  508. package/src/commandContext.ts +0 -27
  509. package/src/commands.ts +0 -404
  510. package/src/generatedApiLedgerCleanup.test.ts +0 -72
  511. package/src/infraApiValidation.test.ts +0 -42
  512. package/src/infraValidation.ts +0 -99
  513. package/src/minikubeImageLifecycle.test.ts +0 -35
  514. package/src/projectDatabase.ts +0 -27
  515. package/src/projectEnvironment.ts +0 -51
  516. package/src/projectInfrastructure.ts +0 -221
  517. package/src/runtime.test.ts +0 -124
  518. package/src/runtime.ts +0 -234
  519. package/src/secretStore.test.ts +0 -76
  520. package/src/secretStore.ts +0 -44
  521. package/src/types.ts +0 -82
@@ -1,3763 +0,0 @@
1
- import type { GeneratedInfrastructureFile } from '../../../types';
2
- import type { InfraManifestInput } from '../../../types';
3
- import type { ResolvedProfileModel } from '../auth/supabase/profile';
4
- import { resolveSupabaseProfileModel } from '../auth/supabase/profile';
5
- import type { MinikubeProviderLifecycle } from '../contracts';
6
- import { getSupabaseMigrationCommandScript } from './supabaseMigrations';
7
-
8
- export const APP_NAMESPACE = 'app';
9
- const SUPABASE_NAMESPACE = 'supabase';
10
-
11
- interface SupabaseHostPorts {
12
- app: number;
13
- gateway: number;
14
- studio: number;
15
- db: number;
16
- }
17
-
18
- interface AppInfraStorageMetadata {
19
- provider: 'supabase';
20
- bucketsCsv: string;
21
- defaultBucket: string;
22
- }
23
-
24
- interface SupabaseImagePins {
25
- postgres: string;
26
- auth: string;
27
- rest: string;
28
- realtime: string;
29
- storage: string;
30
- imgproxy: string;
31
- meta: string;
32
- gateway: string;
33
- studio: string;
34
- }
35
-
36
- interface SupabaseOAuthProviderRuntime {
37
- id: string;
38
- envPrefix: string;
39
- credentialsRef: string;
40
- callbackRoute: string;
41
- }
42
-
43
- const SUPABASE_IMAGES: SupabaseImagePins = {
44
- postgres: 'supabase/postgres:17.6.1.136',
45
- auth: 'supabase/gotrue:v2.189.0',
46
- rest: 'postgrest/postgrest:v14.12',
47
- realtime: 'supabase/realtime:v2.102.3',
48
- storage: 'supabase/storage-api:v1.60.4',
49
- imgproxy: 'darthsim/imgproxy:v3.30.1',
50
- meta: 'supabase/postgres-meta:v0.96.6',
51
- gateway: 'kong/kong:3.9.1',
52
- studio: 'supabase/studio:2026.07.07-sha-a6a04f2',
53
- };
54
-
55
- const PORT_BASE = 18080;
56
- const PORT_BUCKET_SIZE = 20;
57
- const PORT_BUCKET_COUNT = 1000;
58
- const PORT_REFERENCE_SLUG = 'my-app';
59
-
60
- export function generateMinikubeBaseArtifacts(args: {
61
- manifest: InfraManifestInput;
62
- appSlug: string;
63
- appRuntimeEnabled: boolean;
64
- extraResources: string[];
65
- providerNamespaces: string[];
66
- providerLifecycle: MinikubeProviderLifecycle[];
67
- extraEnvEntries: string[];
68
- }): GeneratedInfrastructureFile[] {
69
- const {
70
- manifest,
71
- appSlug,
72
- appRuntimeEnabled,
73
- extraResources,
74
- providerNamespaces,
75
- providerLifecycle,
76
- extraEnvEntries,
77
- } = args;
78
-
79
- const root = 'infra/minikube';
80
- const k8sRoot = `${root}/k8s`;
81
- const scriptsRoot = `${root}/scripts`;
82
- const appImageRoot = `${root}/app-image`;
83
- const defaultAppImage = getDefaultAppImage(appSlug);
84
- const supabaseHostPorts = resolveSupabaseHostPorts(appSlug);
85
-
86
- const authScope = manifest.auth?.scope ?? 'none';
87
- const authProvider = manifest.auth?.provider ?? 'none';
88
- const authzEngine = manifest.auth?.authorization?.engine ?? 'none';
89
- const databaseProvider = manifest.database?.provider ?? 'none';
90
- const secretStoreProvider = manifest.secretStore?.provider ?? 'none';
91
- const storageMetadata = resolveAppInfraStorageMetadata(manifest);
92
- const profileModel = resolveSupabaseProfileModel(manifest);
93
- const oauthProviders = resolveSupabaseOAuthProviders(manifest);
94
- const monitoringEnabled = manifest.deployment?.monitoring === true;
95
- const domain = manifest.networking?.domain ?? '';
96
- const supabaseKubernetesEnabled =
97
- authProvider === 'supabase' ||
98
- databaseProvider === 'supabase' ||
99
- storageMetadata !== null ||
100
- secretStoreProvider === 'supabase-vault';
101
-
102
- const supabaseResources = supabaseKubernetesEnabled ? getSupabaseResourceFiles(k8sRoot) : [];
103
- const namespaceResources = [
104
- 'namespaces/app.yaml',
105
- ...(supabaseKubernetesEnabled ? ['namespaces/supabase.yaml'] : []),
106
- ...providerNamespaces.map((namespace) => `namespaces/${namespace}.yaml`),
107
- ];
108
-
109
- return [
110
- {
111
- path: `${root}/README.md`,
112
- content: getReadmeMarkdown({
113
- appSlug,
114
- appRuntimeEnabled,
115
- defaultAppImage,
116
- monitoringEnabled,
117
- authProvider,
118
- authzEngine,
119
- extraResources,
120
- supabaseHostPorts,
121
- supabaseKubernetesEnabled,
122
- profileModel,
123
- oauthProviders,
124
- providerNamespaces,
125
- providerLifecycle,
126
- }),
127
- },
128
- {
129
- path: `${root}/.env.example`,
130
- content: getEnvExample({
131
- appSlug,
132
- domain,
133
- extraEnvEntries,
134
- defaultAppImage,
135
- supabaseHostPorts,
136
- supabaseKubernetesEnabled,
137
- oauthProviders,
138
- }),
139
- },
140
- {
141
- path: `${k8sRoot}/namespaces/app.yaml`,
142
- content: getNamespaceManifest(APP_NAMESPACE),
143
- },
144
- ...(supabaseKubernetesEnabled
145
- ? [
146
- {
147
- path: `${k8sRoot}/namespaces/supabase.yaml`,
148
- content: getNamespaceManifest(SUPABASE_NAMESPACE),
149
- },
150
- ]
151
- : []),
152
- ...providerNamespaces.map((namespace) => ({
153
- path: `${k8sRoot}/namespaces/${namespace}.yaml`,
154
- content: getNamespaceManifest(namespace),
155
- })),
156
- ...(appRuntimeEnabled
157
- ? [
158
- {
159
- path: `${k8sRoot}/app.configmap.yaml`,
160
- content: getAppConfigMap({
161
- appSlug,
162
- authScope,
163
- authProvider,
164
- databaseProvider,
165
- secretStoreProvider,
166
- storageMetadata,
167
- monitoringEnabled,
168
- domain,
169
- }),
170
- },
171
- {
172
- path: `${k8sRoot}/app/deployment.yaml`,
173
- content: getAppDeploymentManifest(defaultAppImage),
174
- },
175
- {
176
- path: `${k8sRoot}/app/service.yaml`,
177
- content: getAppServiceManifest(),
178
- },
179
- ]
180
- : []),
181
- ...supabaseResources,
182
- {
183
- path: `${k8sRoot}/kustomization.yaml`,
184
- content: getKustomizationManifest({
185
- namespaceResources,
186
- appRuntimeEnabled,
187
- supabaseEnabled: supabaseKubernetesEnabled,
188
- extraResources,
189
- }),
190
- },
191
- {
192
- path: `${scriptsRoot}/up.sh`,
193
- content: getUpScript({
194
- appSlug,
195
- appRuntimeEnabled,
196
- defaultAppImage,
197
- supabaseKubernetesEnabled,
198
- profileModel,
199
- secretStoreProvider,
200
- supabaseHostPorts,
201
- oauthProviders,
202
- providerLifecycle,
203
- }),
204
- executable: true,
205
- },
206
- {
207
- path: `${scriptsRoot}/build-app-image.sh`,
208
- content: getBuildAppImageScript({ appSlug, defaultAppImage }),
209
- executable: true,
210
- },
211
- {
212
- path: `${scriptsRoot}/port-forward.sh`,
213
- content: getPortForwardScript({
214
- appSlug,
215
- appRuntimeEnabled,
216
- supabaseKubernetesEnabled,
217
- supabaseHostPorts,
218
- }),
219
- executable: true,
220
- },
221
- {
222
- path: `${scriptsRoot}/down.sh`,
223
- content: getDownScript(appSlug),
224
- executable: true,
225
- },
226
- {
227
- path: `${scriptsRoot}/reset.sh`,
228
- content: getResetScript({
229
- appSlug,
230
- supabaseKubernetesEnabled,
231
- providerNamespaces,
232
- }),
233
- executable: true,
234
- },
235
- {
236
- path: `${scriptsRoot}/destroy.sh`,
237
- content: getDestroyScript(appSlug),
238
- executable: true,
239
- },
240
- {
241
- path: `${scriptsRoot}/status.sh`,
242
- content: getStatusScript({
243
- appSlug,
244
- appRuntimeEnabled,
245
- supabaseKubernetesEnabled,
246
- profileModel,
247
- supabaseHostPorts,
248
- providerNamespaces,
249
- providerLifecycle,
250
- }),
251
- executable: true,
252
- },
253
- {
254
- path: `${appImageRoot}/Dockerfile`,
255
- content: getAppImageDockerfile(),
256
- },
257
- ];
258
- }
259
-
260
- function getReadmeMarkdown(args: {
261
- appSlug: string;
262
- appRuntimeEnabled: boolean;
263
- defaultAppImage: string;
264
- monitoringEnabled: boolean;
265
- authProvider: string;
266
- authzEngine: string;
267
- extraResources: string[];
268
- supabaseHostPorts: SupabaseHostPorts;
269
- supabaseKubernetesEnabled: boolean;
270
- profileModel: ResolvedProfileModel;
271
- oauthProviders: readonly SupabaseOAuthProviderRuntime[];
272
- providerNamespaces: readonly string[];
273
- providerLifecycle: readonly MinikubeProviderLifecycle[];
274
- }): string {
275
- const {
276
- appSlug,
277
- appRuntimeEnabled,
278
- defaultAppImage,
279
- monitoringEnabled,
280
- authProvider,
281
- authzEngine,
282
- extraResources,
283
- supabaseHostPorts,
284
- supabaseKubernetesEnabled,
285
- profileModel,
286
- oauthProviders,
287
- providerNamespaces,
288
- providerLifecycle,
289
- } = args;
290
- const resourceLines = [
291
- 'namespaces/app.yaml',
292
- ...(supabaseKubernetesEnabled ? ['namespaces/supabase.yaml'] : []),
293
- ...providerNamespaces.map((namespace) => `namespaces/${namespace}.yaml`),
294
- ...(appRuntimeEnabled ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml'] : []),
295
- ...(supabaseKubernetesEnabled
296
- ? [
297
- 'supabase/bootstrap.sql',
298
- 'supabase/postgres.init.configmap.yaml',
299
- 'supabase/postgres.pvc.yaml',
300
- 'supabase/postgres.yaml',
301
- 'supabase/auth.yaml',
302
- 'supabase/rest.yaml',
303
- 'supabase/realtime.yaml',
304
- 'supabase/storage.yaml',
305
- 'supabase/imgproxy.yaml',
306
- 'supabase/meta.yaml',
307
- 'supabase/gateway.template.yml',
308
- 'supabase/kong-entrypoint.sh',
309
- 'supabase/gateway.yaml',
310
- 'supabase/studio.yaml',
311
- ]
312
- : []),
313
- ...extraResources,
314
- ]
315
- .map((resource) => `- \`k8s/${resource}\``)
316
- .join('\n');
317
-
318
- const resetNamespaces = [
319
- APP_NAMESPACE,
320
- ...(supabaseKubernetesEnabled ? [SUPABASE_NAMESPACE] : []),
321
- ...providerNamespaces,
322
- ];
323
- const resetDescription = `deletes and recreates namespaces ${resetNamespaces
324
- .map((namespace) => `\`${namespace}\``)
325
- .join(
326
- ', ',
327
- )} inside the existing profile. This removes app-owned Kubernetes state and provider-owned PVC data for reset namespaces`;
328
- const providerNamespaceLine =
329
- providerNamespaces.length > 0
330
- ? `- Provider namespaces: \`${providerNamespaces.join('`, `')}\`\n`
331
- : '';
332
- const providerLifecycleLines =
333
- providerLifecycle.length > 0
334
- ? `## Provider Lifecycle Contributions\n\n${providerLifecycle
335
- .map((provider) => {
336
- const readiness =
337
- provider.readinessChecks.length > 0
338
- ? provider.readinessChecks
339
- .map((check) => `\`${check.resource}\` in namespace \`${check.namespace}\``)
340
- .join(', ')
341
- : 'none';
342
- const endpoints =
343
- provider.endpoints.length > 0
344
- ? provider.endpoints
345
- .map((endpoint) => `\`${endpoint.name}\` = \`${endpoint.url}\``)
346
- .join(', ')
347
- : 'none';
348
- return `- \`${provider.id}\`: namespace \`${provider.namespace}\`; readiness ${readiness}; endpoints ${endpoints}`;
349
- })
350
- .join('\n')}`
351
- : '';
352
- const oauthDescription =
353
- oauthProviders.length > 0
354
- ? `\nConfigured OAuth providers are wired into GoTrue through generated \`GOTRUE_EXTERNAL_*\` runtime keys. The keys are named in \`.env.example\`; values must come from trusted secret resolution for the configured \`credentialsRef\` entries.`
355
- : '';
356
-
357
- const runtimeForwardDescription = appRuntimeEnabled
358
- ? supabaseKubernetesEnabled
359
- ? '`app` and `supabase-gateway`'
360
- : '`app`'
361
- : supabaseKubernetesEnabled
362
- ? '`supabase-gateway`'
363
- : 'no host endpoints';
364
-
365
- return `# Minikube Infra
366
-
367
- This directory is generated from \`ankh.config.json\` (infra manifest).
368
-
369
- ## Ownership Model
370
-
371
- - App slug / Minikube profile: \`${appSlug}\`
372
- - App runtime: \`${appRuntimeEnabled ? `deployment/app-runtime in namespace ${APP_NAMESPACE}` : 'disabled (no enabled Web deploy target)'}\`
373
- - Supabase namespace: \`${supabaseKubernetesEnabled ? SUPABASE_NAMESPACE : 'unused'}\`
374
- ${providerNamespaceLine}- Default app image: \`${defaultAppImage}\`
375
-
376
- One app owns one complete Minikube profile. Provider-owned workloads are separated by
377
- Kubernetes namespace inside that profile. There is no shared \`minikube\` profile and no
378
- host-level Supabase Compose runtime.
379
-
380
- ## Quick Start
381
-
382
- 1. Copy env template:
383
- \`\`\`bash
384
- cp .env.example .env
385
- \`\`\`
386
- 2. Adjust optional values in \`.env\`; missing Supabase development secrets are generated by \`up.sh\`.
387
- 3. Start infra:
388
- \`\`\`bash
389
- ./scripts/up.sh
390
- \`\`\`
391
- 4. Check status:
392
- \`\`\`bash
393
- ./scripts/status.sh
394
- \`\`\`
395
- 5. Stop the app-owned cluster without deleting data:
396
- \`\`\`bash
397
- ./scripts/down.sh
398
- \`\`\`
399
-
400
- ## Lifecycle Semantics
401
-
402
- - \`up.sh\`: starts \`minikube -p ${appSlug}\`, deploys provider namespaces, runs migrations with CLI telemetry disabled for that command, starts slug-owned port-forwards${appRuntimeEnabled ? ', and deploys the Web app runtime' : ''}.
403
- - \`down.sh\`: stops slug-owned port-forwards, then stops \`minikube -p ${appSlug}\`. Persistent data remains in the profile.
404
- - \`reset.sh\`: requires \`ANKH_RESET_CONFIRM=${appSlug}\`; ${resetDescription}. It does not delete the Minikube profile.
405
- - \`destroy.sh\`: deletes only \`minikube -p ${appSlug}\`.
406
-
407
- ### Port-forward groups
408
-
409
- - \`./scripts/port-forward.sh start runtime\` idempotently ensures ${runtimeForwardDescription}, the generated host endpoints required by the running application. The same group supports \`stop\` and \`status\`.
410
- - \`runtime\` excludes \`studio\` and \`db-migration\`, which are operational/bootstrap endpoints.
411
- - \`all\` retains every generated forward, including operational/bootstrap endpoints.
412
- - Concrete targets remain available for individual lifecycle control.
413
-
414
- ## Generated Resources
415
-
416
- ${resourceLines}
417
-
418
- ## Supabase Runtime
419
-
420
- Supabase runtime ownership is Kubernetes. Migration authoring/history remains Supabase
421
- migration files. Migration execution targets the Kubernetes Postgres endpoint through
422
- \`SUPABASE_TELEMETRY_DISABLED=1 supabase --yes migration up --db-url "$SUPABASE_DB_URL"\`.
423
- The migration process does not require stdin. \`--yes\` remains defense in depth for future
424
- CLI prompts; it is not the hang fix. Command-scoped telemetry disable avoids the unbounded
425
- telemetry shutdown in Supabase CLI 2.106.0 when PostHog is blocked, without changing the
426
- user's persistent telemetry preference. Migration failures stop \`up.sh\`, and the completion
427
- message is printed only after a zero exit status. No migration deadline is added; using a
428
- current Supabase CLI remains recommended.
429
-
430
- Supabase manifests are generated from the current official Supabase self-hosting Docker
431
- topology, service documentation, environment-variable contracts, and pinned official images.
432
- Kubernetes/Helm is treated as community-driven upstream guidance, not an official
433
- distribution copied by this generator.
434
- ${oauthDescription}
435
-
436
- Pinned images:
437
-
438
- - Postgres: \`${SUPABASE_IMAGES.postgres}\`
439
- - Auth: \`${SUPABASE_IMAGES.auth}\`
440
- - PostgREST: \`${SUPABASE_IMAGES.rest}\`
441
- - Realtime: \`${SUPABASE_IMAGES.realtime}\`
442
- - Storage: \`${SUPABASE_IMAGES.storage}\`
443
- - Gateway: \`${SUPABASE_IMAGES.gateway}\`
444
- - Studio: \`${SUPABASE_IMAGES.studio}\`
445
-
446
- ## Host URLs
447
-
448
- ${appRuntimeEnabled ? `- App: \`http://127.0.0.1:${supabaseHostPorts.app}\`\n` : ''}- Supabase gateway: \`http://127.0.0.1:${supabaseHostPorts.gateway}\`
449
- - Studio: \`http://127.0.0.1:${supabaseHostPorts.studio}\`
450
- - DB migration endpoint: \`127.0.0.1:${supabaseHostPorts.db}\`
451
-
452
- ## App Image Export
453
-
454
- \`build-app-image.sh\` requires the app-installed \`node_modules/.bin/expo\` and never resolves
455
- Expo globally or through the network. It clears the Expo/Metro bundler cache before every web
456
- export. Expo inlines \`EXPO_PUBLIC_*\` values into the browser bundle, so cache invalidation
457
- ensures a rotated browser-safe Supabase URL or anon key replaces the previous value before the
458
- Docker image is built. Privileged Supabase credentials are not written to the app's public env
459
- file.
460
-
461
- ## Runtime Conventions
462
-
463
- - Monitoring requested: \`${monitoringEnabled ? 'true' : 'false'}\`
464
- - Auth provider: \`${authProvider}\`
465
- - Authorization engine: \`${authzEngine}\`
466
- ${profileModel.enabled ? '- Generated profile reconciliation lives in `supabase/generated/auth_profiles.sql` and records checksum state in `ankhorage_internal.generated_schema_state`.\n' : ''}
467
- ${providerLifecycleLines}
468
- `;
469
- }
470
-
471
- function getEnvExample(args: {
472
- appSlug: string;
473
- domain: string;
474
- extraEnvEntries: string[];
475
- defaultAppImage: string;
476
- supabaseHostPorts: SupabaseHostPorts;
477
- supabaseKubernetesEnabled: boolean;
478
- oauthProviders: readonly SupabaseOAuthProviderRuntime[];
479
- }): string {
480
- const { appSlug, domain, extraEnvEntries, defaultAppImage, supabaseHostPorts, oauthProviders } =
481
- args;
482
-
483
- const baseEntries = [
484
- '# App-owned Minikube profile configuration',
485
- `ANKH_APP_SLUG=${appSlug}`,
486
- 'MINIKUBE_DRIVER=docker',
487
- 'APP_BUILD_ENABLED=true',
488
- 'APP_SOURCE_DIR=',
489
- 'APP_WEB_EXPORT_DIR=.ankh/web-export',
490
- `APP_IMAGE=${defaultAppImage}`,
491
- 'APP_IMAGE_SYNC_STRATEGY=docker-load',
492
- 'APP_IMAGE_CLEANUP_ON_DESTROY=true',
493
- 'APP_IMAGE_CLEANUP_MINIKUBE=true',
494
- 'APP_IMAGE_CLEANUP_DOCKER=true',
495
- `APP_PORT_FORWARD_LOCAL_PORT=${supabaseHostPorts.app}`,
496
- 'APP_PORT_FORWARD_REMOTE_PORT=80',
497
- `SUPABASE_GATEWAY_FORWARD_LOCAL_PORT=${supabaseHostPorts.gateway}`,
498
- 'SUPABASE_GATEWAY_FORWARD_REMOTE_PORT=8000',
499
- `SUPABASE_STUDIO_FORWARD_LOCAL_PORT=${supabaseHostPorts.studio}`,
500
- 'SUPABASE_STUDIO_FORWARD_REMOTE_PORT=3000',
501
- `SUPABASE_DB_FORWARD_LOCAL_PORT=${supabaseHostPorts.db}`,
502
- 'SUPABASE_DB_FORWARD_REMOTE_PORT=5432',
503
- 'APP_REPLICAS=1',
504
- 'APP_FORCE_ROLLOUT_RESTART=true',
505
- '',
506
- '# Optional private registry auth for APP_IMAGE',
507
- 'APP_IMAGE_PULL_SECRET_NAME=',
508
- 'APP_IMAGE_PULL_SECRET_SERVER=ghcr.io',
509
- 'APP_IMAGE_PULL_SECRET_USERNAME=',
510
- 'APP_IMAGE_PULL_SECRET_PASSWORD=',
511
- 'APP_IMAGE_PULL_SECRET_EMAIL=',
512
- '',
513
- '# Optional app networking domain',
514
- `APP_DOMAIN=${domain}`,
515
- '',
516
- '# Supabase Kubernetes runtime secrets',
517
- 'POSTGRES_PASSWORD=',
518
- 'JWT_SECRET=',
519
- 'SUPABASE_ANON_KEY=',
520
- 'SUPABASE_SERVICE_ROLE_KEY=',
521
- 'SUPABASE_PUBLISHABLE_KEY=',
522
- 'SUPABASE_SECRET_KEY=',
523
- 'DASHBOARD_USERNAME=supabase',
524
- 'DASHBOARD_PASSWORD=',
525
- 'SECRET_KEY_BASE=',
526
- 'REALTIME_DB_ENC_KEY=',
527
- 'PG_META_CRYPTO_KEY=',
528
- 'S3_PROTOCOL_ACCESS_KEY_ID=',
529
- 'S3_PROTOCOL_ACCESS_KEY_SECRET=',
530
- 'JWT_EXPIRY=3600',
531
- 'SITE_URL=',
532
- 'OAUTH_NATIVE_REDIRECT_URLS=',
533
- 'ADDITIONAL_REDIRECT_URLS=',
534
- 'SUPABASE_PUBLIC_URL=',
535
- 'API_EXTERNAL_URL=',
536
- 'ENABLE_EMAIL_SIGNUP=true',
537
- 'ENABLE_EMAIL_AUTOCONFIRM=true',
538
- 'SMTP_ADMIN_EMAIL=admin@example.com',
539
- 'SMTP_HOST=localhost',
540
- 'SMTP_PORT=2500',
541
- 'SMTP_USER=',
542
- 'SMTP_PASS=',
543
- 'SMTP_SENDER_NAME=Ankhorage',
544
- 'PGRST_DB_SCHEMAS=public,storage,graphql_public',
545
- 'PGRST_DB_MAX_ROWS=1000',
546
- 'PGRST_DB_EXTRA_SEARCH_PATH=public',
547
- 'GLOBAL_S3_BUCKET=stub',
548
- 'STORAGE_TENANT_ID=stub',
549
- 'REGION=stub',
550
- 'SUPABASE_DB_URL=',
551
- 'EXPO_PUBLIC_SUPABASE_URL=',
552
- 'EXPO_PUBLIC_SUPABASE_ANON_KEY=',
553
- ...getOAuthEnvExampleEntries(oauthProviders),
554
- '',
555
- '# Adapter-provided runtime keys',
556
- ];
557
-
558
- const merged = [...baseEntries, ...extraEnvEntries];
559
- const unique: string[] = [];
560
- for (const entry of merged) {
561
- if (!unique.includes(entry)) unique.push(entry);
562
- }
563
-
564
- return `${unique.join('\n')}\n`;
565
- }
566
-
567
- function getOAuthEnvExampleEntries(providers: readonly SupabaseOAuthProviderRuntime[]): string[] {
568
- if (providers.length === 0) return [];
569
-
570
- return [
571
- '',
572
- '# OAuth provider runtime values resolved from trusted credentialsRef entries',
573
- ...providers.flatMap((provider) => [
574
- `# ${provider.id}: ${provider.credentialsRef}`,
575
- `GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID=`,
576
- `GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET=`,
577
- `GOTRUE_EXTERNAL_${provider.envPrefix}_REDIRECT_URI=`,
578
- ]),
579
- ];
580
- }
581
-
582
- function getOAuthScriptEnvDefaults(providers: readonly SupabaseOAuthProviderRuntime[]): string {
583
- if (providers.length === 0) return '';
584
-
585
- return providers
586
- .map(
587
- (
588
- provider,
589
- ) => ` export GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID="\${GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID:-}"
590
- export GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET="\${GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET:-}"
591
- export GOTRUE_EXTERNAL_${provider.envPrefix}_REDIRECT_URI="\${GOTRUE_EXTERNAL_${provider.envPrefix}_REDIRECT_URI:-\${API_EXTERNAL_URL}/callback}"`,
592
- )
593
- .join('\n');
594
- }
595
-
596
- function getOAuthRuntimeSecretLines(providers: readonly SupabaseOAuthProviderRuntime[]): string {
597
- if (providers.length === 0) return '';
598
-
599
- return providers
600
- .map(
601
- (provider) => `GOTRUE_EXTERNAL_${provider.envPrefix}_ENABLED=true
602
- GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID=\${GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID}
603
- GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET=\${GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET}
604
- GOTRUE_EXTERNAL_${provider.envPrefix}_REDIRECT_URI=\${GOTRUE_EXTERNAL_${provider.envPrefix}_REDIRECT_URI}`,
605
- )
606
- .join('\n');
607
- }
608
-
609
- function getOAuthRuntimeValidationScript(
610
- providers: readonly SupabaseOAuthProviderRuntime[],
611
- ): string {
612
- if (providers.length === 0) return '';
613
-
614
- const checks = providers
615
- .map(
616
- (
617
- provider,
618
- ) => ` if [[ -z "\${GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID:-}" || -z "\${GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET:-}" ]]; then
619
- echo "OAuth provider ${provider.id} requires trusted runtime values GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID and GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET from credentialsRef ${provider.credentialsRef}."
620
- exit 1
621
- fi`,
622
- )
623
- .join('\n');
624
-
625
- return `
626
- # OAuth provider secrets must be resolved by trusted host code before Kubernetes sync.
627
- ${checks}`;
628
- }
629
-
630
- function getSupabaseOAuthCallbackRoute(providers: readonly SupabaseOAuthProviderRuntime[]): string {
631
- return providers[0]?.callbackRoute ?? '/auth/callback';
632
- }
633
-
634
- function getOAuthVaultResolutionScript(args: {
635
- appSlug: string;
636
- oauthProviders: readonly SupabaseOAuthProviderRuntime[];
637
- secretStoreProvider: string;
638
- }): string {
639
- const { appSlug, oauthProviders, secretStoreProvider } = args;
640
- if (secretStoreProvider !== 'supabase-vault' || oauthProviders.length === 0) {
641
- return `
642
- resolve_oauth_credentials_from_vault() {
643
- return 0
644
- }
645
- `;
646
- }
647
-
648
- const lines = oauthProviders
649
- .map((provider) => {
650
- const ref = escapeSingleQuotedSql(provider.credentialsRef);
651
- const idKey = `GOTRUE_EXTERNAL_${provider.envPrefix}_CLIENT_ID`;
652
- const secretKey = `GOTRUE_EXTERNAL_${provider.envPrefix}_SECRET`;
653
-
654
- return ` if [[ -z "\${${idKey}:-}" || -z "\${${secretKey}:-}" ]]; then
655
- local resolved_client_id resolved_client_secret
656
- resolved_client_id="$(resolve_vault_secret_field '${ref}' 'clientId')"
657
- resolved_client_secret="$(resolve_vault_secret_field '${ref}' 'clientSecret')"
658
- if [[ -n "\${resolved_client_id}" ]]; then
659
- export ${idKey}="\${resolved_client_id}"
660
- fi
661
- if [[ -n "\${resolved_client_secret}" ]]; then
662
- export ${secretKey}="\${resolved_client_secret}"
663
- fi
664
- fi`;
665
- })
666
- .join('\n');
667
-
668
- return `
669
- resolve_vault_secret_field() {
670
- local ref="\${1}"
671
- local field="\${2}"
672
- psql "\${SUPABASE_DB_URL}" -v ON_ERROR_STOP=1 -Atq -c "select coalesce(decrypted.decrypted_secret::jsonb ->> '\${field}', '') from ankh_secret_store.secret_metadata metadata join vault.decrypted_secrets decrypted on decrypted.id = metadata.vault_secret_id where metadata.project_id = '${escapeSingleQuotedSql(
673
- appSlug,
674
- )}' and metadata.environment = 'local' and metadata.secret_ref = '\${ref}' limit 1;" 2>/dev/null || true
675
- }
676
-
677
- resolve_oauth_credentials_from_vault() {
678
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
679
- return 0
680
- fi
681
-
682
- ${lines}
683
- }
684
- `;
685
- }
686
-
687
- function escapeSingleQuotedSql(value: string): string {
688
- return value.replace(/'/gu, "''");
689
- }
690
-
691
- function escapeShellDoubleQuoted(value: string): string {
692
- return value.replace(/["\\$`]/gu, (char) => `\\${char}`);
693
- }
694
-
695
- function getProviderLifecycleStageScripts(
696
- providerLifecycle: readonly MinikubeProviderLifecycle[],
697
- ): string {
698
- return `${getProviderLifecycleCommandFunction({
699
- functionName: 'run_provider_migrations',
700
- emptyReturn: 'return 0',
701
- commands: providerLifecycle.flatMap((provider) =>
702
- provider.migrationCommands.map((command) => ({
703
- providerId: provider.id,
704
- label: command.label,
705
- command: command.command,
706
- })),
707
- ),
708
- })}
709
-
710
- ${getProviderLifecycleCommandFunction({
711
- functionName: 'run_provider_reconciliation',
712
- emptyReturn: 'return 0',
713
- commands: providerLifecycle.flatMap((provider) =>
714
- provider.reconciliationCommands.map((command) => ({
715
- providerId: provider.id,
716
- label: command.label,
717
- command: command.command,
718
- })),
719
- ),
720
- })}
721
-
722
- ${getProviderReadinessFunction(providerLifecycle)}`;
723
- }
724
-
725
- function getProviderLifecycleCommandFunction(args: {
726
- functionName: string;
727
- emptyReturn: string;
728
- commands: readonly { providerId: string; label: string; command: string }[];
729
- }): string {
730
- if (args.commands.length === 0) {
731
- return `${args.functionName}() {
732
- ${args.emptyReturn}
733
- }`;
734
- }
735
-
736
- const body = args.commands
737
- .map(
738
- (command) => ` echo "Running provider ${escapeShellDoubleQuoted(
739
- command.providerId,
740
- )} ${escapeShellDoubleQuoted(command.label)}."
741
- ${indentShellBlock(command.command.trim(), 2)}`,
742
- )
743
- .join('\n\n');
744
-
745
- return `${args.functionName}() {
746
- ${body}
747
- }`;
748
- }
749
-
750
- function getProviderReadinessFunction(
751
- providerLifecycle: readonly MinikubeProviderLifecycle[],
752
- ): string {
753
- const checks = providerLifecycle.flatMap((provider) =>
754
- provider.readinessChecks.map((check) => ({ providerId: provider.id, ...check })),
755
- );
756
-
757
- if (checks.length === 0) {
758
- return `wait_for_provider_readiness() {
759
- return 0
760
- }`;
761
- }
762
-
763
- const body = checks
764
- .map(
765
- (check) =>
766
- ` kubectl --context "\${PROFILE}" -n "${escapeShellDoubleQuoted(
767
- check.namespace,
768
- )}" rollout status "${escapeShellDoubleQuoted(
769
- check.resource,
770
- )}" --timeout=${check.timeoutSeconds}s`,
771
- )
772
- .join('\n');
773
-
774
- return `wait_for_provider_readiness() {
775
- ${body}
776
- }`;
777
- }
778
-
779
- function getProviderStatusScript(providerLifecycle: readonly MinikubeProviderLifecycle[]): string {
780
- if (providerLifecycle.length === 0) {
781
- return '';
782
- }
783
-
784
- return providerLifecycle
785
- .map((provider) => {
786
- const endpointLines = provider.endpoints
787
- .map(
788
- (endpoint) =>
789
- ` echo "- provider ${escapeShellDoubleQuoted(
790
- provider.id,
791
- )} endpoint/${escapeShellDoubleQuoted(endpoint.name)}: ${escapeShellDoubleQuoted(
792
- endpoint.url,
793
- )}"`,
794
- )
795
- .join('\n');
796
- const readinessLines = provider.readinessChecks
797
- .map(
798
- (check) => ` if kubectl --context "\${PROFILE}" -n "${escapeShellDoubleQuoted(
799
- check.namespace,
800
- )}" rollout status "${escapeShellDoubleQuoted(
801
- check.resource,
802
- )}" --timeout=5s >/dev/null 2>&1; then
803
- echo "- provider ${escapeShellDoubleQuoted(provider.id)}/${escapeShellDoubleQuoted(
804
- check.label,
805
- )}: ready"
806
- else
807
- echo "- provider ${escapeShellDoubleQuoted(provider.id)}/${escapeShellDoubleQuoted(
808
- check.label,
809
- )}: not ready"
810
- fi`,
811
- )
812
- .join('\n');
813
- const customStatusLines = provider.statusChecks
814
- .map(
815
- (check) => ` echo "Running provider ${escapeShellDoubleQuoted(
816
- provider.id,
817
- )} status check ${escapeShellDoubleQuoted(check.label)}."
818
- ${indentShellBlock(check.command.trim(), 2)}`,
819
- )
820
- .join('\n');
821
-
822
- return [endpointLines, readinessLines, customStatusLines].filter(Boolean).join('\n');
823
- })
824
- .filter(Boolean)
825
- .join('\n');
826
- }
827
-
828
- function indentShellBlock(value: string, spaces: number): string {
829
- const indent = ' '.repeat(spaces);
830
- return value
831
- .split('\n')
832
- .map((line) => `${indent}${line}`)
833
- .join('\n');
834
- }
835
-
836
- function getNamespaceManifest(namespace: string): string {
837
- return `apiVersion: v1
838
- kind: Namespace
839
- metadata:
840
- name: ${namespace}
841
- labels:
842
- app.kubernetes.io/part-of: ankhorage
843
- ankhorage.io/namespace-role: ${namespace}
844
- `;
845
- }
846
-
847
- function getAppConfigMap(args: {
848
- appSlug: string;
849
- authScope: string;
850
- authProvider: string;
851
- databaseProvider: string;
852
- secretStoreProvider: string;
853
- storageMetadata: AppInfraStorageMetadata | null;
854
- monitoringEnabled: boolean;
855
- domain: string;
856
- }): string {
857
- const {
858
- appSlug,
859
- authScope,
860
- authProvider,
861
- databaseProvider,
862
- secretStoreProvider,
863
- storageMetadata,
864
- monitoringEnabled,
865
- domain,
866
- } = args;
867
-
868
- const storageLines = storageMetadata
869
- ? ` STORAGE_PROVIDER: "${storageMetadata.provider}"
870
- STORAGE_BUCKETS: "${escapeYamlDoubleQuoted(storageMetadata.bucketsCsv)}"
871
- STORAGE_DEFAULT_BUCKET: "${escapeYamlDoubleQuoted(storageMetadata.defaultBucket)}"
872
- `
873
- : '';
874
-
875
- return `apiVersion: v1
876
- kind: ConfigMap
877
- metadata:
878
- name: app-infra-config
879
- namespace: ${APP_NAMESPACE}
880
- data:
881
- ANKH_APP_SLUG: "${appSlug}"
882
- DEPLOYMENT_TARGET: "minikube"
883
- APP_NAMESPACE: "${APP_NAMESPACE}"
884
- SUPABASE_NAMESPACE: "${SUPABASE_NAMESPACE}"
885
- MONITORING_ENABLED: "${monitoringEnabled ? 'true' : 'false'}"
886
- AUTH_SCOPE: "${authScope}"
887
- AUTH_PROVIDER: "${authProvider}"
888
- DATABASE_PROVIDER: "${databaseProvider}"
889
- SECRET_STORE_PROVIDER: "${secretStoreProvider}"
890
- ${storageLines} NETWORK_DOMAIN: "${escapeYamlDoubleQuoted(domain)}"
891
- `;
892
- }
893
-
894
- function resolveAppInfraStorageMetadata(
895
- manifest: InfraManifestInput,
896
- ): AppInfraStorageMetadata | null {
897
- const spec = manifest.storage;
898
- if (!spec) return null;
899
-
900
- const buckets = normalizeBuckets(spec.buckets);
901
- if (buckets.length === 0) return null;
902
- const [defaultBucket] = buckets;
903
- if (!defaultBucket) return null;
904
-
905
- if (spec.provider === 'supabase') {
906
- return {
907
- provider: 'supabase',
908
- bucketsCsv: buckets.join(','),
909
- defaultBucket,
910
- };
911
- }
912
-
913
- if (spec.provider === 'auto') {
914
- const shouldResolveToSupabase =
915
- manifest.database?.provider === 'supabase' || manifest.auth?.provider === 'supabase';
916
-
917
- if (!shouldResolveToSupabase) return null;
918
-
919
- return {
920
- provider: 'supabase',
921
- bucketsCsv: buckets.join(','),
922
- defaultBucket,
923
- };
924
- }
925
-
926
- return null;
927
- }
928
-
929
- function resolveSupabaseOAuthProviders(
930
- manifest: InfraManifestInput,
931
- ): readonly SupabaseOAuthProviderRuntime[] {
932
- const oauth = manifest.auth?.oauth;
933
- if (!oauth?.enabled) return [];
934
- const callbackRoute = normalizeOAuthCallbackRoute(oauth.callbackRoute);
935
-
936
- return oauth.providers
937
- .filter((provider) => provider.enabled !== false)
938
- .map((provider) => {
939
- const id = provider.id.trim();
940
- const credentialsRef = provider.credentialsRef?.trim();
941
- if (!credentialsRef) {
942
- throw new Error(`OAuth provider "${id}" is enabled but does not define credentialsRef.`);
943
- }
944
-
945
- const envPrefix = id
946
- .toUpperCase()
947
- .replace(/[^A-Z0-9]+/gu, '_')
948
- .replace(/^_+|_+$/gu, '');
949
- if (!envPrefix) {
950
- throw new Error(`OAuth provider "${id}" cannot be mapped to GoTrue environment keys.`);
951
- }
952
-
953
- return {
954
- id,
955
- envPrefix,
956
- credentialsRef,
957
- callbackRoute,
958
- };
959
- });
960
- }
961
-
962
- function normalizeOAuthCallbackRoute(rawRoute: string | undefined): string {
963
- const trimmedRoute = rawRoute?.trim();
964
- const route = trimmedRoute && trimmedRoute.length > 0 ? trimmedRoute : '/auth/callback';
965
- const withLeadingSlash = route.startsWith('/') ? route : `/${route}`;
966
- return withLeadingSlash.replace(/\/+/gu, '/');
967
- }
968
-
969
- function normalizeBuckets(rawBuckets: readonly string[]): string[] {
970
- const normalized: string[] = [];
971
- const seen = new Set<string>();
972
-
973
- for (const raw of rawBuckets) {
974
- const bucket = raw.trim();
975
- if (!bucket) continue;
976
- if (seen.has(bucket)) continue;
977
- seen.add(bucket);
978
- normalized.push(bucket);
979
- }
980
-
981
- return normalized;
982
- }
983
-
984
- function getKustomizationManifest(args: {
985
- namespaceResources: string[];
986
- appRuntimeEnabled: boolean;
987
- supabaseEnabled: boolean;
988
- extraResources: string[];
989
- }): string {
990
- const supabaseResources = args.supabaseEnabled
991
- ? [
992
- 'supabase/postgres.init.configmap.yaml',
993
- 'supabase/postgres.pvc.yaml',
994
- 'supabase/postgres.yaml',
995
- 'supabase/auth.yaml',
996
- 'supabase/rest.yaml',
997
- 'supabase/realtime.yaml',
998
- 'supabase/imgproxy.yaml',
999
- 'supabase/storage.yaml',
1000
- 'supabase/meta.yaml',
1001
- 'supabase/gateway.yaml',
1002
- 'supabase/studio.yaml',
1003
- ]
1004
- : [];
1005
- const resources = [
1006
- ...args.namespaceResources,
1007
- ...(args.appRuntimeEnabled
1008
- ? ['app.configmap.yaml', 'app/deployment.yaml', 'app/service.yaml']
1009
- : []),
1010
- ...supabaseResources,
1011
- ...args.extraResources,
1012
- ];
1013
- const resourceLines = resources.map((resource) => ` - ${resource}`).join('\n');
1014
-
1015
- return `apiVersion: kustomize.config.k8s.io/v1beta1
1016
- kind: Kustomization
1017
- resources:
1018
- ${resourceLines}
1019
- `;
1020
- }
1021
-
1022
- function getAppDeploymentManifest(defaultAppImage: string): string {
1023
- return `apiVersion: apps/v1
1024
- kind: Deployment
1025
- metadata:
1026
- name: app-runtime
1027
- namespace: ${APP_NAMESPACE}
1028
- labels:
1029
- app.kubernetes.io/name: app-runtime
1030
- spec:
1031
- replicas: 1
1032
- selector:
1033
- matchLabels:
1034
- app.kubernetes.io/name: app-runtime
1035
- template:
1036
- metadata:
1037
- labels:
1038
- app.kubernetes.io/name: app-runtime
1039
- spec:
1040
- containers:
1041
- - name: app
1042
- image: ${defaultAppImage}
1043
- imagePullPolicy: IfNotPresent
1044
- ports:
1045
- - containerPort: 8080
1046
- name: http
1047
- envFrom:
1048
- - configMapRef:
1049
- name: app-infra-config
1050
- - configMapRef:
1051
- name: app-runtime-auth-env
1052
- optional: true
1053
- - configMapRef:
1054
- name: app-runtime-storage-env
1055
- optional: true
1056
- - secretRef:
1057
- name: supabase-public-runtime
1058
- optional: true
1059
- readinessProbe:
1060
- httpGet:
1061
- path: /
1062
- port: http
1063
- initialDelaySeconds: 5
1064
- periodSeconds: 10
1065
- livenessProbe:
1066
- httpGet:
1067
- path: /
1068
- port: http
1069
- initialDelaySeconds: 15
1070
- periodSeconds: 20
1071
- `;
1072
- }
1073
-
1074
- function getAppServiceManifest(): string {
1075
- return `apiVersion: v1
1076
- kind: Service
1077
- metadata:
1078
- name: app-runtime
1079
- namespace: ${APP_NAMESPACE}
1080
- labels:
1081
- app.kubernetes.io/name: app-runtime
1082
- spec:
1083
- type: ClusterIP
1084
- selector:
1085
- app.kubernetes.io/name: app-runtime
1086
- ports:
1087
- - name: http
1088
- port: 80
1089
- targetPort: http
1090
- `;
1091
- }
1092
-
1093
- function getSupabaseResourceFiles(k8sRoot: string): GeneratedInfrastructureFile[] {
1094
- return [
1095
- {
1096
- path: `${k8sRoot}/supabase/bootstrap.sql`,
1097
- content: getSupabaseBootstrapSql(),
1098
- },
1099
- {
1100
- path: `${k8sRoot}/supabase/postgres.init.configmap.yaml`,
1101
- content: getSupabasePostgresInitConfigMap(),
1102
- },
1103
- {
1104
- path: `${k8sRoot}/supabase/postgres.pvc.yaml`,
1105
- content: getSupabasePvcManifest('supabase-postgres-data', '20Gi'),
1106
- },
1107
- {
1108
- path: `${k8sRoot}/supabase/postgres.yaml`,
1109
- content: getSupabasePostgresManifest(),
1110
- },
1111
- {
1112
- path: `${k8sRoot}/supabase/auth.yaml`,
1113
- content: getSupabaseAuthManifest(),
1114
- },
1115
- {
1116
- path: `${k8sRoot}/supabase/rest.yaml`,
1117
- content: getSupabaseRestManifest(),
1118
- },
1119
- {
1120
- path: `${k8sRoot}/supabase/realtime.yaml`,
1121
- content: getSupabaseRealtimeManifest(),
1122
- },
1123
- {
1124
- path: `${k8sRoot}/supabase/storage.yaml`,
1125
- content: getSupabaseStorageManifest(),
1126
- },
1127
- {
1128
- path: `${k8sRoot}/supabase/imgproxy.yaml`,
1129
- content: getSupabaseImgproxyManifest(),
1130
- },
1131
- {
1132
- path: `${k8sRoot}/supabase/meta.yaml`,
1133
- content: getSupabaseMetaManifest(),
1134
- },
1135
- {
1136
- path: `${k8sRoot}/supabase/gateway.template.yml`,
1137
- content: getSupabaseGatewayTemplate(),
1138
- },
1139
- {
1140
- path: `${k8sRoot}/supabase/kong-entrypoint.sh`,
1141
- content: getSupabaseKongEntrypointScript(),
1142
- executable: true,
1143
- },
1144
- {
1145
- path: `${k8sRoot}/supabase/gateway.yaml`,
1146
- content: getSupabaseGatewayManifest(),
1147
- },
1148
- {
1149
- path: `${k8sRoot}/supabase/studio.yaml`,
1150
- content: getSupabaseStudioManifest(),
1151
- },
1152
- ];
1153
- }
1154
-
1155
- function getSupabaseBootstrapSql(): string {
1156
- return `-- Generated from the official Supabase self-hosting Docker database bootstrap contract.
1157
- -- Applied by scripts/up.sh after Postgres is ready and before Supabase services start.
1158
- \\set pgpass \`echo "$POSTGRES_PASSWORD"\`
1159
- \\set jwt_secret \`echo "$JWT_SECRET"\`
1160
- \\set jwt_exp \`echo "$JWT_EXP"\`
1161
-
1162
- DO $$
1163
- BEGIN
1164
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN
1165
- CREATE ROLE anon NOLOGIN NOINHERIT;
1166
- END IF;
1167
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated') THEN
1168
- CREATE ROLE authenticated NOLOGIN NOINHERIT;
1169
- END IF;
1170
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'service_role') THEN
1171
- CREATE ROLE service_role NOLOGIN NOINHERIT BYPASSRLS;
1172
- END IF;
1173
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticator') THEN
1174
- CREATE ROLE authenticator NOINHERIT LOGIN;
1175
- END IF;
1176
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_admin') THEN
1177
- CREATE ROLE supabase_admin LOGIN CREATEROLE CREATEDB REPLICATION BYPASSRLS;
1178
- END IF;
1179
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_auth_admin') THEN
1180
- CREATE ROLE supabase_auth_admin LOGIN CREATEROLE;
1181
- END IF;
1182
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_storage_admin') THEN
1183
- CREATE ROLE supabase_storage_admin LOGIN CREATEROLE;
1184
- END IF;
1185
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_functions_admin') THEN
1186
- CREATE ROLE supabase_functions_admin LOGIN CREATEROLE;
1187
- END IF;
1188
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'pgbouncer') THEN
1189
- CREATE ROLE pgbouncer LOGIN;
1190
- END IF;
1191
- END
1192
- $$;
1193
-
1194
- ALTER ROLE authenticator WITH PASSWORD :'pgpass';
1195
- ALTER ROLE pgbouncer WITH PASSWORD :'pgpass';
1196
- ALTER ROLE supabase_auth_admin WITH PASSWORD :'pgpass';
1197
- ALTER ROLE supabase_functions_admin WITH PASSWORD :'pgpass';
1198
- ALTER ROLE supabase_storage_admin WITH PASSWORD :'pgpass';
1199
- ALTER ROLE supabase_admin WITH PASSWORD :'pgpass';
1200
-
1201
- GRANT anon, authenticated, service_role TO authenticator;
1202
- GRANT anon, authenticated, service_role TO supabase_storage_admin;
1203
- GRANT postgres TO supabase_auth_admin;
1204
- GRANT postgres TO supabase_storage_admin;
1205
- GRANT postgres TO supabase_functions_admin;
1206
- GRANT pg_monitor TO supabase_admin;
1207
-
1208
- CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_auth_admin;
1209
- CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION supabase_storage_admin;
1210
- CREATE SCHEMA IF NOT EXISTS graphql_public;
1211
- CREATE SCHEMA IF NOT EXISTS _realtime AUTHORIZATION supabase_admin;
1212
-
1213
- GRANT USAGE ON SCHEMA public, graphql_public TO anon, authenticated, service_role;
1214
- GRANT USAGE ON SCHEMA auth TO supabase_auth_admin;
1215
- GRANT USAGE ON SCHEMA storage TO supabase_storage_admin, anon, authenticated, service_role;
1216
- GRANT USAGE ON SCHEMA _realtime TO supabase_admin;
1217
-
1218
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated, service_role;
1219
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO anon, authenticated, service_role;
1220
-
1221
- ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
1222
- ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
1223
-
1224
- SELECT 'CREATE DATABASE _supabase WITH OWNER supabase_admin'
1225
- WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '_supabase')\\gexec
1226
- `;
1227
- }
1228
-
1229
- function getSupabasePostgresInitConfigMap(): string {
1230
- return `apiVersion: v1
1231
- kind: ConfigMap
1232
- metadata:
1233
- name: supabase-postgres-init
1234
- namespace: ${SUPABASE_NAMESPACE}
1235
- data:
1236
- 99-ankhorage-local-dev-extensions.sql: |
1237
- -- Local Minikube bootstrap for extension setup performed before runtime migrations.
1238
- alter role postgres with superuser;
1239
- grant pg_read_server_files to postgres;
1240
- grant pg_read_server_files to supabase_admin;
1241
- grant execute on function pg_read_file(text) to public;
1242
- grant execute on function pg_read_file(text, bigint, bigint) to public;
1243
- `;
1244
- }
1245
-
1246
- function getSupabasePvcManifest(name: string, size: string): string {
1247
- return `apiVersion: v1
1248
- kind: PersistentVolumeClaim
1249
- metadata:
1250
- name: ${name}
1251
- namespace: ${SUPABASE_NAMESPACE}
1252
- spec:
1253
- accessModes:
1254
- - ReadWriteOnce
1255
- resources:
1256
- requests:
1257
- storage: ${size}
1258
- `;
1259
- }
1260
-
1261
- function getSupabasePostgresManifest(): string {
1262
- return `apiVersion: v1
1263
- kind: Service
1264
- metadata:
1265
- name: postgres
1266
- namespace: ${SUPABASE_NAMESPACE}
1267
- spec:
1268
- selector:
1269
- app.kubernetes.io/name: supabase-postgres
1270
- ports:
1271
- - name: postgres
1272
- port: 5432
1273
- targetPort: postgres
1274
- ---
1275
- apiVersion: apps/v1
1276
- kind: Deployment
1277
- metadata:
1278
- name: postgres
1279
- namespace: ${SUPABASE_NAMESPACE}
1280
- spec:
1281
- replicas: 1
1282
- selector:
1283
- matchLabels:
1284
- app.kubernetes.io/name: supabase-postgres
1285
- template:
1286
- metadata:
1287
- labels:
1288
- app.kubernetes.io/name: supabase-postgres
1289
- spec:
1290
- containers:
1291
- - name: postgres
1292
- image: ${SUPABASE_IMAGES.postgres}
1293
- imagePullPolicy: IfNotPresent
1294
- ports:
1295
- - containerPort: 5432
1296
- name: postgres
1297
- env:
1298
- - name: POSTGRES_USER
1299
- value: postgres
1300
- - name: POSTGRES_PASSWORD
1301
- valueFrom:
1302
- secretKeyRef:
1303
- name: supabase-runtime-secrets
1304
- key: POSTGRES_PASSWORD
1305
- - name: PGPASSWORD
1306
- valueFrom:
1307
- secretKeyRef:
1308
- name: supabase-runtime-secrets
1309
- key: POSTGRES_PASSWORD
1310
- - name: POSTGRES_DB
1311
- value: postgres
1312
- - name: PGDATABASE
1313
- value: postgres
1314
- - name: PGPORT
1315
- value: "5432"
1316
- - name: POSTGRES_PORT
1317
- value: "5432"
1318
- - name: POSTGRES_HOST
1319
- value: /var/run/postgresql
1320
- - name: JWT_SECRET
1321
- valueFrom:
1322
- secretKeyRef:
1323
- name: supabase-runtime-secrets
1324
- key: JWT_SECRET
1325
- - name: JWT_EXP
1326
- valueFrom:
1327
- configMapKeyRef:
1328
- name: supabase-runtime-config
1329
- key: JWT_EXPIRY
1330
- volumeMounts:
1331
- - name: data
1332
- mountPath: /var/lib/postgresql/data
1333
- - name: init-scripts
1334
- mountPath: /docker-entrypoint-initdb.d/99-ankhorage-local-dev-extensions.sql
1335
- subPath: 99-ankhorage-local-dev-extensions.sql
1336
- readinessProbe:
1337
- exec:
1338
- command: ["pg_isready", "-U", "postgres"]
1339
- initialDelaySeconds: 15
1340
- periodSeconds: 5
1341
- livenessProbe:
1342
- exec:
1343
- command: ["pg_isready", "-U", "postgres"]
1344
- initialDelaySeconds: 30
1345
- periodSeconds: 10
1346
- args:
1347
- - postgres
1348
- - -c
1349
- - config_file=/etc/postgresql/postgresql.conf
1350
- - -c
1351
- - log_min_messages=fatal
1352
- volumes:
1353
- - name: data
1354
- persistentVolumeClaim:
1355
- claimName: supabase-postgres-data
1356
- - name: init-scripts
1357
- configMap:
1358
- name: supabase-postgres-init
1359
- `;
1360
- }
1361
-
1362
- function getSupabaseAuthManifest(): string {
1363
- return `apiVersion: v1
1364
- kind: Service
1365
- metadata:
1366
- name: auth
1367
- namespace: ${SUPABASE_NAMESPACE}
1368
- spec:
1369
- selector:
1370
- app.kubernetes.io/name: supabase-auth
1371
- ports:
1372
- - name: http
1373
- port: 9999
1374
- targetPort: http
1375
- ---
1376
- apiVersion: apps/v1
1377
- kind: Deployment
1378
- metadata:
1379
- name: auth
1380
- namespace: ${SUPABASE_NAMESPACE}
1381
- spec:
1382
- replicas: 1
1383
- selector:
1384
- matchLabels:
1385
- app.kubernetes.io/name: supabase-auth
1386
- template:
1387
- metadata:
1388
- labels:
1389
- app.kubernetes.io/name: supabase-auth
1390
- spec:
1391
- containers:
1392
- - name: auth
1393
- image: ${SUPABASE_IMAGES.auth}
1394
- imagePullPolicy: IfNotPresent
1395
- ports:
1396
- - containerPort: 9999
1397
- name: http
1398
- env:
1399
- - name: GOTRUE_API_HOST
1400
- value: "0.0.0.0"
1401
- - name: GOTRUE_API_PORT
1402
- value: "9999"
1403
- - name: API_EXTERNAL_URL
1404
- valueFrom:
1405
- configMapKeyRef:
1406
- name: supabase-runtime-config
1407
- key: API_EXTERNAL_URL
1408
- - name: GOTRUE_SITE_URL
1409
- valueFrom:
1410
- configMapKeyRef:
1411
- name: supabase-runtime-config
1412
- key: SITE_URL
1413
- - name: GOTRUE_URI_ALLOW_LIST
1414
- valueFrom:
1415
- configMapKeyRef:
1416
- name: supabase-runtime-config
1417
- key: ADDITIONAL_REDIRECT_URLS
1418
- - name: GOTRUE_JWT_ISSUER
1419
- valueFrom:
1420
- configMapKeyRef:
1421
- name: supabase-runtime-config
1422
- key: API_EXTERNAL_URL
1423
- - name: GOTRUE_DB_DRIVER
1424
- value: postgres
1425
- - name: GOTRUE_DB_DATABASE_URL
1426
- value: "postgres://supabase_auth_admin:$(POSTGRES_PASSWORD)@postgres.${SUPABASE_NAMESPACE}.svc.cluster.local:5432/postgres?search_path=auth&sslmode=disable"
1427
- - name: GOTRUE_JWT_AUD
1428
- value: authenticated
1429
- - name: GOTRUE_JWT_DEFAULT_GROUP_NAME
1430
- value: authenticated
1431
- - name: GOTRUE_JWT_ADMIN_ROLES
1432
- value: service_role
1433
- - name: GOTRUE_JWT_EXP
1434
- valueFrom:
1435
- configMapKeyRef:
1436
- name: supabase-runtime-config
1437
- key: JWT_EXPIRY
1438
- - name: GOTRUE_JWT_SECRET
1439
- valueFrom:
1440
- secretKeyRef:
1441
- name: supabase-runtime-secrets
1442
- key: JWT_SECRET
1443
- - name: GOTRUE_EXTERNAL_EMAIL_ENABLED
1444
- valueFrom:
1445
- configMapKeyRef:
1446
- name: supabase-runtime-config
1447
- key: ENABLE_EMAIL_SIGNUP
1448
- - name: GOTRUE_MAILER_AUTOCONFIRM
1449
- valueFrom:
1450
- configMapKeyRef:
1451
- name: supabase-runtime-config
1452
- key: ENABLE_EMAIL_AUTOCONFIRM
1453
- - name: GOTRUE_SMTP_ADMIN_EMAIL
1454
- valueFrom:
1455
- configMapKeyRef:
1456
- name: supabase-runtime-config
1457
- key: SMTP_ADMIN_EMAIL
1458
- - name: GOTRUE_SMTP_HOST
1459
- valueFrom:
1460
- configMapKeyRef:
1461
- name: supabase-runtime-config
1462
- key: SMTP_HOST
1463
- - name: GOTRUE_SMTP_PORT
1464
- valueFrom:
1465
- configMapKeyRef:
1466
- name: supabase-runtime-config
1467
- key: SMTP_PORT
1468
- - name: GOTRUE_SMTP_USER
1469
- valueFrom:
1470
- secretKeyRef:
1471
- name: supabase-runtime-secrets
1472
- key: SMTP_USER
1473
- - name: GOTRUE_SMTP_PASS
1474
- valueFrom:
1475
- secretKeyRef:
1476
- name: supabase-runtime-secrets
1477
- key: SMTP_PASS
1478
- - name: GOTRUE_SMTP_SENDER_NAME
1479
- valueFrom:
1480
- configMapKeyRef:
1481
- name: supabase-runtime-config
1482
- key: SMTP_SENDER_NAME
1483
- envFrom:
1484
- - secretRef:
1485
- name: supabase-runtime-secrets
1486
- - configMapRef:
1487
- name: supabase-runtime-config
1488
- readinessProbe:
1489
- httpGet:
1490
- path: /health
1491
- port: http
1492
- initialDelaySeconds: 10
1493
- periodSeconds: 10
1494
- `;
1495
- }
1496
-
1497
- function getSupabaseRestManifest(): string {
1498
- return `apiVersion: v1
1499
- kind: Service
1500
- metadata:
1501
- name: rest
1502
- namespace: ${SUPABASE_NAMESPACE}
1503
- spec:
1504
- selector:
1505
- app.kubernetes.io/name: supabase-rest
1506
- ports:
1507
- - name: http
1508
- port: 3000
1509
- targetPort: http
1510
- ---
1511
- apiVersion: apps/v1
1512
- kind: Deployment
1513
- metadata:
1514
- name: rest
1515
- namespace: ${SUPABASE_NAMESPACE}
1516
- spec:
1517
- replicas: 1
1518
- selector:
1519
- matchLabels:
1520
- app.kubernetes.io/name: supabase-rest
1521
- template:
1522
- metadata:
1523
- labels:
1524
- app.kubernetes.io/name: supabase-rest
1525
- spec:
1526
- containers:
1527
- - name: rest
1528
- image: ${SUPABASE_IMAGES.rest}
1529
- imagePullPolicy: IfNotPresent
1530
- ports:
1531
- - containerPort: 3000
1532
- name: http
1533
- env:
1534
- - name: PGRST_DB_URI
1535
- value: "postgres://authenticator:$(POSTGRES_PASSWORD)@postgres.${SUPABASE_NAMESPACE}.svc.cluster.local:5432/postgres"
1536
- - name: PGRST_DB_SCHEMAS
1537
- valueFrom:
1538
- configMapKeyRef:
1539
- name: supabase-runtime-config
1540
- key: PGRST_DB_SCHEMAS
1541
- - name: PGRST_DB_ANON_ROLE
1542
- value: anon
1543
- - name: PGRST_JWT_SECRET
1544
- valueFrom:
1545
- secretKeyRef:
1546
- name: supabase-runtime-secrets
1547
- key: JWT_SECRET
1548
- - name: PGRST_DB_USE_LEGACY_GUCS
1549
- value: "false"
1550
- envFrom:
1551
- - secretRef:
1552
- name: supabase-runtime-secrets
1553
- - configMapRef:
1554
- name: supabase-runtime-config
1555
- `;
1556
- }
1557
-
1558
- function getSupabaseRealtimeManifest(): string {
1559
- return `apiVersion: v1
1560
- kind: Service
1561
- metadata:
1562
- name: realtime
1563
- namespace: ${SUPABASE_NAMESPACE}
1564
- spec:
1565
- selector:
1566
- app.kubernetes.io/name: supabase-realtime
1567
- ports:
1568
- - name: http
1569
- port: 4000
1570
- targetPort: http
1571
- ---
1572
- apiVersion: apps/v1
1573
- kind: Deployment
1574
- metadata:
1575
- name: realtime
1576
- namespace: ${SUPABASE_NAMESPACE}
1577
- spec:
1578
- replicas: 1
1579
- selector:
1580
- matchLabels:
1581
- app.kubernetes.io/name: supabase-realtime
1582
- template:
1583
- metadata:
1584
- labels:
1585
- app.kubernetes.io/name: supabase-realtime
1586
- spec:
1587
- containers:
1588
- - name: realtime
1589
- image: ${SUPABASE_IMAGES.realtime}
1590
- imagePullPolicy: IfNotPresent
1591
- ports:
1592
- - containerPort: 4000
1593
- name: http
1594
- env:
1595
- - name: PORT
1596
- value: "4000"
1597
- - name: DB_HOST
1598
- value: "postgres.${SUPABASE_NAMESPACE}.svc.cluster.local"
1599
- - name: DB_PORT
1600
- value: "5432"
1601
- - name: DB_NAME
1602
- value: postgres
1603
- - name: DB_USER
1604
- value: supabase_admin
1605
- - name: DB_PASSWORD
1606
- valueFrom:
1607
- secretKeyRef:
1608
- name: supabase-runtime-secrets
1609
- key: POSTGRES_PASSWORD
1610
- - name: API_JWT_SECRET
1611
- valueFrom:
1612
- secretKeyRef:
1613
- name: supabase-runtime-secrets
1614
- key: JWT_SECRET
1615
- - name: SECRET_KEY_BASE
1616
- valueFrom:
1617
- secretKeyRef:
1618
- name: supabase-runtime-secrets
1619
- key: SECRET_KEY_BASE
1620
- - name: DB_AFTER_CONNECT_QUERY
1621
- value: "SET search_path TO _realtime"
1622
- - name: DB_ENC_KEY
1623
- valueFrom:
1624
- secretKeyRef:
1625
- name: supabase-runtime-secrets
1626
- key: REALTIME_DB_ENC_KEY
1627
- - name: METRICS_JWT_SECRET
1628
- valueFrom:
1629
- secretKeyRef:
1630
- name: supabase-runtime-secrets
1631
- key: JWT_SECRET
1632
- - name: APP_NAME
1633
- value: realtime
1634
- - name: SEED_SELF_HOST
1635
- value: "true"
1636
- - name: RUN_JANITOR
1637
- value: "true"
1638
- - name: DISABLE_HEALTHCHECK_LOGGING
1639
- value: "true"
1640
- `;
1641
- }
1642
-
1643
- function getSupabaseStorageManifest(): string {
1644
- return `${getSupabasePvcManifest('supabase-storage-data', '10Gi')}
1645
- ---
1646
- apiVersion: v1
1647
- kind: Service
1648
- metadata:
1649
- name: storage
1650
- namespace: ${SUPABASE_NAMESPACE}
1651
- spec:
1652
- selector:
1653
- app.kubernetes.io/name: supabase-storage
1654
- ports:
1655
- - name: http
1656
- port: 5000
1657
- targetPort: http
1658
- ---
1659
- apiVersion: apps/v1
1660
- kind: Deployment
1661
- metadata:
1662
- name: storage
1663
- namespace: ${SUPABASE_NAMESPACE}
1664
- spec:
1665
- replicas: 1
1666
- selector:
1667
- matchLabels:
1668
- app.kubernetes.io/name: supabase-storage
1669
- template:
1670
- metadata:
1671
- labels:
1672
- app.kubernetes.io/name: supabase-storage
1673
- spec:
1674
- containers:
1675
- - name: storage
1676
- image: ${SUPABASE_IMAGES.storage}
1677
- imagePullPolicy: IfNotPresent
1678
- ports:
1679
- - containerPort: 5000
1680
- name: http
1681
- env:
1682
- - name: ANON_KEY
1683
- valueFrom:
1684
- secretKeyRef:
1685
- name: supabase-runtime-secrets
1686
- key: SUPABASE_ANON_KEY
1687
- - name: SERVICE_KEY
1688
- valueFrom:
1689
- secretKeyRef:
1690
- name: supabase-runtime-secrets
1691
- key: SUPABASE_SERVICE_ROLE_KEY
1692
- - name: AUTH_JWT_SECRET
1693
- valueFrom:
1694
- secretKeyRef:
1695
- name: supabase-runtime-secrets
1696
- key: JWT_SECRET
1697
- - name: DATABASE_URL
1698
- value: "postgres://supabase_storage_admin:$(POSTGRES_PASSWORD)@postgres.${SUPABASE_NAMESPACE}.svc.cluster.local:5432/postgres?search_path=storage&sslmode=disable"
1699
- - name: POSTGREST_URL
1700
- value: "http://rest.${SUPABASE_NAMESPACE}.svc.cluster.local:3000"
1701
- - name: STORAGE_BACKEND
1702
- value: file
1703
- - name: STORAGE_PUBLIC_URL
1704
- valueFrom:
1705
- configMapKeyRef:
1706
- name: supabase-runtime-config
1707
- key: SUPABASE_PUBLIC_URL
1708
- - name: REQUEST_ALLOW_X_FORWARDED_PATH
1709
- value: "true"
1710
- - name: FILE_SIZE_LIMIT
1711
- value: "52428800"
1712
- - name: FILE_STORAGE_BACKEND_PATH
1713
- value: /var/lib/storage
1714
- - name: TENANT_ID
1715
- value: stub
1716
- - name: REGION
1717
- value: local
1718
- - name: GLOBAL_S3_BUCKET
1719
- value: local
1720
- - name: IMGPROXY_URL
1721
- value: "http://imgproxy.${SUPABASE_NAMESPACE}.svc.cluster.local:5001"
1722
- envFrom:
1723
- - secretRef:
1724
- name: supabase-runtime-secrets
1725
- - configMapRef:
1726
- name: supabase-runtime-config
1727
- volumeMounts:
1728
- - name: storage
1729
- mountPath: /var/lib/storage
1730
- volumes:
1731
- - name: storage
1732
- persistentVolumeClaim:
1733
- claimName: supabase-storage-data
1734
- `;
1735
- }
1736
-
1737
- function getSupabaseImgproxyManifest(): string {
1738
- return `apiVersion: v1
1739
- kind: Service
1740
- metadata:
1741
- name: imgproxy
1742
- namespace: ${SUPABASE_NAMESPACE}
1743
- spec:
1744
- selector:
1745
- app.kubernetes.io/name: supabase-imgproxy
1746
- ports:
1747
- - name: http
1748
- port: 5001
1749
- targetPort: http
1750
- ---
1751
- apiVersion: apps/v1
1752
- kind: Deployment
1753
- metadata:
1754
- name: imgproxy
1755
- namespace: ${SUPABASE_NAMESPACE}
1756
- spec:
1757
- replicas: 1
1758
- selector:
1759
- matchLabels:
1760
- app.kubernetes.io/name: supabase-imgproxy
1761
- template:
1762
- metadata:
1763
- labels:
1764
- app.kubernetes.io/name: supabase-imgproxy
1765
- spec:
1766
- containers:
1767
- - name: imgproxy
1768
- image: ${SUPABASE_IMAGES.imgproxy}
1769
- imagePullPolicy: IfNotPresent
1770
- ports:
1771
- - containerPort: 5001
1772
- name: http
1773
- env:
1774
- - name: IMGPROXY_BIND
1775
- value: ":5001"
1776
- `;
1777
- }
1778
-
1779
- function getSupabaseMetaManifest(): string {
1780
- return `apiVersion: v1
1781
- kind: Service
1782
- metadata:
1783
- name: meta
1784
- namespace: ${SUPABASE_NAMESPACE}
1785
- spec:
1786
- selector:
1787
- app.kubernetes.io/name: supabase-meta
1788
- ports:
1789
- - name: http
1790
- port: 8080
1791
- targetPort: http
1792
- ---
1793
- apiVersion: apps/v1
1794
- kind: Deployment
1795
- metadata:
1796
- name: meta
1797
- namespace: ${SUPABASE_NAMESPACE}
1798
- spec:
1799
- replicas: 1
1800
- selector:
1801
- matchLabels:
1802
- app.kubernetes.io/name: supabase-meta
1803
- template:
1804
- metadata:
1805
- labels:
1806
- app.kubernetes.io/name: supabase-meta
1807
- spec:
1808
- containers:
1809
- - name: meta
1810
- image: ${SUPABASE_IMAGES.meta}
1811
- imagePullPolicy: IfNotPresent
1812
- ports:
1813
- - containerPort: 8080
1814
- name: http
1815
- env:
1816
- - name: PG_META_PORT
1817
- value: "8080"
1818
- - name: PG_META_DB_HOST
1819
- value: "postgres.${SUPABASE_NAMESPACE}.svc.cluster.local"
1820
- - name: PG_META_DB_PORT
1821
- value: "5432"
1822
- - name: PG_META_DB_NAME
1823
- value: postgres
1824
- - name: PG_META_DB_USER
1825
- value: postgres
1826
- - name: PG_META_DB_PASSWORD
1827
- valueFrom:
1828
- secretKeyRef:
1829
- name: supabase-runtime-secrets
1830
- key: POSTGRES_PASSWORD
1831
- - name: CRYPTO_KEY
1832
- valueFrom:
1833
- secretKeyRef:
1834
- name: supabase-runtime-secrets
1835
- key: PG_META_CRYPTO_KEY
1836
- `;
1837
- }
1838
-
1839
- function getSupabaseGatewayTemplate(): string {
1840
- return `_format_version: "2.1"
1841
- _transform: true
1842
-
1843
- consumers:
1844
- - username: DASHBOARD
1845
- - username: anon
1846
- keyauth_credentials:
1847
- - key: $SUPABASE_ANON_KEY
1848
- - key: $SUPABASE_PUBLISHABLE_KEY
1849
- - username: service_role
1850
- keyauth_credentials:
1851
- - key: $SUPABASE_SERVICE_KEY
1852
- - key: $SUPABASE_SECRET_KEY
1853
-
1854
- acls:
1855
- - consumer: anon
1856
- group: anon
1857
- - consumer: service_role
1858
- group: admin
1859
-
1860
- basicauth_credentials:
1861
- - consumer: DASHBOARD
1862
- username: "$DASHBOARD_USERNAME"
1863
- password: "$DASHBOARD_PASSWORD"
1864
-
1865
- services:
1866
- - name: auth-v1-open
1867
- url: http://auth.${SUPABASE_NAMESPACE}.svc.cluster.local:9999/verify
1868
- routes:
1869
- - name: auth-v1-open
1870
- strip_path: true
1871
- paths: [/auth/v1/verify]
1872
- plugins:
1873
- - name: cors
1874
- - name: auth-v1-open-callback
1875
- url: http://auth.${SUPABASE_NAMESPACE}.svc.cluster.local:9999/callback
1876
- routes:
1877
- - name: auth-v1-open-callback
1878
- strip_path: true
1879
- paths: [/auth/v1/callback]
1880
- plugins:
1881
- - name: cors
1882
- - name: auth-v1-open-authorize
1883
- url: http://auth.${SUPABASE_NAMESPACE}.svc.cluster.local:9999/authorize
1884
- routes:
1885
- - name: auth-v1-open-authorize
1886
- strip_path: true
1887
- paths: [/auth/v1/authorize]
1888
- plugins:
1889
- - name: cors
1890
- - name: auth-v1-open-jwks
1891
- url: http://auth.${SUPABASE_NAMESPACE}.svc.cluster.local:9999/.well-known/jwks.json
1892
- routes:
1893
- - name: auth-v1-open-jwks
1894
- strip_path: true
1895
- paths: [/auth/v1/.well-known/jwks.json]
1896
- plugins:
1897
- - name: cors
1898
- - name: auth-v1
1899
- url: http://auth.${SUPABASE_NAMESPACE}.svc.cluster.local:9999/
1900
- routes:
1901
- - name: auth-v1-all
1902
- strip_path: true
1903
- paths: [/auth/v1/]
1904
- plugins:
1905
- - name: cors
1906
- - name: key-auth
1907
- config:
1908
- hide_credentials: false
1909
- - name: request-transformer
1910
- config:
1911
- add:
1912
- headers:
1913
- - "Authorization: $LUA_AUTH_EXPR"
1914
- replace:
1915
- headers:
1916
- - "Authorization: $LUA_AUTH_EXPR"
1917
- - name: acl
1918
- config:
1919
- hide_groups_header: true
1920
- allow: [admin, anon]
1921
- - name: rest-v1
1922
- url: http://rest.${SUPABASE_NAMESPACE}.svc.cluster.local:3000/
1923
- routes:
1924
- - name: rest-v1-all
1925
- strip_path: true
1926
- paths: [/rest/v1/]
1927
- plugins:
1928
- - name: cors
1929
- - name: key-auth
1930
- config:
1931
- hide_credentials: false
1932
- - name: request-transformer
1933
- config:
1934
- add:
1935
- headers:
1936
- - "Authorization: $LUA_AUTH_EXPR"
1937
- replace:
1938
- headers:
1939
- - "Authorization: $LUA_AUTH_EXPR"
1940
- - name: acl
1941
- config:
1942
- hide_groups_header: true
1943
- allow: [admin, anon]
1944
- - name: graphql-v1
1945
- url: http://rest.${SUPABASE_NAMESPACE}.svc.cluster.local:3000/rpc/graphql
1946
- routes:
1947
- - name: graphql-v1-all
1948
- strip_path: true
1949
- paths: [/graphql/v1]
1950
- plugins:
1951
- - name: cors
1952
- - name: key-auth
1953
- config:
1954
- hide_credentials: false
1955
- - name: request-transformer
1956
- config:
1957
- add:
1958
- headers:
1959
- - "Content-Profile: graphql_public"
1960
- - "Authorization: $LUA_AUTH_EXPR"
1961
- replace:
1962
- headers:
1963
- - "Authorization: $LUA_AUTH_EXPR"
1964
- - name: acl
1965
- config:
1966
- hide_groups_header: true
1967
- allow: [admin, anon]
1968
- - name: realtime-v1-ws
1969
- url: http://realtime.${SUPABASE_NAMESPACE}.svc.cluster.local:4000/socket
1970
- protocol: ws
1971
- routes:
1972
- - name: realtime-v1-ws
1973
- strip_path: true
1974
- paths: [/realtime/v1/]
1975
- plugins:
1976
- - name: cors
1977
- - name: key-auth
1978
- config:
1979
- hide_credentials: false
1980
- - name: request-transformer
1981
- config:
1982
- add:
1983
- headers:
1984
- - "x-api-key:$LUA_RT_WS_EXPR"
1985
- replace:
1986
- querystring:
1987
- - "apikey:$LUA_RT_WS_EXPR"
1988
- - name: acl
1989
- config:
1990
- hide_groups_header: true
1991
- allow: [admin, anon]
1992
- - name: realtime-v1-rest-openapi
1993
- url: http://realtime.${SUPABASE_NAMESPACE}.svc.cluster.local:4000/api/openapi
1994
- protocol: http
1995
- routes:
1996
- - name: realtime-v1-rest-openapi
1997
- strip_path: true
1998
- paths: [/realtime/v1/api/openapi]
1999
- plugins:
2000
- - name: request-termination
2001
- config:
2002
- status_code: 403
2003
- message: "Access is forbidden."
2004
- - name: realtime-v1-rest-tenants
2005
- url: http://realtime.${SUPABASE_NAMESPACE}.svc.cluster.local:4000/api/tenants
2006
- protocol: http
2007
- routes:
2008
- - name: realtime-v1-rest-tenants
2009
- strip_path: true
2010
- paths: [/realtime/v1/api/tenants]
2011
- plugins:
2012
- - name: request-termination
2013
- config:
2014
- status_code: 403
2015
- message: "Access is forbidden."
2016
- - name: realtime-v1-rest
2017
- url: http://realtime.${SUPABASE_NAMESPACE}.svc.cluster.local:4000/api
2018
- protocol: http
2019
- routes:
2020
- - name: realtime-v1-rest
2021
- strip_path: true
2022
- paths: [/realtime/v1/api]
2023
- plugins:
2024
- - name: cors
2025
- - name: key-auth
2026
- config:
2027
- hide_credentials: false
2028
- - name: request-transformer
2029
- config:
2030
- add:
2031
- headers:
2032
- - "Authorization: $LUA_AUTH_EXPR"
2033
- replace:
2034
- headers:
2035
- - "Authorization: $LUA_AUTH_EXPR"
2036
- - name: acl
2037
- config:
2038
- hide_groups_header: true
2039
- allow: [admin, anon]
2040
- - name: storage-v1
2041
- url: http://storage.${SUPABASE_NAMESPACE}.svc.cluster.local:5000/
2042
- routes:
2043
- - name: storage-v1-all
2044
- strip_path: true
2045
- paths: [/storage/v1/]
2046
- plugins:
2047
- - name: cors
2048
- - name: request-transformer
2049
- config:
2050
- add:
2051
- headers:
2052
- - "Authorization: $LUA_AUTH_EXPR"
2053
- replace:
2054
- headers:
2055
- - "Authorization: $LUA_AUTH_EXPR"
2056
- - name: post-function
2057
- config:
2058
- access:
2059
- - |
2060
- local auth = kong.request.get_header("authorization")
2061
- if auth == nil or auth == "" or auth:find("^%s*$") then
2062
- kong.service.request.clear_header("authorization")
2063
- end
2064
- - name: meta
2065
- url: http://meta.${SUPABASE_NAMESPACE}.svc.cluster.local:8080/
2066
- routes:
2067
- - name: meta-all
2068
- strip_path: true
2069
- paths: [/pg/]
2070
- plugins:
2071
- - name: key-auth
2072
- config:
2073
- hide_credentials: false
2074
- - name: acl
2075
- config:
2076
- hide_groups_header: true
2077
- allow: [admin]
2078
- - name: dashboard
2079
- url: http://studio.${SUPABASE_NAMESPACE}.svc.cluster.local:3000/
2080
- routes:
2081
- - name: dashboard-all
2082
- strip_path: true
2083
- paths: [/]
2084
- plugins:
2085
- - name: cors
2086
- - name: basic-auth
2087
- config:
2088
- hide_credentials: true
2089
- `;
2090
- }
2091
-
2092
- function getSupabaseKongEntrypointScript(): string {
2093
- return `#!/bin/sh
2094
- set -eu
2095
-
2096
- if [ -n "\${SUPABASE_SECRET_KEY:-}" ] && [ -n "\${SUPABASE_PUBLISHABLE_KEY:-}" ]; then
2097
- export LUA_AUTH_EXPR="\\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '\${SUPABASE_SECRET_KEY}' and 'Bearer \${SERVICE_ROLE_KEY_ASYMMETRIC:-}') or (headers.apikey == '\${SUPABASE_PUBLISHABLE_KEY}' and 'Bearer \${ANON_KEY_ASYMMETRIC:-}') or headers.apikey)"
2098
- export LUA_RT_WS_EXPR="\\$((query_params.apikey == '\${SUPABASE_SECRET_KEY}' and '\${SERVICE_ROLE_KEY_ASYMMETRIC:-}') or (query_params.apikey == '\${SUPABASE_PUBLISHABLE_KEY}' and '\${ANON_KEY_ASYMMETRIC:-}') or query_params.apikey)"
2099
- else
2100
- export LUA_AUTH_EXPR="\\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or headers.apikey)"
2101
- export LUA_RT_WS_EXPR="\\$(query_params.apikey)"
2102
- fi
2103
-
2104
- awk '{
2105
- result = ""
2106
- rest = $0
2107
- while (match(rest, /\\$[A-Za-z_][A-Za-z_0-9]*/)) {
2108
- varname = substr(rest, RSTART + 1, RLENGTH - 1)
2109
- if (varname in ENVIRON) {
2110
- result = result substr(rest, 1, RSTART - 1) ENVIRON[varname]
2111
- } else {
2112
- result = result substr(rest, 1, RSTART + RLENGTH - 1)
2113
- }
2114
- rest = substr(rest, RSTART + RLENGTH)
2115
- }
2116
- print result rest
2117
- }' /home/kong/temp.yml > "\${KONG_DECLARATIVE_CONFIG}"
2118
-
2119
- sed -i '/^[[:space:]]*- key:[[:space:]]*$/d' "\${KONG_DECLARATIVE_CONFIG}"
2120
-
2121
- exec /entrypoint.sh kong docker-start
2122
- `;
2123
- }
2124
-
2125
- function getSupabaseGatewayManifest(): string {
2126
- return `apiVersion: v1
2127
- kind: Service
2128
- metadata:
2129
- name: gateway
2130
- namespace: ${SUPABASE_NAMESPACE}
2131
- spec:
2132
- selector:
2133
- app.kubernetes.io/name: supabase-gateway
2134
- ports:
2135
- - name: http
2136
- port: 8000
2137
- targetPort: http
2138
- ---
2139
- apiVersion: apps/v1
2140
- kind: Deployment
2141
- metadata:
2142
- name: gateway
2143
- namespace: ${SUPABASE_NAMESPACE}
2144
- spec:
2145
- replicas: 1
2146
- selector:
2147
- matchLabels:
2148
- app.kubernetes.io/name: supabase-gateway
2149
- template:
2150
- metadata:
2151
- labels:
2152
- app.kubernetes.io/name: supabase-gateway
2153
- spec:
2154
- containers:
2155
- - name: gateway
2156
- image: ${SUPABASE_IMAGES.gateway}
2157
- imagePullPolicy: IfNotPresent
2158
- ports:
2159
- - containerPort: 8000
2160
- name: http
2161
- env:
2162
- - name: KONG_DATABASE
2163
- value: "off"
2164
- - name: KONG_DECLARATIVE_CONFIG
2165
- value: /usr/local/kong/kong.yml
2166
- - name: KONG_ROUTER_FLAVOR
2167
- value: expressions
2168
- - name: KONG_DNS_ORDER
2169
- value: LAST,A,CNAME
2170
- - name: KONG_DNS_NOT_FOUND_TTL
2171
- value: "1"
2172
- - name: KONG_PLUGINS
2173
- value: request-transformer,cors,key-auth,acl,basic-auth,request-termination,ip-restriction,post-function
2174
- - name: KONG_PROXY_LISTEN
2175
- value: "0.0.0.0:8000"
2176
- - name: SUPABASE_SERVICE_KEY
2177
- valueFrom:
2178
- secretKeyRef:
2179
- name: supabase-runtime-secrets
2180
- key: SUPABASE_SERVICE_ROLE_KEY
2181
- envFrom:
2182
- - secretRef:
2183
- name: supabase-runtime-secrets
2184
- volumeMounts:
2185
- - name: config
2186
- mountPath: /home/kong/temp.yml
2187
- subPath: temp.yml
2188
- readOnly: true
2189
- - name: entrypoint
2190
- mountPath: /home/kong/kong-entrypoint.sh
2191
- subPath: kong-entrypoint.sh
2192
- readOnly: true
2193
- command: ["/bin/sh", "/home/kong/kong-entrypoint.sh"]
2194
- readinessProbe:
2195
- exec:
2196
- command: ["kong", "health"]
2197
- initialDelaySeconds: 10
2198
- periodSeconds: 10
2199
- volumes:
2200
- - name: config
2201
- configMap:
2202
- name: supabase-gateway-config
2203
- - name: entrypoint
2204
- configMap:
2205
- name: supabase-kong-entrypoint
2206
- defaultMode: 0755
2207
- `;
2208
- }
2209
-
2210
- function getSupabaseStudioManifest(): string {
2211
- return `apiVersion: v1
2212
- kind: Service
2213
- metadata:
2214
- name: studio
2215
- namespace: ${SUPABASE_NAMESPACE}
2216
- spec:
2217
- selector:
2218
- app.kubernetes.io/name: supabase-studio
2219
- ports:
2220
- - name: http
2221
- port: 3000
2222
- targetPort: http
2223
- ---
2224
- apiVersion: apps/v1
2225
- kind: Deployment
2226
- metadata:
2227
- name: studio
2228
- namespace: ${SUPABASE_NAMESPACE}
2229
- spec:
2230
- replicas: 1
2231
- selector:
2232
- matchLabels:
2233
- app.kubernetes.io/name: supabase-studio
2234
- template:
2235
- metadata:
2236
- labels:
2237
- app.kubernetes.io/name: supabase-studio
2238
- spec:
2239
- containers:
2240
- - name: studio
2241
- image: ${SUPABASE_IMAGES.studio}
2242
- imagePullPolicy: IfNotPresent
2243
- ports:
2244
- - containerPort: 3000
2245
- name: http
2246
- env:
2247
- - name: HOSTNAME
2248
- value: "0.0.0.0"
2249
- - name: STUDIO_PG_META_URL
2250
- value: "http://meta.${SUPABASE_NAMESPACE}.svc.cluster.local:8080"
2251
- - name: SUPABASE_URL
2252
- value: "http://gateway.${SUPABASE_NAMESPACE}.svc.cluster.local:8000"
2253
- - name: SUPABASE_PUBLIC_URL
2254
- valueFrom:
2255
- configMapKeyRef:
2256
- name: supabase-runtime-config
2257
- key: SUPABASE_PUBLIC_URL
2258
- - name: SUPABASE_ANON_KEY
2259
- valueFrom:
2260
- secretKeyRef:
2261
- name: supabase-runtime-secrets
2262
- key: SUPABASE_ANON_KEY
2263
- - name: SUPABASE_SERVICE_KEY
2264
- valueFrom:
2265
- secretKeyRef:
2266
- name: supabase-runtime-secrets
2267
- key: SUPABASE_SERVICE_ROLE_KEY
2268
- - name: AUTH_JWT_SECRET
2269
- valueFrom:
2270
- secretKeyRef:
2271
- name: supabase-runtime-secrets
2272
- key: JWT_SECRET
2273
- envFrom:
2274
- - secretRef:
2275
- name: supabase-runtime-secrets
2276
- - configMapRef:
2277
- name: supabase-runtime-config
2278
- `;
2279
- }
2280
-
2281
- function getUpScript(args: {
2282
- appSlug: string;
2283
- appRuntimeEnabled: boolean;
2284
- defaultAppImage: string;
2285
- supabaseKubernetesEnabled: boolean;
2286
- profileModel: ResolvedProfileModel;
2287
- secretStoreProvider: string;
2288
- supabaseHostPorts: SupabaseHostPorts;
2289
- oauthProviders: readonly SupabaseOAuthProviderRuntime[];
2290
- providerLifecycle: readonly MinikubeProviderLifecycle[];
2291
- }): string {
2292
- const {
2293
- appSlug,
2294
- appRuntimeEnabled,
2295
- defaultAppImage,
2296
- supabaseKubernetesEnabled,
2297
- profileModel,
2298
- secretStoreProvider,
2299
- supabaseHostPorts,
2300
- oauthProviders,
2301
- providerLifecycle,
2302
- } = args;
2303
- const oauthEnvDefaults = getOAuthScriptEnvDefaults(oauthProviders);
2304
- const oauthRuntimeSecretLines = getOAuthRuntimeSecretLines(oauthProviders);
2305
- const oauthValidation = getOAuthRuntimeValidationScript(oauthProviders);
2306
- const oauthCallbackRoute = getSupabaseOAuthCallbackRoute(oauthProviders);
2307
- const providerLifecycleStageScripts = getProviderLifecycleStageScripts(providerLifecycle);
2308
- const oauthVaultResolution = getOAuthVaultResolutionScript({
2309
- appSlug,
2310
- oauthProviders,
2311
- secretStoreProvider,
2312
- });
2313
- const supabaseMigrationCommand = getSupabaseMigrationCommandScript();
2314
- const disabledAppRuntimeReconciliation = appRuntimeEnabled
2315
- ? ''
2316
- : `reconcile_disabled_app_runtime() {
2317
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" delete deployment/app-runtime service/app-runtime configmap/app-infra-config --ignore-not-found
2318
- "\${PORT_FORWARD_SCRIPT}" stop obsolete
2319
- }
2320
- `;
2321
- const reconcileDisabledAppRuntime = appRuntimeEnabled ? '' : 'reconcile_disabled_app_runtime';
2322
-
2323
- return `#!/usr/bin/env bash
2324
- set -Eeuo pipefail
2325
-
2326
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
2327
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
2328
- K8S_DIR="\${ROOT_DIR}/k8s"
2329
- BUILD_SCRIPT="\${SCRIPT_DIR}/build-app-image.sh"
2330
- PORT_FORWARD_SCRIPT="\${SCRIPT_DIR}/port-forward.sh"
2331
- APP_SLUG="${appSlug}"
2332
- PROFILE="\${ANKH_APP_SLUG:-}"
2333
- DRIVER="\${MINIKUBE_DRIVER:-}"
2334
- APP_NAMESPACE="${APP_NAMESPACE}"
2335
- SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE}"
2336
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2337
- APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
2338
- APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-}"
2339
- APP_SOURCE_DIR="\${APP_SOURCE_DIR:-}"
2340
- APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-}"
2341
- APP_IMAGE="\${APP_IMAGE:-}"
2342
- APP_IMAGE_SYNC_STRATEGY="\${APP_IMAGE_SYNC_STRATEGY:-}"
2343
- APP_REPLICAS="\${APP_REPLICAS:-}"
2344
- APP_FORCE_ROLLOUT_RESTART="\${APP_FORCE_ROLLOUT_RESTART:-}"
2345
- APP_IMAGE_PULL_SECRET_NAME="\${APP_IMAGE_PULL_SECRET_NAME:-}"
2346
- APP_IMAGE_PULL_SECRET_SERVER="\${APP_IMAGE_PULL_SECRET_SERVER:-}"
2347
- APP_IMAGE_PULL_SECRET_USERNAME="\${APP_IMAGE_PULL_SECRET_USERNAME:-}"
2348
- APP_IMAGE_PULL_SECRET_PASSWORD="\${APP_IMAGE_PULL_SECRET_PASSWORD:-}"
2349
- APP_IMAGE_PULL_SECRET_EMAIL="\${APP_IMAGE_PULL_SECRET_EMAIL:-}"
2350
- APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-}"
2351
- SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-}"
2352
- SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-}"
2353
- SUPABASE_DB_URL="\${SUPABASE_DB_URL:-}"
2354
- SUPABASE_PROFILE_ENABLED="${profileModel.enabled ? 'true' : 'false'}"
2355
- SUPABASE_PROFILE_RECONCILE_FILE="\${ROOT_DIR}/supabase/generated/auth_profiles.sql"
2356
-
2357
- require_command() {
2358
- if ! command -v "\${1}" >/dev/null 2>&1; then
2359
- echo "\${1} is required but not installed."
2360
- exit 1
2361
- fi
2362
- }
2363
-
2364
- load_env_file_preserving_process_env() {
2365
- local line trimmed key value current
2366
- if [[ ! -f "\${ROOT_DIR}/.env" ]]; then
2367
- return 0
2368
- fi
2369
-
2370
- while IFS= read -r line || [[ -n "\${line}" ]]; do
2371
- trimmed="$(printf '%s' "\${line}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
2372
- if [[ -z "\${trimmed}" || "\${trimmed}" == \\#* || "\${trimmed}" != *=* ]]; then
2373
- continue
2374
- fi
2375
-
2376
- key="\${trimmed%%=*}"
2377
- value="\${trimmed#*=}"
2378
- key="\${key//[[:space:]]/}"
2379
- value="$(printf '%s' "\${value}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
2380
- value="\${value%\\"}"
2381
- value="\${value#\\"}"
2382
- value="\${value%\\'}"
2383
- value="\${value#\\'}"
2384
- current="\${!key:-}"
2385
- if [[ -n "\${current}" ]]; then
2386
- continue
2387
- fi
2388
- export "\${key}=\${value}"
2389
- done < "\${ROOT_DIR}/.env"
2390
- }
2391
-
2392
- env_file_value() {
2393
- local key="\${1}"
2394
- if [[ ! -f "\${ROOT_DIR}/.env" ]]; then
2395
- return 0
2396
- fi
2397
- awk -F= -v key="\${key}" '$1 == key { value = substr($0, index($0, "=") + 1) } END { print value }' "\${ROOT_DIR}/.env"
2398
- }
2399
-
2400
- env_file_has_key() {
2401
- local key="\${1}"
2402
- if [[ ! -f "\${ROOT_DIR}/.env" ]]; then
2403
- return 1
2404
- fi
2405
- awk -F= -v key="\${key}" '$1 == key { found = 1 } END { exit found ? 0 : 1 }' "\${ROOT_DIR}/.env"
2406
- }
2407
-
2408
- write_env_value() {
2409
- local key="\${1}"
2410
- local value="\${2}"
2411
- local tmp_env
2412
- mkdir -p "\${ROOT_DIR}"
2413
- touch "\${ROOT_DIR}/.env"
2414
- tmp_env="$(mktemp)"
2415
- awk -F= -v key="\${key}" -v value="\${value}" '
2416
- $1 == key {
2417
- print key "=" value
2418
- found = 1
2419
- next
2420
- }
2421
- { print }
2422
- END {
2423
- if (!found) print key "=" value
2424
- }
2425
- ' "\${ROOT_DIR}/.env" > "\${tmp_env}"
2426
- mv "\${tmp_env}" "\${ROOT_DIR}/.env"
2427
- }
2428
-
2429
- write_key_value_file_value() {
2430
- local file_path="\${1}"
2431
- local key="\${2}"
2432
- local value="\${3}"
2433
- local tmp_env
2434
- mkdir -p "$(dirname "\${file_path}")"
2435
- touch "\${file_path}"
2436
- tmp_env="$(mktemp)"
2437
- awk -F= -v key="\${key}" -v value="\${value}" '
2438
- $1 == key {
2439
- print key "=" value
2440
- found = 1
2441
- next
2442
- }
2443
- { print }
2444
- END {
2445
- if (!found) print key "=" value
2446
- }
2447
- ' "\${file_path}" > "\${tmp_env}"
2448
- mv "\${tmp_env}" "\${file_path}"
2449
- }
2450
-
2451
- sync_app_public_supabase_env() {
2452
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2453
- return 0
2454
- fi
2455
-
2456
- if [[ -z "\${APP_SOURCE_DIR:-}" ]]; then
2457
- echo "APP_SOURCE_DIR is required to write app Supabase public environment."
2458
- exit 1
2459
- fi
2460
-
2461
- local app_env_file="\${APP_SOURCE_DIR}/.env.local"
2462
- write_key_value_file_value "\${app_env_file}" "EXPO_PUBLIC_SUPABASE_URL" "\${EXPO_PUBLIC_SUPABASE_URL}"
2463
- write_key_value_file_value "\${app_env_file}" "EXPO_PUBLIC_SUPABASE_ANON_KEY" "\${EXPO_PUBLIC_SUPABASE_ANON_KEY}"
2464
- }
2465
-
2466
- set_required_env_default() {
2467
- local key="\${1}"
2468
- local value="\${2}"
2469
- local current="\${!key:-}"
2470
- if [[ -n "\${current}" ]]; then
2471
- write_env_value "\${key}" "\${current}"
2472
- return 0
2473
- fi
2474
-
2475
- current="$(env_file_value "\${key}")"
2476
- if [[ -n "\${current}" ]]; then
2477
- export "\${key}=\${current}"
2478
- return 0
2479
- fi
2480
-
2481
- write_env_value "\${key}" "\${value}"
2482
- export "\${key}=\${value}"
2483
- }
2484
-
2485
- set_optional_env_default() {
2486
- local key="\${1}"
2487
- local value="\${2}"
2488
- local current="\${!key:-}"
2489
- if env_file_has_key "\${key}"; then
2490
- export "\${key}=\${current}"
2491
- return 0
2492
- fi
2493
- if [[ -n "\${current}" ]]; then
2494
- write_env_value "\${key}" "\${current}"
2495
- return 0
2496
- fi
2497
- write_env_value "\${key}" "\${value}"
2498
- export "\${key}=\${value}"
2499
- }
2500
-
2501
- set_env_default() {
2502
- set_required_env_default "\${1}" "\${2}"
2503
- }
2504
-
2505
- derive_additional_redirect_urls() {
2506
- local callback_route="${oauthCallbackRoute}"
2507
- local callback_path="\${callback_route#/}"
2508
- local site_url_without_slash="\${SITE_URL%/}"
2509
- local urls="\${site_url_without_slash},\${site_url_without_slash}/\${callback_path}"
2510
- if [[ -n "\${OAUTH_NATIVE_REDIRECT_URLS:-}" ]]; then
2511
- urls="\${urls},\${OAUTH_NATIVE_REDIRECT_URLS}"
2512
- fi
2513
- printf '%s' "\${urls}"
2514
- }
2515
-
2516
- random_base64() {
2517
- openssl rand -base64 "\${1}" | tr -d '\\n'
2518
- }
2519
-
2520
- random_hex() {
2521
- openssl rand -hex "\${1}" | tr -d '\\n'
2522
- }
2523
-
2524
- base64url() {
2525
- openssl base64 -A | tr '+/' '-_' | tr -d '='
2526
- }
2527
-
2528
- generate_supabase_jwt() {
2529
- local role="\${1}"
2530
- local iat exp header payload signature
2531
- iat="$(date +%s)"
2532
- exp="$((iat + 315360000))"
2533
- header="$(printf '{"alg":"HS256","typ":"JWT"}' | base64url)"
2534
- payload="$(printf '{"role":"%s","iss":"supabase-demo","iat":%s,"exp":%s}' "\${role}" "\${iat}" "\${exp}" | base64url)"
2535
- signature="$(printf '%s.%s' "\${header}" "\${payload}" | openssl dgst -binary -sha256 -hmac "\${JWT_SECRET}" | base64url)"
2536
- printf '%s.%s.%s' "\${header}" "\${payload}" "\${signature}"
2537
- }
2538
-
2539
- ensure_supabase_runtime_env() {
2540
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2541
- return 0
2542
- fi
2543
-
2544
- require_command openssl
2545
-
2546
- set_required_env_default POSTGRES_PASSWORD "$(random_hex 32)"
2547
- set_required_env_default JWT_SECRET "$(random_base64 48)"
2548
- set_required_env_default SUPABASE_ANON_KEY "$(generate_supabase_jwt anon)"
2549
- set_required_env_default SUPABASE_SERVICE_ROLE_KEY "$(generate_supabase_jwt service_role)"
2550
- set_required_env_default DASHBOARD_USERNAME "supabase"
2551
- set_required_env_default DASHBOARD_PASSWORD "$(random_base64 24)"
2552
- set_required_env_default SECRET_KEY_BASE "$(random_base64 48)"
2553
- set_required_env_default REALTIME_DB_ENC_KEY "$(random_hex 8)"
2554
- set_required_env_default PG_META_CRYPTO_KEY "$(random_base64 32)"
2555
- set_required_env_default S3_PROTOCOL_ACCESS_KEY_ID "$(random_hex 16)"
2556
- set_required_env_default S3_PROTOCOL_ACCESS_KEY_SECRET "$(random_hex 32)"
2557
- set_required_env_default JWT_EXPIRY "3600"
2558
- set_required_env_default SUPABASE_GATEWAY_FORWARD_LOCAL_PORT "\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
2559
- set_required_env_default APP_PORT_FORWARD_LOCAL_PORT "\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
2560
- set_required_env_default SUPABASE_PUBLIC_URL "http://127.0.0.1:\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT}"
2561
- set_required_env_default API_EXTERNAL_URL "\${SUPABASE_PUBLIC_URL}/auth/v1"
2562
- set_required_env_default SITE_URL "http://127.0.0.1:\${APP_PORT_FORWARD_LOCAL_PORT}"
2563
- export OAUTH_NATIVE_REDIRECT_URLS="\${OAUTH_NATIVE_REDIRECT_URLS:-}"
2564
- set_required_env_default ADDITIONAL_REDIRECT_URLS "$(derive_additional_redirect_urls)"
2565
- set_required_env_default ENABLE_EMAIL_SIGNUP "true"
2566
- set_required_env_default ENABLE_EMAIL_AUTOCONFIRM "true"
2567
- set_required_env_default SMTP_ADMIN_EMAIL "admin@example.com"
2568
- set_required_env_default SMTP_HOST "localhost"
2569
- set_required_env_default SMTP_PORT "2500"
2570
- set_optional_env_default SMTP_USER ""
2571
- set_optional_env_default SMTP_PASS ""
2572
- set_required_env_default SMTP_SENDER_NAME "Ankhorage"
2573
- set_required_env_default PGRST_DB_SCHEMAS "public,storage,graphql_public"
2574
- set_required_env_default PGRST_DB_MAX_ROWS "1000"
2575
- set_required_env_default PGRST_DB_EXTRA_SEARCH_PATH "public"
2576
- set_required_env_default GLOBAL_S3_BUCKET "stub"
2577
- set_required_env_default STORAGE_TENANT_ID "stub"
2578
- set_required_env_default REGION "stub"
2579
- set_required_env_default SUPABASE_DB_FORWARD_LOCAL_PORT "\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
2580
- set_required_env_default SUPABASE_DB_URL "postgres://postgres:\${POSTGRES_PASSWORD}@127.0.0.1:\${SUPABASE_DB_FORWARD_LOCAL_PORT}/postgres?sslmode=disable"
2581
- set_required_env_default EXPO_PUBLIC_SUPABASE_URL "\${SUPABASE_PUBLIC_URL}"
2582
- set_required_env_default EXPO_PUBLIC_SUPABASE_ANON_KEY "\${SUPABASE_ANON_KEY}"
2583
- ${oauthEnvDefaults}
2584
- }
2585
- ${oauthVaultResolution}
2586
-
2587
- sync_supabase_secrets() {
2588
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2589
- return 0
2590
- fi
2591
-
2592
- local tmp_runtime tmp_public
2593
- tmp_runtime="$(mktemp)"
2594
- tmp_public="$(mktemp)"
2595
- trap 'rm -f "\${tmp_runtime}" "\${tmp_public}"' EXIT
2596
-
2597
- cat > "\${tmp_runtime}" <<EOF
2598
- POSTGRES_PASSWORD=\${POSTGRES_PASSWORD}
2599
- JWT_SECRET=\${JWT_SECRET}
2600
- SUPABASE_ANON_KEY=\${SUPABASE_ANON_KEY}
2601
- SUPABASE_SERVICE_ROLE_KEY=\${SUPABASE_SERVICE_ROLE_KEY}
2602
- SUPABASE_PUBLISHABLE_KEY=\${SUPABASE_PUBLISHABLE_KEY:-}
2603
- SUPABASE_SECRET_KEY=\${SUPABASE_SECRET_KEY:-}
2604
- DASHBOARD_USERNAME=\${DASHBOARD_USERNAME:-supabase}
2605
- DASHBOARD_PASSWORD=\${DASHBOARD_PASSWORD:-}
2606
- SMTP_USER=\${SMTP_USER:-}
2607
- SMTP_PASS=\${SMTP_PASS:-}
2608
- SECRET_KEY_BASE=\${SECRET_KEY_BASE}
2609
- REALTIME_DB_ENC_KEY=\${REALTIME_DB_ENC_KEY}
2610
- PG_META_CRYPTO_KEY=\${PG_META_CRYPTO_KEY}
2611
- S3_PROTOCOL_ACCESS_KEY_ID=\${S3_PROTOCOL_ACCESS_KEY_ID}
2612
- S3_PROTOCOL_ACCESS_KEY_SECRET=\${S3_PROTOCOL_ACCESS_KEY_SECRET}
2613
- ${oauthRuntimeSecretLines}
2614
- EOF
2615
-
2616
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" create secret generic supabase-runtime-secrets --from-env-file="\${tmp_runtime}" --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" apply -f -
2617
-
2618
- cat > "\${tmp_public}" <<EOF
2619
- EXPO_PUBLIC_SUPABASE_URL=\${EXPO_PUBLIC_SUPABASE_URL}
2620
- EXPO_PUBLIC_SUPABASE_ANON_KEY=\${EXPO_PUBLIC_SUPABASE_ANON_KEY}
2621
- SUPABASE_URL=http://gateway.${SUPABASE_NAMESPACE}.svc.cluster.local:8000
2622
- SUPABASE_ANON_KEY=\${SUPABASE_ANON_KEY}
2623
- EOF
2624
-
2625
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" create secret generic supabase-public-runtime --from-env-file="\${tmp_public}" --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" apply -f -
2626
-
2627
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" create configmap supabase-runtime-config \
2628
- --from-literal=SUPABASE_PUBLIC_URL="\${SUPABASE_PUBLIC_URL}" \
2629
- --from-literal=API_EXTERNAL_URL="\${API_EXTERNAL_URL}" \
2630
- --from-literal=SITE_URL="\${SITE_URL}" \
2631
- --from-literal=ADDITIONAL_REDIRECT_URLS="\${ADDITIONAL_REDIRECT_URLS}" \
2632
- --from-literal=JWT_EXPIRY="\${JWT_EXPIRY}" \
2633
- --from-literal=ENABLE_EMAIL_SIGNUP="\${ENABLE_EMAIL_SIGNUP}" \
2634
- --from-literal=ENABLE_EMAIL_AUTOCONFIRM="\${ENABLE_EMAIL_AUTOCONFIRM}" \
2635
- --from-literal=SMTP_ADMIN_EMAIL="\${SMTP_ADMIN_EMAIL}" \
2636
- --from-literal=SMTP_HOST="\${SMTP_HOST}" \
2637
- --from-literal=SMTP_PORT="\${SMTP_PORT}" \
2638
- --from-literal=SMTP_SENDER_NAME="\${SMTP_SENDER_NAME}" \
2639
- --from-literal=PGRST_DB_SCHEMAS="\${PGRST_DB_SCHEMAS}" \
2640
- --from-literal=PGRST_DB_MAX_ROWS="\${PGRST_DB_MAX_ROWS}" \
2641
- --from-literal=PGRST_DB_EXTRA_SEARCH_PATH="\${PGRST_DB_EXTRA_SEARCH_PATH}" \
2642
- --from-literal=GLOBAL_S3_BUCKET="\${GLOBAL_S3_BUCKET}" \
2643
- --from-literal=STORAGE_TENANT_ID="\${STORAGE_TENANT_ID}" \
2644
- --from-literal=REGION="\${REGION}" \
2645
- --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" apply -f -
2646
-
2647
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" create configmap supabase-gateway-config \
2648
- --from-file=temp.yml="\${K8S_DIR}/supabase/gateway.template.yml" \
2649
- --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" apply -f -
2650
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" create configmap supabase-kong-entrypoint \
2651
- --from-file=kong-entrypoint.sh="\${K8S_DIR}/supabase/kong-entrypoint.sh" \
2652
- --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" apply -f -
2653
-
2654
- rm -f "\${tmp_runtime}" "\${tmp_public}"
2655
- trap - EXIT
2656
- }
2657
-
2658
- docker_load_image_to_minikube() {
2659
- if command -v docker >/dev/null 2>&1 && docker image inspect "\${APP_IMAGE}" >/dev/null 2>&1; then
2660
- minikube -p "\${PROFILE}" image load --daemon=true --overwrite=true "\${APP_IMAGE}" >/dev/null
2661
- return 0
2662
- fi
2663
-
2664
- return 1
2665
- }
2666
-
2667
- run_supabase_migrations() {
2668
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2669
- return 0
2670
- fi
2671
-
2672
- export SUPABASE_DB_URL PGSSLMODE=disable
2673
-
2674
- if [[ -d "\${ROOT_DIR}/supabase/migrations" ]]; then
2675
- require_command supabase
2676
- (
2677
- cd "\${ROOT_DIR}"
2678
- ${indentShellBlock(supabaseMigrationCommand, 6)}
2679
- )
2680
- fi
2681
- }
2682
-
2683
- reconcile_supabase_profile() {
2684
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2685
- return 0
2686
- fi
2687
- if [[ "\${SUPABASE_PROFILE_ENABLED}" == "true" && -f "\${SUPABASE_PROFILE_RECONCILE_FILE}" ]]; then
2688
- psql "\${SUPABASE_DB_URL}" -v ON_ERROR_STOP=1 -q -f "\${SUPABASE_PROFILE_RECONCILE_FILE}"
2689
- fi
2690
- }
2691
-
2692
- reload_postgrest_schema() {
2693
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2694
- return 0
2695
- fi
2696
-
2697
- psql "\${SUPABASE_DB_URL}" -v ON_ERROR_STOP=1 -q -c "notify pgrst, 'reload schema';"
2698
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout restart deployment/rest >/dev/null
2699
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/rest --timeout=600s
2700
- }
2701
-
2702
- bootstrap_supabase_vault_migration() {
2703
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2704
- return 0
2705
- fi
2706
- if [[ -f "\${ROOT_DIR}/supabase/migrations/202607120001_ankhorage_supabase_vault.sql" ]]; then
2707
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" exec deployment/postgres -- psql -U postgres -d postgres -v ON_ERROR_STOP=1 -q -c "alter role postgres with superuser;"
2708
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" exec deployment/postgres -- psql -U postgres -d postgres -v ON_ERROR_STOP=1 -q -c "create schema if not exists vault; grant pg_read_server_files to postgres; grant pg_read_server_files to supabase_admin; grant execute on function pg_read_file(text) to public; grant execute on function pg_read_file(text, bigint, bigint) to public;"
2709
- psql "\${SUPABASE_DB_URL}" -v ON_ERROR_STOP=1 -q -f "\${ROOT_DIR}/supabase/migrations/202607120001_ankhorage_supabase_vault.sql"
2710
- fi
2711
- }
2712
-
2713
- bootstrap_supabase_database() {
2714
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2715
- return 0
2716
- fi
2717
-
2718
- require_command psql
2719
- export POSTGRES_PASSWORD JWT_SECRET JWT_EXP="\${JWT_EXPIRY}" POSTGRES_USER=postgres
2720
- psql "\${SUPABASE_DB_URL}" -v ON_ERROR_STOP=1 -q -f "\${K8S_DIR}/supabase/bootstrap.sql"
2721
- }
2722
-
2723
- wait_for_supabase_database() {
2724
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2725
- return 0
2726
- fi
2727
-
2728
- require_command psql
2729
- if ! "\${PORT_FORWARD_SCRIPT}" start db-migration; then
2730
- echo "Failed to start db-migration port-forward for Supabase bootstrap."
2731
- "\${PORT_FORWARD_SCRIPT}" status db-migration || true
2732
- exit 1
2733
- fi
2734
- for attempt in {1..60}; do
2735
- if PGPASSWORD="\${POSTGRES_PASSWORD}" psql -h 127.0.0.1 -p "\${SUPABASE_DB_FORWARD_LOCAL_PORT}" -U postgres -d postgres -v ON_ERROR_STOP=1 -Atq -c 'select 1' >/dev/null 2>&1; then
2736
- return 0
2737
- fi
2738
- sleep 2
2739
- done
2740
-
2741
- echo "Postgres did not accept SQL connections as postgres before bootstrap."
2742
- "\${PORT_FORWARD_SCRIPT}" status db-migration || true
2743
- exit 1
2744
- }
2745
-
2746
- apply_supabase_runtime_workloads() {
2747
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
2748
- return 0
2749
- fi
2750
-
2751
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/auth.yaml"
2752
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/rest.yaml"
2753
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/realtime.yaml"
2754
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/imgproxy.yaml"
2755
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/storage.yaml"
2756
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/meta.yaml"
2757
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/gateway.yaml"
2758
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/studio.yaml"
2759
- }
2760
-
2761
- apply_namespace_manifests() {
2762
- local namespace_manifest
2763
- for namespace_manifest in "\${K8S_DIR}"/namespaces/*.yaml; do
2764
- kubectl --context "\${PROFILE}" apply -f "\${namespace_manifest}"
2765
- done
2766
- }
2767
-
2768
- ${disabledAppRuntimeReconciliation}
2769
- ${providerLifecycleStageScripts}
2770
-
2771
- load_env_file_preserving_process_env
2772
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
2773
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
2774
- DRIVER="\${MINIKUBE_DRIVER:-docker}"
2775
- APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-true}"
2776
- APP_SOURCE_DIR="\${APP_SOURCE_DIR:-$(cd "\${ROOT_DIR}/../.." && pwd)}"
2777
- APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-.ankh/web-export}"
2778
- APP_IMAGE="\${APP_IMAGE:-${defaultAppImage}}"
2779
- APP_IMAGE_SYNC_STRATEGY="\${APP_IMAGE_SYNC_STRATEGY:-docker-load}"
2780
- APP_REPLICAS="\${APP_REPLICAS:-1}"
2781
- APP_FORCE_ROLLOUT_RESTART="\${APP_FORCE_ROLLOUT_RESTART:-true}"
2782
- APP_IMAGE_PULL_SECRET_NAME="\${APP_IMAGE_PULL_SECRET_NAME:-}"
2783
- APP_IMAGE_PULL_SECRET_SERVER="\${APP_IMAGE_PULL_SECRET_SERVER:-ghcr.io}"
2784
- APP_IMAGE_PULL_SECRET_USERNAME="\${APP_IMAGE_PULL_SECRET_USERNAME:-}"
2785
- APP_IMAGE_PULL_SECRET_PASSWORD="\${APP_IMAGE_PULL_SECRET_PASSWORD:-}"
2786
- APP_IMAGE_PULL_SECRET_EMAIL="\${APP_IMAGE_PULL_SECRET_EMAIL:-}"
2787
- APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
2788
- SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
2789
- SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
2790
- SUPABASE_DB_URL="\${SUPABASE_DB_URL:-}"
2791
-
2792
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
2793
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
2794
- exit 1
2795
- fi
2796
-
2797
- cleanup_failed_up() {
2798
- "\${PORT_FORWARD_SCRIPT}" stop all >/dev/null 2>&1 || true
2799
- }
2800
-
2801
- trap cleanup_failed_up ERR
2802
-
2803
- require_command minikube
2804
- require_command kubectl
2805
- ensure_supabase_runtime_env
2806
- sync_app_public_supabase_env
2807
-
2808
- HOST_STATUS="$(minikube -p "\${PROFILE}" status --format='{{.Host}}' 2>/dev/null || true)"
2809
- if [[ "\${HOST_STATUS}" != "Running" ]]; then
2810
- minikube start -p "\${PROFILE}" --driver="\${DRIVER}"
2811
- fi
2812
-
2813
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
2814
- apply_namespace_manifests
2815
- sync_supabase_secrets
2816
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/postgres.init.configmap.yaml"
2817
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/postgres.pvc.yaml"
2818
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/supabase/postgres.yaml"
2819
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/postgres --timeout=900s
2820
- wait_for_supabase_database
2821
- bootstrap_supabase_database
2822
- bootstrap_supabase_vault_migration
2823
- resolve_oauth_credentials_from_vault
2824
- ${oauthValidation}
2825
- sync_supabase_secrets
2826
- apply_supabase_runtime_workloads
2827
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/auth --timeout=600s
2828
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/rest --timeout=600s
2829
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/realtime --timeout=600s
2830
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/storage --timeout=600s
2831
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/gateway --timeout=600s
2832
- kubectl --context "\${PROFILE}" -n "\${SUPABASE_NAMESPACE}" rollout status deployment/studio --timeout=600s
2833
- run_supabase_migrations
2834
- reconcile_supabase_profile
2835
- reload_postgrest_schema
2836
- "\${PORT_FORWARD_SCRIPT}" start supabase-gateway >/dev/null
2837
- "\${PORT_FORWARD_SCRIPT}" start studio >/dev/null
2838
- else
2839
- kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2840
- fi
2841
-
2842
- if [[ "\${APP_RUNTIME_ENABLED}" == "true" && "\${APP_BUILD_ENABLED}" == "true" ]]; then
2843
- if [[ ! -x "\${BUILD_SCRIPT}" ]]; then
2844
- echo "Missing build helper script: \${BUILD_SCRIPT}"
2845
- exit 1
2846
- fi
2847
-
2848
- "\${BUILD_SCRIPT}"
2849
- fi
2850
-
2851
- if [[ "\${APP_WEB_EXPORT_DIR}" = /* ]]; then
2852
- EXPORT_DIR="\${APP_WEB_EXPORT_DIR}"
2853
- else
2854
- EXPORT_DIR="\${APP_SOURCE_DIR}/\${APP_WEB_EXPORT_DIR}"
2855
- fi
2856
-
2857
- if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2858
- case "\${APP_IMAGE_SYNC_STRATEGY}" in
2859
- minikube-build)
2860
- if [[ ! -f "\${EXPORT_DIR}/index.html" ]]; then
2861
- echo "Expected web export output not found at \${EXPORT_DIR}/index.html"
2862
- exit 1
2863
- fi
2864
- minikube -p "\${PROFILE}" image build -t "\${APP_IMAGE}" -f "\${ROOT_DIR}/app-image/Dockerfile" "\${EXPORT_DIR}" >/dev/null
2865
- ;;
2866
- docker-load)
2867
- if ! docker_load_image_to_minikube; then
2868
- echo "Skipping docker-load image sync; local Docker image \${APP_IMAGE} not found."
2869
- fi
2870
- ;;
2871
- none)
2872
- echo "Skipping local image sync (APP_IMAGE_SYNC_STRATEGY=none)."
2873
- ;;
2874
- *)
2875
- echo "Unsupported APP_IMAGE_SYNC_STRATEGY=\${APP_IMAGE_SYNC_STRATEGY}. Use minikube-build, docker-load, or none."
2876
- exit 1
2877
- ;;
2878
- esac
2879
- fi
2880
-
2881
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
2882
- kubectl --context "\${PROFILE}" apply -k "\${K8S_DIR}"
2883
- fi
2884
-
2885
- ${reconcileDisabledAppRuntime}
2886
- wait_for_provider_readiness
2887
- run_provider_migrations
2888
- run_provider_reconciliation
2889
-
2890
- if [[ "\${APP_RUNTIME_ENABLED}" == "true" ]]; then
2891
- if [[ -n "\${APP_IMAGE_PULL_SECRET_NAME}" ]]; then
2892
- if [[ -z "\${APP_IMAGE_PULL_SECRET_USERNAME}" || -z "\${APP_IMAGE_PULL_SECRET_PASSWORD}" ]]; then
2893
- echo "APP_IMAGE_PULL_SECRET_NAME is set, but username/password are missing."
2894
- exit 1
2895
- fi
2896
-
2897
- SECRET_EMAIL_ARG=()
2898
- if [[ -n "\${APP_IMAGE_PULL_SECRET_EMAIL}" ]]; then
2899
- SECRET_EMAIL_ARG=(--docker-email="\${APP_IMAGE_PULL_SECRET_EMAIL}")
2900
- fi
2901
-
2902
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" create secret docker-registry "\${APP_IMAGE_PULL_SECRET_NAME}" --docker-server="\${APP_IMAGE_PULL_SECRET_SERVER}" --docker-username="\${APP_IMAGE_PULL_SECRET_USERNAME}" --docker-password="\${APP_IMAGE_PULL_SECRET_PASSWORD}" "\${SECRET_EMAIL_ARG[@]}" --dry-run=client -o yaml | kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" apply -f -
2903
-
2904
- PULL_SECRET_PATCH="$(cat <<EOF
2905
- {"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"\${APP_IMAGE_PULL_SECRET_NAME}"}]}}}}
2906
- EOF
2907
- )"
2908
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" patch deployment app-runtime --type=merge --patch "\${PULL_SECRET_PATCH}" >/dev/null
2909
- else
2910
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" patch deployment app-runtime --type=json --patch='[{"op":"remove","path":"/spec/template/spec/imagePullSecrets"}]' >/dev/null 2>&1 || true
2911
- fi
2912
-
2913
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" set image deployment/app-runtime app="\${APP_IMAGE}" >/dev/null
2914
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" scale deployment/app-runtime --replicas="\${APP_REPLICAS}" >/dev/null
2915
-
2916
- if [[ "\${APP_FORCE_ROLLOUT_RESTART}" == "true" ]]; then
2917
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" rollout restart deployment/app-runtime >/dev/null
2918
- fi
2919
-
2920
- kubectl --context "\${PROFILE}" -n "\${APP_NAMESPACE}" rollout status deployment/app-runtime --timeout=180s >/dev/null
2921
- "\${PORT_FORWARD_SCRIPT}" start app >/dev/null
2922
- fi
2923
-
2924
- trap - ERR
2925
- echo "Minikube infrastructure for '\${PROFILE}' is running."
2926
- `;
2927
- }
2928
-
2929
- function getBuildAppImageScript(args: { appSlug: string; defaultAppImage: string }): string {
2930
- const { appSlug, defaultAppImage } = args;
2931
-
2932
- return `#!/usr/bin/env bash
2933
- set -euo pipefail
2934
-
2935
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
2936
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
2937
- DOCKERFILE_PATH="\${ROOT_DIR}/app-image/Dockerfile"
2938
- APP_SLUG="${appSlug}"
2939
-
2940
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
2941
- set -a
2942
- # shellcheck disable=SC1090
2943
- source "\${ROOT_DIR}/.env"
2944
- set +a
2945
- fi
2946
-
2947
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
2948
- APP_BUILD_ENABLED="\${APP_BUILD_ENABLED:-true}"
2949
- APP_SOURCE_DIR="\${APP_SOURCE_DIR:-$(cd "\${ROOT_DIR}/../.." && pwd)}"
2950
- APP_WEB_EXPORT_DIR="\${APP_WEB_EXPORT_DIR:-.ankh/web-export}"
2951
- APP_IMAGE="\${APP_IMAGE:-${defaultAppImage}}"
2952
-
2953
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
2954
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
2955
- exit 1
2956
- fi
2957
-
2958
- if [[ "\${APP_BUILD_ENABLED}" != "true" ]]; then
2959
- echo "Skipping app image build (APP_BUILD_ENABLED=\${APP_BUILD_ENABLED})."
2960
- exit 0
2961
- fi
2962
-
2963
- if [[ ! -f "\${APP_SOURCE_DIR}/package.json" ]]; then
2964
- echo "APP_SOURCE_DIR does not look like an app root: \${APP_SOURCE_DIR}"
2965
- exit 1
2966
- fi
2967
-
2968
- if [[ ! -f "\${DOCKERFILE_PATH}" ]]; then
2969
- echo "Dockerfile for app image build is missing: \${DOCKERFILE_PATH}"
2970
- exit 1
2971
- fi
2972
-
2973
- EXPO_CLI="\${APP_SOURCE_DIR}/node_modules/.bin/expo"
2974
- if [[ ! -x "\${EXPO_CLI}" ]]; then
2975
- echo "App-owned Expo CLI is missing: \${EXPO_CLI}" >&2
2976
- echo "Run 'bun install --frozen-lockfile' in \${APP_SOURCE_DIR} before building the app image." >&2
2977
- exit 1
2978
- fi
2979
-
2980
- if ! command -v docker >/dev/null 2>&1; then
2981
- echo "docker is required to build app image."
2982
- exit 1
2983
- fi
2984
-
2985
- (
2986
- cd "\${APP_SOURCE_DIR}"
2987
- "\${EXPO_CLI}" export --platform web --clear --output-dir "\${APP_WEB_EXPORT_DIR}"
2988
- )
2989
-
2990
- if [[ "\${APP_WEB_EXPORT_DIR}" = /* ]]; then
2991
- EXPORT_DIR="\${APP_WEB_EXPORT_DIR}"
2992
- else
2993
- EXPORT_DIR="\${APP_SOURCE_DIR}/\${APP_WEB_EXPORT_DIR}"
2994
- fi
2995
-
2996
- if [[ ! -f "\${EXPORT_DIR}/index.html" ]]; then
2997
- echo "Expected web export output not found at \${EXPORT_DIR}/index.html"
2998
- exit 1
2999
- fi
3000
-
3001
- docker build -t "\${APP_IMAGE}" \
3002
- --label "ankhorage.kind=generated-app" \
3003
- --label "ankhorage.app_slug=\${PROFILE}" \
3004
- --label "ankhorage.image=\${APP_IMAGE}" \
3005
- -f "\${DOCKERFILE_PATH}" \
3006
- "\${EXPORT_DIR}"
3007
- echo "Built app image: \${APP_IMAGE} (profile: \${PROFILE})"
3008
- `;
3009
- }
3010
-
3011
- function getAppImageDockerfile(): string {
3012
- return `FROM nginx:1.27-alpine
3013
-
3014
- RUN cat <<'EOF' > /etc/nginx/conf.d/default.conf
3015
- server {
3016
- listen 8080;
3017
- absolute_redirect off;
3018
- server_name _;
3019
- root /usr/share/nginx/html;
3020
- index index.html;
3021
-
3022
- location / {
3023
- try_files $uri $uri/ /index.html;
3024
- }
3025
- }
3026
- EOF
3027
-
3028
- COPY . /usr/share/nginx/html
3029
-
3030
- EXPOSE 8080
3031
-
3032
- CMD ["nginx", "-g", "daemon off;"]
3033
- `;
3034
- }
3035
-
3036
- function getPortForwardScript(args: {
3037
- appSlug: string;
3038
- appRuntimeEnabled: boolean;
3039
- supabaseKubernetesEnabled: boolean;
3040
- supabaseHostPorts: SupabaseHostPorts;
3041
- }): string {
3042
- const { appSlug, appRuntimeEnabled, supabaseKubernetesEnabled, supabaseHostPorts } = args;
3043
- const runtimeForwardNames = [
3044
- ...(appRuntimeEnabled ? ['app'] : []),
3045
- ...(supabaseKubernetesEnabled ? ['supabase-gateway'] : []),
3046
- ];
3047
- const allForwardNames = [
3048
- ...runtimeForwardNames,
3049
- ...(supabaseKubernetesEnabled ? ['studio', 'db-migration'] : []),
3050
- ];
3051
- const managedForwardNames = ['app', 'supabase-gateway', 'studio', 'db-migration'];
3052
- const obsoleteForwardNames = managedForwardNames.filter(
3053
- (name) => !allForwardNames.includes(name),
3054
- );
3055
- const appTargetCase = ` app)
3056
- echo "${APP_NAMESPACE} service/app-runtime \${APP_PORT_FORWARD_LOCAL_PORT} \${APP_PORT_FORWARD_REMOTE_PORT}"
3057
- ;;`;
3058
- const supabaseTargetCases = ` supabase-gateway)
3059
- echo "${SUPABASE_NAMESPACE} service/gateway \${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT} \${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT}"
3060
- ;;
3061
- studio)
3062
- echo "${SUPABASE_NAMESPACE} service/studio \${SUPABASE_STUDIO_FORWARD_LOCAL_PORT} \${SUPABASE_STUDIO_FORWARD_REMOTE_PORT}"
3063
- ;;
3064
- db-migration)
3065
- echo "${SUPABASE_NAMESPACE} service/postgres \${SUPABASE_DB_FORWARD_LOCAL_PORT} \${SUPABASE_DB_FORWARD_REMOTE_PORT}"
3066
- ;;`;
3067
- const concreteForwardNames = [
3068
- ...(appRuntimeEnabled ? ['app'] : []),
3069
- ...(supabaseKubernetesEnabled ? ['supabase-gateway', 'studio', 'db-migration'] : []),
3070
- ];
3071
- const forwardUsage = [...concreteForwardNames, 'runtime', 'all'].join('|');
3072
- const forwardNamesDescription =
3073
- concreteForwardNames.length > 0
3074
- ? `${concreteForwardNames.join(', ')}, runtime, or all`
3075
- : 'runtime or all';
3076
-
3077
- return `#!/usr/bin/env bash
3078
- set -euo pipefail
3079
-
3080
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
3081
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
3082
- STATE_DIR="\${ROOT_DIR}/.state/forwards"
3083
- APP_SLUG="${appSlug}"
3084
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3085
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3086
- APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3087
- APP_PORT_FORWARD_LOCAL_PORT="\${APP_PORT_FORWARD_LOCAL_PORT:-${supabaseHostPorts.app}}"
3088
- APP_PORT_FORWARD_REMOTE_PORT="\${APP_PORT_FORWARD_REMOTE_PORT:-80}"
3089
- SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
3090
- SUPABASE_GATEWAY_FORWARD_REMOTE_PORT="\${SUPABASE_GATEWAY_FORWARD_REMOTE_PORT:-8000}"
3091
- SUPABASE_STUDIO_FORWARD_LOCAL_PORT="\${SUPABASE_STUDIO_FORWARD_LOCAL_PORT:-${supabaseHostPorts.studio}}"
3092
- SUPABASE_STUDIO_FORWARD_REMOTE_PORT="\${SUPABASE_STUDIO_FORWARD_REMOTE_PORT:-3000}"
3093
- SUPABASE_DB_FORWARD_LOCAL_PORT="\${SUPABASE_DB_FORWARD_LOCAL_PORT:-${supabaseHostPorts.db}}"
3094
- SUPABASE_DB_FORWARD_REMOTE_PORT="\${SUPABASE_DB_FORWARD_REMOTE_PORT:-5432}"
3095
- RUNTIME_FORWARD_NAMES=(${runtimeForwardNames.join(' ')})
3096
- ALL_FORWARD_NAMES=(${allForwardNames.join(' ')})
3097
- MANAGED_FORWARD_NAMES=(${managedForwardNames.join(' ')})
3098
- OBSOLETE_FORWARD_NAMES=(${obsoleteForwardNames.join(' ')})
3099
-
3100
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
3101
- set -a
3102
- # shellcheck disable=SC1090
3103
- source "\${ROOT_DIR}/.env"
3104
- set +a
3105
- fi
3106
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3107
- APP_RUNTIME_ENABLED="${appRuntimeEnabled ? 'true' : 'false'}"
3108
-
3109
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3110
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
3111
- exit 1
3112
- fi
3113
-
3114
- if ! command -v kubectl >/dev/null 2>&1; then
3115
- echo "kubectl is required but not installed."
3116
- exit 1
3117
- fi
3118
-
3119
- mkdir -p "\${STATE_DIR}"
3120
-
3121
- pid_file_for() {
3122
- echo "\${STATE_DIR}/\${PROFILE}-\${1}.pid"
3123
- }
3124
-
3125
- target_for() {
3126
- case "\${1}" in
3127
- ${appTargetCase}
3128
- ${supabaseTargetCases}
3129
- *)
3130
- echo "Unknown forward '\${1}'. Use ${forwardNamesDescription}." >&2
3131
- return 1
3132
- ;;
3133
- esac
3134
- }
3135
-
3136
- is_pid_running() {
3137
- local pid="\${1}"
3138
- [[ -n "\${pid}" ]] && kill -0 "\${pid}" >/dev/null 2>&1
3139
- }
3140
-
3141
- is_port_accepting() {
3142
- local port="\${1}"
3143
- (echo >/dev/tcp/127.0.0.1/"\${port}") >/dev/null 2>&1
3144
- }
3145
-
3146
- print_forward_log_tail() {
3147
- local name="\${1}"
3148
- local log_file="\${STATE_DIR}/\${PROFILE}-\${name}.log"
3149
- if [[ -s "\${log_file}" ]]; then
3150
- echo "\${name}: recent kubectl port-forward log:"
3151
- tail -n 20 "\${log_file}"
3152
- fi
3153
- }
3154
-
3155
- process_command_for_pid() {
3156
- local pid="\${1}"
3157
- ps -p "\${pid}" -o command= 2>/dev/null || true
3158
- }
3159
-
3160
- process_name_for_pid() {
3161
- local pid="\${1}"
3162
- ps -p "\${pid}" -o comm= 2>/dev/null || true
3163
- }
3164
-
3165
- is_owned_forward_process() {
3166
- local pid="\${1}"
3167
- local namespace="\${2}"
3168
- local resource="\${3}"
3169
- local local_port="\${4}"
3170
- local remote_port="\${5}"
3171
- local command
3172
- local process_name
3173
- command="$(process_command_for_pid "\${pid}")"
3174
- process_name="$(process_name_for_pid "\${pid}")"
3175
-
3176
- case "\${process_name}" in
3177
- *kubectl) ;;
3178
- *) return 1 ;;
3179
- esac
3180
-
3181
- case "\${command}" in
3182
- *"kubectl --context \${PROFILE} -n \${namespace} port-forward \${resource} \${local_port}:\${remote_port}"*)
3183
- return 0
3184
- ;;
3185
- *)
3186
- return 1
3187
- ;;
3188
- esac
3189
- }
3190
-
3191
- handle_non_owned_forward_pid() {
3192
- local name="\${1}"
3193
- local pid="\${2}"
3194
- local pid_file="\${3}"
3195
- local local_port="\${4}"
3196
- echo "\${name}: stale pid file pointed at non-owned live pid \${pid}; removing ownership record \${pid_file}"
3197
- rm -f "\${pid_file}"
3198
-
3199
- if is_port_accepting "\${local_port}"; then
3200
- echo "\${name}: port occupied by non-owned process on local port \${local_port}"
3201
- return 1
3202
- fi
3203
-
3204
- return 0
3205
- }
3206
-
3207
- terminate_forward_process() {
3208
- local name="\${1}"
3209
- local pid="\${2}"
3210
- local namespace="\${3}"
3211
- local resource="\${4}"
3212
- local local_port="\${5}"
3213
- local remote_port="\${6}"
3214
-
3215
- if [[ -z "\${pid}" ]]; then
3216
- return 0
3217
- fi
3218
-
3219
- if is_pid_running "\${pid}" && ! is_owned_forward_process "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}"; then
3220
- echo "\${name}: refusing to terminate non-owned live pid \${pid}"
3221
- return 1
3222
- fi
3223
-
3224
- if is_pid_running "\${pid}"; then
3225
- kill "\${pid}" >/dev/null 2>&1 || true
3226
- for attempt in {1..20}; do
3227
- if ! is_pid_running "\${pid}"; then
3228
- break
3229
- fi
3230
- sleep 0.25
3231
- done
3232
- fi
3233
-
3234
- if is_pid_running "\${pid}"; then
3235
- kill -KILL "\${pid}" >/dev/null 2>&1 || true
3236
- for attempt in {1..20}; do
3237
- if ! is_pid_running "\${pid}"; then
3238
- break
3239
- fi
3240
- sleep 0.25
3241
- done
3242
- fi
3243
-
3244
- if is_pid_running "\${pid}"; then
3245
- echo "\${name}: failed to stop owned port-forward pid \${pid}"
3246
- return 1
3247
- fi
3248
-
3249
- for attempt in {1..20}; do
3250
- if ! is_port_accepting "\${local_port}"; then
3251
- return 0
3252
- fi
3253
- sleep 0.25
3254
- done
3255
-
3256
- echo "\${name}: owned port-forward pid \${pid} stopped, but local port \${local_port} is still accepting connections"
3257
- return 1
3258
- }
3259
-
3260
- start_forward() {
3261
- local name="\${1}"
3262
- local pid_file
3263
- pid_file="$(pid_file_for "\${name}")"
3264
- read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
3265
-
3266
- if [[ "\${name}" == "app" && "\${APP_RUNTIME_ENABLED}" != "true" ]]; then
3267
- echo "\${name}: skipped (Web app runtime disabled)"
3268
- return 0
3269
- fi
3270
-
3271
- if [[ -f "\${pid_file}" ]]; then
3272
- local existing_pid
3273
- existing_pid="$(cat "\${pid_file}")"
3274
- if is_pid_running "\${existing_pid}"; then
3275
- if ! is_owned_forward_process "\${existing_pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}"; then
3276
- handle_non_owned_forward_pid "\${name}" "\${existing_pid}" "\${pid_file}" "\${local_port}" || return 1
3277
- elif is_port_accepting "\${local_port}"; then
3278
- echo "\${name}: running (pid \${existing_pid})"
3279
- return 0
3280
- else
3281
- echo "\${name}: stale running pid \${existing_pid} was not accepting local port \${local_port}; restarting"
3282
- terminate_forward_process "\${name}" "\${existing_pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3283
- rm -f "\${pid_file}"
3284
- fi
3285
- else
3286
- rm -f "\${pid_file}"
3287
- fi
3288
- fi
3289
-
3290
- if [[ "\${namespace}" == "${SUPABASE_NAMESPACE}" && "\${SUPABASE_RUNTIME_ENABLED}" != "true" ]]; then
3291
- echo "\${name}: skipped (Supabase Kubernetes disabled)"
3292
- return 0
3293
- fi
3294
-
3295
- if ! kubectl --context "\${PROFILE}" -n "\${namespace}" get "\${resource}" >/dev/null 2>&1; then
3296
- echo "\${name}: target \${namespace}/\${resource} not found"
3297
- return 1
3298
- fi
3299
-
3300
- for start_attempt in {1..5}; do
3301
- nohup kubectl --context "\${PROFILE}" -n "\${namespace}" port-forward "\${resource}" "\${local_port}:\${remote_port}" >"\${STATE_DIR}/\${PROFILE}-\${name}.log" 2>&1 &
3302
- local pid="$!"
3303
- echo "\${pid}" > "\${pid_file}"
3304
-
3305
- for readiness_attempt in {1..20}; do
3306
- if ! is_pid_running "\${pid}"; then
3307
- rm -f "\${pid_file}"
3308
- if [[ "\${start_attempt}" -lt 5 ]]; then
3309
- echo "\${name}: port-forward attempt \${start_attempt} exited before readiness; retrying"
3310
- sleep 2
3311
- break
3312
- fi
3313
-
3314
- echo "\${name}: failed to start local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3315
- print_forward_log_tail "\${name}"
3316
- return 1
3317
- fi
3318
- if is_port_accepting "\${local_port}"; then
3319
- sleep 1
3320
- if is_pid_running "\${pid}" && is_port_accepting "\${local_port}"; then
3321
- sleep 0.5
3322
- if is_pid_running "\${pid}"; then
3323
- echo "\${name}: started (pid \${pid}, local port \${local_port})"
3324
- return 0
3325
- fi
3326
- fi
3327
- fi
3328
- sleep 0.5
3329
- done
3330
-
3331
- if [[ -f "\${pid_file}" ]]; then
3332
- terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3333
- rm -f "\${pid_file}"
3334
-
3335
- if [[ "\${start_attempt}" -lt 5 ]]; then
3336
- echo "\${name}: port-forward attempt \${start_attempt} did not become ready; retrying"
3337
- sleep 2
3338
- else
3339
- echo "\${name}: did not become ready on local port \${local_port} for \${namespace}/\${resource}:\${remote_port} after \${start_attempt} attempts"
3340
- print_forward_log_tail "\${name}"
3341
- return 1
3342
- fi
3343
- fi
3344
- done
3345
-
3346
- return 1
3347
- }
3348
-
3349
- stop_forward() {
3350
- local name="\${1}"
3351
- local pid_file
3352
- pid_file="$(pid_file_for "\${name}")"
3353
- read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
3354
- if [[ ! -f "\${pid_file}" ]]; then
3355
- echo "\${name}: stopped"
3356
- return 0
3357
- fi
3358
-
3359
- local pid
3360
- pid="$(cat "\${pid_file}")"
3361
- if is_pid_running "\${pid}" && ! is_owned_forward_process "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}"; then
3362
- handle_non_owned_forward_pid "\${name}" "\${pid}" "\${pid_file}" "\${local_port}" || return 1
3363
- echo "\${name}: stopped"
3364
- return 0
3365
- fi
3366
-
3367
- terminate_forward_process "\${name}" "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}" || return 1
3368
- rm -f "\${pid_file}"
3369
- echo "\${name}: stopped"
3370
- }
3371
-
3372
- status_forward() {
3373
- local name="\${1}"
3374
- local pid_file
3375
- pid_file="$(pid_file_for "\${name}")"
3376
- if [[ ! -f "\${pid_file}" ]]; then
3377
- echo "\${name}: stopped"
3378
- return 0
3379
- fi
3380
-
3381
- local pid
3382
- pid="$(cat "\${pid_file}")"
3383
- if is_pid_running "\${pid}"; then
3384
- read -r namespace resource local_port remote_port <<<"$(target_for "\${name}")"
3385
- if ! is_owned_forward_process "\${pid}" "\${namespace}" "\${resource}" "\${local_port}" "\${remote_port}"; then
3386
- echo "\${name}: stale_pid_non_owned=\${pid}"
3387
- rm -f "\${pid_file}"
3388
- return 0
3389
- fi
3390
- if is_port_accepting "\${local_port}"; then
3391
- echo "\${name}: running pid=\${pid} url=127.0.0.1:\${local_port} target=\${namespace}/\${resource}:\${remote_port}"
3392
- return 0
3393
- fi
3394
- echo "\${name}: not ready pid=\${pid} url=127.0.0.1:\${local_port} target=\${namespace}/\${resource}:\${remote_port}"
3395
- return 0
3396
- fi
3397
-
3398
- echo "\${name}: crashed stale_pid=\${pid}"
3399
- rm -f "\${pid_file}"
3400
- }
3401
-
3402
- for_each_forward() {
3403
- local action="\${1}"
3404
- shift
3405
- local name
3406
- while [[ "\${#}" -gt 0 ]]; do
3407
- name="\${1}"
3408
- shift
3409
- "\${action}_forward" "\${name}"
3410
- done
3411
- }
3412
-
3413
- ACTION="\${1:-start}"
3414
- NAME="\${2:-all}"
3415
-
3416
- case "\${ACTION}:\${NAME}" in
3417
- start:runtime) for_each_forward start ${runtimeForwardNames.join(' ')} ;;
3418
- stop:runtime)
3419
- for_each_forward stop ${runtimeForwardNames.join(' ')}
3420
- for_each_forward stop ${obsoleteForwardNames.join(' ')}
3421
- ;;
3422
- status:runtime) for_each_forward status ${runtimeForwardNames.join(' ')} ;;
3423
- start:all) for_each_forward start ${allForwardNames.join(' ')} ;;
3424
- stop:all) for_each_forward stop ${managedForwardNames.join(' ')} ;;
3425
- status:all) for_each_forward status ${allForwardNames.join(' ')} ;;
3426
- stop:obsolete) for_each_forward stop ${obsoleteForwardNames.join(' ')} ;;
3427
- start:*) start_forward "\${NAME}" ;;
3428
- stop:*) stop_forward "\${NAME}" ;;
3429
- status:*) status_forward "\${NAME}" ;;
3430
- *)
3431
- echo "Usage: ./scripts/port-forward.sh {start|stop|status} {${forwardUsage}}"
3432
- exit 1
3433
- ;;
3434
- esac
3435
- `;
3436
- }
3437
-
3438
- function getDownScript(appSlug: string): string {
3439
- return `#!/usr/bin/env bash
3440
- set -euo pipefail
3441
-
3442
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
3443
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
3444
- APP_SLUG="${appSlug}"
3445
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3446
-
3447
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
3448
- set -a
3449
- # shellcheck disable=SC1090
3450
- source "\${ROOT_DIR}/.env"
3451
- set +a
3452
- fi
3453
-
3454
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3455
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
3456
- exit 1
3457
- fi
3458
-
3459
- "\${SCRIPT_DIR}/port-forward.sh" stop all >/dev/null || true
3460
-
3461
- if command -v minikube >/dev/null 2>&1; then
3462
- minikube stop -p "\${PROFILE}"
3463
- else
3464
- echo "minikube is required but not installed."
3465
- exit 1
3466
- fi
3467
-
3468
- echo "Stopped Minikube profile '\${PROFILE}'. Persistent data remains in the profile."
3469
- `;
3470
- }
3471
-
3472
- function getResetScript(args: {
3473
- appSlug: string;
3474
- supabaseKubernetesEnabled: boolean;
3475
- providerNamespaces: readonly string[];
3476
- }): string {
3477
- const { appSlug, supabaseKubernetesEnabled, providerNamespaces } = args;
3478
- const resetNamespaces = [
3479
- APP_NAMESPACE,
3480
- ...(supabaseKubernetesEnabled ? [SUPABASE_NAMESPACE] : []),
3481
- ...providerNamespaces,
3482
- ];
3483
- const resetNamespaceCommands = resetNamespaces
3484
- .map(
3485
- (
3486
- namespace,
3487
- ) => `kubectl --context "\${PROFILE}" delete namespace ${namespace} --ignore-not-found
3488
- kubectl --context "\${PROFILE}" apply -f "\${K8S_DIR}/namespaces/${namespace}.yaml"`,
3489
- )
3490
- .join('\n');
3491
- const resetScope = `Kubernetes namespaces ${resetNamespaces.join(', ')}`;
3492
- const resetDataDescription = `reset.sh deletes and recreates namespaces ${resetNamespaces.join(
3493
- ', ',
3494
- )}. This resets app workloads and provider-owned data in those namespaces. It does not delete the Minikube profile.`;
3495
- const completionDescription = `Reset namespaces ${resetNamespaces.join(', ')}`;
3496
-
3497
- return `#!/usr/bin/env bash
3498
- set -euo pipefail
3499
-
3500
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
3501
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
3502
- K8S_DIR="\${ROOT_DIR}/k8s"
3503
- APP_SLUG="${appSlug}"
3504
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3505
- CONFIRM="\${ANKH_RESET_CONFIRM:-}"
3506
-
3507
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
3508
- set -a
3509
- # shellcheck disable=SC1090
3510
- source "\${ROOT_DIR}/.env"
3511
- set +a
3512
- fi
3513
-
3514
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3515
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
3516
- exit 1
3517
- fi
3518
-
3519
- if [[ "\${CONFIRM}" != "\${APP_SLUG}" ]]; then
3520
- echo "Refusing reset. Set ANKH_RESET_CONFIRM=\${APP_SLUG} to reset ${resetScope} inside profile '\${PROFILE}'."
3521
- echo "${resetDataDescription}"
3522
- exit 1
3523
- fi
3524
-
3525
- if ! command -v kubectl >/dev/null 2>&1; then
3526
- echo "kubectl is required but not installed."
3527
- exit 1
3528
- fi
3529
-
3530
- "\${SCRIPT_DIR}/port-forward.sh" stop all >/dev/null || true
3531
- ${resetNamespaceCommands}
3532
- echo "${completionDescription} for profile '\${PROFILE}'. Run ./scripts/up.sh to recreate runtime secrets, migrations, forwards, and app rollout."
3533
- `;
3534
- }
3535
-
3536
- function getDestroyScript(appSlug: string): string {
3537
- return `#!/usr/bin/env bash
3538
- set -euo pipefail
3539
-
3540
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
3541
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
3542
- APP_SLUG="${appSlug}"
3543
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3544
-
3545
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
3546
- set -a
3547
- # shellcheck disable=SC1090
3548
- source "\${ROOT_DIR}/.env"
3549
- set +a
3550
- fi
3551
-
3552
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3553
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
3554
- exit 1
3555
- fi
3556
-
3557
- "\${SCRIPT_DIR}/port-forward.sh" stop all >/dev/null || true
3558
- minikube delete -p "\${PROFILE}"
3559
- echo "Deleted Minikube profile '\${PROFILE}'."
3560
- `;
3561
- }
3562
-
3563
- function getStatusScript(args: {
3564
- appSlug: string;
3565
- appRuntimeEnabled: boolean;
3566
- supabaseKubernetesEnabled: boolean;
3567
- profileModel: ResolvedProfileModel;
3568
- supabaseHostPorts: SupabaseHostPorts;
3569
- providerNamespaces: readonly string[];
3570
- providerLifecycle: readonly MinikubeProviderLifecycle[];
3571
- }): string {
3572
- const {
3573
- appSlug,
3574
- appRuntimeEnabled,
3575
- supabaseKubernetesEnabled,
3576
- profileModel,
3577
- supabaseHostPorts,
3578
- providerNamespaces,
3579
- providerLifecycle,
3580
- } = args;
3581
- const namespaces = [
3582
- APP_NAMESPACE,
3583
- ...(supabaseKubernetesEnabled ? [SUPABASE_NAMESPACE] : []),
3584
- ...providerNamespaces,
3585
- ].join(' ');
3586
- const providerStatusScript = getProviderStatusScript(providerLifecycle);
3587
-
3588
- return `#!/usr/bin/env bash
3589
- set -euo pipefail
3590
-
3591
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
3592
- ROOT_DIR="$(cd "\${SCRIPT_DIR}/.." && pwd)"
3593
- APP_SLUG="${appSlug}"
3594
- PROFILE="\${ANKH_APP_SLUG:-${appSlug}}"
3595
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3596
- SUPABASE_PROFILE_ENABLED="${profileModel.enabled ? 'true' : 'false'}"
3597
- SUPABASE_GATEWAY_FORWARD_LOCAL_PORT="\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT:-${supabaseHostPorts.gateway}}"
3598
-
3599
- if [[ -f "\${ROOT_DIR}/.env" ]]; then
3600
- set -a
3601
- # shellcheck disable=SC1090
3602
- source "\${ROOT_DIR}/.env"
3603
- set +a
3604
- fi
3605
- SUPABASE_RUNTIME_ENABLED="${supabaseKubernetesEnabled ? 'true' : 'false'}"
3606
-
3607
- if [[ "\${PROFILE}" != "\${APP_SLUG}" ]]; then
3608
- echo "ANKH_APP_SLUG must remain the generated canonical slug '\${APP_SLUG}' for this infra directory."
3609
- exit 1
3610
- fi
3611
-
3612
- echo "Infra status for profile: \${PROFILE}"
3613
-
3614
- if ! command -v minikube >/dev/null 2>&1; then
3615
- echo "- minikube: missing"
3616
- exit 1
3617
- fi
3618
-
3619
- HOST_STATUS="$(minikube -p "\${PROFILE}" status --format='{{.Host}}' 2>/dev/null || true)"
3620
- echo "- minikube host: \${HOST_STATUS:-missing}"
3621
-
3622
- if [[ "\${HOST_STATUS}" != "Running" ]]; then
3623
- echo "- cluster: stopped"
3624
- "\${SCRIPT_DIR}/port-forward.sh" status all || true
3625
- exit 0
3626
- fi
3627
-
3628
- if ! command -v kubectl >/dev/null 2>&1; then
3629
- echo "- kubectl: missing"
3630
- exit 1
3631
- fi
3632
-
3633
- check_secret_key() {
3634
- local secret="\${1}"
3635
- local key="\${2}"
3636
- local encoded
3637
- encoded="$(kubectl --context "\${PROFILE}" -n ${SUPABASE_NAMESPACE} get secret "\${secret}" -o "jsonpath={.data.\${key}}" 2>/dev/null || true)"
3638
- if [[ -n "\${encoded}" ]]; then
3639
- echo "- secret/\${secret}:\${key}: present"
3640
- else
3641
- echo "- secret/\${secret}:\${key}: missing"
3642
- fi
3643
- }
3644
-
3645
- postgres_query() {
3646
- local sql="\${1}"
3647
- kubectl --context "\${PROFILE}" -n ${SUPABASE_NAMESPACE} exec deployment/postgres -- psql -U postgres -d postgres -Atq -c "\${sql}" 2>/dev/null || true
3648
- }
3649
-
3650
- for namespace in ${namespaces}; do
3651
- if kubectl --context "\${PROFILE}" get namespace "\${namespace}" >/dev/null 2>&1; then
3652
- echo "- namespace/\${namespace}: present"
3653
- kubectl --context "\${PROFILE}" -n "\${namespace}" get pods
3654
- else
3655
- echo "- namespace/\${namespace}: missing"
3656
- fi
3657
- done
3658
-
3659
- if [[ "${appRuntimeEnabled ? 'true' : 'false'}" == "true" ]] && kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} get deployment app-runtime >/dev/null 2>&1; then
3660
- kubectl --context "\${PROFILE}" -n ${APP_NAMESPACE} rollout status deployment/app-runtime --timeout=5s >/dev/null 2>&1 && echo "- app-runtime: ready" || echo "- app-runtime: not ready"
3661
- fi
3662
-
3663
- ${providerStatusScript}
3664
-
3665
- if [[ "\${SUPABASE_RUNTIME_ENABLED}" == "true" ]]; then
3666
- for deployment in postgres auth rest realtime storage gateway studio; do
3667
- if kubectl --context "\${PROFILE}" -n ${SUPABASE_NAMESPACE} get deployment "\${deployment}" >/dev/null 2>&1; then
3668
- kubectl --context "\${PROFILE}" -n ${SUPABASE_NAMESPACE} rollout status "deployment/\${deployment}" --timeout=5s >/dev/null 2>&1 && echo "- supabase/\${deployment}: ready" || echo "- supabase/\${deployment}: not ready"
3669
- else
3670
- echo "- supabase/\${deployment}: missing"
3671
- fi
3672
- done
3673
-
3674
- for key in POSTGRES_PASSWORD JWT_SECRET SUPABASE_ANON_KEY SUPABASE_SERVICE_ROLE_KEY SECRET_KEY_BASE REALTIME_DB_ENC_KEY PG_META_CRYPTO_KEY; do
3675
- check_secret_key supabase-runtime-secrets "\${key}"
3676
- done
3677
-
3678
- db_check="$(postgres_query 'select 1')"
3679
- if [[ "\${db_check}" == "1" ]]; then
3680
- echo "- postgres connectivity: ok"
3681
- else
3682
- echo "- postgres connectivity: failed"
3683
- fi
3684
-
3685
- migration_count="$(postgres_query "select count(*) from supabase_migrations.schema_migrations" || true)"
3686
- if [[ "\${migration_count}" =~ ^[0-9]+$ ]]; then
3687
- echo "- migration history: \${migration_count} applied"
3688
- else
3689
- echo "- migration history: unavailable"
3690
- fi
3691
-
3692
- if [[ "\${SUPABASE_PROFILE_ENABLED}" == "true" ]]; then
3693
- profile_state="$(postgres_query "select count(*) from ankhorage_internal.generated_schema_state where artifact_key = 'auth.profile'" || true)"
3694
- if [[ "\${profile_state}" =~ ^[1-9][0-9]*$ ]]; then
3695
- echo "- profile reconciliation: applied"
3696
- else
3697
- echo "- profile reconciliation: missing"
3698
- fi
3699
- else
3700
- state_table="$(postgres_query "select to_regclass('ankhorage_internal.generated_schema_state') is not null" || true)"
3701
- if [[ "\${state_table}" != "t" ]]; then
3702
- echo "- profile reconciliation: disabled"
3703
- else
3704
- profile_state="$(postgres_query "select coalesce((select case when table_name is null then 'stale-state-missing-table-name' when to_regclass(format('public.%I', table_name)) is null then 'stale-state-missing-table:' || table_name else 'stale-state-table-present:' || table_name end from ankhorage_internal.generated_schema_state where artifact_key = 'auth.profile'), 'disabled')" || true)"
3705
- if [[ "\${profile_state}" == "disabled" ]]; then
3706
- echo "- profile reconciliation: disabled"
3707
- else
3708
- echo "- profile reconciliation: unexpected \${profile_state}"
3709
- fi
3710
- fi
3711
- fi
3712
-
3713
- vault_state="$(postgres_query "select exists(select 1 from pg_namespace where nspname = 'vault')" || true)"
3714
- if [[ "\${vault_state}" == "t" ]]; then
3715
- echo "- vault schema: present"
3716
- else
3717
- echo "- vault schema: absent"
3718
- fi
3719
-
3720
- if command -v curl >/dev/null 2>&1; then
3721
- public_url="\${SUPABASE_PUBLIC_URL:-http://127.0.0.1:\${SUPABASE_GATEWAY_FORWARD_LOCAL_PORT}}"
3722
- if curl -fsS "\${public_url}/auth/v1/health" >/dev/null 2>&1; then
3723
- echo "- supabase public endpoint: ok (\${public_url})"
3724
- else
3725
- echo "- supabase public endpoint: unavailable (\${public_url})"
3726
- fi
3727
- fi
3728
- fi
3729
-
3730
- "\${SCRIPT_DIR}/port-forward.sh" status all || true
3731
- `;
3732
- }
3733
-
3734
- function getDefaultAppImage(appSlug: string): string {
3735
- return `ankh/${appSlug}:dev`;
3736
- }
3737
-
3738
- function resolveSupabaseHostPorts(appSlug: string): SupabaseHostPorts {
3739
- const base = PORT_BASE + resolvePortBucket(appSlug) * PORT_BUCKET_SIZE;
3740
- return {
3741
- app: base,
3742
- gateway: base + 1,
3743
- studio: base + 2,
3744
- db: base + 3,
3745
- };
3746
- }
3747
-
3748
- function resolvePortBucket(appSlug: string): number {
3749
- const rawBucket = hashSlug(appSlug) - hashSlug(PORT_REFERENCE_SLUG);
3750
- return (rawBucket + PORT_BUCKET_COUNT) % PORT_BUCKET_COUNT;
3751
- }
3752
-
3753
- function hashSlug(value: string): number {
3754
- let hash = 0;
3755
- for (const char of value) {
3756
- hash = (hash * 31 + char.charCodeAt(0)) % PORT_BUCKET_COUNT;
3757
- }
3758
- return hash;
3759
- }
3760
-
3761
- function escapeYamlDoubleQuoted(value: string): string {
3762
- return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
3763
- }