@percepta/create 3.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 (138) hide show
  1. package/README.md +93 -0
  2. package/dist/chunk-GEVZERMP.js +108 -0
  3. package/dist/chunk-R4FWPE4A.js +49 -0
  4. package/dist/chunk-WMJT7CB5.js +57 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +974 -0
  7. package/dist/init-Z4VGBHAK.js +96 -0
  8. package/dist/status-MITGDLTT.js +76 -0
  9. package/dist/sync-J4SFZHDX.js +136 -0
  10. package/dist/upstream-AQI7P4EU.js +144 -0
  11. package/package.json +58 -0
  12. package/template-versions.json +4 -0
  13. package/templates/library/README.md +30 -0
  14. package/templates/library/eslint.config.js +10 -0
  15. package/templates/library/gitignore.template +18 -0
  16. package/templates/library/package.json.template +29 -0
  17. package/templates/library/src/index.ts +9 -0
  18. package/templates/library/tsconfig.json +19 -0
  19. package/templates/monorepo/README.md +41 -0
  20. package/templates/monorepo/eslint.config.js +10 -0
  21. package/templates/monorepo/gitignore.template +31 -0
  22. package/templates/monorepo/npmrc.template +4 -0
  23. package/templates/monorepo/package.json.template +25 -0
  24. package/templates/monorepo/packages/.gitkeep +0 -0
  25. package/templates/monorepo/pnpm-workspace.yaml +2 -0
  26. package/templates/monorepo/tsconfig.json +16 -0
  27. package/templates/webapp/.claude/commands/sync.md +19 -0
  28. package/templates/webapp/.claude/commands/upstream.md +17 -0
  29. package/templates/webapp/.dockerignore +59 -0
  30. package/templates/webapp/.gitattributes +1 -0
  31. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
  32. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
  33. package/templates/webapp/.github/workflows/ci.yml +149 -0
  34. package/templates/webapp/.node-version +2 -0
  35. package/templates/webapp/.prettierrc.mjs +5 -0
  36. package/templates/webapp/AGENTS.md +240 -0
  37. package/templates/webapp/Dockerfile +64 -0
  38. package/templates/webapp/README.md +200 -0
  39. package/templates/webapp/agent-skills/database.md +140 -0
  40. package/templates/webapp/agent-skills/deploy.md +94 -0
  41. package/templates/webapp/agent-skills/inngest.md +147 -0
  42. package/templates/webapp/agent-skills/langfuse.md +117 -0
  43. package/templates/webapp/agent-skills/oneshot.md +216 -0
  44. package/templates/webapp/agent-skills/ryvn.md +25 -0
  45. package/templates/webapp/deploy/README.md +39 -0
  46. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
  47. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
  48. package/templates/webapp/docker-compose.yml +19 -0
  49. package/templates/webapp/drizzle.config.ts +30 -0
  50. package/templates/webapp/env.example.template +44 -0
  51. package/templates/webapp/eslint.config.mjs +52 -0
  52. package/templates/webapp/gitignore.template +53 -0
  53. package/templates/webapp/next.config.ts +8 -0
  54. package/templates/webapp/npmrc.template +4 -0
  55. package/templates/webapp/package.json.template +122 -0
  56. package/templates/webapp/postcss.config.mjs +5 -0
  57. package/templates/webapp/scripts/create-user.ts +47 -0
  58. package/templates/webapp/scripts/migrate.ts +18 -0
  59. package/templates/webapp/scripts/seed.ts +62 -0
  60. package/templates/webapp/scripts/setup-database.ts +57 -0
  61. package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
  62. package/templates/webapp/scripts/start.sh +52 -0
  63. package/templates/webapp/src/app/(app)/layout.tsx +21 -0
  64. package/templates/webapp/src/app/(app)/page.tsx +30 -0
  65. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
  66. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
  67. package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
  68. package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
  69. package/templates/webapp/src/app/api/healthz/route.ts +10 -0
  70. package/templates/webapp/src/app/api/inngest/route.ts +31 -0
  71. package/templates/webapp/src/app/api/readyz/route.ts +31 -0
  72. package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
  73. package/templates/webapp/src/app/favicon.ico +0 -0
  74. package/templates/webapp/src/app/global-error.tsx +27 -0
  75. package/templates/webapp/src/app/layout.tsx +18 -0
  76. package/templates/webapp/src/components/FaroProvider.tsx +37 -0
  77. package/templates/webapp/src/components/Header.tsx +70 -0
  78. package/templates/webapp/src/components/Providers.tsx +45 -0
  79. package/templates/webapp/src/components/form/FormItem.tsx +82 -0
  80. package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
  81. package/templates/webapp/src/config/getEnvConfig.ts +62 -0
  82. package/templates/webapp/src/config/isDev.ts +7 -0
  83. package/templates/webapp/src/drizzle/db.ts +28 -0
  84. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
  85. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
  86. package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
  87. package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
  88. package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
  89. package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
  90. package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
  91. package/templates/webapp/src/drizzle/schema/index.ts +4 -0
  92. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
  93. package/templates/webapp/src/instrumentation.ts +35 -0
  94. package/templates/webapp/src/lib/auth/index.ts +85 -0
  95. package/templates/webapp/src/lib/auth-client.ts +6 -0
  96. package/templates/webapp/src/lib/trpc.ts +15 -0
  97. package/templates/webapp/src/server/api/root.ts +5 -0
  98. package/templates/webapp/src/server/trpc.ts +61 -0
  99. package/templates/webapp/src/services/AuthContextService.ts +63 -0
  100. package/templates/webapp/src/services/DatabaseService.ts +54 -0
  101. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
  102. package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
  103. package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
  104. package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
  105. package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
  106. package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
  107. package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
  108. package/templates/webapp/src/services/observability/initFaro.ts +22 -0
  109. package/templates/webapp/src/startup-checks.ts +32 -0
  110. package/templates/webapp/src/styles/globals.css +27 -0
  111. package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
  112. package/templates/webapp/src/utils/cn.ts +6 -0
  113. package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
  114. package/templates/webapp/terraform/README.md +147 -0
  115. package/templates/webapp/terraform/deploy.sh +97 -0
  116. package/templates/webapp/terraform/main.tf +101 -0
  117. package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
  118. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
  119. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
  120. package/templates/webapp/terraform/modules/networking/main.tf +118 -0
  121. package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
  122. package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
  123. package/templates/webapp/terraform/modules/rds/main.tf +227 -0
  124. package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
  125. package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
  126. package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
  127. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
  128. package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
  129. package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
  130. package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
  131. package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
  132. package/templates/webapp/terraform/outputs.tf +102 -0
  133. package/templates/webapp/terraform/providers.tf +32 -0
  134. package/templates/webapp/terraform/terraform.tfvars.example +65 -0
  135. package/templates/webapp/terraform/variables.tf +129 -0
  136. package/templates/webapp/tsconfig.json +14 -0
  137. package/templates/webapp/vitest.config.ts +9 -0
  138. package/templates/webapp/vitest.setup.ts +5 -0
