@hasna/uptime 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/infra/aws/main.tf CHANGED
@@ -17,16 +17,21 @@ data "aws_caller_identity" "current" {}
17
17
  data "aws_partition" "current" {}
18
18
 
19
19
  locals {
20
- prefix = "${var.service_name}-${var.stage}"
21
- container_port = 3899
22
- evidence_bucket = "hasna-${var.stage}-${var.service_name}-evidence"
23
- efs_uid = 10001
24
- efs_gid = 10001
25
- hosted_sqlite_db_path = "/data/uptime/uptime.db"
26
- efs_enabled_services = toset(["web"])
27
- use_alb_https = var.protected_access_mode == "alb_https_cert"
28
- use_cloudfront = var.protected_access_mode == "cloudfront_default_domain"
29
- use_origin_verify = local.use_cloudfront && var.enable_cloudfront_origin_verify_header
20
+ prefix = "${var.service_name}-${var.stage}"
21
+ container_port = 3899
22
+ evidence_bucket = "hasna-${var.stage}-${var.service_name}-evidence"
23
+ efs_uid = 10001
24
+ efs_gid = 10001
25
+ hosted_sqlite_db_path = "/data/uptime/uptime.db"
26
+ efs_enabled_services = toset(["web"])
27
+ expected_runtime_package_integrity = coalesce(var.runtime_package_integrity, "")
28
+ use_alb_https = var.protected_access_mode == "alb_https_cert"
29
+ use_cloudfront = var.protected_access_mode == "cloudfront_default_domain"
30
+ cloudfront_https_origin = (
31
+ local.use_cloudfront && var.cloudfront_origin_protocol_policy == "https-only"
32
+ )
33
+ alb_https_listener_enabled = local.use_alb_https || local.cloudfront_https_origin
34
+ use_origin_verify = local.use_cloudfront && var.enable_cloudfront_origin_verify_header
30
35
  services = {
31
36
  web = {
32
37
  desired_count = lookup(var.desired_counts, "web", 0)
@@ -35,22 +40,22 @@ locals {
35
40
  }
36
41
  scheduler = {
37
42
  desired_count = lookup(var.desired_counts, "scheduler", 0)
38
- command = ["bun", "dist/cli/index.js", "cloud", "plan"]
43
+ command = ["bun", "dist/cli/index.js", "cloud", "workers", "run", "--role", "scheduler"]
39
44
  secrets = { APP_ENV = var.app_env_secret_arn }
40
45
  }
41
46
  "public-probe" = {
42
47
  desired_count = lookup(var.desired_counts, "public-probe", 0)
43
- command = ["bun", "dist/cli/index.js", "cloud", "plan"]
48
+ command = ["bun", "dist/cli/index.js", "cloud", "workers", "run", "--role", "public-probe"]
44
49
  secrets = { PROBE_CONFIG = var.public_probe_secret_arn }
45
50
  }
46
51
  reporter = {
47
52
  desired_count = lookup(var.desired_counts, "reporter", 0)
48
- command = ["bun", "dist/cli/index.js", "cloud", "plan"]
53
+ command = ["bun", "dist/cli/index.js", "cloud", "workers", "run", "--role", "reporter"]
49
54
  secrets = { REPORTING_CONFIG = var.reporting_secret_arn }
50
55
  }
51
56
  migration = {
52
57
  desired_count = lookup(var.desired_counts, "migration", 0)
53
- command = ["bun", "dist/cli/index.js", "cloud", "plan"]
58
+ command = ["bun", "dist/cli/index.js", "cloud", "workers", "run", "--role", "migration"]
54
59
  secrets = { APP_ENV = var.app_env_secret_arn }
55
60
  }
56
61
  }
@@ -89,28 +94,28 @@ locals {
89
94
  startPeriod = 30
90
95
  }
91
96
  scheduler = {
92
- command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'scheduler' ? 0 : 1)\""]
97
+ command = ["CMD-SHELL", "bun dist/cli/index.js cloud workers preflight --role scheduler --healthcheck --json >/tmp/open-uptime-worker-preflight.json"]
93
98
  interval = 30
94
99
  timeout = 5
95
100
  retries = 3
96
101
  startPeriod = 30
97
102
  }
98
103
  "public-probe" = {
99
- command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'public-probe' ? 0 : 1)\""]
104
+ command = ["CMD-SHELL", "bun dist/cli/index.js cloud workers preflight --role public-probe --healthcheck --json >/tmp/open-uptime-worker-preflight.json"]
100
105
  interval = 30
