@hasna/uptime 0.1.6 → 0.1.7
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/.dockerignore +0 -1
- package/CHANGELOG.md +20 -1
- package/Dockerfile +2 -1
- package/Dockerfile.package +22 -0
- package/README.md +7 -1
- package/dist/api.js +38 -8
- package/dist/cli/index.js +91 -43
- package/dist/cloud-plan.d.ts +11 -4
- package/dist/cloud-plan.d.ts.map +1 -1
- package/dist/cloud-plan.js +43 -31
- package/dist/index.js +81 -39
- package/dist/mcp/index.js +38 -8
- package/dist/service.d.ts +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +38 -8
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +40 -9
- package/docs/aws-deployment-runbook.md +30 -20
- package/infra/aws/README.md +17 -6
- package/infra/aws/main.tf +282 -33
- package/infra/aws/outputs.tf +12 -0
- package/infra/aws/terraform.tfvars.example +10 -10
- package/infra/aws/variables.tf +25 -21
- package/package.json +2 -1
package/.dockerignore
CHANGED
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.7] - 2026-06-28
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Explicit hosted EFS-backed SQLite runtime path with
|
|
14
|
+
`HASNA_UPTIME_HOSTED_SQLITE_DB` and `hosted-efs-sqlite` health metadata.
|
|
15
|
+
- AWS Terraform EFS file system, access point, ECS volume mount, and AWS Backup
|
|
16
|
+
plan for the hosted SQLite data store.
|
|
17
|
+
- `Dockerfile.package` plus AWS CodeBuild image-builder Terraform resources to
|
|
18
|
+
build the published npm package into ECR without relying on local Docker.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Hosted AWS deployment artifacts no longer inject `HASNA_UPTIME_DATABASE_URL`;
|
|
23
|
+
the async Postgres adapter remains future work.
|
|
24
|
+
- The EFS-backed SQLite bridge is single-writer only: one web task maximum and
|
|
25
|
+
scheduler/public-probe/reporter services remain disabled until Postgres and
|
|
26
|
+
cloud leases exist.
|
|
27
|
+
|
|
9
28
|
## [0.1.6] - 2026-06-28
|
|
10
29
|
|
|
11
30
|
### Added
|
|
@@ -29,7 +48,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
29
48
|
|
|
30
49
|
### Added
|
|
31
50
|
|
|
32
|
-
- Dry-run AWS deployment plan generator for
|
|
51
|
+
- Dry-run AWS deployment plan generator for a reviewed AWS target,
|
|
33
52
|
covering ECS/Fargate services, ECR image commands, ALB/RDS/S3/Secrets/Logs
|
|
34
53
|
resources, rollback steps, and safety assertions.
|
|
35
54
|
- Spark01 hosted-targeted private probe preflight config generator with JSON and
|
package/Dockerfile
CHANGED
|
@@ -15,7 +15,8 @@ ENV NODE_ENV=production \
|
|
|
15
15
|
HASNA_UPTIME_MODE=hosted
|
|
16
16
|
WORKDIR /app
|
|
17
17
|
|
|
18
|
-
RUN addgroup --system
|
|
18
|
+
RUN addgroup --system --gid 10001 uptime \
|
|
19
|
+
&& adduser --system --uid 10001 --ingroup uptime uptime
|
|
19
20
|
|
|
20
21
|
COPY --from=build /app/package.json ./package.json
|
|
21
22
|
COPY --from=build /app/node_modules ./node_modules
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
|
|
3
|
+
FROM oven/bun:1.3.13-slim AS runtime
|
|
4
|
+
ENV NODE_ENV=production \
|
|
5
|
+
HASNA_UPTIME_MODE=hosted
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
|
|
8
|
+
RUN addgroup --system --gid 10001 uptime \
|
|
9
|
+
&& adduser --system --uid 10001 --ingroup uptime uptime
|
|
10
|
+
|
|
11
|
+
COPY package.json ./package.json
|
|
12
|
+
COPY dist ./dist
|
|
13
|
+
|
|
14
|
+
RUN bun install --production
|
|
15
|
+
|
|
16
|
+
USER uptime
|
|
17
|
+
EXPOSE 3899
|
|
18
|
+
|
|
19
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
20
|
+
CMD bun -e "const r = await fetch('http://127.0.0.1:3899/health'); process.exit(r.ok ? 0 : 1)"
|
|
21
|
+
|
|
22
|
+
CMD ["bun", "dist/cli/index.js", "serve", "--mode", "hosted", "--host", "0.0.0.0", "--port", "3899"]
|
package/README.md
CHANGED
|
@@ -48,7 +48,13 @@ in `docs/aws-deployment-runbook.md` is satisfied.
|
|
|
48
48
|
|
|
49
49
|
Deployment review artifacts live in `Dockerfile` and `infra/aws`. The Terraform
|
|
50
50
|
desired counts default to zero, and `uptime cloud plan --json` exposes the
|
|
51
|
-
format/init/validate/plan commands with `applyAllowed: false`.
|
|
51
|
+
format/init/validate/plan commands with `applyAllowed: false`. Hosted AWS
|
|
52
|
+
runtime state currently uses explicit EFS-backed SQLite via
|
|
53
|
+
`HASNA_UPTIME_HOSTED_SQLITE_DB=/data/uptime/uptime.db` for one protected web
|
|
54
|
+
task maximum; do not set `HASNA_UPTIME_DATABASE_URL` until the async Postgres
|
|
55
|
+
adapter is implemented.
|
|
56
|
+
`Dockerfile.package` is used by the Terraform CodeBuild image builder to build
|
|
57
|
+
the published npm package into ECR from inside AWS.
|
|
52
58
|
|
|
53
59
|
Private/local probes can submit signed results from another machine:
|
|
54
60
|
|
package/dist/api.js
CHANGED
|
@@ -820,10 +820,12 @@ function ensureUptimeHome() {
|
|
|
820
820
|
}
|
|
821
821
|
|
|
822
822
|
// src/store.ts
|
|
823
|
-
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
823
|
+
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, statfsSync, statSync } from "fs";
|
|
824
824
|
import { dirname, join as join2 } from "path";
|
|
825
825
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
826
826
|
import { Database } from "bun:sqlite";
|
|
827
|
+
var DEFAULT_HOSTED_SQLITE_DB_PATH = "/data/uptime/uptime.db";
|
|
828
|
+
var NFS_SUPER_MAGIC = 26985;
|
|
827
829
|
var SECRET_URL_PARAM_PATTERN = /(token|secret|password|passwd|api[_-]?key|access[_-]?token|auth|credential|session)/i;
|
|
828
830
|
var REQUIRED_TABLES = [
|
|
829
831
|
"schema_migrations",
|
|
@@ -860,18 +862,39 @@ class UptimeStore {
|
|
|
860
862
|
this.mode = resolveRuntimeMode(options.mode ?? "local");
|
|
861
863
|
const cloudDatabaseUrl = options.cloudDatabaseUrl ?? process.env.HASNA_UPTIME_DATABASE_URL;
|
|
862
864
|
if (this.mode === "hosted" && cloudDatabaseUrl) {
|
|
863
|
-
throw new Error("hosted
|
|
865
|
+
throw new Error("hosted Postgres adapter is not implemented yet; use HASNA_UPTIME_HOSTED_SQLITE_DB on cloud-mounted storage for the current hosted deployment path");
|
|
864
866
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
+
const hostedSqliteDbPath = options.hostedSqliteDbPath ?? process.env.HASNA_UPTIME_HOSTED_SQLITE_DB;
|
|
868
|
+
if (this.mode === "hosted" && hostedSqliteDbPath) {
|
|
869
|
+
if (hostedSqliteDbPath === ":memory:" || !hostedSqliteDbPath.startsWith("/")) {
|
|
870
|
+
throw new Error("HASNA_UPTIME_HOSTED_SQLITE_DB must be an absolute path on mounted cloud storage");
|
|
871
|
+
}
|
|
872
|
+
const approvedHostedPath = hostedSqliteDbPath === DEFAULT_HOSTED_SQLITE_DB_PATH;
|
|
873
|
+
if (!approvedHostedPath && !allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
874
|
+
throw new Error(`HASNA_UPTIME_HOSTED_SQLITE_DB must be ${DEFAULT_HOSTED_SQLITE_DB_PATH}; set HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE=1 only for explicit local fallback testing`);
|
|
875
|
+
}
|
|
876
|
+
const verifiedCloudMount = approvedHostedPath && isNfsMount(dirname(hostedSqliteDbPath));
|
|
877
|
+
if (approvedHostedPath && !verifiedCloudMount && !allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
878
|
+
throw new Error(`${DEFAULT_HOSTED_SQLITE_DB_PATH} must be on a mounted EFS/NFS filesystem; refusing to create hosted task-local SQLite`);
|
|
879
|
+
}
|
|
880
|
+
this.dataMode = verifiedCloudMount ? "hosted-efs-sqlite" : "hosted-local-sqlite";
|
|
881
|
+
this.dbPath = hostedSqliteDbPath;
|
|
882
|
+
} else if (this.mode === "hosted") {
|
|
883
|
+
if (!allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
884
|
+
throw new Error("hosted mode requires HASNA_UPTIME_HOSTED_SQLITE_DB on mounted cloud storage; set HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE=1 only for explicit local fallback testing");
|
|
885
|
+
}
|
|
886
|
+
this.dataMode = "hosted-local-sqlite";
|
|
887
|
+
this.dbPath = options.dbPath ?? uptimeHostedFallbackDbPath();
|
|
888
|
+
} else {
|
|
889
|
+
this.dataMode = "local-sqlite";
|
|
890
|
+
this.dbPath = options.dbPath ?? uptimeDbPath();
|
|
867
891
|
}
|
|
868
|
-
|
|
869
|
-
this.dbPath = options.dbPath ?? (this.mode === "hosted" ? uptimeHostedFallbackDbPath() : uptimeDbPath());
|
|
870
|
-
if (this.dbPath !== ":memory:") {
|
|
892
|
+
if (this.dbPath !== ":memory:" && this.dataMode !== "hosted-efs-sqlite") {
|
|
871
893
|
mkdirSync2(dirname(this.dbPath), { recursive: true });
|
|
872
894
|
}
|
|
873
895
|
this.db = new Database(this.dbPath, { create: true });
|
|
874
|
-
this.db.run("PRAGMA journal_mode = WAL");
|
|
896
|
+
this.db.run(this.dataMode === "hosted-efs-sqlite" ? "PRAGMA journal_mode = DELETE" : "PRAGMA journal_mode = WAL");
|
|
897
|
+
this.db.run("PRAGMA busy_timeout = 5000");
|
|
875
898
|
this.db.run("PRAGMA foreign_keys = ON");
|
|
876
899
|
this.migrate();
|
|
877
900
|
}
|
|
@@ -1751,6 +1774,13 @@ function resolveRuntimeMode(mode) {
|
|
|
1751
1774
|
function allowHostedLocalStore(value) {
|
|
1752
1775
|
return value === true || process.env.HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE === "1";
|
|
1753
1776
|
}
|
|
1777
|
+
function isNfsMount(path) {
|
|
1778
|
+
try {
|
|
1779
|
+
return statfsSync(path).type === NFS_SUPER_MAGIC;
|
|
1780
|
+
} catch {
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1754
1784
|
function verifyBackupFile(backupPath) {
|
|
1755
1785
|
const db = new Database(backupPath, { readonly: true });
|
|
1756
1786
|
try {
|
package/dist/cli/index.js
CHANGED
|
@@ -3381,7 +3381,7 @@ function stableJson(value) {
|
|
|
3381
3381
|
}
|
|
3382
3382
|
|
|
3383
3383
|
// src/store.ts
|
|
3384
|
-
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, statSync } from "fs";
|
|
3384
|
+
import { copyFileSync, existsSync, mkdirSync as mkdirSync2, statfsSync, statSync } from "fs";
|
|
3385
3385
|
import { dirname, join as join2 } from "path";
|
|
3386
3386
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3387
3387
|
import { Database } from "bun:sqlite";
|
|
@@ -3406,6 +3406,8 @@ function ensureUptimeHome() {
|
|
|
3406
3406
|
}
|
|
3407
3407
|
|
|
3408
3408
|
// src/store.ts
|
|
3409
|
+
var DEFAULT_HOSTED_SQLITE_DB_PATH = "/data/uptime/uptime.db";
|
|
3410
|
+
var NFS_SUPER_MAGIC = 26985;
|
|
3409
3411
|
var SECRET_URL_PARAM_PATTERN = /(token|secret|password|passwd|api[_-]?key|access[_-]?token|auth|credential|session)/i;
|
|
3410
3412
|
var REQUIRED_TABLES = [
|
|
3411
3413
|
"schema_migrations",
|
|
@@ -3442,18 +3444,39 @@ class UptimeStore {
|
|
|
3442
3444
|
this.mode = resolveRuntimeMode(options.mode ?? "local");
|
|
3443
3445
|
const cloudDatabaseUrl = options.cloudDatabaseUrl ?? process.env.HASNA_UPTIME_DATABASE_URL;
|
|
3444
3446
|
if (this.mode === "hosted" && cloudDatabaseUrl) {
|
|
3445
|
-
throw new Error("hosted
|
|
3447
|
+
throw new Error("hosted Postgres adapter is not implemented yet; use HASNA_UPTIME_HOSTED_SQLITE_DB on cloud-mounted storage for the current hosted deployment path");
|
|
3446
3448
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
+
const hostedSqliteDbPath = options.hostedSqliteDbPath ?? process.env.HASNA_UPTIME_HOSTED_SQLITE_DB;
|
|
3450
|
+
if (this.mode === "hosted" && hostedSqliteDbPath) {
|
|
3451
|
+
if (hostedSqliteDbPath === ":memory:" || !hostedSqliteDbPath.startsWith("/")) {
|
|
3452
|
+
throw new Error("HASNA_UPTIME_HOSTED_SQLITE_DB must be an absolute path on mounted cloud storage");
|
|
3453
|
+
}
|
|
3454
|
+
const approvedHostedPath = hostedSqliteDbPath === DEFAULT_HOSTED_SQLITE_DB_PATH;
|
|
3455
|
+
if (!approvedHostedPath && !allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
3456
|
+
throw new Error(`HASNA_UPTIME_HOSTED_SQLITE_DB must be ${DEFAULT_HOSTED_SQLITE_DB_PATH}; set HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE=1 only for explicit local fallback testing`);
|
|
3457
|
+
}
|
|
3458
|
+
const verifiedCloudMount = approvedHostedPath && isNfsMount(dirname(hostedSqliteDbPath));
|
|
3459
|
+
if (approvedHostedPath && !verifiedCloudMount && !allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
3460
|
+
throw new Error(`${DEFAULT_HOSTED_SQLITE_DB_PATH} must be on a mounted EFS/NFS filesystem; refusing to create hosted task-local SQLite`);
|
|
3461
|
+
}
|
|
3462
|
+
this.dataMode = verifiedCloudMount ? "hosted-efs-sqlite" : "hosted-local-sqlite";
|
|
3463
|
+
this.dbPath = hostedSqliteDbPath;
|
|
3464
|
+
} else if (this.mode === "hosted") {
|
|
3465
|
+
if (!allowHostedLocalStore(options.allowHostedLocalStore)) {
|
|
3466
|
+
throw new Error("hosted mode requires HASNA_UPTIME_HOSTED_SQLITE_DB on mounted cloud storage; set HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE=1 only for explicit local fallback testing");
|
|
3467
|
+
}
|
|
3468
|
+
this.dataMode = "hosted-local-sqlite";
|
|
3469
|
+
this.dbPath = options.dbPath ?? uptimeHostedFallbackDbPath();
|
|
3470
|
+
} else {
|
|
3471
|
+
this.dataMode = "local-sqlite";
|
|
3472
|
+
this.dbPath = options.dbPath ?? uptimeDbPath();
|
|
3449
3473
|
}
|
|
3450
|
-
|
|
3451
|
-
this.dbPath = options.dbPath ?? (this.mode === "hosted" ? uptimeHostedFallbackDbPath() : uptimeDbPath());
|
|
3452
|
-
if (this.dbPath !== ":memory:") {
|
|
3474
|
+
if (this.dbPath !== ":memory:" && this.dataMode !== "hosted-efs-sqlite") {
|
|
3453
3475
|
mkdirSync2(dirname(this.dbPath), { recursive: true });
|
|
3454
3476
|
}
|
|
3455
3477
|
this.db = new Database(this.dbPath, { create: true });
|
|
3456
|
-
this.db.run("PRAGMA journal_mode = WAL");
|
|
3478
|
+
this.db.run(this.dataMode === "hosted-efs-sqlite" ? "PRAGMA journal_mode = DELETE" : "PRAGMA journal_mode = WAL");
|
|
3479
|
+
this.db.run("PRAGMA busy_timeout = 5000");
|
|
3457
3480
|
this.db.run("PRAGMA foreign_keys = ON");
|
|
3458
3481
|
this.migrate();
|
|
3459
3482
|
}
|
|
@@ -4333,6 +4356,13 @@ function resolveRuntimeMode(mode) {
|
|
|
4333
4356
|
function allowHostedLocalStore(value) {
|
|
4334
4357
|
return value === true || process.env.HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE === "1";
|
|
4335
4358
|
}
|
|
4359
|
+
function isNfsMount(path) {
|
|
4360
|
+
try {
|
|
4361
|
+
return statfsSync(path).type === NFS_SUPER_MAGIC;
|
|
4362
|
+
} catch {
|
|
4363
|
+
return false;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4336
4366
|
function verifyBackupFile(backupPath) {
|
|
4337
4367
|
const db = new Database(backupPath, { readonly: true });
|
|
4338
4368
|
try {
|
|
@@ -6388,14 +6418,14 @@ class ApiError extends Error {
|
|
|
6388
6418
|
}
|
|
6389
6419
|
|
|
6390
6420
|
// src/cloud-plan.ts
|
|
6391
|
-
var DEFAULT_ACCOUNT = "
|
|
6421
|
+
var DEFAULT_ACCOUNT = "aws-profile";
|
|
6392
6422
|
var DEFAULT_REGION = "us-east-1";
|
|
6393
6423
|
var DEFAULT_STAGE = "prod";
|
|
6394
6424
|
var DEFAULT_PREFIX = "open-uptime";
|
|
6395
|
-
var DEFAULT_HOSTNAME = "uptime.
|
|
6396
|
-
var DEFAULT_WORKSPACE_ID = "
|
|
6397
|
-
var DEFAULT_VPC_ID = "vpc-
|
|
6398
|
-
var
|
|
6425
|
+
var DEFAULT_HOSTNAME = "uptime.example.com";
|
|
6426
|
+
var DEFAULT_WORKSPACE_ID = "workspace-id";
|
|
6427
|
+
var DEFAULT_VPC_ID = "vpc-xxxxxxxx";
|
|
6428
|
+
var DEFAULT_HOSTED_SQLITE_DB = "/data/uptime/uptime.db";
|
|
6399
6429
|
function buildAwsDeploymentPlan(options = {}) {
|
|
6400
6430
|
const region = clean(options.region, DEFAULT_REGION);
|
|
6401
6431
|
const stage = clean(options.stage, DEFAULT_STAGE);
|
|
@@ -6403,37 +6433,39 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6403
6433
|
const accountName = clean(options.accountName, DEFAULT_ACCOUNT);
|
|
6404
6434
|
const hostname = clean(options.hostname, DEFAULT_HOSTNAME);
|
|
6405
6435
|
const workspaceId = clean(options.workspaceId, DEFAULT_WORKSPACE_ID);
|
|
6406
|
-
const ecrRepository = clean(options.ecrRepository,
|
|
6436
|
+
const ecrRepository = clean(options.ecrRepository, prefix);
|
|
6407
6437
|
const imageRepositoryUri = `<account-id>.dkr.ecr.${region}.amazonaws.com/${ecrRepository}`;
|
|
6408
6438
|
const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
|
|
6409
6439
|
const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
|
|
6440
|
+
const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
|
|
6441
|
+
const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.7");
|
|
6410
6442
|
const cluster = `${prefix}-${stage}`;
|
|
6411
6443
|
const secrets = {
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
reporting: clean(options.reportingSecretName, `hasna/xyz/opensource/uptime/${stage}/reporting`)
|
|
6444
|
+
appEnv: clean(options.appEnvSecretName, `open-uptime/${stage}/app/env`),
|
|
6445
|
+
hostedToken: clean(options.hostedTokenSecretName, `open-uptime/${stage}/hosted-token`),
|
|
6446
|
+
publicProbe: clean(options.publicProbeSecretName, `open-uptime/${stage}/probe/public`),
|
|
6447
|
+
privateProbe: clean(options.privateProbeSecretName, `open-uptime/${stage}/probe/private`),
|
|
6448
|
+
reporting: clean(options.reportingSecretName, `open-uptime/${stage}/reporting`)
|
|
6418
6449
|
};
|
|
6419
6450
|
const services = [
|
|
6420
|
-
servicePlan(prefix, stage, "web",
|
|
6451
|
+
servicePlan(prefix, stage, "web", 1, image, workspaceId, secrets, {
|
|
6421
6452
|
HASNA_UPTIME_MODE: "hosted",
|
|
6453
|
+
HASNA_UPTIME_HOSTED_SQLITE_DB: hostedSqliteDbPath,
|
|
6422
6454
|
HASNA_UPTIME_WORKSPACE_ID: workspaceId,
|
|
6423
6455
|
HASNA_UPTIME_HOSTNAME: hostname
|
|
6424
6456
|
}),
|
|
6425
|
-
servicePlan(prefix, stage, "scheduler",
|
|
6457
|
+
servicePlan(prefix, stage, "scheduler", 0, image, workspaceId, secrets, {
|
|
6426
6458
|
HASNA_UPTIME_MODE: "hosted",
|
|
6427
6459
|
HASNA_UPTIME_WORKSPACE_ID: workspaceId,
|
|
6428
6460
|
HASNA_UPTIME_COMPONENT: "scheduler"
|
|
6429
6461
|
}),
|
|
6430
|
-
servicePlan(prefix, stage, "public-probe",
|
|
6462
|
+
servicePlan(prefix, stage, "public-probe", 0, image, workspaceId, secrets, {
|
|
6431
6463
|
HASNA_UPTIME_MODE: "hosted",
|
|
6432
6464
|
HASNA_UPTIME_WORKSPACE_ID: workspaceId,
|
|
6433
6465
|
HASNA_UPTIME_COMPONENT: "public-probe",
|
|
6434
6466
|
HASNA_UPTIME_PROBE_LOCATION: region
|
|
6435
6467
|
}),
|
|
6436
|
-
servicePlan(prefix, stage, "reporter",
|
|
6468
|
+
servicePlan(prefix, stage, "reporter", 0, image, workspaceId, secrets, {
|
|
6437
6469
|
HASNA_UPTIME_MODE: "hosted",
|
|
6438
6470
|
HASNA_UPTIME_WORKSPACE_ID: workspaceId,
|
|
6439
6471
|
HASNA_UPTIME_COMPONENT: "reporter"
|
|
@@ -6446,7 +6478,7 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6446
6478
|
];
|
|
6447
6479
|
return {
|
|
6448
6480
|
kind: "open-uptime.aws-deployment-plan",
|
|
6449
|
-
version:
|
|
6481
|
+
version: 2,
|
|
6450
6482
|
generatedAt: new Date().toISOString(),
|
|
6451
6483
|
status: "blocked",
|
|
6452
6484
|
canApply: false,
|
|
@@ -6459,10 +6491,13 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6459
6491
|
mode: "hosted",
|
|
6460
6492
|
resources: {
|
|
6461
6493
|
ecrRepository,
|
|
6494
|
+
imageBuilder: `${prefix}-${stage}-image-builder`,
|
|
6462
6495
|
ecsCluster: cluster,
|
|
6463
6496
|
services,
|
|
6464
6497
|
vpcId: clean(options.vpcId, DEFAULT_VPC_ID),
|
|
6465
|
-
|
|
6498
|
+
efsFileSystem: `${prefix}-${stage}-data`,
|
|
6499
|
+
efsAccessPoint: `${prefix}-${stage}-uptime`,
|
|
6500
|
+
hostedSqliteDbPath,
|
|
6466
6501
|
evidenceBucket,
|
|
6467
6502
|
loadBalancer: `${prefix}-${stage}-alb`,
|
|
6468
6503
|
targetGroups: [`${prefix}-${stage}-web-tg`],
|
|
@@ -6472,7 +6507,8 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6472
6507
|
`${prefix}-${stage}-scheduler-sg`,
|
|
6473
6508
|
`${prefix}-${stage}-public-probe-sg`,
|
|
6474
6509
|
`${prefix}-${stage}-reporter-sg`,
|
|
6475
|
-
`${prefix}-${stage}-migration-sg
|
|
6510
|
+
`${prefix}-${stage}-migration-sg`,
|
|
6511
|
+
`${prefix}-${stage}-efs-sg`
|
|
6476
6512
|
],
|
|
6477
6513
|
secrets,
|
|
6478
6514
|
logGroups: services.map((service) => service.logGroup),
|
|
@@ -6484,10 +6520,10 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6484
6520
|
image: {
|
|
6485
6521
|
repository: ecrRepository,
|
|
6486
6522
|
uri: image,
|
|
6487
|
-
dockerfile: "Dockerfile",
|
|
6488
|
-
buildCommand: `
|
|
6523
|
+
dockerfile: "Dockerfile.package",
|
|
6524
|
+
buildCommand: `BLOCKED: after infra approval, AWS CodeBuild builds Dockerfile.package from @hasna/uptime@${runtimePackageVersion} into ${imageRepositoryUri}`,
|
|
6489
6525
|
pushCommands: [
|
|
6490
|
-
|
|
6526
|
+
`BLOCKED: start ${prefix}-${stage}-image-builder only through the approved deploy pipeline after @hasna/uptime@${runtimePackageVersion} is published`,
|
|
6491
6527
|
"BLOCKED: deploy services by immutable image digest, not by mutable tags"
|
|
6492
6528
|
]
|
|
6493
6529
|
},
|
|
@@ -6502,19 +6538,22 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6502
6538
|
runbook: {
|
|
6503
6539
|
preflight: [
|
|
6504
6540
|
`aws sts get-caller-identity --profile ${accountName}`,
|
|
6505
|
-
`aws rds describe-db-instances --db-instance-identifier ${clean(options.rdsInstanceId, DEFAULT_RDS)} --region ${region}`,
|
|
6506
6541
|
`aws ec2 describe-vpcs --vpc-ids ${clean(options.vpcId, DEFAULT_VPC_ID)} --region ${region}`,
|
|
6542
|
+
`aws efs describe-file-systems --region ${region}`,
|
|
6507
6543
|
"Confirm the infra repository and Terraform/CloudFormation owner before live mutation."
|
|
6508
6544
|
],
|
|
6509
6545
|
provision: [
|
|
6510
6546
|
`Infra PR must declare or update ECR repository ${ecrRepository}.`,
|
|
6547
|
+
`Infra PR must declare CodeBuild image builder ${prefix}-${stage}-image-builder for @hasna/uptime@${runtimePackageVersion}.`,
|
|
6511
6548
|
`Infra PR must declare hardened S3 evidence bucket ${evidenceBucket} with KMS, versioning, lifecycle, and public access block.`,
|
|
6549
|
+
`Infra PR must declare encrypted EFS ${prefix}-${stage}-data with access point, mount targets, and AWS Backup plan.`,
|
|
6512
6550
|
`Infra PR must declare ECS/Fargate cluster ${cluster}, ALB, target groups, security groups, IAM roles, CloudWatch log groups, and Secrets Manager refs.`,
|
|
6513
6551
|
"Only apply the infra plan from the approved infrastructure repository after review evidence is attached."
|
|
6514
6552
|
],
|
|
6515
6553
|
deploy: [
|
|
6516
6554
|
"Build and publish the image only after the Dockerfile/container target is reviewed.",
|
|
6517
|
-
|
|
6555
|
+
`Start the AWS image builder for @hasna/uptime@${runtimePackageVersion} and record the pushed image digest.`,
|
|
6556
|
+
"For the EFS SQLite bridge, do not run migration, scheduler, public-probe, or reporter tasks; keep them at desired count 0 until Postgres and cloud leases exist.",
|
|
6518
6557
|
`Register task definitions for ${services.map((service) => service.name).join(", ")} using valueFrom secrets.`,
|
|
6519
6558
|
`Update ECS services in cluster ${cluster} one component at a time through the approved deploy pipeline.`,
|
|
6520
6559
|
`Create Route53/edge record for ${hostname} only after ALB health checks pass and auth denial smokes succeed.`
|
|
@@ -6523,7 +6562,7 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6523
6562
|
"Keep previous task definition ARNs before each service update.",
|
|
6524
6563
|
"Rollback through the approved deploy pipeline to the previously recorded task definition ARNs.",
|
|
6525
6564
|
"Disable scheduler/reporter services before data rollback.",
|
|
6526
|
-
"Restore
|
|
6565
|
+
"Restore EFS backup recovery point only after explicit operator approval and audit record."
|
|
6527
6566
|
],
|
|
6528
6567
|
spark01: [
|
|
6529
6568
|
"Create a private probe identity with a caller-managed public key.",
|
|
@@ -6532,18 +6571,19 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6532
6571
|
]
|
|
6533
6572
|
},
|
|
6534
6573
|
blockers: [
|
|
6535
|
-
"The
|
|
6536
|
-
"
|
|
6574
|
+
"The infrastructure owner repository was not found in this workspace.",
|
|
6575
|
+
"The EFS SQLite bridge is single-writer only: web target desired count is 1 and scheduler/public-probe/reporter targets remain 0 until Postgres and cloud leases exist.",
|
|
6537
6576
|
"Hosted production auth/RBAC must replace broad static hosted-token operation before exposure.",
|
|
6538
6577
|
"Public probe execution still needs DNS, redirect, and rebinding SSRF enforcement plus cloud check-job leases.",
|
|
6539
6578
|
"Spark01 hosted probe enrollment, claim, submit, heartbeat, revocation, and rotation are not cloud-backed yet."
|
|
6540
6579
|
],
|
|
6541
6580
|
requiredEvidence: [
|
|
6542
6581
|
"Infrastructure PR/synth/plan from the approved infra repository.",
|
|
6543
|
-
"
|
|
6582
|
+
"CodeBuild image-builder run, container smoke, and immutable image digest.",
|
|
6544
6583
|
"ECS task definitions using secrets.valueFrom only.",
|
|
6545
6584
|
"ALB/TLS/DNS/auth denial smokes and web alarm checks.",
|
|
6546
|
-
"
|
|
6585
|
+
"Single-writer ECS evidence: one web task maximum and no scheduler/public-probe/reporter EFS mounts.",
|
|
6586
|
+
"EFS encryption, access point, mount-target, AWS Backup, and restore-drill evidence.",
|
|
6547
6587
|
"S3 bucket KMS, versioning, lifecycle, and public-access-block evidence.",
|
|
6548
6588
|
"Spark01 private-probe registration, key-file mode, heartbeat, and revocation evidence."
|
|
6549
6589
|
],
|
|
@@ -6553,7 +6593,9 @@ function buildAwsDeploymentPlan(options = {}) {
|
|
|
6553
6593
|
hostedLocalSqliteAllowed: false,
|
|
6554
6594
|
notes: [
|
|
6555
6595
|
"This plan generator does not call AWS.",
|
|
6556
|
-
"
|
|
6596
|
+
"Blocked plan output intentionally avoids copy-pastable AWS mutation commands.",
|
|
6597
|
+
"Hosted runtime uses explicit EFS-backed SQLite at HASNA_UPTIME_HOSTED_SQLITE_DB until the async Postgres adapter exists.",
|
|
6598
|
+
"Do not set HASNA_UPTIME_DATABASE_URL for hosted tasks until the Postgres adapter is implemented.",
|
|
6557
6599
|
"Secrets are represented as secret names/refs and must be injected with valueFrom.",
|
|
6558
6600
|
"Actual deploy belongs in the deploy_release_operate_final goal node after infra review."
|
|
6559
6601
|
]
|
|
@@ -6645,7 +6687,7 @@ function servicePlan(prefix, stage, role, desiredCount, image, workspaceId, secr
|
|
|
6645
6687
|
HASNA_UPTIME_IMAGE: image,
|
|
6646
6688
|
...environment
|
|
6647
6689
|
},
|
|
6648
|
-
secrets: role === "web" ? {
|
|
6690
|
+
secrets: role === "web" ? { APP_ENV: secrets.appEnv, HASNA_UPTIME_HOSTED_TOKEN: secrets.hostedToken } : role === "public-probe" ? { PROBE_CONFIG: secrets.publicProbe } : role === "reporter" ? { REPORTING_CONFIG: secrets.reporting } : { APP_ENV: secrets.appEnv }
|
|
6649
6691
|
};
|
|
6650
6692
|
}
|
|
6651
6693
|
function clean(value, fallback) {
|
|
@@ -6966,7 +7008,7 @@ program2.command("audit").description("List local audit events").option("--resou
|
|
|
6966
7008
|
}
|
|
6967
7009
|
});
|
|
6968
7010
|
var cloud = program2.command("cloud").description("Generate dry-run cloud deployment and Spark01 configuration artifacts");
|
|
6969
|
-
cloud.command("plan").description("Generate a dry-run AWS deployment plan
|
|
7011
|
+
cloud.command("plan").description("Generate a dry-run AWS deployment plan").option("--account <name>", "AWS account/profile label", "aws-profile").option("--region <region>", "AWS region", "us-east-1").option("--stage <stage>", "deployment stage", "prod").option("--hostname <hostname>", "hosted Open Uptime hostname", "uptime.example.com").option("--workspace-id <id>", "workspace id", "workspace-id").option("--vpc-id <id>", "target VPC id").option("--hosted-sqlite-db <path>", "hosted SQLite path on the EFS mount").option("--rds-instance-id <id>", "deprecated; ignored until the hosted Postgres adapter exists").option("--database-secret-name <name>", "deprecated; ignored until the hosted Postgres adapter exists").option("--ecr-repository <name>", "ECR repository name").option("--image <uri>", "container image URI").option("--runtime-package-version <version>", "published @hasna/uptime version for the AWS image builder").option("--evidence-bucket <name>", "S3 evidence bucket name").option("-j, --json", "print JSON").action((opts) => {
|
|
6970
7012
|
try {
|
|
6971
7013
|
const plan = buildAwsDeploymentPlan({
|
|
6972
7014
|
accountName: opts.account,
|
|
@@ -6975,9 +7017,12 @@ cloud.command("plan").description("Generate a dry-run AWS deployment plan for ha
|
|
|
6975
7017
|
hostname: opts.hostname,
|
|
6976
7018
|
workspaceId: opts.workspaceId,
|
|
6977
7019
|
vpcId: opts.vpcId,
|
|
7020
|
+
hostedSqliteDbPath: opts.hostedSqliteDb,
|
|
6978
7021
|
rdsInstanceId: opts.rdsInstanceId,
|
|
7022
|
+
databaseSecretName: opts.databaseSecretName,
|
|
6979
7023
|
ecrRepository: opts.ecrRepository,
|
|
6980
7024
|
image: opts.image,
|
|
7025
|
+
runtimePackageVersion: opts.runtimePackageVersion,
|
|
6981
7026
|
evidenceBucket: opts.evidenceBucket
|
|
6982
7027
|
});
|
|
6983
7028
|
print(plan, renderCloudPlan(plan), opts);
|
|
@@ -6985,7 +7030,7 @@ cloud.command("plan").description("Generate a dry-run AWS deployment plan for ha
|
|
|
6985
7030
|
fail(error);
|
|
6986
7031
|
}
|
|
6987
7032
|
});
|
|
6988
|
-
cloud.command("spark01-config").description("Generate Spark01 hosted-targeted private probe preflight configuration").option("--api-url <url>", "hosted Open Uptime API URL", "https://uptime.
|
|
7033
|
+
cloud.command("spark01-config").description("Generate Spark01 hosted-targeted private probe preflight configuration").option("--api-url <url>", "hosted Open Uptime API URL", "https://uptime.example.com/api/v1").option("--workspace-id <id>", "workspace id", "workspace-id").option("--probe-id <id>", "cloud registered private probe id").option("--private-key-file <path>", "Spark01 private probe key file", "~/.hasna/uptime/probes/spark01.key.pem").option("--machine-id <id>", "machine id", "spark01").option("--log-level <level>", "probe log level", "info").option("--env", "print shell env file instead of summary text").option("-j, --json", "print JSON").action((opts) => {
|
|
6989
7034
|
try {
|
|
6990
7035
|
const config = buildSpark01CloudConfig({
|
|
6991
7036
|
apiUrl: opts.apiUrl,
|
|
@@ -7185,7 +7230,7 @@ program2.command("restore <backup-path>").description("Restore a verified local
|
|
|
7185
7230
|
fail(error);
|
|
7186
7231
|
}
|
|
7187
7232
|
});
|
|
7188
|
-
program2.command("serve").description("Serve the local API and dashboard").option("--host <host>", "host to bind", "127.0.0.1").option("--port <port>", "port", parseInteger, 3899).option("--check", "run the scheduler while serving").addOption(new Option("--mode <mode>", "runtime mode").choices(["local", "hosted"]).default("local")).option("--api-token <token>", "token required for non-loopback mutation hosts").option("--hosted-token <token>", "scoped hosted-mode token").option("--allow-hosted-local-store", "allow hosted mode to use local SQLite as an explicit fallback").option("--allow-unsafe-remote-mutations", "allow state-changing requests from non-loopback hosts without a token").option("-j, --json", "print JSON").action((opts) => {
|
|
7233
|
+
program2.command("serve").description("Serve the local API and dashboard").option("--host <host>", "host to bind", "127.0.0.1").option("--port <port>", "port", parseInteger, 3899).option("--check", "run the scheduler while serving").addOption(new Option("--mode <mode>", "runtime mode").choices(["local", "hosted"]).default("local")).option("--api-token <token>", "token required for non-loopback mutation hosts").option("--hosted-token <token>", "scoped hosted-mode token").option("--hosted-sqlite-db <path>", "absolute SQLite database path on hosted cloud-mounted storage").option("--allow-hosted-local-store", "allow hosted mode to use local SQLite as an explicit fallback").option("--allow-unsafe-remote-mutations", "allow state-changing requests from non-loopback hosts without a token").option("-j, --json", "print JSON").action((opts) => {
|
|
7189
7234
|
try {
|
|
7190
7235
|
const { server } = serveUptime({
|
|
7191
7236
|
host: opts.host,
|
|
@@ -7194,6 +7239,7 @@ program2.command("serve").description("Serve the local API and dashboard").optio
|
|
|
7194
7239
|
mode: opts.mode,
|
|
7195
7240
|
apiToken: opts.apiToken,
|
|
7196
7241
|
hostedToken: opts.hostedToken,
|
|
7242
|
+
hostedSqliteDbPath: opts.hostedSqliteDb,
|
|
7197
7243
|
allowHostedLocalStore: opts.allowHostedLocalStore,
|
|
7198
7244
|
allowUnsafeRemoteMutations: opts.allowUnsafeRemoteMutations
|
|
7199
7245
|
});
|
|
@@ -7364,10 +7410,12 @@ function renderCloudPlan(plan) {
|
|
|
7364
7410
|
`host: ${plan.hostname}`,
|
|
7365
7411
|
`cluster: ${plan.resources.ecsCluster}`,
|
|
7366
7412
|
`image: ${plan.image.uri}`,
|
|
7413
|
+
`image builder: ${plan.resources.imageBuilder}`,
|
|
7367
7414
|
`dockerfile: ${plan.image.dockerfile}`,
|
|
7368
7415
|
`infra: ${plan.infra.path}`,
|
|
7369
7416
|
`vpc: ${plan.resources.vpcId}`,
|
|
7370
|
-
`
|
|
7417
|
+
`efs: ${plan.resources.efsFileSystem}`,
|
|
7418
|
+
`hosted sqlite: ${plan.resources.hostedSqliteDbPath}`,
|
|
7371
7419
|
`services: ${plan.resources.services.map((service2) => `${service2.name}:${service2.desiredCount}/${service2.targetDesiredCount}`).join(", ")}`,
|
|
7372
7420
|
`evidence bucket: ${plan.resources.evidenceBucket}`,
|
|
7373
7421
|
`blockers: ${plan.blockers.length}`,
|
package/dist/cloud-plan.d.ts
CHANGED
|
@@ -6,12 +6,16 @@ export interface AwsDeploymentPlanOptions {
|
|
|
6
6
|
hostname?: string;
|
|
7
7
|
workspaceId?: string;
|
|
8
8
|
vpcId?: string;
|
|
9
|
-
rdsInstanceId?: string;
|
|
10
9
|
ecrRepository?: string;
|
|
11
10
|
image?: string;
|
|
12
11
|
evidenceBucket?: string;
|
|
13
|
-
|
|
12
|
+
hostedSqliteDbPath?: string;
|
|
13
|
+
runtimePackageVersion?: string;
|
|
14
|
+
/** @deprecated Postgres is target-state only until the async adapter is implemented. */
|
|
15
|
+
rdsInstanceId?: string;
|
|
16
|
+
/** @deprecated Postgres is target-state only until the async adapter is implemented. */
|
|
14
17
|
databaseSecretName?: string;
|
|
18
|
+
hostedTokenSecretName?: string;
|
|
15
19
|
appEnvSecretName?: string;
|
|
16
20
|
publicProbeSecretName?: string;
|
|
17
21
|
privateProbeSecretName?: string;
|
|
@@ -19,7 +23,7 @@ export interface AwsDeploymentPlanOptions {
|
|
|
19
23
|
}
|
|
20
24
|
export interface AwsDeploymentPlan {
|
|
21
25
|
kind: "open-uptime.aws-deployment-plan";
|
|
22
|
-
version:
|
|
26
|
+
version: 2;
|
|
23
27
|
generatedAt: string;
|
|
24
28
|
status: "blocked";
|
|
25
29
|
canApply: false;
|
|
@@ -32,10 +36,13 @@ export interface AwsDeploymentPlan {
|
|
|
32
36
|
mode: "hosted";
|
|
33
37
|
resources: {
|
|
34
38
|
ecrRepository: string;
|
|
39
|
+
imageBuilder: string;
|
|
35
40
|
ecsCluster: string;
|
|
36
41
|
services: AwsServicePlan[];
|
|
37
42
|
vpcId: string;
|
|
38
|
-
|
|
43
|
+
efsFileSystem: string;
|
|
44
|
+
efsAccessPoint: string;
|
|
45
|
+
hostedSqliteDbPath: string;
|
|
39
46
|
evidenceBucket: string;
|
|
40
47
|
loadBalancer: string;
|
|
41
48
|
targetGroups: string[];
|
package/dist/cloud-plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-plan.d.ts","sourceRoot":"","sources":["../src/cloud-plan.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,
|
|
1
|
+
{"version":3,"file":"cloud-plan.d.ts","sourceRoot":"","sources":["../src/cloud-plan.ts"],"names":[],"mappings":"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"}
|