@hasna/machines 0.0.48 → 0.0.49

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/README.md CHANGED
@@ -76,6 +76,8 @@ instead of importing private dependencies.
76
76
  It also reports noninteractive sudo readiness, SSH certificate support, and
77
77
  GitHub App secret-reference readiness without printing credentials or private
78
78
  keys.
79
+ `machines self-test --json` includes `overall` and `counts` fields so agents
80
+ can branch on readiness without scanning every check.
79
81
 
80
82
  Apple device management belongs in the private deployment layer. The public
81
83
  setup plan can report enrollment status with `profiles status -type enrollment`,
@@ -288,6 +290,13 @@ Configure database storage with `HASNA_MACHINES_DATABASE_URL` or fallback
288
290
  `HASNA_MACHINES_STORAGE_MODE` or `MACHINES_STORAGE_MODE` with `local`,
289
291
  `hybrid`, or `remote`.
290
292
 
293
+ Remote PostgreSQL storage is fail-closed for TLS. Non-loopback database hosts
294
+ default to verified TLS, and `sslmode=disable`, `ssl=false`,
295
+ `sslmode=no-verify`, or `HASNA_MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED=0`
296
+ are rejected. For loopback development databases only, set
297
+ `HASNA_MACHINES_ALLOW_INSECURE_DATABASE_TLS=1` to permit disabled or
298
+ non-verified TLS.
299
+
291
300
  ## Fleet daemon
292
301
 
293
302
  `machines-agent` can run as a managed heartbeat daemon. The daemon writes local
@@ -444,6 +453,16 @@ back to the tailnet IP. The local machine is skipped. The managed block is delim
444
453
  by markers, so re-running `apply` only rewrites that block and leaves the rest of
445
454
  `/etc/hosts` untouched.
446
455
 
456
+ ### Direct @hasna/events bins
457
+
458
+ `@hasna/events` is a dependency of `@hasna/machines` and publishes its own
459
+ dependency-owned `events` and `hasna-events` binaries. Package managers may
460
+ install those aliases into an application's top-level `node_modules/.bin`, but
461
+ they are not part of the `@hasna/machines` command surface, release scripts,
462
+ daemon plans, MCP tools, or approval model. Use `machines events` and
463
+ `machines webhooks` for machines-scoped event operations; those commands enforce
464
+ machines mutation approval and bind scoped tokens to canonical arguments.
465
+
447
466
  ## Dashboard
448
467
 