101
106
  timeout = 5
102
107
  retries = 3
103
108
  startPeriod = 30
104
109
  }
105
110
  reporter = {
106
- command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'reporter' ? 0 : 1)\""]
111
+ command = ["CMD-SHELL", "bun dist/cli/index.js cloud workers preflight --role reporter --healthcheck --json >/tmp/open-uptime-worker-preflight.json"]
107
112
  interval = 30
108
113
  timeout = 5
109
114
  retries = 3
110
115
  startPeriod = 30
111
116
  }
112
117
  migration = {
113
- command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'migration' ? 0 : 1)\""]
118
+ command = ["CMD-SHELL", "bun dist/cli/index.js cloud workers preflight --role migration --healthcheck --json >/tmp/open-uptime-worker-preflight.json"]
114
119
  interval = 30
115
120
  timeout = 5
116
121
  retries = 3
@@ -236,9 +241,18 @@ resource "aws_codebuild_project" "image_builder" {
236
241
  - aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.region}.amazonaws.com
237
242
  build:
238
243
  commands:
239
- - npm pack @hasna/uptime@${var.runtime_package_version}
244
+ - EXPECTED_RUNTIME_PACKAGE_INTEGRITY='${local.expected_runtime_package_integrity}'
245
+ - PACKAGE_TARBALL=$(npm pack @hasna/uptime@${var.runtime_package_version} --silent)
246
+ - PACKAGE_INTEGRITY=$(npm view @hasna/uptime@${var.runtime_package_version} dist.integrity --json | tr -d '"')
247
+ - test -n "$PACKAGE_INTEGRITY"
248
+ - |
249
+ if [ -n "$EXPECTED_RUNTIME_PACKAGE_INTEGRITY" ] && [ "$PACKAGE_INTEGRITY" != "$EXPECTED_RUNTIME_PACKAGE_INTEGRITY" ]; then
250
+ echo "runtime package integrity mismatch" >&2
251
+ exit 1
252
+ fi
253
+ - printf 'runtime package integrity %s\n' "$PACKAGE_INTEGRITY"
240
254
  - mkdir package
241
- - tar -xzf hasna-uptime-*.tgz -C package --strip-components=1
255
+ - tar -xzf "$PACKAGE_TARBALL" -C package --strip-components=1
242
256
  - cd package
243
257
  - docker build -f Dockerfile.package -t ${aws_ecr_repository.open_uptime.repository_url}:${var.runtime_package_version} .
244
258
  - docker push ${aws_ecr_repository.open_uptime.repository_url}:${var.runtime_package_version}
@@ -366,8 +380,19 @@ resource "aws_security_group_rule" "alb_https_ingress" {
366
380
  cidr_blocks = var.alb_ingress_cidr_blocks
367
381
  }
368
382
 
