@rulebricks/cli 1.9.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 (93) hide show
  1. package/README.md +62 -0
  2. package/dist/commands/clone.d.ts +6 -0
  3. package/dist/commands/clone.js +60 -0
  4. package/dist/commands/deploy.d.ts +8 -0
  5. package/dist/commands/deploy.js +409 -0
  6. package/dist/commands/destroy.d.ts +8 -0
  7. package/dist/commands/destroy.js +298 -0
  8. package/dist/commands/init.d.ts +7 -0
  9. package/dist/commands/init.js +201 -0
  10. package/dist/commands/logs.d.ts +9 -0
  11. package/dist/commands/logs.js +222 -0
  12. package/dist/commands/open.d.ts +7 -0
  13. package/dist/commands/open.js +139 -0
  14. package/dist/commands/status.d.ts +5 -0
  15. package/dist/commands/status.js +125 -0
  16. package/dist/commands/upgrade.d.ts +7 -0
  17. package/dist/commands/upgrade.js +239 -0
  18. package/dist/components/DNSWaitScreen.d.ts +9 -0
  19. package/dist/components/DNSWaitScreen.js +73 -0
  20. package/dist/components/Wizard/WizardContext.d.ts +176 -0
  21. package/dist/components/Wizard/WizardContext.js +346 -0
  22. package/dist/components/Wizard/index.d.ts +2 -0
  23. package/dist/components/Wizard/index.js +2 -0
  24. package/dist/components/Wizard/steps/CloudProviderStep.d.ts +6 -0
  25. package/dist/components/Wizard/steps/CloudProviderStep.js +210 -0
  26. package/dist/components/Wizard/steps/CredentialsStep.d.ts +6 -0
  27. package/dist/components/Wizard/steps/CredentialsStep.js +22 -0
  28. package/dist/components/Wizard/steps/DatabaseStep.d.ts +6 -0
  29. package/dist/components/Wizard/steps/DatabaseStep.js +80 -0
  30. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +5 -0
  31. package/dist/components/Wizard/steps/DeploymentModeStep.js +26 -0
  32. package/dist/components/Wizard/steps/DomainStep.d.ts +6 -0
  33. package/dist/components/Wizard/steps/DomainStep.js +126 -0
  34. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +6 -0
  35. package/dist/components/Wizard/steps/FeatureConfigStep.js +765 -0
  36. package/dist/components/Wizard/steps/FeaturesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/FeaturesStep.js +119 -0
  38. package/dist/components/Wizard/steps/ReviewStep.d.ts +6 -0
  39. package/dist/components/Wizard/steps/ReviewStep.js +56 -0
  40. package/dist/components/Wizard/steps/SMTPStep.d.ts +6 -0
  41. package/dist/components/Wizard/steps/SMTPStep.js +191 -0
  42. package/dist/components/Wizard/steps/SupabaseCredentialsStep.d.ts +6 -0
  43. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +76 -0
  44. package/dist/components/Wizard/steps/TierStep.d.ts +6 -0
  45. package/dist/components/Wizard/steps/TierStep.js +29 -0
  46. package/dist/components/Wizard/steps/VersionStep.d.ts +6 -0
  47. package/dist/components/Wizard/steps/VersionStep.js +113 -0
  48. package/dist/components/Wizard/steps/index.d.ts +12 -0
  49. package/dist/components/Wizard/steps/index.js +12 -0
  50. package/dist/components/common/AppShell.d.ts +31 -0
  51. package/dist/components/common/AppShell.js +31 -0
  52. package/dist/components/common/Box.d.ts +20 -0
  53. package/dist/components/common/Box.js +20 -0
  54. package/dist/components/common/Logo.d.ts +7 -0
  55. package/dist/components/common/Logo.js +22 -0
  56. package/dist/components/common/Spinner.d.ts +12 -0
  57. package/dist/components/common/Spinner.js +28 -0
  58. package/dist/components/common/index.d.ts +6 -0
  59. package/dist/components/common/index.js +5 -0
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.js +202 -0
  62. package/dist/lib/cloudCli.d.ts +156 -0
  63. package/dist/lib/cloudCli.js +691 -0
  64. package/dist/lib/config.d.ts +91 -0
  65. package/dist/lib/config.js +278 -0
  66. package/dist/lib/dns.d.ts +41 -0
  67. package/dist/lib/dns.js +235 -0
  68. package/dist/lib/dockerHub.d.ts +57 -0
  69. package/dist/lib/dockerHub.js +128 -0
  70. package/dist/lib/helm.d.ts +53 -0
  71. package/dist/lib/helm.js +209 -0
  72. package/dist/lib/helmValues.d.ts +17 -0
  73. package/dist/lib/helmValues.js +693 -0
  74. package/dist/lib/kubernetes.d.ts +161 -0
  75. package/dist/lib/kubernetes.js +755 -0
  76. package/dist/lib/terraform.d.ts +44 -0
  77. package/dist/lib/terraform.js +230 -0
  78. package/dist/lib/theme.d.ts +81 -0
  79. package/dist/lib/theme.js +115 -0
  80. package/dist/lib/validation.d.ts +47 -0
  81. package/dist/lib/validation.js +164 -0
  82. package/dist/lib/versions.d.ts +69 -0
  83. package/dist/lib/versions.js +139 -0
  84. package/dist/types/index.d.ts +718 -0
  85. package/dist/types/index.js +556 -0
  86. package/email-templates/email_change.html +325 -0
  87. package/email-templates/invite.html +383 -0
  88. package/email-templates/password_change.html +414 -0
  89. package/email-templates/verify.html +396 -0
  90. package/package.json +78 -0
  91. package/terraform/aws/main.tf +327 -0
  92. package/terraform/azure/main.tf +326 -0
  93. package/terraform/gcp/main.tf +369 -0
