@hasna/machines 0.0.35 → 0.0.37

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
@@ -59,10 +59,21 @@ machines self-test
59
59
  `machines setup` is a dry-run plan by default. The generated playbook favors
60
60
  idempotent operations (`mkdir -p`, command-existence guards, package-manager
61
61
  installs) and only executes when both `--apply` and `--yes` are provided.
62
+ The default plan also adds update-check/download settings without enabling
63
+ automatic OS installation: Linux uses apt periodic download-only settings, and
64
+ macOS uses `softwareupdate`/`defaults` with `AutomaticallyInstallMacOSUpdates`
65
+ left disabled.
62
66
  `doctor --json` includes public-safe source/ref diagnostics plus optional
63
67
  adapter hook results for secrets, configs, monitors, repos, MCPs, and shield
64
68
  checks. When no adapter is configured, those checks report a skipped fallback
65
69
  instead of importing private dependencies.
70
+ It also reports noninteractive sudo readiness, SSH certificate support, and
71
+ GitHub App secret-reference readiness without printing credentials or private
72
+ keys.
73
+
74
+ Apple device management belongs in the private deployment layer. The public
75
+ setup plan can report enrollment status with `profiles status -type enrollment`,
76
+ but it does not enroll devices, install profiles, or publish team identifiers.
66
77
 
67
78
  ## Topology SDK
68
79
 
@@ -168,6 +179,11 @@ machines defaults to
168
179
  `machines/screen-sharing/screen-<machine>-vnc-password`, or the namespace set in
169
180
  `HASNA_MACHINES_SCREEN_SECRET_NAMESPACE`. The user comes from the manifest
170
181
  (`metadata.user`) when present, or `--user`.
182
+
183
+ For GitHub automation, prefer GitHub App installation tokens over personal user
184
+ tokens. Public manifests and docs should store only opaque secret references
185
+ for the app id/private key material; private adapters or `open-secrets` should
186
+ resolve those references at runtime.
171
187
  `screen-credentials` verifies the resolved user and secret key for a machine or
172
188
  the full fleet without printing secret values.
173
189
 
package/dist/cli/index.js CHANGED
@@ -8386,6 +8386,13 @@ function buildBaseSteps(machine) {
8386
8386
  manager: "apt",
8387
8387
  privileged: true
8388
8388
  });
8389
+ steps.push({
8390
+ id: "linux-update-downloads",
8391
+ title: "Enable Linux package list refresh and download-only upgrades",
8392
+ command: `printf '%s\\n' 'APT::Periodic::Update-Package-Lists "1";' 'APT::Periodic::Download-Upgradeable-Packages "1";' 'APT::Periodic::Unattended-Upgrade "0";' | sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null`,
8393
+ manager: "apt",
8394
+ privileged: true
8395
+ });
8389
8396
  } else if (machine.platform === "macos") {
8390
8397
  steps.push({
8391
8398
  id: "brew-base",
@@ -8399,7 +8406,26 @@ function buildBaseSteps(machine) {
8399
8406
  command: "brew install git coreutils",
8400
8407
  manager: "brew"
8401
8408
  });
8409
+ steps.push({
8410
+ id: "macos-update-downloads",
8411
+ title: "Enable macOS update checks and downloads without automatic install",
8412
+ command: "sudo softwareupdate --schedule on && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -int 1 && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -int 0",
8413
+ manager: "custom",
8414
+ privileged: true
8415
+ });
8416
+ steps.push({
8417
+ id: "macos-management-readiness",
8418
+ title: "Report Apple management readiness without enrolling devices",
8419
+ command: "profiles status -type enrollment 2>/dev/null || true",
8420
+ manager: "custom"
8421
+ });
8402
8422
  }
8423
+ steps.push({
8424
+ id: "github-app-auth-readiness",
8425
+ title: "Check GitHub CLI/App auth readiness without printing credentials",
8426
+ command: "command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 || true",
8427
+ manager: "custom"
8428
+ });
8403
8429
  return steps;
8404
8430
  }