383
+ resource "aws_security_group_rule" "alb_https_from_cloudfront" {
384
+ count = local.cloudfront_https_origin ? 1 : 0
385
+ type = "ingress"
386
+ description = "HTTPS from CloudFront origin-facing ranges"
387
+ security_group_id = aws_security_group.alb.id
388
+ from_port = 443
389
+ to_port = 443
390
+ protocol = "tcp"
391
+ prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront_origin_facing[0].id]
392
+ }
393
+
369
394
  resource "aws_security_group_rule" "alb_http_from_cloudfront" {
370
- count = local.use_cloudfront ? 1 : 0
395
+ count = local.use_cloudfront && !local.cloudfront_https_origin ? 1 : 0
371
396
  type = "ingress"
372
397
  description = "HTTP from CloudFront origin-facing ranges"
373
398
  security_group_id = aws_security_group.alb.id
@@ -881,21 +906,37 @@ resource "aws_lb_target_group" "web" {
881
906
  }
882
907
 
883
908
  resource "aws_lb_listener" "https" {
884
- count = local.use_alb_https ? 1 : 0
909
+ count = local.alb_https_listener_enabled ? 1 : 0
885
910
  load_balancer_arn = aws_lb.open_uptime.arn
886
911
  port = 443
887
912
  protocol = "HTTPS"
888
913
  certificate_arn = var.certificate_arn
889
914
  tags = local.tags
890
915
 
891
- default_action {
892
- type = "forward"
893
- target_group_arn = aws_lb_target_group.web.arn
916
+ dynamic "default_action" {
917
+ for_each = local.cloudfront_https_origin && local.use_origin_verify ? [] : [1]
918
+ content {
919
+ type = "forward"
920
+ target_group_arn = aws_lb_target_group.web.arn
921
+ }
922
+ }
923
+
924
+ dynamic "default_action" {
925
+ for_each = local.cloudfront_https_origin && local.use_origin_verify ? [1] : []
926
+ content {
927
+ type = "fixed-response"
928
+
929
+ fixed_response {
930
+ content_type = "text/plain"
931
+ message_body = "forbidden"
932
+ status_code = "403"
933
+ }
934
+ }
894
935
  }
895
936
  }
896
937
 
897
938
  resource "aws_lb_listener" "http_cloudfront" {
898
- count = local.use_cloudfront ? 1 : 0
939
+ count = local.use_cloudfront && !local.cloudfront_https_origin ? 1 : 0
899
940
  load_balancer_arn = aws_lb.open_uptime.arn
900
941
  port = 80
901
942
  protocol = "HTTP"
@@ -924,7 +965,7 @@ resource "aws_lb_listener" "http_cloudfront" {
924
965
  }
925
966
 
926
967
  resource "aws_lb_listener_rule" "http_cloudfront_origin_verify" {
927
- count = local.use_origin_verify ? 1 : 0
968
+ count = local.use_origin_verify && !local.cloudfront_https_origin ? 1 : 0
928
969
  listener_arn = aws_lb_listener.http_cloudfront[0].arn
929
970
  priority = var.cloudfront_origin_verify_listener_rule_priority
930
971
  tags = local.tags
@@ -942,6 +983,25 @@ resource "aws_lb_listener_rule" "http_cloudfront_origin_verify" {
942
983
  }
943
984
  }
944
985
 
986
+ resource "aws_lb_listener_rule" "https_cloudfront_origin_verify" {
987
+ count = local.use_origin_verify && local.cloudfront_https_origin ? 1 : 0
988
+ listener_arn = aws_lb_listener.https[0].arn
989
+ priority = var.cloudfront_origin_verify_listener_rule_priority
990
+ tags = local.tags
991
+
992
+ action {
993
+ type = "forward"
994
+ target_group_arn = aws_lb_target_group.web.arn
995
+ }
996
+
997
+ condition {
998
+ http_header {
999
+ http_header_name = var.cloudfront_origin_verify_header_name
1000
+ values = [var.cloudfront_origin_verify_header_value]
1001
+ }
1002
+ }
1003
+ }
1004
+
945
1005
  resource "aws_cloudfront_distribution" "open_uptime" {
946
1006
  count = local.use_cloudfront ? 1 : 0
947
1007
  enabled = true
@@ -951,7 +1011,7 @@ resource "aws_cloudfront_distribution" "open_uptime" {
951
1011
  tags = local.tags
952
1012
 
953
1013
  origin {
954
- domain_name = aws_lb.open_uptime.dns_name
1014
+ domain_name = local.cloudfront_https_origin ? var.cloudfront_origin_domain_name : aws_lb.open_uptime.dns_name
955
1015
  origin_id = "${local.prefix}-alb"
956
1016
 
957
1017
  dynamic "custom_header" {
@@ -965,7 +1025,7 @@ resource "aws_cloudfront_distribution" "open_uptime" {
965
1025
  custom_origin_config {
966
1026
  http_port = 80
967
1027
  https_port = 443
968
- origin_protocol_policy = "http-only"
1028
+ origin_protocol_policy = var.cloudfront_origin_protocol_policy
969
1029
  origin_ssl_protocols = ["TLSv1.2"]
970
1030
  }
971
1031
  }
@@ -1000,7 +1060,12 @@ resource "aws_cloudfront_distribution" "open_uptime" {
1000
1060
  cloudfront_default_certificate = true
1001
1061
  }
1002
1062
 
1003
- depends_on = [aws_lb_listener.http_cloudfront, aws_lb_listener_rule.http_cloudfront_origin_verify]
1063
+ depends_on = [
1064
+ aws_lb_listener.http_cloudfront,
1065
+ aws_lb_listener.https,
1066
+ aws_lb_listener_rule.http_cloudfront_origin_verify,
1067
+ aws_lb_listener_rule.https_cloudfront_origin_verify,
1068
+ ]
1004
1069
  }
1005
1070
 
1006
1071
  resource "aws_route53_record" "open_uptime" {
@@ -1016,6 +1081,19 @@ resource "aws_route53_record" "open_uptime" {
1016
1081
  }
1017
1082
  }
1018
1083
 
1084
+ resource "aws_route53_record" "cloudfront_origin" {
1085
+ count = local.cloudfront_https_origin && var.hosted_zone_id != null ? 1 : 0
1086
+ zone_id = var.hosted_zone_id
1087
+ name = var.cloudfront_origin_domain_name
1088
+ type = "A"
1089
+
1090
+ alias {
1091
+ name = aws_lb.open_uptime.dns_name
1092
+ zone_id = aws_lb.open_uptime.zone_id
1093
+ evaluate_target_health = true
1094
+ }
1095
+ }
1096
+
1019
1097
  data "aws_iam_policy_document" "ecs_assume_role" {
1020
1098
  statement {
1021
1099
  actions = ["sts:AssumeRole"]
@@ -1194,12 +1272,14 @@ resource "aws_ecs_task_definition" "service" {
1194
1272
  }
1195
1273
 
1196
1274
  resource "aws_ecs_service" "web" {
1197
- name = "${local.prefix}-web"
1198
- cluster = aws_ecs_cluster.open_uptime.id
1199
- task_definition = aws_ecs_task_definition.service["web"].arn
1200
- desired_count = local.services.web.desired_count
1201
- launch_type = "FARGATE"
1202
- tags = local.tags
1275
+ name = "${local.prefix}-web"
1276
+ cluster = aws_ecs_cluster.open_uptime.id
1277
+ task_definition = aws_ecs_task_definition.service["web"].arn
1278
+ desired_count = local.services.web.desired_count
1279
+ launch_type = "FARGATE"
1280
+ enable_ecs_managed_tags = true
1281
+ propagate_tags = "SERVICE"
1282
+ tags = local.tags
1203
1283
 
1204
1284
  deployment_circuit_breaker {
1205
1285
  enable = true
@@ -1226,12 +1306,14 @@ resource "aws_ecs_service" "worker" {
1226
1306
  for key, value in local.services : key => value if key != "web" && key != "migration"
1227
1307
  }
1228
1308
 
1229
- name = "${local.prefix}-${each.key}"
1230
- cluster = aws_ecs_cluster.open_uptime.id
1231
- task_definition = aws_ecs_task_definition.service[each.key].arn
1232
- desired_count = each.value.desired_count
1233
- launch_type = "FARGATE"
1234
- tags = local.tags
1309
+ name = "${local.prefix}-${each.key}"
1310
+ cluster = aws_ecs_cluster.open_uptime.id
1311
+ task_definition = aws_ecs_task_definition.service[each.key].arn
1312
+ desired_count = each.value.desired_count
1313
+ launch_type = "FARGATE"
1314
+ enable_ecs_managed_tags = true
1315
+ propagate_tags = "SERVICE"
1316
+ tags = local.tags
1235
1317
 
1236
1318
  deployment_circuit_breaker {
1237
1319
  enable = true
@@ -18,6 +18,14 @@ output "cloudfront_domain_name" {
18
18
  value = try(aws_cloudfront_distribution.open_uptime[0].domain_name, null)
19
19
  }
20
20
 
21
+ output "cloudfront_origin_protocol_policy" {
22
+ value = local.use_cloudfront ? var.cloudfront_origin_protocol_policy : null
23
+ }
24
+
25
+ output "cloudfront_origin_domain_name" {
26
+ value = local.use_cloudfront ? (local.cloudfront_https_origin ? var.cloudfront_origin_domain_name : aws_lb.open_uptime.dns_name) : null
27
+ }
28
+
21
29
  output "protected_access_url" {
22
30
  value = var.protected_access_mode == "cloudfront_default_domain" ? "https://${aws_cloudfront_distribution.open_uptime[0].domain_name}" : "https://${var.hostname}"
23
31
  }
@@ -11,6 +11,9 @@ workspace_id = "workspace-id"
11
11
  vpc_id = "vpc-xxxxxxxx"
12
12
  ecr_repository_name = "open-uptime"
13
13
  protected_access_mode = "cloudfront_default_domain"
14
+ cloudfront_origin_protocol_policy = "http-only"
15
+ allow_cloudfront_http_origin_live_traffic = false
16
+ cloudfront_origin_domain_name = null
14
17
  enable_cloudfront_origin_verify_header = false
15
18
  cloudfront_origin_verify_header_name = "X-Open-Uptime-Origin-Verify"
16
19
  cloudfront_origin_verify_header_value = null
@@ -19,7 +22,8 @@ alb_ingress_cidr_blocks = []
19
22
  private_subnet_ids = ["subnet-replace-private-a", "subnet-replace-private-b"]
20
23
  private_route_table_ids = ["rtb-replace-private"]
21
24
  container_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/open-uptime@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
22
- runtime_package_version = "0.1.24"
25
+ runtime_package_version = "0.1.26"
26
+ runtime_package_integrity = null
23
27
  certificate_arn = null
24
28
  hosted_zone_id = null
25
29
  app_env_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/app/env"
@@ -77,7 +77,7 @@ variable "ecr_repository_name" {
77
77
  }
78
78
 
79
79
  variable "protected_access_mode" {
80
- description = "Protected web access mode. cloudfront_default_domain uses the CloudFront HTTPS default domain and restricts ALB HTTP to CloudFront origin-facing ranges. alb_https_cert uses an ALB HTTPS listener with certificate_arn."
80
+ description = "Protected web access mode. cloudfront_default_domain uses the CloudFront HTTPS default domain and restricts the ALB origin to CloudFront origin-facing ranges. alb_https_cert uses an ALB HTTPS listener with certificate_arn."
81
81
  type = string
82
82
  default = "cloudfront_default_domain"
83
83
 
@@ -87,6 +87,46 @@ variable "protected_access_mode" {
87
87
  }
88
88
  }
89
89
 
90
+ variable "cloudfront_origin_protocol_policy" {
91
+ description = "CloudFront-to-ALB origin protocol policy. Keep http-only until an origin hostname and matching ACM certificate are approved; set https-only with cloudfront_origin_domain_name and certificate_arn before token-bearing live traffic."
92
+ type = string
93
+ default = "http-only"
94
+
95
+ validation {
96
+ condition = contains(["http-only", "https-only"], var.cloudfront_origin_protocol_policy)
97
+ error_message = "cloudfront_origin_protocol_policy must be http-only or https-only."
98
+ }
99
+ }
100
+
101
+ variable "allow_cloudfront_http_origin_live_traffic" {
102
+ description = "Explicit risk acceptance for setting web desired count above 0 while CloudFront-to-ALB origin transport is http-only. Keep false unless a named operator accepts the temporary HTTP-origin bridge risk for a bounded smoke."
103
+ type = bool
104
+ default = false
105
+ }
106
+
107
+ variable "cloudfront_origin_domain_name" {
108
+ description = "DNS hostname CloudFront uses for the ALB custom origin when cloudfront_origin_protocol_policy is https-only. The hostname must resolve to the ALB and match certificate_arn. Leave null for the default HTTP-origin bridge."
109
+ type = string
110
+ default = null
111
+ nullable = true
112
+
113
+ validation {
114
+ condition = (
115
+ var.cloudfront_origin_domain_name == null
116
+ || can(regex("^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$", var.cloudfront_origin_domain_name))
117
+ )
118
+ error_message = "cloudfront_origin_domain_name must be null or a valid DNS hostname."
119
+ }
120
+
121
+ validation {
122
+ condition = (
123
+ !(var.protected_access_mode == "cloudfront_default_domain" && var.cloudfront_origin_protocol_policy == "https-only")
124
+ || var.cloudfront_origin_domain_name != null
125
+ )
126
+ error_message = "cloudfront_origin_domain_name is required when CloudFront HTTPS origin is enabled."
127
+ }
128
+ }
129
+
90
130
  variable "enable_cloudfront_origin_verify_header" {
91
131
  description = "When true in cloudfront_default_domain mode, CloudFront sends a private origin header and the ALB listener rejects requests missing the matching value."
92
132
  type = bool
@@ -201,7 +241,7 @@ variable "container_image" {
201
241
  variable "runtime_package_version" {
202
242
  description = "Published @hasna/uptime package version that CodeBuild should build into the ECR image."
203
243
  type = string
204
- default = "0.1.24"
244
+ default = "0.1.26"
205
245
 
206
246
  validation {
207
247
  condition = can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", var.runtime_package_version))
@@ -209,8 +249,20 @@ variable "runtime_package_version" {
209
249
  }
210
250
  }
211
251
 
252
+ variable "runtime_package_integrity" {
253
+ description = "Optional expected npm dist.integrity value for @hasna/uptime@runtime_package_version. When set, CodeBuild verifies the registry tarball integrity before building the image."
254
+ type = string
255
+ default = null
256
+ nullable = true
257
+
258
+ validation {
259
+ condition = var.runtime_package_integrity == null || can(regex("^sha512-[A-Za-z0-9+/=]+$", var.runtime_package_integrity))
260
+ error_message = "runtime_package_integrity must be null or an npm sha512 integrity string."
261
+ }
262
+ }
263
+
212
264
  variable "certificate_arn" {
213
- description = "ACM certificate ARN for ALB HTTPS mode. Leave null when protected_access_mode is cloudfront_default_domain."
265
+ description = "ACM certificate ARN for ALB HTTPS mode or CloudFront HTTPS-origin mode. Leave null only when the ALB is not serving HTTPS."
214
266
  type = string
215
267
  default = null
216
268
 
@@ -220,8 +272,11 @@ variable "certificate_arn" {
220
272
  }
221
273
 
222
274
  validation {
223
- condition = var.protected_access_mode != "alb_https_cert" || var.certificate_arn != null
224
- error_message = "certificate_arn is required when protected_access_mode is alb_https_cert."
275
+ condition = (
276
+ !(var.protected_access_mode == "alb_https_cert" || (var.protected_access_mode == "cloudfront_default_domain" && var.cloudfront_origin_protocol_policy == "https-only"))
277
+ || var.certificate_arn != null
278
+ )
279
+ error_message = "certificate_arn is required when protected_access_mode is alb_https_cert or CloudFront HTTPS origin is enabled."
225
280
  }
226
281
  }
227
282
 
@@ -298,6 +353,25 @@ variable "desired_counts" {
298
353
  ])
299
354
  error_message = "EFS SQLite bridge requires web desired count 0 or 1 and scheduler/public-probe/reporter/migration desired counts 0."
300
355
  }
356
+
357
+ validation {
358
+ condition = (
359
+ lookup(var.desired_counts, "web", 0) == 0
360
+ || var.protected_access_mode != "cloudfront_default_domain"
361
+ || var.enable_cloudfront_origin_verify_header
362
+ )
363
+ error_message = "web desired count above 0 in cloudfront_default_domain mode requires enable_cloudfront_origin_verify_header=true."
364
+ }
365
+
366
+ validation {
367
+ condition = (
368
+ lookup(var.desired_counts, "web", 0) == 0
369
+ || var.protected_access_mode != "cloudfront_default_domain"
370
+ || var.cloudfront_origin_protocol_policy == "https-only"
371
+ || var.allow_cloudfront_http_origin_live_traffic
372
+ )
373
+ error_message = "web desired count above 0 requires CloudFront HTTPS-origin mode, or explicit allow_cloudfront_http_origin_live_traffic=true risk acceptance for a bounded smoke."
374
+ }
301
375
  }
302
376
 
303
377
  variable "alarm_actions" {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/uptime",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Local-first uptime and downtime monitoring service with CLI, MCP, SDK, SQLite persistence, and a dashboard.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -26,6 +26,7 @@
26
26
  "Dockerfile",
27
27
  "Dockerfile.package",
28
28
  ".dockerignore",
29
+ "bun.lock",
29
30
  "infra/aws/README.md",
30
31
  "infra/aws/.terraform.lock.hcl",
31
32
  "infra/aws/*.tf",
@@ -68,10 +69,14 @@
68
69
  "./cloud-plan": {
69
70
  "types": "./dist/cloud-plan.d.ts",
70
71
  "import": "./dist/cloud-plan.js"
72
+ },
73
+ "./workers": {
74
+ "types": "./dist/workers.d.ts",
75
+ "import": "./dist/workers.js"
71
76
  }
72
77
  },
73
78
  "scripts": {
74
- "build": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk && bun build src/index.ts src/api.ts src/service.ts src/store.ts src/checks.ts src/imports.ts src/report.ts src/probes.ts src/cloud-plan.ts src/types.ts src/paths.ts src/dashboard.ts src/version.ts --root src --outdir dist --target bun && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist && chmod +x dist/cli/index.js dist/mcp/index.js",
79
+ "build": "rm -rf dist && bun build src/cli/index.ts --outdir dist/cli --target bun --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk && bun build src/index.ts src/api.ts src/service.ts src/store.ts src/checks.ts src/imports.ts src/report.ts src/probes.ts src/cloud-plan.ts src/workers.ts src/types.ts src/paths.ts src/dashboard.ts src/version.ts --root src --outdir dist --target bun && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist && chmod +x dist/cli/index.js dist/mcp/index.js",
75
80
  "typecheck": "tsc --noEmit",
76
81
  "test": "bun test ./tests",
77
82
  "dev:cli": "bun run src/cli/index.ts",