@hasna/uptime 0.1.16 → 0.1.18
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/CHANGELOG.md +19 -0
- package/Dockerfile.package +1 -1
- package/dist/api.js +11 -0
- package/dist/cli/index.js +13 -2
- package/dist/cloud-plan.js +2 -2
- package/dist/index.js +13 -2
- package/dist/mcp/index.js +11 -0
- package/dist/report.js +11 -0
- package/dist/service.js +11 -0
- package/infra/aws/README.md +6 -0
- package/infra/aws/main.tf +38 -0
- package/infra/aws/terraform.tfvars.example +1 -1
- package/infra/aws/variables.tf +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.18] - 2026-06-28
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Added explicit ECS container health checks to the AWS Terraform module task
|
|
14
|
+
definitions. The web task checks `/health`, while disabled non-web roles use
|
|
15
|
+
a hosted-environment sanity check until their worker entrypoints are enabled.
|
|
16
|
+
- Updated cloud planning and AWS deployment docs to keep the zero-count
|
|
17
|
+
deployment status clear while the private root is repinned and verified.
|
|
18
|
+
|
|
19
|
+
## [0.1.17] - 2026-06-28
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Added Alpine `libgcc` and `libstdc++` runtime packages to the packaged
|
|
24
|
+
production Dockerfile so the copied Bun binary can run in the final image.
|
|
25
|
+
- Read Open Logs structured ingest IDs from nested `events[]` responses when
|
|
26
|
+
recording report delivery receipts.
|
|
27
|
+
|
|
9
28
|
## [0.1.16] - 2026-06-28
|
|
10
29
|
|
|
11
30
|
### Changed
|
package/Dockerfile.package
CHANGED
|
@@ -9,7 +9,7 @@ ENV NODE_ENV=production \
|
|
|
9
9
|
HASNA_UPTIME_MODE=hosted
|
|
10
10
|
WORKDIR /app
|
|
11
11
|
|
|
12
|
-
RUN apk add --no-cache ca-certificates \
|
|
12
|
+
RUN apk add --no-cache ca-certificates libgcc libstdc++ \
|
|
13
13
|
&& addgroup -g 10001 -S uptime \
|
|
14
14
|
&& adduser -S -D -H -u 10001 -G uptime uptime
|
|
15
15
|
|
package/dist/api.js
CHANGED
|
@@ -2988,6 +2988,17 @@ function idFromResponse(data) {
|
|
|
2988
2988
|
if (typeof record[key] === "string")
|
|
2989
2989
|
return record[key];
|
|
2990
2990
|
}
|
|
2991
|
+
if (Array.isArray(record.events)) {
|
|
2992
|
+
for (const event of record.events) {
|
|
2993
|
+
if (!event || typeof event !== "object")
|
|
2994
|
+
continue;
|
|
2995
|
+
const eventRecord = event;
|
|
2996
|
+
for (const key of ["id", "event_id"]) {
|
|
2997
|
+
if (typeof eventRecord[key] === "string")
|
|
2998
|
+
return eventRecord[key];
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
2991
3002
|
return;
|
|
2992
3003
|
}
|
|
2993
3004
|
function errorFromResponse(data, fallback, secrets = []) {
|
package/dist/cli/index.js
CHANGED
|
@@ -5567,6 +5567,17 @@ function idFromResponse(data) {
|
|
|
5567
5567
|
if (typeof record[key] === "string")
|
|
5568
5568
|
return record[key];
|
|
5569
5569
|
}
|
|
5570
|
+
if (Array.isArray(record.events)) {
|
|
5571
|
+
for (const event of record.events) {
|
|
5572
|
+
if (!event || typeof event !== "object")
|
|
5573
|
+
continue;
|
|
5574
|
+
const eventRecord = event;
|
|
5575
|
+
for (const key of ["id", "event_id"]) {
|
|
5576
|
+
if (typeof eventRecord[key] === "string")
|
|
5577
|
+
return eventRecord[key];
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5570
5581
|
return;
|
|
5571
5582
|
}
|
|
5572
5583
|
function errorFromResponse(data, fallback, secrets = []) {
|
|
@@ -6932,7 +6943,7 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6932
6943
|
const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
|
|
6933
6944
|
const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
|
|
6934
6945
|
const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
|
|
6935
|
-
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.
|
|
6946
|
+
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.18");
|
|
6936
6947
|
const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
|
|
6937
6948
|
const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
|
|
6938
6949
|
const cluster = `${prefix}-${stage}`;
|
|
@@ -7183,7 +7194,7 @@ function servicePlan(prefix, stage, role, desiredCount, image, workspaceId, secr
|
|
|
7183
7194
|
taskRole: `${name}-task-role`,
|
|
7184
7195
|
executionRole: `${prefix}-${stage}-execution-role`,
|
|
7185
7196
|
logGroup: `/ecs/${name}`,
|
|
7186
|
-
healthCommand: role === "web" ? "GET /health" :
|
|
7197
|
+
healthCommand: role === "web" ? "GET /health" : "hosted environment sanity check",
|
|
7187
7198
|
environment: {
|
|
7188
7199
|
HASNA_UPTIME_IMAGE: image,
|
|
7189
7200
|
...environment
|
package/dist/cloud-plan.js
CHANGED
|
@@ -21,7 +21,7 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
21
21
|
const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
|
|
22
22
|
const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
|
|
23
23
|
const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
|
|
24
|
-
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.
|
|
24
|
+
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.18");
|
|
25
25
|
const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
|
|
26
26
|
const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
|
|
27
27
|
const cluster = `${prefix}-${stage}`;
|
|
@@ -272,7 +272,7 @@ function servicePlan(prefix, stage, role, desiredCount, image, workspaceId, secr
|
|
|
272
272
|
taskRole: `${name}-task-role`,
|
|
273
273
|
executionRole: `${prefix}-${stage}-execution-role`,
|
|
274
274
|
logGroup: `/ecs/${name}`,
|
|
275
|
-
healthCommand: role === "web" ? "GET /health" :
|
|
275
|
+
healthCommand: role === "web" ? "GET /health" : "hosted environment sanity check",
|
|
276
276
|
environment: {
|
|
277
277
|
HASNA_UPTIME_IMAGE: image,
|
|
278
278
|
...environment
|
package/dist/index.js
CHANGED
|
@@ -2988,6 +2988,17 @@ function idFromResponse(data) {
|
|
|
2988
2988
|
if (typeof record[key] === "string")
|
|
2989
2989
|
return record[key];
|
|
2990
2990
|
}
|
|
2991
|
+
if (Array.isArray(record.events)) {
|
|
2992
|
+
for (const event of record.events) {
|
|
2993
|
+
if (!event || typeof event !== "object")
|
|
2994
|
+
continue;
|
|
2995
|
+
const eventRecord = event;
|
|
2996
|
+
for (const key of ["id", "event_id"]) {
|
|
2997
|
+
if (typeof eventRecord[key] === "string")
|
|
2998
|
+
return eventRecord[key];
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
2991
3002
|
return;
|
|
2992
3003
|
}
|
|
2993
3004
|
function errorFromResponse(data, fallback, secrets = []) {
|
|
@@ -4338,7 +4349,7 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
4338
4349
|
const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
|
|
4339
4350
|
const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
|
|
4340
4351
|
const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
|
|
4341
|
-
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.
|
|
4352
|
+
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.18");
|
|
4342
4353
|
const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
|
|
4343
4354
|
const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
|
|
4344
4355
|
const cluster = `${prefix}-${stage}`;
|
|
@@ -4589,7 +4600,7 @@ function servicePlan(prefix, stage, role, desiredCount, image, workspaceId, secr
|
|
|
4589
4600
|
taskRole: `${name}-task-role`,
|
|
4590
4601
|
executionRole: `${prefix}-${stage}-execution-role`,
|
|
4591
4602
|
logGroup: `/ecs/${name}`,
|
|
4592
|
-
healthCommand: role === "web" ? "GET /health" :
|
|
4603
|
+
healthCommand: role === "web" ? "GET /health" : "hosted environment sanity check",
|
|
4593
4604
|
environment: {
|
|
4594
4605
|
HASNA_UPTIME_IMAGE: image,
|
|
4595
4606
|
...environment
|
package/dist/mcp/index.js
CHANGED
|
@@ -17277,6 +17277,17 @@ function idFromResponse(data) {
|
|
|
17277
17277
|
if (typeof record2[key] === "string")
|
|
17278
17278
|
return record2[key];
|
|
17279
17279
|
}
|
|
17280
|
+
if (Array.isArray(record2.events)) {
|
|
17281
|
+
for (const event of record2.events) {
|
|
17282
|
+
if (!event || typeof event !== "object")
|
|
17283
|
+
continue;
|
|
17284
|
+
const eventRecord = event;
|
|
17285
|
+
for (const key of ["id", "event_id"]) {
|
|
17286
|
+
if (typeof eventRecord[key] === "string")
|
|
17287
|
+
return eventRecord[key];
|
|
17288
|
+
}
|
|
17289
|
+
}
|
|
17290
|
+
}
|
|
17280
17291
|
return;
|
|
17281
17292
|
}
|
|
17282
17293
|
function errorFromResponse(data, fallback, secrets = []) {
|
package/dist/report.js
CHANGED
|
@@ -215,6 +215,17 @@ function idFromResponse(data) {
|
|
|
215
215
|
if (typeof record[key] === "string")
|
|
216
216
|
return record[key];
|
|
217
217
|
}
|
|
218
|
+
if (Array.isArray(record.events)) {
|
|
219
|
+
for (const event of record.events) {
|
|
220
|
+
if (!event || typeof event !== "object")
|
|
221
|
+
continue;
|
|
222
|
+
const eventRecord = event;
|
|
223
|
+
for (const key of ["id", "event_id"]) {
|
|
224
|
+
if (typeof eventRecord[key] === "string")
|
|
225
|
+
return eventRecord[key];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
218
229
|
return;
|
|
219
230
|
}
|
|
220
231
|
function errorFromResponse(data, fallback, secrets = []) {
|
package/dist/service.js
CHANGED
|
@@ -2988,6 +2988,17 @@ function idFromResponse(data) {
|
|
|
2988
2988
|
if (typeof record[key] === "string")
|
|
2989
2989
|
return record[key];
|
|
2990
2990
|
}
|
|
2991
|
+
if (Array.isArray(record.events)) {
|
|
2992
|
+
for (const event of record.events) {
|
|
2993
|
+
if (!event || typeof event !== "object")
|
|
2994
|
+
continue;
|
|
2995
|
+
const eventRecord = event;
|
|
2996
|
+
for (const key of ["id", "event_id"]) {
|
|
2997
|
+
if (typeof eventRecord[key] === "string")
|
|
2998
|
+
return eventRecord[key];
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
2991
3002
|
return;
|
|
2992
3003
|
}
|
|
2993
3004
|
function errorFromResponse(data, fallback, secrets = []) {
|
package/infra/aws/README.md
CHANGED
|
@@ -71,6 +71,12 @@ Keep this disabled when private endpoints are the approved egress path. Runtime
|
|
|
71
71
|
scale-up still requires ECS task evidence for image pull, secret injection, log
|
|
72
72
|
delivery, S3 access, and EFS mount behavior.
|
|
73
73
|
|
|
74
|
+
Every ECS task definition includes an explicit container health check. The web
|
|
75
|
+
task checks `GET /health` through Bun's built-in `fetch`; disabled non-web roles
|
|
76
|
+
currently run a hosted-environment sanity check so scheduler, public-probe,
|
|
77
|
+
reporter, and migration tasks do not start from empty ECS health semantics when
|
|
78
|
+
their long-running workers are later enabled.
|
|
79
|
+
|
|
74
80
|
Interface endpoint private DNS is VPC-wide. In shared VPCs, either keep endpoint
|
|
75
81
|
creation in the approved networking root, or pass
|
|
76
82
|
`additional_vpc_endpoint_source_security_group_ids` for every workload that must
|
package/infra/aws/main.tf
CHANGED
|
@@ -79,6 +79,43 @@ locals {
|
|
|
79
79
|
: ["arn:${data.aws_partition.current.partition}:ssm:${var.region}:${data.aws_caller_identity.current.account_id}:parameter/${local.prefix}/no-ssm-refs-configured"]
|
|
80
80
|
)
|
|
81
81
|
service_log_group_arns = [for group in aws_cloudwatch_log_group.service : "${group.arn}:*"]
|
|
82
|
+
service_health_checks = {
|
|
83
|
+
web = {
|
|
84
|
+
command = ["CMD-SHELL", "bun -e \"const r = await fetch('http://127.0.0.1:${local.container_port}/health'); process.exit(r.ok ? 0 : 1)\""]
|
|
85
|
+
interval = 30
|
|
86
|
+
timeout = 5
|
|
87
|
+
retries = 3
|
|
88
|
+
startPeriod = 30
|
|
89
|
+
}
|
|
90
|
+
scheduler = {
|
|
91
|
+
command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'scheduler' ? 0 : 1)\""]
|
|
92
|
+
interval = 30
|
|
93
|
+
timeout = 5
|
|
94
|
+
retries = 3
|
|
95
|
+
startPeriod = 30
|
|
96
|
+
}
|
|
97
|
+
"public-probe" = {
|
|
98
|
+
command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'public-probe' ? 0 : 1)\""]
|
|
99
|
+
interval = 30
|
|
100
|
+
timeout = 5
|
|
101
|
+
retries = 3
|
|
102
|
+
startPeriod = 30
|
|
103
|
+
}
|
|
104
|
+
reporter = {
|
|
105
|
+
command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'reporter' ? 0 : 1)\""]
|
|
106
|
+
interval = 30
|
|
107
|
+
timeout = 5
|
|
108
|
+
retries = 3
|
|
109
|
+
startPeriod = 30
|
|
110
|
+
}
|
|
111
|
+
migration = {
|
|
112
|
+
command = ["CMD-SHELL", "bun -e \"process.exit(process.env.HASNA_UPTIME_MODE === 'hosted' && process.env.HASNA_UPTIME_COMPONENT === 'migration' ? 0 : 1)\""]
|
|
113
|
+
interval = 30
|
|
114
|
+
timeout = 5
|
|
115
|
+
retries = 3
|
|
116
|
+
startPeriod = 30
|
|
117
|
+
}
|
|
118
|
+
}
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
data "aws_vpc" "target" {
|
|
@@ -1090,6 +1127,7 @@ resource "aws_ecs_task_definition" "service" {
|
|
|
1090
1127
|
readOnly = false
|
|
1091
1128
|
}
|
|
1092
1129
|
] : []
|
|
1130
|
+
healthCheck = local.service_health_checks[each.key]
|
|
1093
1131
|
secrets = [
|
|
1094
1132
|
for name, value_from in each.value.secrets : {
|
|
1095
1133
|
name = name
|
|
@@ -16,7 +16,7 @@ alb_ingress_cidr_blocks = []
|
|
|
16
16
|
private_subnet_ids = ["subnet-replace-private-a", "subnet-replace-private-b"]
|
|
17
17
|
private_route_table_ids = ["rtb-replace-private"]
|
|
18
18
|
container_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/open-uptime@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
19
|
-
runtime_package_version = "0.1.
|
|
19
|
+
runtime_package_version = "0.1.18"
|
|
20
20
|
certificate_arn = null
|
|
21
21
|
hosted_zone_id = null
|
|
22
22
|
app_env_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/app/env"
|
package/infra/aws/variables.tf
CHANGED
|
@@ -116,7 +116,7 @@ variable "container_image" {
|
|
|
116
116
|
variable "runtime_package_version" {
|
|
117
117
|
description = "Published @hasna/uptime package version that CodeBuild should build into the ECR image."
|
|
118
118
|
type = string
|
|
119
|
-
default = "0.1.
|
|
119
|
+
default = "0.1.18"
|
|
120
120
|
|
|
121
121
|
validation {
|
|
122
122
|
condition = can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", var.runtime_package_version))
|
package/package.json
CHANGED