8405
8431
  function buildPackageSteps(machine) {
@@ -10192,7 +10218,12 @@ function buildDoctorCommand() {
10192
10218
  `printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
10193
10219
  `printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
10194
10220
  `printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
10195
- `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
10221
+ `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`,
10222
+ `printf 'sudo_noninteractive=%s\\n' "$(sudo -n true >/dev/null 2>&1 && printf ok || printf unavailable)"`,
10223
+ `printf 'ssh_cert_support=%s\\n' "$(ssh -Q key-cert 2>/dev/null | grep -q 'ssh-ed25519-cert-v01@openssh.com' && printf ok || printf unavailable)"`,
10224
+ `printf 'gh_cli=%s\\n' "$(command -v gh 2>/dev/null || printf missing)"`,
10225
+ `printf 'gh_auth=%s\\n' "$(gh auth status >/dev/null 2>&1 && printf ok || printf unavailable)"`,
10226
+ "printf 'github_app_ref=%s\\n' \"$(test -n \\\"${HASNA_GITHUB_APP_ID:-}\\\" -a -n \\\"${HASNA_GITHUB_APP_PRIVATE_KEY_REF:-}\\\" && printf configured || printf missing)\""
10196
10227
  ].join("; ");
10197
10228
  }
10198
10229
  function fallbackAdapterCheck(domain) {
@@ -10235,14 +10266,20 @@ function runOptionalAdapterChecks(context, adapters) {
10235
10266
  }
10236
10267
  return checks;
10237
10268
  }
10238
- function runDoctor(machineId = getLocalMachineId(), options = {}) {
10269
+ function runDoctor(machineId, options = {}) {
10270
+ const implicitLocalMachine = !machineId;
10271
+ const requestedMachineId = machineId ?? getLocalMachineId();
10272
+ const reportedMachineId = implicitLocalMachine ? "local" : requestedMachineId;
10239
10273
  const now = options.now ?? new Date;
10240
10274
  const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
10241
- const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
10275
+ const commandChecks = runMachineCommand(requestedMachineId, buildDoctorCommand());
10242
10276
  const details = parseKeyValueOutput(commandChecks.stdout);
10243
- const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
10277
+ const machineInManifest = manifest.machines.find((machine) => machine.id === requestedMachineId);
10278
+ const diagnosticMachine = machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null;
10279
+ if (implicitLocalMachine && diagnosticMachine)
10280
+ diagnosticMachine.id = reportedMachineId;
10244
10281
  const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
10245
- machineId,
10282
+ machineId: requestedMachineId,
10246
10283
  manifest,
10247
10284
  manifestSource,
10248
10285
  commandDetails: details,
@@ -10258,10 +10295,10 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
10258
10295
  },
10259
10296
  remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
10260
10297
  }),
10261
- makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(redactManifestForDiagnostics(machineInManifest)) : `No manifest entry for ${machineId}`, {
10298
+ makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", diagnosticMachine ? JSON.stringify(diagnosticMachine) : `No manifest entry for ${reportedMachineId}`, {
10262
10299
  data: {
10263
10300
  declared: Boolean(machineInManifest),
10264
- machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
10301
+ machine: diagnosticMachine
10265
10302
  }
10266
10303
  }),