@@ -0,0 +1,693 @@
1
+ import { TIER_CONFIGS, isSupportedDnsProvider, getLoggingDestinationLabel, } from "../types/index.js";
2
+ import { saveHelmValues, getHelmValuesPath } from "./config.js";
3
+ import fs from "fs/promises";
4
+ import YAML from "yaml";
5
+ /**
6
+ * Generates Vector sink configuration based on logging settings
7
+ */
8
+ function generateVectorSinks(config) {
9
+ const sinks = {
10
+ // Console sink is always enabled
11
+ console: {
12
+ type: "console",
13
+ inputs: ["kafka"],
14
+ encoding: {
15
+ codec: "json",
16
+ },
17
+ },
18
+ };
19
+ // Add external sink if configured
20
+ if (config.features.logging.sink !== "console" &&
21
+ config.features.logging.sink !== "pending") {
22
+ const { sink, bucket, region } = config.features.logging;
23
+ switch (sink) {
24
+ // Cloud Storage sinks
25
+ case "s3":
26
+ sinks.s3 = {
27
+ type: "aws_s3",
28
+ inputs: ["kafka"],
29
+ bucket: bucket,
30
+ region: region,
31
+ key_prefix: "rulebricks/logs/%Y/%m/%d/",
32
+ compression: "gzip",
33
+ encoding: {
34
+ codec: "json",
35
+ },
36
+ };
37
+ break;
38
+ case "azure-blob":
39
+ sinks.azure_blob = {
40
+ type: "azure_blob",
41
+ inputs: ["kafka"],
42
+ container_name: bucket,
43
+ storage_account: "rulebrickslogs", // Will be configured via env var
44
+ blob_prefix: "rulebricks/logs/%Y/%m/%d/",
45
+ compression: "gzip",
46
+ encoding: {
47
+ codec: "json",
48
+ },
49
+ };
50
+ break;
51
+ case "gcs":
52
+ sinks.gcs = {
53
+ type: "gcp_cloud_storage",
54
+ inputs: ["kafka"],
55
+ bucket: bucket,
56
+ key_prefix: "rulebricks/logs/%Y/%m/%d/",
57
+ compression: "gzip",
58
+ encoding: {
59
+ codec: "json",
60
+ },
61
+ };
62
+ break;
63
+ // Logging platform sinks
64
+ // For platforms, bucket is repurposed for API key/token, region for site/URL
65
+ case "datadog":
66
+ sinks.datadog = {
67
+ type: "datadog_logs",
68
+ inputs: ["kafka"],
69
+ default_api_key: bucket, // API key stored in bucket field
70
+ site: region || "datadoghq.com", // Site stored in region field
71
+ compression: "gzip",
72
+ encoding: {
73
+ codec: "json",
74
+ },
75
+ };
76
+ break;
77
+ case "splunk":
78
+ sinks.splunk = {
79
+ type: "splunk_hec_logs",
80
+ inputs: ["kafka"],
81
+ endpoint: region, // URL stored in region field
82
+ default_token: bucket, // HEC token stored in bucket field
83
+ compression: "gzip",
84
+ encoding: {
85
+ codec: "json",
86
+ },
87
+ };
88
+ break;
89
+ case "elasticsearch":
90
+ // Elasticsearch config is JSON-encoded in bucket field
91
+ try {
92
+ const esConfig = JSON.parse(bucket || "{}");
93
+ sinks.elasticsearch = {
94
+ type: "elasticsearch",
95
+ inputs: ["kafka"],
96
+ endpoints: [esConfig.url],
97
+ bulk: {
98
+ index: esConfig.index || "rulebricks-logs",
99
+ },
100
+ ...(esConfig.user && esConfig.password
101
+ ? {
102
+ auth: {
103
+ strategy: "basic",
104
+ user: esConfig.user,
105
+ password: esConfig.password,
106
+ },
107
+ }
108
+ : {}),
109
+ };
110
+ }
111
+ catch {
112
+ // Fallback if JSON parsing fails
113
+ sinks.elasticsearch = {
114
+ type: "elasticsearch",
115
+ inputs: ["kafka"],
116
+ endpoints: [bucket],
117
+ bulk: {
118
+ index: region || "rulebricks-logs",
119
+ },
120
+ };
121
+ }
122
+ break;
123
+ case "loki":
124
+ sinks.loki = {
125
+ type: "loki",
126
+ inputs: ["kafka"],
127
+ endpoint: bucket, // Loki URL stored in bucket field
128
+ labels: {
129
+ app: "rulebricks",
130
+ source: "decision-logs",
131
+ },
132
+ encoding: {
133
+ codec: "json",
134
+ },
135
+ };
136
+ break;
137
+ case "newrelic":
138
+ sinks.newrelic = {
139
+ type: "new_relic",
140
+ inputs: ["kafka"],
141
+ license_key: bucket, // License key stored in bucket field
142
+ account_id: region, // Account ID stored in region field
143
+ api: "logs",
144
+ compression: "gzip",
145
+ encoding: {
146
+ codec: "json",
147
+ },
148
+ };
149
+ break;
150
+ case "axiom":
151
+ sinks.axiom = {
152
+ type: "axiom",
153
+ inputs: ["kafka"],
154
+ token: bucket, // API token stored in bucket field
155
+ dataset: region || "rulebricks", // Dataset stored in region field
156
+ compression: "gzip",
157
+ encoding: {
158
+ codec: "json",
159
+ },
160
+ };
161
+ break;
162
+ }
163
+ }
164
+ return sinks;
165
+ }
166
+ /**
167
+ * Maps DNS provider to external-dns provider name
168
+ */
169
+ function getExternalDnsProvider(dnsProvider) {
170
+ const mapping = {
171
+ route53: "aws",
172
+ cloudflare: "cloudflare",
173
+ google: "google",
174
+ azure: "azure",
175
+ };
176
+ return mapping[dnsProvider] || "aws";
177
+ }
178
+ /**
179
+ * Generates Kafka extra environment variables for tuning
180
+ */
181
+ function generateKafkaExtraEnvVars() {
182
+ return [
183
+ {
184
+ name: "KAFKA_JVM_PERFORMANCE_OPTS",
185
+ value: "-XX:MaxDirectMemorySize=256M -Djdk.nio.maxCachedBufferSize=262144",
186
+ },
187
+ { name: "KAFKA_CFG_QUEUED_MAX_REQUESTS", value: "10000" },
188
+ { name: "KAFKA_CFG_NUM_NETWORK_THREADS", value: "8" },
189
+ { name: "KAFKA_CFG_NUM_IO_THREADS", value: "8" },
190
+ { name: "KAFKA_CFG_SOCKET_SEND_BUFFER_BYTES", value: "1048576" },
191
+ { name: "KAFKA_CFG_SOCKET_RECEIVE_BUFFER_BYTES", value: "1048576" },
192
+ { name: "KAFKA_CFG_SOCKET_REQUEST_MAX_BYTES", value: "209715200" },
193
+ { name: "KAFKA_CFG_LOG_RETENTION_BYTES", value: "4294967296" },
194
+ { name: "KAFKA_CFG_LOG_SEGMENT_BYTES", value: "1073741824" },
195
+ { name: "KAFKA_CFG_NUM_REPLICA_FETCHERS", value: "4" },
196
+ { name: "KAFKA_CFG_REPLICA_SOCKET_RECEIVE_BUFFER_BYTES", value: "1048576" },
197
+ { name: "KAFKA_CFG_LOG_CLEANER_DEDUPE_BUFFER_SIZE", value: "268435456" },
198
+ { name: "KAFKA_CFG_LOG_CLEANER_IO_BUFFER_SIZE", value: "1048576" },
199
+ { name: "KAFKA_CFG_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION", value: "10" },
200
+ ];
201
+ }
202
+ /**
203
+ * Generates Helm values from the deployment configuration
204
+ */
205
+ export async function generateHelmValues(config, options = {}) {
206
+ const tierConfig = TIER_CONFIGS[config.tier];
207
+ const { tlsEnabled = true } = options;
208
+ // Determine if external-dns should be enabled
209
+ const externalDnsEnabled = config.dns.autoManage && isSupportedDnsProvider(config.dns.provider);
210
+ // Determine storage class based on provider
211
+ const storageClass = config.infrastructure.provider === "aws"
212
+ ? "gp3"
213
+ : config.infrastructure.provider === "gcp"
214
+ ? "standard"
215
+ : config.infrastructure.provider === "azure"
216
+ ? "managed-premium"
217
+ : "gp3";
218
+ // Build global.supabase configuration
219
+ const supabaseGlobalConfig = config.database.type === "supabase-cloud"
220
+ ? {
221
+ url: config.database.supabaseUrl,
222
+ anonKey: config.database.supabaseAnonKey,
223
+ serviceKey: config.database.supabaseServiceKey,
224
+ accessToken: config.database.supabaseAccessToken || undefined,
225
+ projectRef: config.database.supabaseProjectRef || undefined,
226
+ }
227
+ : {
228
+ jwtSecret: config.database.supabaseJwtSecret || undefined,
229
+ anonKey: undefined,
230
+ serviceKey: undefined,
231
+ };
232
+ // Add custom email templates if enabled
233
+ if (config.features.customEmails?.enabled &&
234
+ config.features.customEmails.subjects &&
235
+ config.features.customEmails.templates) {
236
+ supabaseGlobalConfig.emails = {
237
+ subjects: {
238
+ invite: config.features.customEmails.subjects.invite,
239
+ confirmation: config.features.customEmails.subjects.confirmation,
240
+ recovery: config.features.customEmails.subjects.recovery,
241
+ emailChange: config.features.customEmails.subjects.emailChange,
242
+ },
243
+ templates: {
244
+ invite: config.features.customEmails.templates.invite,
245
+ confirmation: config.features.customEmails.templates.confirmation,
246
+ recovery: config.features.customEmails.templates.recovery,
247
+ emailChange: config.features.customEmails.templates.emailChange,
248
+ },
249
+ };
250
+ }
251
+ const values = {
252
+ // =============================================================================
253
+ // GLOBAL CONFIGURATION
254
+ // =============================================================================
255
+ global: {
256
+ domain: config.domain,
257
+ email: config.adminEmail,
258
+ tlsEnabled,
259
+ licenseKey: config.licenseKey,
260
+ externalDnsEnabled,
261
+ // SMTP Configuration
262
+ smtp: {
263
+ host: config.smtp.host,
264
+ port: config.smtp.port,
265
+ user: config.smtp.user,
266
+ pass: config.smtp.pass,
267
+ from: config.smtp.from,
268
+ fromName: config.smtp.fromName,
269
+ },
270
+ // Supabase configuration
271
+ supabase: supabaseGlobalConfig,
272
+ // AI configuration
273
+ ai: {
274
+ enabled: config.features.ai.enabled,
275
+ openaiApiKey: config.features.ai.enabled
276
+ ? config.features.ai.openaiApiKey
277
+ : undefined,
278
+ },
279
+ // SSO configuration
280
+ sso: config.features.sso.enabled
281
+ ? {
282
+ enabled: true,
283
+ provider: config.features.sso.provider,
284
+ url: config.features.sso.url,
285
+ clientId: config.features.sso.clientId,
286
+ clientSecret: config.features.sso.clientSecret,
287
+ }
288
+ : {
289
+ enabled: false,
290
+ },
291
+ },
292
+ // =============================================================================
293
+ // RULEBRICKS APPLICATION STACK
294
+ // =============================================================================
295
+ rulebricks: {
296
+ app: {
297
+ ...(config.appVersion
298
+ ? {
299
+ image: {
300
+ repository: "index.docker.io/rulebricks/app",
301
+ tag: config.appVersion,
302
+ pullPolicy: "IfNotPresent",
303
+ },
304
+ }
305
+ : {}),
306
+ replicaCount: tierConfig.appReplicas,
307
+ resources: tierConfig.appResources,
308
+ // Logging configuration
309
+ logging: {
310
+ enabled: true,
311
+ kafkaBrokers: "", // Auto-discover from Kafka subchart
312
+ kafkaTopic: "logs",
313
+ loggingDestination: getLoggingDestinationLabel(config.features.logging.sink),
314
+ },
315
+ },
316
+ // HPS (High Performance Server)
317
+ hps: {
318
+ enabled: true,
319
+ ...(config.hpsVersion
320
+ ? {
321
+ image: {
322
+ repository: "index.docker.io/rulebricks/hps",
323
+ tag: config.hpsVersion,
324
+ pullPolicy: "Always",
325
+ },
326
+ }
327
+ : {}),
328
+ replicas: tierConfig.hpsReplicas,
329
+ resources: tierConfig.hpsResources,
330
+ // HPS Workers with KEDA autoscaling
331
+ workers: {
332
+ enabled: true,
333
+ replicas: tierConfig.hpsWorkerReplicas.min,
334
+ keda: {
335
+ enabled: true,
336
+ minReplicaCount: tierConfig.hpsWorkerReplicas.min,
337
+ maxReplicaCount: tierConfig.hpsWorkerReplicas.max,
338
+ pollingInterval: 10,
339
+ cooldownPeriod: 300,
340
+ lagThreshold: 50,
341
+ cpuThreshold: 25,
342
+ },
343
+ resources: tierConfig.hpsWorkerResources,
344
+ },
345
+ },
346
+ // Ingress configuration
347
+ ingress: {
348
+ enabled: true,
349
+ className: "traefik",
350
+ paths: [{ path: "/", pathType: "Prefix" }],
351
+ },
352
+ // Redis configuration
353
+ redis: {
354
+ resources: tierConfig.redisResources,
355
+ persistence: {
356
+ enabled: true,
357
+ size: tierConfig.redisPersistenceSize,
358
+ storageClass: storageClass,
359
+ },
360
+ },
361
+ },
362
+ // =============================================================================
363
+ // KAFKA (Message Queue)
364
+ // =============================================================================
365
+ kafka: {
366
+ enabled: true,
367
+ // KRaft mode (no Zookeeper)
368
+ kraft: {
369
+ enabled: true,
370
+ },
371
+ zookeeper: {
372
+ enabled: false,
373
+ },
374
+ // Kafka broker configuration
375
+ overrideConfiguration: {
376
+ "auto.create.topics.enable": "true",
377
+ "log.retention.hours": "24",
378
+ "default.replication.factor": String(tierConfig.kafkaReplication),
379
+ "offsets.topic.replication.factor": String(tierConfig.kafkaReplication),
380
+ "num.partitions": String(tierConfig.hpsWorkerReplicas.max), // Match max workers for parallel consumption
381
+ },
382
+ controller: {
383
+ replicaCount: tierConfig.kafkaReplication,
384
+ resources: tierConfig.kafkaResources,
385
+ persistence: {
386
+ enabled: true,
387
+ size: tierConfig.kafkaStorage,
388
+ storageClass: storageClass,
389
+ },
390
+ heapOpts: tierConfig.kafkaHeapOpts,
391
+ extraEnvVars: generateKafkaExtraEnvVars(),
392
+ },
393
+ listeners: {
394
+ client: {
395
+ protocol: "PLAINTEXT",
396
+ },
397
+ controller: {
398
+ protocol: "PLAINTEXT",
399
+ },
400
+ interbroker: {
401
+ protocol: "PLAINTEXT",
402
+ },
403
+ },
404
+ },
405
+ // =============================================================================
406
+ // TRAEFIK (Ingress Controller)
407
+ // =============================================================================
408
+ traefik: {
409
+ enabled: true,
410
+ ingressClass: {
411
+ name: "traefik",
412
+ },
413
+ autoscaling: {
414
+ enabled: true,
415
+ minReplicas: 1,
416
+ maxReplicas: 2,
417
+ },
418
+ resources: {
419
+ requests: {
420
+ cpu: "100m",
421
+ memory: "256Mi",
422
+ },
423
+ limits: {
424
+ cpu: "1000m",
425
+ memory: "2Gi",
426
+ },
427
+ },
428
+ service: {
429
+ type: "LoadBalancer",
430
+ },
431
+ ports: {
432
+ web: {
433
+ port: 8000,
434
+ exposedPort: 80,
435
+ },
436
+ websecure: {
437
+ port: 8443,
438
+ exposedPort: 443,
439
+ tls: {
440
+ enabled: tlsEnabled,
441
+ },
442
+ },
443
+ },
444
+ persistence: {
445
+ enabled: false,
446
+ },
447
+ },
448
+ // =============================================================================
449
+ // KEDA (Autoscaling)
450
+ // =============================================================================
451
+ keda: {
452
+ enabled: true,
453
+ crds: {
454
+ install: false, // CRDs managed in parent chart
455
+ },
456
+ },
457
+ // =============================================================================
458
+ // CERT-MANAGER (TLS Certificates)
459
+ // =============================================================================
460
+ "cert-manager": {
461
+ enabled: tlsEnabled,
462
+ installCRDs: false, // CRDs managed in parent chart
463
+ },
464
+ // Cluster Issuer for Let's Encrypt
465
+ clusterIssuer: {
466
+ enabled: tlsEnabled,
467
+ email: config.tlsEmail,
468
+ server: "https://acme-v02.api.letsencrypt.org/directory",
469
+ },
470
+ // =============================================================================
471
+ // VECTOR (Decision Logs)
472
+ // =============================================================================
473
+ vector: {
474
+ enabled: true,
475
+ role: "Stateless-Aggregator",
476
+ replicas: tierConfig.vectorReplicas,
477
+ resources: tierConfig.vectorResources,
478
+ service: {
479
+ enabled: true,
480
+ ports: [{ name: "api", port: 8686, protocol: "TCP", targetPort: 8686 }],
481
+ },
482
+ // Load KAFKA_BOOTSTRAP_SERVERS from templated ConfigMap
483
+ env: [
484
+ {
485
+ name: "KAFKA_BOOTSTRAP_SERVERS",
486
+ valueFrom: {
487
+ configMapKeyRef: {
488
+ name: "vector-kafka-env",
489
+ key: "KAFKA_BOOTSTRAP_SERVERS",
490
+ },
491
+ },
492
+ },
493
+ ],
494
+ customConfig: {
495
+ sources: {
496
+ kafka: {
497
+ type: "kafka",
498
+ bootstrap_servers: "${KAFKA_BOOTSTRAP_SERVERS:-rulebricks-kafka:9092}",
499
+ topics: ["logs"],
500
+ group_id: "vector-consumers",
501
+ auto_offset_reset: "latest",
502
+ },
503
+ },
504
+ sinks: generateVectorSinks(config),
505
+ },
506
+ },
507
+ // =============================================================================
508
+ // SUPABASE (Self-hosted Database)
509
+ // =============================================================================
510
+ supabase: {
511
+ enabled: config.database.type === "self-hosted",
512
+ ...(config.database.type === "self-hosted"
513
+ ? {
514
+ secret: {
515
+ db: {
516
+ username: "postgres",
517
+ password: config.database.supabaseDbPassword,
518
+ database: "postgres",
519
+ },
520
+ dashboard: {
521
+ username: config.database.supabaseDashboardUser || "supabase",
522
+ password: config.database.supabaseDashboardPass,
523
+ },
524
+ jwt: {
525
+ secret: config.database.supabaseJwtSecret,
526
+ },
527
+ },
528
+ db: {
529
+ resources: tierConfig.dbResources,
530
+ persistence: {
531
+ enabled: true,
532
+ size: tierConfig.dbPersistenceSize,
533
+ storageClassName: storageClass,
534
+ },
535
+ },
536
+ kong: {
537
+ ingress: {
538
+ enabled: true,
539
+ className: "traefik",
540
+ annotations: {},
541
+ },
542
+ },
543
+ }
544
+ : {}),
545
+ },
546
+ // =============================================================================
547
+ // MONITORING
548
+ // =============================================================================
549
+ monitoring: {
550
+ enabled: config.features.monitoring.enabled,
551
+ },
552
+ "kube-prometheus-stack": {
553
+ enabled: config.features.monitoring.enabled,
554
+ alertmanager: {
555
+ enabled: false,
556
+ },
557
+ grafana: {
558
+ enabled: false,
559
+ },
560
+ prometheus: {
561
+ enabled: config.features.monitoring.enabled,
562
+ prometheusSpec: {
563
+ retention: "30d",
564
+ storageSpec: {
565
+ volumeClaimTemplate: {
566
+ spec: {
567
+ storageClassName: storageClass,
568
+ accessModes: ["ReadWriteOnce"],
569
+ resources: {
570
+ requests: {
571
+ storage: "50Gi",
572
+ },
573
+ },
574
+ },
575
+ },
576
+ },
577
+ ...(config.features.monitoring.remoteWriteUrl
578
+ ? {
579
+ remoteWrite: [
580
+ { url: config.features.monitoring.remoteWriteUrl },
581
+ ],
582
+ }
583
+ : { remoteWrite: [] }),
584
+ },
585
+ },
586
+ },
587
+ // =============================================================================
588
+ // STORAGE CLASS
589
+ // =============================================================================
590
+ storageClass: {
591
+ create: true,
592
+ name: storageClass,
593
+ provisioner: config.infrastructure.provider === "aws"
594
+ ? "ebs.csi.aws.com"
595
+ : config.infrastructure.provider === "gcp"
596
+ ? "pd.csi.storage.gke.io"
597
+ : config.infrastructure.provider === "azure"
598
+ ? "disk.csi.azure.com"
599
+ : "ebs.csi.aws.com",
600
+ type: config.infrastructure.provider === "aws"
601
+ ? "gp3"
602
+ : config.infrastructure.provider === "gcp"
603
+ ? "pd-ssd"
604
+ : config.infrastructure.provider === "azure"
605
+ ? "Premium_LRS"
606
+ : "gp3",
607
+ fsType: "ext4",
608
+ reclaimPolicy: "Delete",
609
+ volumeBindingMode: "WaitForFirstConsumer",
610
+ allowVolumeExpansion: true,
611
+ },
612
+ // =============================================================================
613
+ // EXTERNAL DNS
614
+ // =============================================================================
615
+ "external-dns": externalDnsEnabled
616
+ ? {
617
+ enabled: true,
618
+ provider: getExternalDnsProvider(config.dns.provider),
619
+ domainFilters: [config.domain],
620
+ sources: ["ingress", "service"],
621
+ policy: "upsert-only",
622
+ }
623
+ : {
624
+ enabled: false,
625
+ },
626
+ };
627
+ await saveHelmValues(config.name, values);
628
+ }
629
+ /**
630
+ * Updates existing Helm values to enable or disable TLS
631
+ */
632
+ export async function updateHelmValuesForTLS(deploymentName, tlsEnabled) {
633
+ const valuesPath = getHelmValuesPath(deploymentName);
634
+ try {
635
+ const content = await fs.readFile(valuesPath, "utf8");
636
+ const values = YAML.parse(content);
637
+ // Update TLS settings
638
+ if (values.global && typeof values.global === "object") {
639
+ values.global.tlsEnabled = tlsEnabled;
640
+ }
641
+ // Update cert-manager
642
+ if (values["cert-manager"] && typeof values["cert-manager"] === "object") {
643
+ values["cert-manager"].enabled = tlsEnabled;
644
+ }
645
+ // Update cluster issuer
646
+ if (values.clusterIssuer && typeof values.clusterIssuer === "object") {
647
+ values.clusterIssuer.enabled = tlsEnabled;
648
+ }
649
+ // Update traefik TLS
650
+ if (values.traefik && typeof values.traefik === "object") {
651
+ const traefik = values.traefik;
652
+ if (traefik.ports && typeof traefik.ports === "object") {
653
+ const ports = traefik.ports;
654
+ if (ports.websecure && typeof ports.websecure === "object") {
655
+ const websecure = ports.websecure;
656
+ if (websecure.tls && typeof websecure.tls === "object") {
657
+ websecure.tls.enabled = tlsEnabled;
658
+ }
659
+ }
660
+ }
661
+ }
662
+ // Save updated values
663
+ await fs.writeFile(valuesPath, YAML.stringify(values), "utf8");
664
+ }
665
+ catch (error) {
666
+ throw new Error(`Failed to update Helm values: ${error}`);
667
+ }
668
+ }
669
+ /**
670
+ * Updates existing Helm values with new configuration
671
+ */
672
+ export function mergeHelmValues(existing, updates) {
673
+ return deepMerge(existing, updates);
674
+ }
675
+ function deepMerge(target, source) {
676
+ const result = { ...target };
677
+ for (const key of Object.keys(source)) {
678
+ const sourceValue = source[key];
679
+ const targetValue = target[key];
680
+ if (sourceValue &&
681
+ typeof sourceValue === "object" &&
682
+ !Array.isArray(sourceValue) &&
683
+ targetValue &&
684
+ typeof targetValue === "object" &&
685
+ !Array.isArray(targetValue)) {
686
+ result[key] = deepMerge(targetValue, sourceValue);
687
+ }
688
+ else {
689
+ result[key] = sourceValue;
690
+ }
691
+ }
692
+ return result;
693
+ }