449
468
  ```bash
@@ -992,7 +992,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
992
992
  this._exitCallback = (err) => {
993
993
  if (err.code !== "commander.executeSubCommandAsync") {
994
994
  throw err;
995
- } else {}
995
+ }
996
996
  };
997
997
  }
998
998
  return this;
@@ -12399,6 +12399,8 @@ var defaults = import_lib.default.defaults;
12399
12399
  var esm_default = import_lib.default;
12400
12400
 
12401
12401
  // src/remote-storage.ts
12402
+ var MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV = "HASNA_MACHINES_ALLOW_INSECURE_DATABASE_TLS";
12403
+ var MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED_ENV = "HASNA_MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED";
12402
12404
  function translatePlaceholders(sql) {
12403
12405
  let index = 0;
12404
12406
  return sql.replace(/\?/g, () => `$${++index}`);
@@ -12407,7 +12409,18 @@ function normalizeParams(params) {
12407
12409
  const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
12408
12410
  return flat.map((value) => value === undefined ? null : value);
12409
12411
  }
12410
- function sslConfigFor(connectionString) {
12412
+ function envFlag(env, name) {
12413
+ const value = env[name]?.trim().toLowerCase();
12414
+ return value === "1" || value === "true" || value === "yes" || value === "on";
12415
+ }
12416
+ function isLoopbackHost(hostname4) {
12417
+ const normalized = hostname4.replace(/^\[|\]$/g, "").toLowerCase();
12418
+ return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
12419
+ }
12420
+ function allowsLocalInsecureTls(url, env) {
12421
+ return isLoopbackHost(url.hostname) && envFlag(env, MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV);
12422
+ }
12423
+ function sslConfigFor(connectionString, env = process.env) {
12411
12424
  let url;
12412
12425
  try {
12413
12426
  url = new URL(connectionString);
@@ -12416,12 +12429,21 @@ function sslConfigFor(connectionString) {
12416
12429
  }
12417
12430
  const sslMode = url.searchParams.get("sslmode")?.toLowerCase();
12418
12431
  const ssl = url.searchParams.get("ssl")?.toLowerCase();
12419
- if (sslMode === "disable" || ssl === "false")
12420
- return;
12421
- if (sslMode === "no-verify" || process.env["HASNA_MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED"] === "0") {
12432
+ const rejectUnauthorizedOverride = env[MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED_ENV]?.trim() === "0";
12433
+ if (sslMode === "disable" || ssl === "false") {
12434
+ if (allowsLocalInsecureTls(url, env))
12435
+ return;
12436
+ throw new Error(`Insecure PostgreSQL TLS mode is rejected for remote storage; use sslmode=require or set ${MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV}=1 only for loopback development databases.`);
12437
+ }
12438
+ if (sslMode === "no-verify" || rejectUnauthorizedOverride) {
12439
+ if (!allowsLocalInsecureTls(url, env)) {
12440
+ throw new Error(`PostgreSQL TLS certificate verification cannot be disabled for remote storage; set ${MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV}=1 only for loopback development databases.`);
12441
+ }
12422
12442
  return { rejectUnauthorized: false };
12423
12443
  }
12424
- return sslMode || ssl === "true" ? { rejectUnauthorized: true } : undefined;
12444
+ if (sslMode || ssl === "true")
12445
+ return { rejectUnauthorized: true };
12446
+ return isLoopbackHost(url.hostname) ? undefined : { rejectUnauthorized: true };
12425
12447
  }
12426
12448
 
12427
12449
  class PgAdapterAsync {
@@ -12653,12 +12675,12 @@ function summarizeTailscale(privateMetadata) {
12653
12675
  }
12654
12676
  return sanitizeRecord(summary, privateMetadata);
12655
12677
  }
12656
- function envFlag(name) {
12678
+ function envFlag2(name) {
12657
12679
  const value = process.env[name]?.trim().toLowerCase();
12658
12680
  return value === "1" || value === "true" || value === "yes" || value === "on";
12659
12681
  }
12660
12682
  function shouldCollectDoctorSummary(value) {
12661
- return value ?? envFlag("HASNA_MACHINES_AGENT_DOCTOR_SUMMARY");
12683
+ return value ?? envFlag2("HASNA_MACHINES_AGENT_DOCTOR_SUMMARY");
12662
12684
  }
12663
12685
  function collectDoctorSummary(machineId, enabled) {
12664
12686
  if (!enabled)
@@ -12853,7 +12875,7 @@ function getAgentStatus(machineId, options = {}) {
12853
12875
 
12854
12876
  // src/agent/index.ts
12855
12877
  var program2 = new Command;
12856
- function envFlag2(name) {
12878
+ function envFlag3(name) {
12857
12879
  const value = process.env[name]?.trim().toLowerCase();
12858
12880
  return value === "1" || value === "true" || value === "yes" || value === "on";
12859
12881
  }
@@ -12862,9 +12884,9 @@ var options = program2.parse(process.argv).opts();
12862
12884
  var parsedIntervalSeconds = options.interval ? Number.parseFloat(options.interval) : NaN;
12863
12885
  var parsedIntervalMs = Number.parseInt(options.intervalMs, 10);
12864
12886
  var intervalMs = Number.isFinite(parsedIntervalSeconds) ? Math.max(1, Math.floor(parsedIntervalSeconds * 1000)) : Number.isFinite(parsedIntervalMs) ? Math.max(1, parsedIntervalMs) : 30000;
12865
- var storagePushEnabled = options.storagePush || envFlag2("HASNA_MACHINES_AGENT_STORAGE_PUSH");
12866
- var privateMetadataEnabled = options.privateMetadata || envFlag2("HASNA_MACHINES_PRIVATE_METADATA") || envFlag2("MACHINES_PRIVATE_METADATA");
12867
- var doctorSummaryEnabled = options.doctorSummary || envFlag2("HASNA_MACHINES_AGENT_DOCTOR_SUMMARY");
12887
+ var storagePushEnabled = options.storagePush || envFlag3("HASNA_MACHINES_AGENT_STORAGE_PUSH");
12888
+ var privateMetadataEnabled = options.privateMetadata || envFlag3("HASNA_MACHINES_PRIVATE_METADATA") || envFlag3("MACHINES_PRIVATE_METADATA");
12889
+ var doctorSummaryEnabled = options.doctorSummary || envFlag3("HASNA_MACHINES_AGENT_DOCTOR_SUMMARY");
12868
12890
  var storagePushRetries = Math.max(0, Number.parseInt(options.storagePushRetries, 10) || 0);
12869
12891
  var storagePushBackoffMs = Math.max(0, Number.parseInt(options.storagePushBackoffMs, 10) || 0);
12870
12892
  async function tick() {