10267
10304
  makeCheck2("data-dir", details["data_dir_exists"] === "yes" ? "ok" : "warn", "Data directory check", `${redactPath(details["data_dir"] || "unknown")} ${details["data_dir_exists"] === "yes" ? "exists" : "missing"}`, {
@@ -10293,10 +10330,26 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
10293
10330
  makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
10294
10331
  makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
10295
10332
  makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
10333
+ makeCheck2("sudo-noninteractive", details["sudo_noninteractive"] === "ok" ? "ok" : "warn", "Noninteractive sudo availability", details["sudo_noninteractive"] === "ok" ? "sudo -n is available" : "sudo -n unavailable; setup may require user-provided approval or password handling.", {
10334
+ data: { available: details["sudo_noninteractive"] === "ok" },
10335
+ remediation: details["sudo_noninteractive"] === "ok" ? undefined : ["Configure explicit sudo policy or run setup commands manually; do not store sudo passwords in public manifests."]
10336
+ }),
10337
+ makeCheck2("ssh-cert-support", details["ssh_cert_support"] === "ok" ? "ok" : "warn", "SSH certificate support", details["ssh_cert_support"] === "ok" ? "OpenSSH reports ed25519 certificate support" : "OpenSSH certificate support not detected.", {
10338
+ data: { supported: details["ssh_cert_support"] === "ok" },
10339
+ remediation: details["ssh_cert_support"] === "ok" ? undefined : ["Install or update OpenSSH before adopting SSH certificate auth for this machine."]
10340
+ }),
10341
+ makeCheck2("github-app-auth", details["github_app_ref"] === "configured" ? "ok" : "warn", "GitHub App auth references", details["github_app_ref"] === "configured" ? "GitHub App id and private-key reference are configured" : "GitHub App id/private-key reference missing; use secret references, not user tokens or raw private keys.", {
10342
+ data: {
10343
+ gh_cli: details["gh_cli"] && details["gh_cli"] !== "missing",
10344
+ gh_auth: details["gh_auth"] === "ok",
10345
+ app_ref_configured: details["github_app_ref"] === "configured"
10346
+ },
10347
+ remediation: details["github_app_ref"] === "configured" ? undefined : ["Set HASNA_GITHUB_APP_ID plus HASNA_GITHUB_APP_PRIVATE_KEY_REF or provide an equivalent open-secrets adapter."]
10348
+ }),
10296
10349
  ...optionalAdapterChecks
10297
10350
  ];
10298
10351
  return {
10299
- machineId,
10352
+ machineId: reportedMachineId,
10300
10353
  source: commandChecks.source,
10301
10354
  schemaVersion: 1,
10302
10355
  generatedAt: now.toISOString(),
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9F,eAAO,MAAM,+BAA+B,uEAAwE,CAAC;AAErH,MAAM,MAAM,2BAA2B,GAAG,OAAO,+BAA+B,CAAC,MAAM,CAAC,CAAC;AAEzF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,gBAAgB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,WAAW,GAAG,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAElH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAkHD,wBAAgB,SAAS,CAAC,SAAS,SAAsB,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CA0IpG"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9F,eAAO,MAAM,+BAA+B,uEAAwE,CAAC;AAErH,MAAM,MAAM,2BAA2B,GAAG,OAAO,+BAA+B,CAAC,MAAM,CAAC,CAAC;AAEzF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,gBAAgB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,WAAW,GAAG,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAElH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAuHD,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CA6LvF"}
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoD,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,KAAK,EAAmB,WAAW,EAAa,MAAM,aAAa,CAAC;AAsE3E,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAyB9D;AAED,wBAAgB,QAAQ,CACtB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,EAChD,MAAM,GAAE,oBAAwC,GAC/C,WAAW,CAmCb"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoD,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC3G,OAAO,KAAK,EAAmB,WAAW,EAAa,MAAM,aAAa,CAAC;AAiG3E,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAyB9D;AAED,wBAAgB,QAAQ,CACtB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,EAChD,MAAM,GAAE,oBAAwC,GAC/C,WAAW,CAmCb"}
package/dist/index.js CHANGED
@@ -13079,7 +13079,12 @@ function buildDoctorCommand() {
13079
13079
  `printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
13080
13080
  `printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
13081
13081
  `printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
13082
- `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
13082
+ `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`,
13083
+ `printf 'sudo_noninteractive=%s\\n' "$(sudo -n true >/dev/null 2>&1 && printf ok || printf unavailable)"`,
13084
+ `printf 'ssh_cert_support=%s\\n' "$(ssh -Q key-cert 2>/dev/null | grep -q 'ssh-ed25519-cert-v01@openssh.com' && printf ok || printf unavailable)"`,
13085
+ `printf 'gh_cli=%s\\n' "$(command -v gh 2>/dev/null || printf missing)"`,
13086
+ `printf 'gh_auth=%s\\n' "$(gh auth status >/dev/null 2>&1 && printf ok || printf unavailable)"`,
13087
+ "printf 'github_app_ref=%s\\n' \"$(test -n \\\"${HASNA_GITHUB_APP_ID:-}\\\" -a -n \\\"${HASNA_GITHUB_APP_PRIVATE_KEY_REF:-}\\\" && printf configured || printf missing)\""
13083
13088
  ].join("; ");
13084
13089
  }
13085
13090
  function fallbackAdapterCheck(domain) {
@@ -13122,14 +13127,20 @@ function runOptionalAdapterChecks(context, adapters) {
13122
13127
  }
13123
13128
  return checks;
13124
13129
  }
13125
- function runDoctor(machineId = getLocalMachineId(), options = {}) {
13130
+ function runDoctor(machineId, options = {}) {
13131
+ const implicitLocalMachine = !machineId;
13132
+ const requestedMachineId = machineId ?? getLocalMachineId();
13133
+ const reportedMachineId = implicitLocalMachine ? "local" : requestedMachineId;
13126
13134
  const now = options.now ?? new Date;
13127
13135
  const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
13128
- const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
13136
+ const commandChecks = runMachineCommand(requestedMachineId, buildDoctorCommand());
13129
13137
  const details = parseKeyValueOutput(commandChecks.stdout);
13130
- const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
13138
+ const machineInManifest = manifest.machines.find((machine) => machine.id === requestedMachineId);
13139
+ const diagnosticMachine = machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null;
13140
+ if (implicitLocalMachine && diagnosticMachine)
13141
+ diagnosticMachine.id = reportedMachineId;
13131
13142
  const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
13132
- machineId,
13143
+ machineId: requestedMachineId,
13133
13144
  manifest,
13134
13145
  manifestSource,
13135
13146
  commandDetails: details,
@@ -13145,10 +13156,10 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
13145
13156
  },
13146
13157
  remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
13147
13158
  }),
