@hasna/uptime 0.1.6 → 0.1.8

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/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 cloud database adapter is not implemented yet");
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
- if (this.mode === "hosted" && !allowHostedLocalStore(options.allowHostedLocalStore)) {
3448
- throw new Error("hosted mode requires a cloud data layer; set HASNA_UPTIME_ALLOW_HOSTED_LOCAL_STORE=1 only for explicit local fallback testing");
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
- this.dataMode = this.mode === "hosted" ? "hosted-local-sqlite" : "local-sqlite";
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 {
@@ -6077,6 +6107,7 @@ function serveUptime(options = {}) {
6077
6107
  apiToken: options.apiToken,
6078
6108
  hostedToken: options.hostedToken,
6079
6109
  hostedTokens: options.hostedTokens,
6110
+ hostedAllowedOrigins: options.hostedAllowedOrigins,
6080
6111
  allowUnsafeRemoteMutations: options.allowUnsafeRemoteMutations,
6081
6112
  trustedLoopback: isLoopbackHost(options.host ?? "127.0.0.1"),
6082
6113
  mode
@@ -6136,13 +6167,23 @@ async function handleHostedRequest(service, request, url, options) {
6136
6167
  const scope = hostedScopeFor(request.method, apiPath);
6137
6168
  requireHostedActor(request, url, options, scope);
6138
6169
  if (["POST", "PATCH", "DELETE"].includes(request.method)) {
6139
- const origin = request.headers.get("origin");
6140
- if (origin && origin !== `${url.protocol}//${url.host}`) {
6141
- throw new ApiError("cross-origin mutation rejected", 403);
6142
- }
6170
+ validateHostedMutationOrigin(request, url, options);
6143
6171
  }
6144
6172
  return handleApiRoute(service, request, url, apiPath, options, true);
6145
6173
  }
6174
+ function validateHostedMutationOrigin(request, url, options) {
6175
+ const rawOrigin = request.headers.get("origin");
6176
+ const origin = normalizeOrigin(rawOrigin);
6177
+ if (rawOrigin && !origin) {
6178
+ throw new ApiError("cross-origin mutation rejected", 403);
6179
+ }
6180
+ if (!origin)
6181
+ return;
6182
+ const allowedOrigins = new Set([`${url.protocol}//${url.host}`, ...resolveHostedAllowedOrigins(options)]);
6183
+ if (!allowedOrigins.has(origin)) {
6184
+ throw new ApiError("cross-origin mutation rejected", 403);
6185
+ }
6186
+ }
6146
6187
  async function handleApiRoute(service, request, url, apiPath, options, hosted) {
6147
6188
  if (request.method === "GET" && apiPath === "/api/summary") {
6148
6189
  return json(service.summary());
@@ -6361,6 +6402,34 @@ function resolveHostedTokens(options) {
6361
6402
  workspaceId: process.env.HASNA_UPTIME_WORKSPACE_ID ?? "default"
6362
6403
  }];
6363
6404
  }
6405
+ function resolveHostedAllowedOrigins(options) {
6406
+ const configured = options.hostedAllowedOrigins ?? splitCsv(process.env.HASNA_UPTIME_ALLOWED_ORIGINS);
6407
+ return configured.map((origin) => normalizeAllowedOrigin(origin)).filter((origin) => Boolean(origin));
6408
+ }
6409
+ function splitCsv(value) {
6410
+ if (!value)
6411
+ return [];
6412
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
6413
+ }
6414
+ function normalizeAllowedOrigin(value) {
6415
+ const origin = normalizeOrigin(value);
6416
+ if (!origin) {
6417
+ throw new ApiError(`invalid hosted allowed origin: ${value}`, 500);
6418
+ }
6419
+ return origin;
6420
+ }
6421
+ function normalizeOrigin(value) {
6422
+ if (!value?.trim())
6423
+ return;
6424
+ try {
6425
+ const parsed = new URL(value.trim());
6426
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
6427
+ return;
6428
+ return `${parsed.protocol}//${parsed.host}`;
6429
+ } catch {
6430
+ return;
6431
+ }
6432
+ }
6364
6433
  function safeTokenEqual(candidate, expected) {
6365
6434
  if (!candidate)
6366
6435
  return false;
@@ -6388,14 +6457,15 @@ class ApiError extends Error {
6388
6457
  }
6389
6458
 
6390
6459
  // src/cloud-plan.ts
6391
- var DEFAULT_ACCOUNT = "hasna-xyz-infra";
6460
+ var DEFAULT_ACCOUNT = "aws-profile";
6392
6461
  var DEFAULT_REGION = "us-east-1";
6393
6462
  var DEFAULT_STAGE = "prod";
6394
6463
  var DEFAULT_PREFIX = "open-uptime";
6395
- var DEFAULT_HOSTNAME = "uptime.hasna.xyz";
6396
- var DEFAULT_WORKSPACE_ID = "wks_2tyysw05cwap";
6397
- var DEFAULT_VPC_ID = "vpc-04c7f7abc1d3c3f56";
6398
- var DEFAULT_RDS = "hasna-xyz-infra-apps-prod-postgres";
6464
+ var DEFAULT_HOSTNAME = "uptime.example.com";
6465
+ var DEFAULT_WORKSPACE_ID = "workspace-id";
6466
+ var DEFAULT_VPC_ID = "vpc-xxxxxxxx";
6467
+ var DEFAULT_HOSTED_SQLITE_DB = "/data/uptime/uptime.db";
6468
+ var DEFAULT_PROTECTED_ACCESS_MODE = "cloudfront_default_domain";
6399
6469
  function buildAwsDeploymentPlan(options = {}) {
6400
6470
  const region = clean(options.region, DEFAULT_REGION);
6401
6471
  const stage = clean(options.stage, DEFAULT_STAGE);
@@ -6403,37 +6473,42 @@ function buildAwsDeploymentPlan(options = {}) {
6403
6473
  const accountName = clean(options.accountName, DEFAULT_ACCOUNT);
6404
6474
  const hostname = clean(options.hostname, DEFAULT_HOSTNAME);
6405
6475
  const workspaceId = clean(options.workspaceId, DEFAULT_WORKSPACE_ID);
6406
- const ecrRepository = clean(options.ecrRepository, `hasna/opensource/${prefix}`);
6476
+ const ecrRepository = clean(options.ecrRepository, prefix);
6407
6477
  const imageRepositoryUri = `<account-id>.dkr.ecr.${region}.amazonaws.com/${ecrRepository}`;
6408
6478
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
6409
6479
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
6480
+ const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
6481
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.8");
6482
+ const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
6483
+ const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
6410
6484
  const cluster = `${prefix}-${stage}`;
6411
6485
  const secrets = {
6412
- database: clean(options.databaseSecretName, `hasna/xyz/opensource/uptime/${stage}/rds`),
6413
- appEnv: clean(options.appEnvSecretName, `hasna/xyz/opensource/uptime/${stage}/app/env`),
6414
- hostedToken: clean(options.hostedTokenSecretName, `hasna/xyz/opensource/uptime/${stage}/hosted-token`),
6415
- publicProbe: clean(options.publicProbeSecretName, `hasna/xyz/opensource/uptime/${stage}/probe/public`),
6416
- privateProbe: clean(options.privateProbeSecretName, `hasna/xyz/opensource/uptime/${stage}/probe/private`),
6417
- reporting: clean(options.reportingSecretName, `hasna/xyz/opensource/uptime/${stage}/reporting`)
6486
+ appEnv: clean(options.appEnvSecretName, `open-uptime/${stage}/app/env`),
6487
+ hostedToken: clean(options.hostedTokenSecretName, `open-uptime/${stage}/hosted-token`),
6488
+ publicProbe: clean(options.publicProbeSecretName, `open-uptime/${stage}/probe/public`),
6489
+ privateProbe: clean(options.privateProbeSecretName, `open-uptime/${stage}/probe/private`),
6490
+ reporting: clean(options.reportingSecretName, `open-uptime/${stage}/reporting`)
6418
6491
  };
6419
6492
  const services = [
6420
- servicePlan(prefix, stage, "web", 2, image, workspaceId, secrets, {
6493
+ servicePlan(prefix, stage, "web", 1, image, workspaceId, secrets, {
6421
6494
  HASNA_UPTIME_MODE: "hosted",
6495
+ HASNA_UPTIME_HOSTED_SQLITE_DB: hostedSqliteDbPath,
6422
6496
  HASNA_UPTIME_WORKSPACE_ID: workspaceId,
6423
- HASNA_UPTIME_HOSTNAME: hostname
6497
+ HASNA_UPTIME_HOSTNAME: hostname,
6498
+ HASNA_UPTIME_ALLOWED_ORIGINS: protectedAccessUrl
6424
6499
  }),
6425
- servicePlan(prefix, stage, "scheduler", 1, image, workspaceId, secrets, {
6500
+ servicePlan(prefix, stage, "scheduler", 0, image, workspaceId, secrets, {
6426
6501
  HASNA_UPTIME_MODE: "hosted",
6427
6502
  HASNA_UPTIME_WORKSPACE_ID: workspaceId,
6428
6503
  HASNA_UPTIME_COMPONENT: "scheduler"
6429
6504
  }),
6430
- servicePlan(prefix, stage, "public-probe", 1, image, workspaceId, secrets, {
6505
+ servicePlan(prefix, stage, "public-probe", 0, image, workspaceId, secrets, {
6431
6506
  HASNA_UPTIME_MODE: "hosted",
6432
6507
  HASNA_UPTIME_WORKSPACE_ID: workspaceId,
6433
6508
  HASNA_UPTIME_COMPONENT: "public-probe",
6434
6509
  HASNA_UPTIME_PROBE_LOCATION: region
6435
6510
  }),
6436
- servicePlan(prefix, stage, "reporter", 1, image, workspaceId, secrets, {
6511
+ servicePlan(prefix, stage, "reporter", 0, image, workspaceId, secrets, {
6437
6512
  HASNA_UPTIME_MODE: "hosted",
6438
6513
  HASNA_UPTIME_WORKSPACE_ID: workspaceId,
6439
6514
  HASNA_UPTIME_COMPONENT: "reporter"
@@ -6446,7 +6521,7 @@ function buildAwsDeploymentPlan(options = {}) {
6446
6521
  ];
6447
6522
  return {
6448
6523
  kind: "open-uptime.aws-deployment-plan",
6449
- version: 1,
6524
+ version: 3,
6450
6525
  generatedAt: new Date().toISOString(),
6451
6526
  status: "blocked",
6452
6527
  canApply: false,
@@ -6459,12 +6534,18 @@ function buildAwsDeploymentPlan(options = {}) {
6459
6534
  mode: "hosted",
6460
6535
  resources: {
6461
6536
  ecrRepository,
6537
+ imageBuilder: `${prefix}-${stage}-image-builder`,
6462
6538
  ecsCluster: cluster,
6463
6539
  services,
6464
6540
  vpcId: clean(options.vpcId, DEFAULT_VPC_ID),
6465
- rdsInstanceId: clean(options.rdsInstanceId, DEFAULT_RDS),
6541
+ efsFileSystem: `${prefix}-${stage}-data`,
6542
+ efsAccessPoint: `${prefix}-${stage}-uptime`,
6543
+ hostedSqliteDbPath,
6466
6544
  evidenceBucket,
6467
6545
  loadBalancer: `${prefix}-${stage}-alb`,
6546
+ protectedAccessMode,
6547
+ edgeDistribution: protectedAccessMode === "cloudfront_default_domain" ? `${prefix}-${stage}-edge` : undefined,
6548
+ protectedAccessUrl,
6468
6549
  targetGroups: [`${prefix}-${stage}-web-tg`],
6469
6550
  securityGroups: [
6470
6551
  `${prefix}-${stage}-alb-sg`,
@@ -6472,7 +6553,8 @@ function buildAwsDeploymentPlan(options = {}) {
6472
6553
  `${prefix}-${stage}-scheduler-sg`,
6473
6554
  `${prefix}-${stage}-public-probe-sg`,
6474
6555
  `${prefix}-${stage}-reporter-sg`,
6475
- `${prefix}-${stage}-migration-sg`
6556
+ `${prefix}-${stage}-migration-sg`,
6557
+ `${prefix}-${stage}-efs-sg`
6476
6558
  ],
6477
6559
  secrets,
6478
6560
  logGroups: services.map((service) => service.logGroup),
@@ -6484,10 +6566,10 @@ function buildAwsDeploymentPlan(options = {}) {
6484
6566
  image: {
6485
6567
  repository: ecrRepository,
6486
6568
  uri: image,
6487
- dockerfile: "Dockerfile",
6488
- buildCommand: `docker build --pull -t ${imageRepositoryUri}:<git-sha> .`,
6569
+ dockerfile: "Dockerfile.package",
6570
+ buildCommand: `BLOCKED: after infra approval, AWS CodeBuild builds Dockerfile.package from @hasna/uptime@${runtimePackageVersion} into ${imageRepositoryUri}`,
6489
6571
  pushCommands: [
6490
- "BLOCKED: push only from approved CI/CD after the ECR repository and image digest policy exist",
6572
+ `BLOCKED: start ${prefix}-${stage}-image-builder only through the approved deploy pipeline after @hasna/uptime@${runtimePackageVersion} is published`,
6491
6573
  "BLOCKED: deploy services by immutable image digest, not by mutable tags"
6492
6574
  ]
6493
6575
  },
@@ -6502,28 +6584,31 @@ function buildAwsDeploymentPlan(options = {}) {
6502
6584
  runbook: {
6503
6585
  preflight: [
6504
6586
  `aws sts get-caller-identity --profile ${accountName}`,
6505
- `aws rds describe-db-instances --db-instance-identifier ${clean(options.rdsInstanceId, DEFAULT_RDS)} --region ${region}`,
6506
6587
  `aws ec2 describe-vpcs --vpc-ids ${clean(options.vpcId, DEFAULT_VPC_ID)} --region ${region}`,
6588
+ `aws efs describe-file-systems --region ${region}`,
6507
6589
  "Confirm the infra repository and Terraform/CloudFormation owner before live mutation."
6508
6590
  ],
6509
6591
  provision: [
6510
6592
  `Infra PR must declare or update ECR repository ${ecrRepository}.`,
6593
+ `Infra PR must declare CodeBuild image builder ${prefix}-${stage}-image-builder for @hasna/uptime@${runtimePackageVersion}.`,
6511
6594
  `Infra PR must declare hardened S3 evidence bucket ${evidenceBucket} with KMS, versioning, lifecycle, and public access block.`,
6512
- `Infra PR must declare ECS/Fargate cluster ${cluster}, ALB, target groups, security groups, IAM roles, CloudWatch log groups, and Secrets Manager refs.`,
6595
+ `Infra PR must declare encrypted EFS ${prefix}-${stage}-data with access point, mount targets, and AWS Backup plan.`,
6596
+ protectedAccessMode === "cloudfront_default_domain" ? "Infra PR must declare CloudFront default-domain HTTPS edge, ALB HTTP listener restricted to CloudFront origin-facing ranges, ECS/Fargate cluster, target groups, security groups, IAM roles, CloudWatch log groups, and Secrets Manager refs." : `Infra PR must declare ECS/Fargate cluster ${cluster}, ALB HTTPS listener, target groups, security groups, IAM roles, CloudWatch log groups, and Secrets Manager refs.`,
6513
6597
  "Only apply the infra plan from the approved infrastructure repository after review evidence is attached."
6514
6598
  ],
6515
6599
  deploy: [
6516
6600
  "Build and publish the image only after the Dockerfile/container target is reviewed.",
6517
- "Run the migration task with the migrator role before web/scheduler/probe services.",
6601
+ `Start the AWS image builder for @hasna/uptime@${runtimePackageVersion} and record the pushed image digest.`,
6602
+ "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
6603
  `Register task definitions for ${services.map((service) => service.name).join(", ")} using valueFrom secrets.`,
6519
6604
  `Update ECS services in cluster ${cluster} one component at a time through the approved deploy pipeline.`,
6520
- `Create Route53/edge record for ${hostname} only after ALB health checks pass and auth denial smokes succeed.`
6605
+ protectedAccessMode === "cloudfront_default_domain" ? "Use the CloudFront default HTTPS domain for first protected access; add custom DNS/certificate only after edge ownership is approved." : `Create Route53/edge record for ${hostname} only after ALB health checks pass and auth denial smokes succeed.`
6521
6606
  ],
6522
6607
  rollback: [
6523
6608
  "Keep previous task definition ARNs before each service update.",
6524
6609
  "Rollback through the approved deploy pipeline to the previously recorded task definition ARNs.",
6525
6610
  "Disable scheduler/reporter services before data rollback.",
6526
- "Restore RDS snapshot only after explicit operator approval and audit record."
6611
+ "Restore EFS backup recovery point only after explicit operator approval and audit record."
6527
6612
  ],
6528
6613
  spark01: [
6529
6614
  "Create a private probe identity with a caller-managed public key.",
@@ -6532,18 +6617,19 @@ function buildAwsDeploymentPlan(options = {}) {
6532
6617
  ]
6533
6618
  },
6534
6619
  blockers: [
6535
- "The hasna-xyz-infra infrastructure owner repository was not found in this workspace.",
6536
- "Hosted Postgres storage adapter and migrations are not implemented.",
6620
+ "The infrastructure owner repository was not found in this workspace.",
6621
+ "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
6622
  "Hosted production auth/RBAC must replace broad static hosted-token operation before exposure.",
6538
6623
  "Public probe execution still needs DNS, redirect, and rebinding SSRF enforcement plus cloud check-job leases.",
6539
6624
  "Spark01 hosted probe enrollment, claim, submit, heartbeat, revocation, and rotation are not cloud-backed yet."
6540
6625
  ],
6541
6626
  requiredEvidence: [
6542
6627
  "Infrastructure PR/synth/plan from the approved infra repository.",
6543
- "Container build smoke and immutable image digest.",
6628
+ "CodeBuild image-builder run, container smoke, and immutable image digest.",
6544
6629
  "ECS task definitions using secrets.valueFrom only.",
6545
- "ALB/TLS/DNS/auth denial smokes and web alarm checks.",
6546
- "RDS TLS, backups/PITR, scoped roles, and migration dry-run evidence.",
6630
+ "CloudFront-default-domain or ALB TLS auth-denial smokes, direct-origin denial evidence, and web alarm checks.",
6631
+ "Single-writer ECS evidence: one web task maximum and no scheduler/public-probe/reporter EFS mounts.",
6632
+ "EFS encryption, access point, mount-target, AWS Backup, and restore-drill evidence.",
6547
6633
  "S3 bucket KMS, versioning, lifecycle, and public-access-block evidence.",
6548
6634
  "Spark01 private-probe registration, key-file mode, heartbeat, and revocation evidence."
6549
6635
  ],
@@ -6553,7 +6639,10 @@ function buildAwsDeploymentPlan(options = {}) {
6553
6639
  hostedLocalSqliteAllowed: false,
6554
6640
  notes: [
6555
6641
  "This plan generator does not call AWS.",
6556
- "Hosted runtime must use Postgres; SQLite remains local/dev fallback only.",
6642
+ "Blocked plan output intentionally avoids copy-pastable AWS mutation commands.",
6643
+ "Default protected access uses CloudFront's HTTPS default domain so first deploy is not blocked on custom DNS or ACM.",
6644
+ "Hosted runtime uses explicit EFS-backed SQLite at HASNA_UPTIME_HOSTED_SQLITE_DB until the async Postgres adapter exists.",
6645
+ "Do not set HASNA_UPTIME_DATABASE_URL for hosted tasks until the Postgres adapter is implemented.",
6557
6646
  "Secrets are represented as secret names/refs and must be injected with valueFrom.",
6558
6647
  "Actual deploy belongs in the deploy_release_operate_final goal node after infra review."
6559
6648
  ]
@@ -6645,7 +6734,7 @@ function servicePlan(prefix, stage, role, desiredCount, image, workspaceId, secr
6645
6734
  HASNA_UPTIME_IMAGE: image,
6646
6735
  ...environment
6647
6736
  },
6648
- secrets: role === "web" ? { HASNA_UPTIME_DATABASE_URL: secrets.database, APP_ENV: secrets.appEnv, HASNA_UPTIME_HOSTED_TOKEN: secrets.hostedToken } : role === "public-probe" ? { PROBE_CONFIG: secrets.publicProbe } : role === "reporter" ? { HASNA_UPTIME_DATABASE_URL: secrets.database, REPORTING_CONFIG: secrets.reporting } : { HASNA_UPTIME_DATABASE_URL: secrets.database, APP_ENV: secrets.appEnv }
6737
+ 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
6738
  };
6650
6739
  }
6651
6740
  function clean(value, fallback) {
@@ -6966,7 +7055,7 @@ program2.command("audit").description("List local audit events").option("--resou
6966
7055
  }
6967
7056
  });
6968
7057
  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 for hasna-xyz-infra").option("--account <name>", "AWS account/profile label", "hasna-xyz-infra").option("--region <region>", "AWS region", "us-east-1").option("--stage <stage>", "deployment stage", "prod").option("--hostname <hostname>", "hosted Open Uptime hostname", "uptime.hasna.xyz").option("--workspace-id <id>", "workspace id", "wks_2tyysw05cwap").option("--vpc-id <id>", "target VPC id").option("--rds-instance-id <id>", "existing RDS instance id").option("--ecr-repository <name>", "ECR repository name").option("--image <uri>", "container image URI").option("--evidence-bucket <name>", "S3 evidence bucket name").option("-j, --json", "print JSON").action((opts) => {
7058
+ 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").addOption(new Option("--protected-access-mode <mode>", "protected web access mode").choices(["cloudfront_default_domain", "alb_https_cert"]).default("cloudfront_default_domain")).option("--evidence-bucket <name>", "S3 evidence bucket name").option("-j, --json", "print JSON").action((opts) => {
6970
7059
  try {
6971
7060
  const plan = buildAwsDeploymentPlan({
6972
7061
  accountName: opts.account,
@@ -6975,9 +7064,13 @@ cloud.command("plan").description("Generate a dry-run AWS deployment plan for ha
6975
7064
  hostname: opts.hostname,
6976
7065
  workspaceId: opts.workspaceId,
6977
7066
  vpcId: opts.vpcId,
7067
+ hostedSqliteDbPath: opts.hostedSqliteDb,
6978
7068
  rdsInstanceId: opts.rdsInstanceId,
7069
+ databaseSecretName: opts.databaseSecretName,
6979
7070
  ecrRepository: opts.ecrRepository,
6980
7071
  image: opts.image,
7072
+ runtimePackageVersion: opts.runtimePackageVersion,
7073
+ protectedAccessMode: opts.protectedAccessMode,
6981
7074
  evidenceBucket: opts.evidenceBucket
6982
7075
  });
6983
7076
  print(plan, renderCloudPlan(plan), opts);
@@ -6985,7 +7078,7 @@ cloud.command("plan").description("Generate a dry-run AWS deployment plan for ha
6985
7078
  fail(error);
6986
7079
  }
6987
7080
  });
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.hasna.xyz/api/v1").option("--workspace-id <id>", "workspace id", "wks_2tyysw05cwap").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) => {
7081
+ 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
7082
  try {
6990
7083
  const config = buildSpark01CloudConfig({
6991
7084
  apiUrl: opts.apiUrl,
@@ -7185,7 +7278,7 @@ program2.command("restore <backup-path>").description("Restore a verified local
7185
7278
  fail(error);
7186
7279
  }
7187
7280
  });
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) => {
7281
+ 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
7282
  try {
7190
7283
  const { server } = serveUptime({
7191
7284
  host: opts.host,
@@ -7194,6 +7287,7 @@ program2.command("serve").description("Serve the local API and dashboard").optio
7194
7287
  mode: opts.mode,
7195
7288
  apiToken: opts.apiToken,
7196
7289
  hostedToken: opts.hostedToken,
7290
+ hostedSqliteDbPath: opts.hostedSqliteDb,
7197
7291
  allowHostedLocalStore: opts.allowHostedLocalStore,
7198
7292
  allowUnsafeRemoteMutations: opts.allowUnsafeRemoteMutations
7199
7293
  });
@@ -7364,10 +7458,13 @@ function renderCloudPlan(plan) {
7364
7458
  `host: ${plan.hostname}`,
7365
7459
  `cluster: ${plan.resources.ecsCluster}`,
7366
7460
  `image: ${plan.image.uri}`,
7461
+ `image builder: ${plan.resources.imageBuilder}`,
7367
7462
  `dockerfile: ${plan.image.dockerfile}`,
7368
7463
  `infra: ${plan.infra.path}`,
7369
7464
  `vpc: ${plan.resources.vpcId}`,
7370
- `rds: ${plan.resources.rdsInstanceId}`,
7465
+ `efs: ${plan.resources.efsFileSystem}`,
7466
+ `hosted sqlite: ${plan.resources.hostedSqliteDbPath}`,
7467
+ `protected access: ${plan.resources.protectedAccessMode} ${plan.resources.protectedAccessUrl}`,
7371
7468
  `services: ${plan.resources.services.map((service2) => `${service2.name}:${service2.desiredCount}/${service2.targetDesiredCount}`).join(", ")}`,
7372
7469
  `evidence bucket: ${plan.resources.evidenceBucket}`,
7373
7470
  `blockers: ${plan.blockers.length}`,
@@ -6,12 +6,17 @@ 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
- hostedTokenSecretName?: string;
12
+ hostedSqliteDbPath?: string;
13
+ runtimePackageVersion?: string;
14
+ protectedAccessMode?: "cloudfront_default_domain" | "alb_https_cert";
15
+ /** @deprecated Postgres is target-state only until the async adapter is implemented. */
16
+ rdsInstanceId?: string;
17
+ /** @deprecated Postgres is target-state only until the async adapter is implemented. */
14
18
  databaseSecretName?: string;
19
+ hostedTokenSecretName?: string;
15
20
  appEnvSecretName?: string;
16
21
  publicProbeSecretName?: string;
17
22
  privateProbeSecretName?: string;
@@ -19,7 +24,7 @@ export interface AwsDeploymentPlanOptions {
19
24
  }
20
25
  export interface AwsDeploymentPlan {
21
26
  kind: "open-uptime.aws-deployment-plan";
22
- version: 1;
27
+ version: 3;
23
28
  generatedAt: string;
24
29
  status: "blocked";
25
30
  canApply: false;
@@ -32,12 +37,18 @@ export interface AwsDeploymentPlan {
32
37
  mode: "hosted";
33
38
  resources: {
34
39
  ecrRepository: string;
40
+ imageBuilder: string;
35
41
  ecsCluster: string;
36
42
  services: AwsServicePlan[];
37
43
  vpcId: string;
38
- rdsInstanceId: string;
44
+ efsFileSystem: string;
45
+ efsAccessPoint: string;
46
+ hostedSqliteDbPath: string;
39
47
  evidenceBucket: string;
40
48
  loadBalancer: string;
49
+ protectedAccessMode: "cloudfront_default_domain" | "alb_https_cert";
50
+ edgeDistribution?: string;
51
+ protectedAccessUrl: string;
41
52
  targetGroups: string[];
42
53
  securityGroups: string[];
43
54
  secrets: Record<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-plan.d.ts","sourceRoot":"","sources":["../src/cloud-plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cloud-plan.d.ts","sourceRoot":"","sources":["../src/cloud-plan.ts"],"names":[],"mappings":"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"}