@@ -0,0 +1,227 @@
1
+ data "aws_region" "current" {}
2
+
3
+ data "http" "rds_ca" {
4
+ url = "https://truststore.pki.rds.amazonaws.com/${data.aws_region.current.name}/${data.aws_region.current.name}-bundle.pem"
5
+ }
6
+
7
+ data "aws_vpc" "vpc" {
8
+ id = var.vpc_id
9
+ }
10
+
11
+ data "aws_subnet" "subnet" {
12
+ count = length(var.subnet_ids)
13
+ id = var.subnet_ids[count.index]
14
+ }
15
+
16
+ # Check if existing cluster exists
17
+ data "aws_rds_cluster" "existing" {
18
+ count = var.existing_cluster_name != null ? 1 : 0
19
+ cluster_identifier = var.existing_cluster_name
20
+ }
21
+
22
+ resource "random_pet" "name" {
23
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
24
+ length = 2
25
+ }
26
+
27
+ resource "random_password" "master_password" {
28
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
29
+ length = 16
30
+ special = true
31
+ override_special = "_!%^"
32
+ }
33
+
34
+
35
+ locals {
36
+ cluster_name = var.existing_cluster_name != null ? var.existing_cluster_name : (var.create_new_cluster ? "${var.name}-${random_pet.name[0].id}" : null)
37
+
38
+ # Use existing cluster details if available, otherwise use new cluster details
39
+ cluster_endpoint = var.existing_cluster_name != null ? data.aws_rds_cluster.existing[0].endpoint : (var.create_new_cluster ? aws_rds_cluster.rds[0].endpoint : null)
40
+ cluster_port = var.existing_cluster_name != null ? data.aws_rds_cluster.existing[0].port : (var.create_new_cluster ? aws_rds_cluster.rds[0].port : null)
41
+ master_username = var.existing_cluster_name != null ? data.aws_rds_cluster.existing[0].master_username : (var.create_new_cluster ? aws_rds_cluster.rds[0].master_username : null)
42
+ master_password = var.existing_cluster_name != null ? null : (var.create_new_cluster ? random_password.master_password[0].result : null)
43
+ }
44
+
45
+ # Security group for new RDS cluster
46
+ resource "aws_security_group" "security_group" {
47
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
48
+ name = "rds-__APP_NAME__-${random_pet.name[0].id}"
49
+ description = "Security group for __APP_NAME_UPPER__ RDS ${random_pet.name[0].id}"
50
+ vpc_id = data.aws_vpc.vpc.id
51
+
52
+ ingress {
53
+ description = "Allow Postgres from VPC"
54
+ from_port = var.port
55
+ to_port = var.port
56
+ protocol = "tcp"
57
+ cidr_blocks = [data.aws_vpc.vpc.cidr_block]
58
+ }
59
+
60
+ egress {
61
+ from_port = 0
62
+ to_port = 0
63
+ protocol = "-1"
64
+ cidr_blocks = ["0.0.0.0/0"]
65
+ }
66
+ }
67
+
68
+ # Subnet group for new RDS cluster
69
+ resource "aws_db_subnet_group" "subnet_group" {
70
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
71
+ name = "rds-__APP_NAME__-${random_pet.name[0].id}"
72
+ description = "Subnet group for __APP_NAME_UPPER__ RDS ${random_pet.name[0].id}"
73
+ subnet_ids = var.subnet_ids
74
+ }
75
+
76
+ # New RDS cluster
77
+ resource "aws_rds_cluster" "rds" {
78
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
79
+ cluster_identifier = local.cluster_name
80
+ engine = "aurora-postgresql"
81
+ engine_mode = "provisioned"
82
+ engine_version = var.engine_version
83
+ vpc_security_group_ids = [aws_security_group.security_group[0].id]
84
+ db_subnet_group_name = aws_db_subnet_group.subnet_group[0].name
85
+ availability_zones = slice(data.aws_subnet.subnet[*].availability_zone, 0, min(3, length(data.aws_subnet.subnet)))
86
+ database_name = "__DB_NAME__"
87
+ port = var.port
88
+ master_username = "__APP_NAME_SNAKE___admin"
89
+ master_password = random_password.master_password[0].result
90
+ storage_encrypted = true
91
+ backup_retention_period = 5
92
+ preferred_backup_window = "07:00-09:00"
93
+
94
+ skip_final_snapshot = true
95
+ enable_http_endpoint = true
96
+
97
+ serverlessv2_scaling_configuration {
98
+ max_capacity = 1.0
99
+ min_capacity = 0.0
100
+ seconds_until_auto_pause = 3600
101
+ }
102
+ }
103
+
104
+ # RDS cluster instance for new cluster
105
+ resource "aws_rds_cluster_instance" "instance" {
106
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
107
+ identifier = "${local.cluster_name}-instance-${count.index}"
108
+ cluster_identifier = aws_rds_cluster.rds[0].id
109
+ instance_class = var.instance_class
110
+ engine = aws_rds_cluster.rds[0].engine
111
+ engine_version = aws_rds_cluster.rds[0].engine_version
112
+ publicly_accessible = false
113
+ }
114
+
115
+ # Store master password in AWS Secrets Manager for new cluster
116
+ resource "aws_secretsmanager_secret" "master_password" {
117
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
118
+ name_prefix = "rds-__APP_NAME__-master-password-${random_pet.name[0].id}-"
119
+ }
120
+
121
+ resource "aws_secretsmanager_secret_version" "master_password" {
122
+ count = var.create_new_cluster && var.existing_cluster_name == null ? 1 : 0
123
+ secret_id = aws_secretsmanager_secret.master_password[0].id
124
+ secret_string = random_password.master_password[0].result
125
+ }
126
+
127
+ ################################################################################
128
+ # EDW Readonly User Resources
129
+ ################################################################################
130
+
131
+ # Generate secure password for readonly user
132
+ resource "random_password" "readonly_user_password" {
133
+ length = 32
134
+ special = true
135
+ # Use PostgreSQL-safe special characters
136
+ override_special = "!#$%&*()-_=+[]{}<>:?"
137
+ }
138
+
139
+ # Store readonly user credentials in Secrets Manager
140
+ resource "aws_secretsmanager_secret" "readonly_user_credentials" {
141
+ name_prefix = "rds-__APP_NAME__-readonly-${var.name}-${var.environment}-"
142
+ description = "Readonly database credentials for EDW access to __APP_NAME_UPPER__ database"
143
+
144
+ tags = {
145
+ Name = "__APP_NAME__-readonly-credentials"
146
+ Environment = var.environment
147
+ Purpose = "EDW"
148
+ }
149
+ }
150
+
151
+ resource "aws_secretsmanager_secret_version" "readonly_user_credentials" {
152
+ secret_id = aws_secretsmanager_secret.readonly_user_credentials.id
153
+ secret_string = jsonencode({
154
+ username = "__APP_NAME_SNAKE___readonly"
155
+ password = random_password.readonly_user_password.result
156
+ host = local.cluster_endpoint
157
+ port = local.cluster_port
158
+ database = "__DB_NAME__"
159
+ engine = "postgres"
160
+ })
161
+ }
162
+
163
+ # Cross-account IAM role for EDW to assume
164
+ resource "aws_iam_role" "edw_secret_reader_role" {
165
+ count = length(var.edw_allowed_principals) > 0 ? 1 : 0
166
+ name = "__APP_NAME_UPPER__-EDW-SecretReader-${var.name}-${var.environment}"
167
+
168
+ assume_role_policy = jsonencode({
169
+ Version = "2012-10-17"
170
+ Statement = [
171
+ {
172
+ Effect = "Allow"
173
+ Principal = {
174
+ AWS = var.edw_allowed_principals
175
+ }
176
+ Action = "sts:AssumeRole"
177
+ }
178
+ ]
179
+ })
180
+
181
+ tags = {
182
+ Name = "__APP_NAME__-edw-secret-reader"
183
+ Environment = var.environment
184
+ Purpose = "EDW"
185
+ }
186
+ }
187
+
188
+ # IAM policy to allow reading the readonly credentials secret
189
+ resource "aws_iam_policy" "edw_secret_reader_policy" {
190
+ count = length(var.edw_allowed_principals) > 0 ? 1 : 0
191
+ name = "__APP_NAME_UPPER__-EDW-SecretReader-Policy-${var.name}-${var.environment}"
192
+ description = "Allows EDW to read __APP_NAME_UPPER__ readonly database credentials from Secrets Manager"
193
+
194
+ policy = jsonencode({
195
+ Version = "2012-10-17"
196
+ Statement = [
197
+ {
198
+ Effect = "Allow"
199
+ Action = [
200
+ "secretsmanager:GetSecretValue",
201
+ "secretsmanager:DescribeSecret"
202
+ ]
203
+ Resource = aws_secretsmanager_secret.readonly_user_credentials.arn
204
+ }
205
+ ]
206
+ })
207
+ }
208
+
209
+ resource "aws_iam_role_policy_attachment" "edw_secret_reader_attach" {
210
+ count = length(var.edw_allowed_principals) > 0 ? 1 : 0
211
+ role = aws_iam_role.edw_secret_reader_role[0].name
212
+ policy_arn = aws_iam_policy.edw_secret_reader_policy[0].arn
213
+ }
214
+
215
+ # Add ingress rule to security group for EDW VPC CIDR blocks
216
+ resource "aws_security_group_rule" "edw_ingress" {
217
+ count = var.create_new_cluster && var.existing_cluster_name == null && length(var.edw_vpc_cidr_blocks) > 0 ? 1 : 0
218
+ type = "ingress"
219
+ description = "Allow Postgres from EDW VPC via VPC peering"
220
+ from_port = var.port
221
+ to_port = var.port
222
+ protocol = "tcp"
223
+ cidr_blocks = var.edw_vpc_cidr_blocks
224
+ security_group_id = aws_security_group.security_group[0].id
225
+ }
226
+
227
+
@@ -0,0 +1,73 @@
1
+ output "host" {
2
+ description = "RDS cluster endpoint"
3
+ value = local.cluster_endpoint
4
+ }
5
+
6
+ output "port" {
7
+ description = "RDS cluster port"
8
+ value = local.cluster_port
9
+ }
10
+
11
+ output "database_name" {
12
+ description = "Database name"
13
+ value = "__DB_NAME__"
14
+ }
15
+
16
+ output "username" {
17
+ description = "Database username (master user)"
18
+ value = local.master_username
19
+ }
20
+
21
+ output "password" {
22
+ description = "Database password (master user)"
23
+ value = local.master_password
24
+ sensitive = true
25
+ }
26
+
27
+ output "ssl_cert" {
28
+ description = "RDS CA certificate"
29
+ value = data.http.rds_ca.response_body
30
+ sensitive = true
31
+ }
32
+
33
+ output "connection_url" {
34
+ description = "PostgreSQL connection URL"
35
+ value = "postgresql://${local.master_username}:${local.master_password}@${local.cluster_endpoint}:${local.cluster_port}/__DB_NAME__"
36
+ sensitive = true
37
+ }
38
+
39
+ output "cluster_name" {
40
+ description = "RDS cluster name"
41
+ value = local.cluster_name
42
+ }
43
+
44
+ output "master_username" {
45
+ description = "RDS master username (for new clusters only)"
46
+ value = local.master_username
47
+ }
48
+
49
+ output "master_password" {
50
+ description = "RDS master password (for new clusters only)"
51
+ value = local.master_password
52
+ sensitive = true
53
+ }
54
+
55
+ output "readonly_username" {
56
+ description = "Readonly database username for EDW access"
57
+ value = "__APP_NAME_SNAKE___readonly"
58
+ }
59
+
60
+ output "readonly_user_secret_arn" {
61
+ description = "ARN of the Secrets Manager secret containing readonly user credentials"
62
+ value = aws_secretsmanager_secret.readonly_user_credentials.arn
63
+ }
64
+
65
+ output "readonly_user_secret_name" {
66
+ description = "Name of the Secrets Manager secret containing readonly user credentials"
67
+ value = aws_secretsmanager_secret.readonly_user_credentials.name
68
+ }
69
+
70
+ output "readonly_user_secret_reader_role_arn" {
71
+ description = "ARN of the IAM role that EDW can assume to read the readonly credentials secret"
72
+ value = length(var.edw_allowed_principals) > 0 ? aws_iam_role.edw_secret_reader_role[0].arn : null
73
+ }
@@ -0,0 +1,61 @@
1
+ variable "name" {
2
+ description = "Base name for resources"
3
+ type = string
4
+ }
5
+
6
+ variable "environment" {
7
+ description = "Environment name (e.g. dev, staging, prod)"
8
+ type = string
9
+ }
10
+
11
+ variable "existing_cluster_name" {
12
+ description = "Name of existing RDS cluster to use (optional)"
13
+ type = string
14
+ default = null
15
+ }
16
+
17
+ variable "create_new_cluster" {
18
+ description = "Whether to create a new RDS cluster if existing_cluster_name is not provided"
19
+ type = bool
20
+ default = true
21
+ }
22
+
23
+ variable "vpc_id" {
24
+ description = "VPC ID where resources will be deployed"
25
+ type = string
26
+ }
27
+
28
+ variable "subnet_ids" {
29
+ description = "List of subnet IDs for RDS"
30
+ type = list(string)
31
+ }
32
+
33
+ variable "engine_version" {
34
+ description = "PostgreSQL engine version"
35
+ type = string
36
+ default = "16.8"
37
+ }
38
+
39
+ variable "port" {
40
+ description = "Port for RDS"
41
+ type = number
42
+ default = 5432
43
+ }
44
+
45
+ variable "instance_class" {
46
+ description = "RDS instance class"
47
+ type = string
48
+ default = "db.serverless"
49
+ }
50
+
51
+ variable "edw_allowed_principals" {
52
+ description = "List of IAM principal ARNs allowed to assume the EDW secret reader role"
53
+ type = list(string)
54
+ default = []
55
+ }
56
+
57
+ variable "edw_vpc_cidr_blocks" {
58
+ description = "List of CIDR blocks from EDW VPC to allow database access via VPC peering"
59
+ type = list(string)
60
+ default = []
61
+ }
@@ -0,0 +1,148 @@
1
+ data "aws_caller_identity" "current" {}
2
+
3
+ resource "random_string" "suffix" {
4
+ length = 8
5
+ special = false
6
+ upper = false
7
+ }
8
+
9
+ ################################################################################
10
+ # S3 Bucket for Access Logs
11
+ ################################################################################
12
+
13
+ resource "aws_s3_bucket" "logs" {
14
+ bucket = "${var.name}-logs-${random_string.suffix.result}"
15
+ }
16
+
17
+ resource "aws_s3_bucket_public_access_block" "logs" {
18
+ bucket = aws_s3_bucket.logs.id
19
+ block_public_acls = true
20
+ block_public_policy = true
21
+ ignore_public_acls = true
22
+ restrict_public_buckets = true
23
+ }
24
+
25
+ resource "aws_s3_bucket_ownership_controls" "logs" {
26
+ bucket = aws_s3_bucket.logs.id
27
+ rule {
28
+ object_ownership = "BucketOwnerPreferred"
29
+ }
30
+ }
31
+
32
+ resource "aws_s3_bucket_versioning" "logs" {
33
+ bucket = aws_s3_bucket.logs.id
34
+ versioning_configuration {
35
+ status = "Enabled"
36
+ }
37
+ }
38
+
39
+ resource "aws_s3_bucket_acl" "logs" {
40
+ depends_on = [aws_s3_bucket_ownership_controls.logs]
41
+ bucket = aws_s3_bucket.logs.id
42
+ acl = "private"
43
+ }
44
+
45
+ resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
46
+ bucket = aws_s3_bucket.logs.id
47
+
48
+ rule {
49
+ apply_server_side_encryption_by_default {
50
+ sse_algorithm = "AES256"
51
+ }
52
+ bucket_key_enabled = true
53
+ }
54
+ }
55
+
56
+ resource "aws_s3_bucket_lifecycle_configuration" "logs" {
57
+ count = var.s3_expiration_days != null ? 1 : 0
58
+ bucket = aws_s3_bucket.logs.bucket
59
+
60
+ rule {
61
+ id = "expire-objects"
62
+ status = "Enabled"
63
+ filter {
64
+ prefix = ""
65
+ }
66
+ expiration {
67
+ days = var.s3_expiration_days
68
+ }
69
+ }
70
+ }
71
+
72
+ # Bucket policy to allow S3 and CloudTrail services to write logs
73
+ resource "aws_s3_bucket_policy" "logs" {
74
+ bucket = aws_s3_bucket.logs.bucket
75
+ policy = jsonencode({
76
+ Version = "2012-10-17"
77
+ Statement = [
78
+ {
79
+ Sid = "AWSCloudTrailAclCheck"
80
+ Effect = "Allow"
81
+ Principal = {
82
+ Service = "cloudtrail.amazonaws.com"
83
+ }
84
+ Action = "s3:GetBucketAcl"
85
+ Resource = aws_s3_bucket.logs.arn
86
+ Condition = {
87
+ StringEquals = {
88
+ "aws:SourceAccount" = data.aws_caller_identity.current.account_id
89
+ }
90
+ }
91
+ },
92
+ {
93
+ Sid = "AWSCloudTrailWrite"
94
+ Effect = "Allow"
95
+ Principal = {
96
+ Service = "cloudtrail.amazonaws.com"
97
+ }
98
+ Action = "s3:PutObject"
99
+ Resource = "${aws_s3_bucket.logs.arn}/cloudtrail/*"
100
+ Condition = {
101
+ StringEquals = {
102
+ "s3:x-amz-acl" = "bucket-owner-full-control"
103
+ "aws:SourceAccount" = data.aws_caller_identity.current.account_id
104
+ }
105
+ }
106
+ },
107
+ {
108
+ Sid = "AllowS3LogDeliveryAcl"
109
+ Effect = "Allow"
110
+ Principal = {
111
+ Service = "s3.amazonaws.com"
112
+ }
113
+ Action = [
114
+ "s3:GetBucketAcl",
115
+ "s3:GetBucketLocation"
116
+ ]
117
+ Resource = aws_s3_bucket.logs.arn
118
+ },
119
+ {
120
+ Sid = "AllowS3LogDeliveryPut"
121
+ Effect = "Allow"
122
+ Principal = {
123
+ Service = "s3.amazonaws.com"
124
+ }
125
+ Action = "s3:PutObject"
126
+ Resource = "${aws_s3_bucket.logs.arn}/*"
127
+ Condition = {
128
+ StringEquals = {
129
+ "s3:x-amz-acl" = "bucket-owner-full-control"
130
+ }
131
+ }
132
+ },
133
+ {
134
+ Sid = "DenyUnEncryptedObjectUploads"
135
+ Effect = "Deny"
136
+ Principal = "*"
137
+ Action = "s3:*"
138
+ Resource = "${aws_s3_bucket.logs.arn}/*"
139
+ Condition = {
140
+ Bool = {
141
+ "aws:SecureTransport" = "false"
142
+ }
143
+ }
144
+ }
145
+ ]
146
+ })
147
+ }
148
+
@@ -0,0 +1,10 @@
1
+ output "s3_bucket_name" {
2
+ description = "Name of the S3 logging bucket"
3
+ value = aws_s3_bucket.logs.bucket
4
+ }
5
+
6
+ output "s3_bucket_arn" {
7
+ description = "ARN of the S3 logging bucket"
8
+ value = aws_s3_bucket.logs.arn
9
+ }
10
+
@@ -0,0 +1,16 @@
1
+ variable "name" {
2
+ description = "Base name for resources"
3
+ type = string
4
+ }
5
+
6
+ variable "environment" {
7
+ description = "Environment name (e.g. dev, staging, prod)"
8
+ type = string
9
+ }
10
+
11
+ variable "s3_expiration_days" {
12
+ description = "Number of days after which S3 objects expire (null to disable expiration)"
13
+ type = number
14
+ default = null
15
+ }
16
+
@@ -0,0 +1,39 @@
1
+ data "aws_region" "current" {}
2
+
3
+ ################################################################################
4
+ # Database Credentials Secret
5
+ ################################################################################
6
+
7
+ resource "kubernetes_secret" "database_credentials" {
8
+ metadata {
9
+ name = "__APP_NAME__-database-credentials"
10
+ namespace = var.namespace
11
+ }
12
+ type = "Opaque"
13
+ data = {
14
+ host = var.db_host
15
+ port = tostring(var.db_port)
16
+ database = var.db_name
17
+ username = var.db_username
18
+ password = var.db_password
19
+ ssl_cert = var.db_ssl_cert
20
+ database_url = var.db_url
21
+ }
22
+ }
23
+
24
+ # PostgreSQL Secret (Langfuse-style structure)
25
+ resource "kubernetes_secret" "postgresql" {
26
+ metadata {
27
+ name = "__APP_NAME__-postgresql"
28
+ namespace = var.namespace
29
+ }
30
+ type = "Opaque"
31
+ data = {
32
+ postgres-password = var.db_password
33
+ database_url = "postgresql://${var.db_username}:${var.db_password}@${var.db_host}:${var.db_port}/${var.db_name}"
34
+ database_name = var.db_name
35
+ host = var.db_host
36
+ port = tostring(var.db_port)
37
+ username = var.db_username
38
+ }
39
+ }
@@ -0,0 +1,9 @@
1
+ output "database_secret_name" {
2
+ description = "Name of the Kubernetes secret containing database credentials"
3
+ value = kubernetes_secret.database_credentials.metadata[0].name
4
+ }
5
+
6
+ output "postgresql_secret_name" {
7
+ description = "Name of the Kubernetes secret containing PostgreSQL credentials (Langfuse-style)"
8
+ value = kubernetes_secret.postgresql.metadata[0].name
9
+ }
@@ -0,0 +1,51 @@
1
+ variable "namespace" {
2
+ description = "Kubernetes namespace for secrets"
3
+ type = string
4
+ }
5
+
6
+ variable "cluster_name" {
7
+ description = "EKS cluster name"
8
+ type = string
9
+ }
10
+
11
+ ################################################################################
12
+ # Database Variables
13
+ ################################################################################
14
+
15
+ variable "db_host" {
16
+ description = "Database host"
17
+ type = string
18
+ }
19
+
20
+ variable "db_port" {
21
+ description = "Database port"
22
+ type = number
23
+ }
24
+
25
+ variable "db_name" {
26
+ description = "Database name"
27
+ type = string
28
+ }
29
+
30
+ variable "db_username" {
31
+ description = "Database username"
32
+ type = string
33
+ }
34
+
35
+ variable "db_password" {
36
+ description = "Database password"
37
+ type = string
38
+ sensitive = true
39
+ }
40
+
41
+ variable "db_ssl_cert" {
42
+ description = "Database SSL certificate"
43
+ type = string
44
+ sensitive = true
45
+ }
46
+
47
+ variable "db_url" {
48
+ description = "Database connection URL"
49
+ type = string
50
+ sensitive = true
51
+ }