13148
- makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(redactManifestForDiagnostics(machineInManifest)) : `No manifest entry for ${machineId}`, {
13159
+ makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", diagnosticMachine ? JSON.stringify(diagnosticMachine) : `No manifest entry for ${reportedMachineId}`, {
13149
13160
  data: {
13150
13161
  declared: Boolean(machineInManifest),
13151
- machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
13162
+ machine: diagnosticMachine
13152
13163
  }
13153
13164
  }),
13154
13165
  makeCheck2("data-dir", details["data_dir_exists"] === "yes" ? "ok" : "warn", "Data directory check", `${redactPath(details["data_dir"] || "unknown")} ${details["data_dir_exists"] === "yes" ? "exists" : "missing"}`, {
@@ -13180,10 +13191,26 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
13180
13191
  makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
13181
13192
  makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
13182
13193
  makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
13194
+ makeCheck2("sudo-noninteractive", details["sudo_noninteractive"] === "ok" ? "ok" : "warn", "Noninteractive sudo availability", details["sudo_noninteractive"] === "ok" ? "sudo -n is available" : "sudo -n unavailable; setup may require user-provided approval or password handling.", {
13195
+ data: { available: details["sudo_noninteractive"] === "ok" },
13196
+ remediation: details["sudo_noninteractive"] === "ok" ? undefined : ["Configure explicit sudo policy or run setup commands manually; do not store sudo passwords in public manifests."]
13197
+ }),
13198
+ makeCheck2("ssh-cert-support", details["ssh_cert_support"] === "ok" ? "ok" : "warn", "SSH certificate support", details["ssh_cert_support"] === "ok" ? "OpenSSH reports ed25519 certificate support" : "OpenSSH certificate support not detected.", {
13199
+ data: { supported: details["ssh_cert_support"] === "ok" },
13200
+ remediation: details["ssh_cert_support"] === "ok" ? undefined : ["Install or update OpenSSH before adopting SSH certificate auth for this machine."]
13201
+ }),
13202
+ makeCheck2("github-app-auth", details["github_app_ref"] === "configured" ? "ok" : "warn", "GitHub App auth references", details["github_app_ref"] === "configured" ? "GitHub App id and private-key reference are configured" : "GitHub App id/private-key reference missing; use secret references, not user tokens or raw private keys.", {
13203
+ data: {
13204
+ gh_cli: details["gh_cli"] && details["gh_cli"] !== "missing",
13205
+ gh_auth: details["gh_auth"] === "ok",
13206
+ app_ref_configured: details["github_app_ref"] === "configured"
13207
+ },
13208
+ remediation: details["github_app_ref"] === "configured" ? undefined : ["Set HASNA_GITHUB_APP_ID plus HASNA_GITHUB_APP_PRIVATE_KEY_REF or provide an equivalent open-secrets adapter."]
13209
+ }),
13183
13210
  ...optionalAdapterChecks
13184
13211
  ];
13185
13212
  return {
13186
- machineId,
13213
+ machineId: reportedMachineId,
13187
13214
  source: commandChecks.source,
13188
13215
  schemaVersion: 1,
13189
13216
  generatedAt: now.toISOString(),
@@ -14233,6 +14260,13 @@ function buildBaseSteps(machine) {
14233
14260
  manager: "apt",
14234
14261
  privileged: true
14235
14262
  });
14263
+ steps.push({
14264
+ id: "linux-update-downloads",
14265
+ title: "Enable Linux package list refresh and download-only upgrades",
14266
+ command: `printf '%s\\n' 'APT::Periodic::Update-Package-Lists "1";' 'APT::Periodic::Download-Upgradeable-Packages "1";' 'APT::Periodic::Unattended-Upgrade "0";' | sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null`,
14267
+ manager: "apt",
14268
+ privileged: true
14269
+ });
14236
14270
  } else if (machine.platform === "macos") {
14237
14271
  steps.push({
14238
14272
  id: "brew-base",
@@ -14246,7 +14280,26 @@ function buildBaseSteps(machine) {
14246
14280
  command: "brew install git coreutils",
14247
14281
  manager: "brew"
14248
14282
  });
14283
+ steps.push({
14284
+ id: "macos-update-downloads",
14285
+ title: "Enable macOS update checks and downloads without automatic install",
14286
+ command: "sudo softwareupdate --schedule on && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -int 1 && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -int 0",
14287
+ manager: "custom",
14288
+ privileged: true
14289
+ });
14290
+ steps.push({
14291
+ id: "macos-management-readiness",
14292
+ title: "Report Apple management readiness without enrolling devices",
14293
+ command: "profiles status -type enrollment 2>/dev/null || true",
14294
+ manager: "custom"
14295
+ });
14249
14296
  }
14297
+ steps.push({
14298
+ id: "github-app-auth-readiness",
14299
+ title: "Check GitHub CLI/App auth readiness without printing credentials",
14300
+ command: "command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 || true",
14301
+ manager: "custom"
14302
+ });
14250
14303
  return steps;
14251
14304
  }
14252
14305
  function buildPackageSteps(machine) {
package/dist/mcp/index.js CHANGED
@@ -5825,7 +5825,12 @@ function buildDoctorCommand() {
5825
5825
  `printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
5826
5826
  `printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
5827
5827
  `printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
5828
- `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
5828
+ `printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`,
5829
+ `printf 'sudo_noninteractive=%s\\n' "$(sudo -n true >/dev/null 2>&1 && printf ok || printf unavailable)"`,
5830
+ `printf 'ssh_cert_support=%s\\n' "$(ssh -Q key-cert 2>/dev/null | grep -q 'ssh-ed25519-cert-v01@openssh.com' && printf ok || printf unavailable)"`,
5831
+ `printf 'gh_cli=%s\\n' "$(command -v gh 2>/dev/null || printf missing)"`,
5832
+ `printf 'gh_auth=%s\\n' "$(gh auth status >/dev/null 2>&1 && printf ok || printf unavailable)"`,
5833
+ "printf 'github_app_ref=%s\\n' \"$(test -n \\\"${HASNA_GITHUB_APP_ID:-}\\\" -a -n \\\"${HASNA_GITHUB_APP_PRIVATE_KEY_REF:-}\\\" && printf configured || printf missing)\""
5829
5834
  ].join("; ");
5830
5835
  }
5831
5836
  function fallbackAdapterCheck(domain) {
@@ -5868,14 +5873,20 @@ function runOptionalAdapterChecks(context, adapters) {
5868
5873
  }
5869
5874
  return checks;
5870
5875
  }
5871
- function runDoctor(machineId = getLocalMachineId(), options = {}) {
5876
+ function runDoctor(machineId, options = {}) {
5877
+ const implicitLocalMachine = !machineId;
5878
+ const requestedMachineId = machineId ?? getLocalMachineId();
5879
+ const reportedMachineId = implicitLocalMachine ? "local" : requestedMachineId;
5872
5880
  const now = options.now ?? new Date;
5873
5881
  const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
5874
- const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
5882
+ const commandChecks = runMachineCommand(requestedMachineId, buildDoctorCommand());
5875
5883
  const details = parseKeyValueOutput(commandChecks.stdout);
5876
- const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
5884
+ const machineInManifest = manifest.machines.find((machine) => machine.id === requestedMachineId);
5885
+ const diagnosticMachine = machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null;
5886
+ if (implicitLocalMachine && diagnosticMachine)
5887
+ diagnosticMachine.id = reportedMachineId;
5877
5888
  const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
5878
- machineId,
5889
+ machineId: requestedMachineId,
5879
5890
  manifest,
5880
5891
  manifestSource,
5881
5892
  commandDetails: details,
@@ -5891,10 +5902,10 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
5891
5902
  },
5892
5903
  remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
5893
5904
  }),
5894
- makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(redactManifestForDiagnostics(machineInManifest)) : `No manifest entry for ${machineId}`, {
5905
+ makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", diagnosticMachine ? JSON.stringify(diagnosticMachine) : `No manifest entry for ${reportedMachineId}`, {
5895
5906
  data: {
5896
5907
  declared: Boolean(machineInManifest),
5897
- machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
5908
+ machine: diagnosticMachine
5898
5909
  }
5899
5910
  }),
5900
5911
  makeCheck("data-dir", details["data_dir_exists"] === "yes" ? "ok" : "warn", "Data directory check", `${redactPath(details["data_dir"] || "unknown")} ${details["data_dir_exists"] === "yes" ? "exists" : "missing"}`, {
@@ -5926,10 +5937,26 @@ function runDoctor(machineId = getLocalMachineId(), options = {}) {
5926
5937
  makeCheck("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
5927
5938
  makeCheck("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
5928
5939
  makeCheck("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
5940
+ makeCheck("sudo-noninteractive", details["sudo_noninteractive"] === "ok" ? "ok" : "warn", "Noninteractive sudo availability", details["sudo_noninteractive"] === "ok" ? "sudo -n is available" : "sudo -n unavailable; setup may require user-provided approval or password handling.", {
5941
+ data: { available: details["sudo_noninteractive"] === "ok" },
5942
+ remediation: details["sudo_noninteractive"] === "ok" ? undefined : ["Configure explicit sudo policy or run setup commands manually; do not store sudo passwords in public manifests."]
5943
+ }),
5944
+ makeCheck("ssh-cert-support", details["ssh_cert_support"] === "ok" ? "ok" : "warn", "SSH certificate support", details["ssh_cert_support"] === "ok" ? "OpenSSH reports ed25519 certificate support" : "OpenSSH certificate support not detected.", {
5945
+ data: { supported: details["ssh_cert_support"] === "ok" },
5946
+ remediation: details["ssh_cert_support"] === "ok" ? undefined : ["Install or update OpenSSH before adopting SSH certificate auth for this machine."]
5947
+ }),
5948
+ makeCheck("github-app-auth", details["github_app_ref"] === "configured" ? "ok" : "warn", "GitHub App auth references", details["github_app_ref"] === "configured" ? "GitHub App id and private-key reference are configured" : "GitHub App id/private-key reference missing; use secret references, not user tokens or raw private keys.", {
5949
+ data: {
5950
+ gh_cli: details["gh_cli"] && details["gh_cli"] !== "missing",
5951
+ gh_auth: details["gh_auth"] === "ok",
5952
+ app_ref_configured: details["github_app_ref"] === "configured"
5953
+ },
5954
+ remediation: details["github_app_ref"] === "configured" ? undefined : ["Set HASNA_GITHUB_APP_ID plus HASNA_GITHUB_APP_PRIVATE_KEY_REF or provide an equivalent open-secrets adapter."]
5955
+ }),
5929
5956
  ...optionalAdapterChecks
5930
5957
  ];
5931
5958
  return {
5932
- machineId,
5959
+ machineId: reportedMachineId,
5933
5960
  source: commandChecks.source,
5934
5961
  schemaVersion: 1,
5935
5962
  generatedAt: now.toISOString(),
@@ -6720,6 +6747,13 @@ function buildBaseSteps(machine) {
6720
6747
  manager: "apt",
6721
6748
  privileged: true
6722
6749
  });
6750
+ steps.push({
6751
+ id: "linux-update-downloads",
6752
+ title: "Enable Linux package list refresh and download-only upgrades",
6753
+ command: `printf '%s\\n' 'APT::Periodic::Update-Package-Lists "1";' 'APT::Periodic::Download-Upgradeable-Packages "1";' 'APT::Periodic::Unattended-Upgrade "0";' | sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null`,
6754
+ manager: "apt",
6755
+ privileged: true
6756
+ });
6723
6757
  } else if (machine.platform === "macos") {
6724
6758
  steps.push({
6725
6759
  id: "brew-base",
@@ -6733,7 +6767,26 @@ function buildBaseSteps(machine) {
6733
6767
  command: "brew install git coreutils",
6734
6768
  manager: "brew"
6735
6769
  });
6770
+ steps.push({
6771
+ id: "macos-update-downloads",
6772
+ title: "Enable macOS update checks and downloads without automatic install",
6773
+ command: "sudo softwareupdate --schedule on && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -int 1 && sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -int 0",
6774
+ manager: "custom",
6775
+ privileged: true
6776
+ });
6777
+ steps.push({
6778
+ id: "macos-management-readiness",
6779
+ title: "Report Apple management readiness without enrolling devices",
6780
+ command: "profiles status -type enrollment 2>/dev/null || true",
6781
+ manager: "custom"
6782
+ });
6736
6783
  }
6784
+ steps.push({
6785
+ id: "github-app-auth-readiness",
6786
+ title: "Check GitHub CLI/App auth readiness without printing credentials",
6787
+ command: "command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 || true",
6788
+ manager: "custom"
6789
+ });
6737
6790
  return steps;
6738
6791
  }
6739
6792
  function buildPackageSteps(machine) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",