@hasna/machines 0.0.34 → 0.0.36
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 +33 -0
- package/dist/cli/index.js +358 -25
- package/dist/commands/doctor.d.ts +23 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/consumer.js +188 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -25
- package/dist/manifests.d.ts +22 -1
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +351 -20
- package/dist/redaction.d.ts +11 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/topology.d.ts.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/consumer-conformance.mjs +3 -2
package/dist/index.js
CHANGED
|
@@ -10982,7 +10982,103 @@ var coerce = {
|
|
|
10982
10982
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
10983
10983
|
};
|
|
10984
10984
|
var NEVER = INVALID;
|
|
10985
|
+
// src/redaction.ts
|
|
10986
|
+
var REDACTED_VALUE = "[redacted]";
|
|
10987
|
+
var SENSITIVE_KEY_PATTERN = /(password|passwd|token|credential|private[_-]?key|privateKey|api[_-]?key|github.*key|pem|secret)/i;
|
|
10988
|
+
var SECRET_REFERENCE_KEY_PATTERN = /(secret(ref(erence)?|key)?|secretRef|secretKey)$/i;
|
|
10989
|
+
var SENSITIVE_VALUE_PATTERNS = [
|
|
10990
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
10991
|
+
/\bghp_[A-Za-z0-9_]{20,}\b/,
|
|
10992
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
10993
|
+
/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/,
|
|
10994
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
10995
|
+
/\bsk-[A-Za-z0-9_-]{20,}\b/
|
|
10996
|
+
];
|
|
10997
|
+
function isSensitiveKey(key) {
|
|
10998
|
+
return SENSITIVE_KEY_PATTERN.test(key);
|
|
10999
|
+
}
|
|
11000
|
+
function isSecretReferenceKey(key) {
|
|
11001
|
+
return SECRET_REFERENCE_KEY_PATTERN.test(key);
|
|
11002
|
+
}
|
|
11003
|
+
function looksSensitiveString(value) {
|
|
11004
|
+
return SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(value));
|
|
11005
|
+
}
|
|
11006
|
+
function isRecord(value) {
|
|
11007
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11008
|
+
}
|
|
11009
|
+
function redactPath(value) {
|
|
11010
|
+
return value.replace(/\/home\/[^/\s]+/g, "/home/<user>").replace(/\/Users\/[^/\s]+/g, "/Users/<user>").replace(/[A-Za-z]:\\Users\\[^\\\s]+/g, "C:\\Users\\<user>");
|
|
11011
|
+
}
|
|
11012
|
+
function redactPrivateRef(value) {
|
|
11013
|
+
const trimmed = value.trim();
|
|
11014
|
+
const scheme = trimmed.match(/^([a-z][a-z0-9+.-]*:\/\/)/i);
|
|
11015
|
+
if (scheme)
|
|
11016
|
+
return `${scheme[1]}<redacted>`;
|
|
11017
|
+
const colon = trimmed.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
11018
|
+
if (colon)
|
|
11019
|
+
return `${colon[1]}:<redacted>`;
|
|
11020
|
+
return "<private-manifest-ref:redacted>";
|
|
11021
|
+
}
|
|
11022
|
+
function redactIdentifier(value) {
|
|
11023
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80) || "adapter";
|
|
11024
|
+
}
|
|
11025
|
+
function redactSensitiveValue(value, key = "") {
|
|
11026
|
+
if (typeof value === "string") {
|
|
11027
|
+
if (isSensitiveKey(key) && !(isSecretReferenceKey(key) && !looksSensitiveString(value))) {
|
|
11028
|
+
return REDACTED_VALUE;
|
|
11029
|
+
}
|
|
11030
|
+
if (looksSensitiveString(value))
|
|
11031
|
+
return REDACTED_VALUE;
|
|
11032
|
+
return redactPath(value);
|
|
11033
|
+
}
|
|
11034
|
+
if (Array.isArray(value)) {
|
|
11035
|
+
return value.map((entry) => redactSensitiveValue(entry, key));
|
|
11036
|
+
}
|
|
11037
|
+
if (isRecord(value)) {
|
|
11038
|
+
const redacted = {};
|
|
11039
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
11040
|
+
redacted[entryKey] = redactSensitiveValue(entryValue, entryKey);
|
|
11041
|
+
}
|
|
11042
|
+
return redacted;
|
|
11043
|
+
}
|
|
11044
|
+
return value;
|
|
11045
|
+
}
|
|
11046
|
+
function publicMetadataKeys(metadata) {
|
|
11047
|
+
return Object.keys(metadata ?? {}).filter((key) => !isSensitiveKey(key)).sort();
|
|
11048
|
+
}
|
|
11049
|
+
function redactMetadata(metadata) {
|
|
11050
|
+
return redactSensitiveValue(metadata ?? {});
|
|
11051
|
+
}
|
|
11052
|
+
function redactManifestForDiagnostics(machine) {
|
|
11053
|
+
const metadata = redactMetadata(machine.metadata);
|
|
11054
|
+
for (const key of ["user", "username", "login"]) {
|
|
11055
|
+
if (typeof metadata[key] === "string")
|
|
11056
|
+
metadata[key] = REDACTED_VALUE;
|
|
11057
|
+
}
|
|
11058
|
+
return {
|
|
11059
|
+
id: machine.id,
|
|
11060
|
+
hostname: machine.hostname ? REDACTED_VALUE : undefined,
|
|
11061
|
+
sshAddress: machine.sshAddress ? REDACTED_VALUE : undefined,
|
|
11062
|
+
tailscaleName: machine.tailscaleName ? REDACTED_VALUE : undefined,
|
|
11063
|
+
platform: machine.platform,
|
|
11064
|
+
connection: machine.connection,
|
|
11065
|
+
workspacePath: redactPath(machine.workspacePath),
|
|
11066
|
+
bunPath: machine.bunPath ? redactPath(machine.bunPath) : undefined,
|
|
11067
|
+
tags: machine.tags ?? [],
|
|
11068
|
+
metadata,
|
|
11069
|
+
packages: machine.packages?.map((pkg) => ({ ...pkg })),
|
|
11070
|
+
apps: machine.apps?.map((app) => ({ ...app })),
|
|
11071
|
+
files: machine.files?.map((file) => ({
|
|
11072
|
+
...file,
|
|
11073
|
+
source: redactPath(file.source),
|
|
11074
|
+
target: redactPath(file.target)
|
|
11075
|
+
}))
|
|
11076
|
+
};
|
|
11077
|
+
}
|
|
11078
|
+
|
|
10985
11079
|
// src/manifests.ts
|
|
11080
|
+
var PRIVATE_MANIFEST_REF_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_REF";
|
|
11081
|
+
var PRIVATE_MANIFEST_BACKEND_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_BACKEND";
|
|
10986
11082
|
var packageSchema = exports_external.object({
|
|
10987
11083
|
name: exports_external.string(),
|
|
10988
11084
|
manager: exports_external.enum(["bun", "brew", "apt", "custom"]).optional(),
|
|
@@ -11035,6 +11131,42 @@ function normalizePlatform() {
|
|
|
11035
11131
|
function normalizeMachines(machines) {
|
|
11036
11132
|
return [...machines].sort((left, right) => left.id.localeCompare(right.id));
|
|
11037
11133
|
}
|
|
11134
|
+
function inferPrivateBackend(rawRef, explicitBackend) {
|
|
11135
|
+
if (explicitBackend?.trim())
|
|
11136
|
+
return explicitBackend.trim();
|
|
11137
|
+
const scheme = rawRef.trim().match(/^([a-z][a-z0-9+.-]*)(?::\/\/|:)/i);
|
|
11138
|
+
return scheme?.[1] ?? null;
|
|
11139
|
+
}
|
|
11140
|
+
function fileSourceRef(path) {
|
|
11141
|
+
return {
|
|
11142
|
+
kind: "file",
|
|
11143
|
+
ref: redactPath(path),
|
|
11144
|
+
backend: "file",
|
|
11145
|
+
private: false,
|
|
11146
|
+
publicSafe: true
|
|
11147
|
+
};
|
|
11148
|
+
}
|
|
11149
|
+
function privateSourceRef(rawRef, backend) {
|
|
11150
|
+
return {
|
|
11151
|
+
kind: "private-ref",
|
|
11152
|
+
ref: redactPrivateRef(rawRef),
|
|
11153
|
+
backend: inferPrivateBackend(rawRef, backend),
|
|
11154
|
+
private: true,
|
|
11155
|
+
publicSafe: true
|
|
11156
|
+
};
|
|
11157
|
+
}
|
|
11158
|
+
function privateRefFromOptions(options) {
|
|
11159
|
+
const env = options.env ?? process.env;
|
|
11160
|
+
return options.privateRef?.trim() || env[PRIVATE_MANIFEST_REF_ENV]?.trim() || env["MACHINES_PRIVATE_MANIFEST_REF"]?.trim() || null;
|
|
11161
|
+
}
|
|
11162
|
+
function getManifestSourceRef(options = {}) {
|
|
11163
|
+
const rawPrivateRef = privateRefFromOptions(options);
|
|
11164
|
+
if (rawPrivateRef) {
|
|
11165
|
+
const env = options.env ?? process.env;
|
|
11166
|
+
return privateSourceRef(rawPrivateRef, options.privateBackend ?? env[PRIVATE_MANIFEST_BACKEND_ENV]);
|
|
11167
|
+
}
|
|
11168
|
+
return fileSourceRef(options.path ?? getManifestPath());
|
|
11169
|
+
}
|
|
11038
11170
|
function getDefaultManifest() {
|
|
11039
11171
|
return {
|
|
11040
11172
|
version: 1,
|
|
@@ -11049,6 +11181,53 @@ function readManifest(path = getManifestPath()) {
|
|
|
11049
11181
|
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
11050
11182
|
return fleetSchema.parse(raw);
|
|
11051
11183
|
}
|
|
11184
|
+
function readManifestWithSource(options = {}) {
|
|
11185
|
+
const path = options.path ?? getManifestPath();
|
|
11186
|
+
const source = getManifestSourceRef(options);
|
|
11187
|
+
const warnings = [];
|
|
11188
|
+
if (source.kind === "private-ref") {
|
|
11189
|
+
const rawRef = privateRefFromOptions(options);
|
|
11190
|
+
if (rawRef && options.adapter) {
|
|
11191
|
+
try {
|
|
11192
|
+
const manifest2 = options.adapter.readManifest({ source, rawRef });
|
|
11193
|
+
if (manifest2) {
|
|
11194
|
+
return {
|
|
11195
|
+
manifest: fleetSchema.parse(manifest2),
|
|
11196
|
+
info: {
|
|
11197
|
+
source,
|
|
11198
|
+
loadedFrom: "private-ref",
|
|
11199
|
+
warnings
|
|
11200
|
+
}
|
|
11201
|
+
};
|
|
11202
|
+
}
|
|
11203
|
+
warnings.push(`private_manifest_adapter_empty:${redactIdentifier(options.adapter.id)}`);
|
|
11204
|
+
} catch (error) {
|
|
11205
|
+
warnings.push(`private_manifest_adapter_failed:${redactIdentifier(options.adapter.id)}`);
|
|
11206
|
+
}
|
|
11207
|
+
} else {
|
|
11208
|
+
warnings.push("private_manifest_ref_without_adapter");
|
|
11209
|
+
}
|
|
11210
|
+
const fallbackSource = fileSourceRef(path);
|
|
11211
|
+
const manifest = readManifest(path);
|
|
11212
|
+
return {
|
|
11213
|
+
manifest,
|
|
11214
|
+
info: {
|
|
11215
|
+
source,
|
|
11216
|
+
loadedFrom: existsSync2(path) ? "fallback" : "default",
|
|
11217
|
+
fallbackSource,
|
|
11218
|
+
warnings
|
|
11219
|
+
}
|
|
11220
|
+
};
|
|
11221
|
+
}
|
|
11222
|
+
return {
|
|
11223
|
+
manifest: readManifest(path),
|
|
11224
|
+
info: {
|
|
11225
|
+
source,
|
|
11226
|
+
loadedFrom: existsSync2(path) ? "file" : "default",
|
|
11227
|
+
warnings
|
|
11228
|
+
}
|
|
11229
|
+
};
|
|
11230
|
+
}
|
|
11052
11231
|
function validateManifest(path = getManifestPath()) {
|
|
11053
11232
|
return readManifest(path);
|
|
11054
11233
|
}
|
|
@@ -11375,7 +11554,7 @@ function buildEntry(input) {
|
|
|
11375
11554
|
},
|
|
11376
11555
|
route_hints: hints,
|
|
11377
11556
|
tags: manifest?.tags ?? [],
|
|
11378
|
-
metadata: manifest?.metadata
|
|
11557
|
+
metadata: redactMetadata(manifest?.metadata)
|
|
11379
11558
|
};
|
|
11380
11559
|
}
|
|
11381
11560
|
function discoverMachineTopology(options = {}) {
|
|
@@ -11644,7 +11823,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11644
11823
|
warnings
|
|
11645
11824
|
};
|
|
11646
11825
|
}
|
|
11647
|
-
function
|
|
11826
|
+
function isRecord2(value) {
|
|
11648
11827
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11649
11828
|
}
|
|
11650
11829
|
function metadataString(metadata, keys) {
|
|
@@ -11674,13 +11853,13 @@ function metadataStringArray(metadata, keys) {
|
|
|
11674
11853
|
function readMappedPath(input) {
|
|
11675
11854
|
for (const containerName of input.containers) {
|
|
11676
11855
|
const container = input.metadata[containerName];
|
|
11677
|
-
if (!
|
|
11856
|
+
if (!isRecord2(container))
|
|
11678
11857
|
continue;
|
|
11679
11858
|
for (const key of input.keys) {
|
|
11680
11859
|
const value = container[key];
|
|
11681
11860
|
if (typeof value === "string" && value.trim())
|
|
11682
11861
|
return value.trim();
|
|
11683
|
-
if (
|
|
11862
|
+
if (isRecord2(value)) {
|
|
11684
11863
|
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
11685
11864
|
if (path)
|
|
11686
11865
|
return path;
|
|
@@ -11934,7 +12113,7 @@ function primaryMachine(machine, projectId, primaryMachineId) {
|
|
|
11934
12113
|
return machine.tags.includes("primary");
|
|
11935
12114
|
}
|
|
11936
12115
|
function metadataKeysForDiagnostics(metadata) {
|
|
11937
|
-
return
|
|
12116
|
+
return publicMetadataKeys(metadata);
|
|
11938
12117
|
}
|
|
11939
12118
|
function resolveMachineWorkspace(options) {
|
|
11940
12119
|
const now = options.now ?? new Date;
|
|
@@ -12859,12 +13038,28 @@ function renderDomainMapping(domain) {
|
|
|
12859
13038
|
};
|
|
12860
13039
|
}
|
|
12861
13040
|
// src/commands/doctor.ts
|
|
12862
|
-
|
|
12863
|
-
|
|
13041
|
+
var DOCTOR_OPTIONAL_ADAPTER_DOMAINS = ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
|
|
13042
|
+
function makeCheck2(id, status, summary, detail, extra = {}) {
|
|
13043
|
+
const { data, ...rest } = extra;
|
|
13044
|
+
return {
|
|
13045
|
+
...rest,
|
|
13046
|
+
id,
|
|
13047
|
+
status,
|
|
13048
|
+
summary,
|
|
13049
|
+
detail,
|
|
13050
|
+
data: data ? redactSensitiveValue(data) : undefined
|
|
13051
|
+
};
|
|
12864
13052
|
}
|
|
12865
13053
|
function parseKeyValueOutput(stdout) {
|
|
12866
|
-
|
|
12867
|
-
|
|
13054
|
+
const result = {};
|
|
13055
|
+
for (const line of stdout.trim().split(`
|
|
13056
|
+
`)) {
|
|
13057
|
+
const index = line.indexOf("=");
|
|
13058
|
+
if (index <= 0)
|
|
13059
|
+
continue;
|
|
13060
|
+
result[line.slice(0, index)] = line.slice(index + 1);
|
|
13061
|
+
}
|
|
13062
|
+
return result;
|
|
12868
13063
|
}
|
|
12869
13064
|
function buildDoctorCommand() {
|
|
12870
13065
|
return [
|
|
@@ -12872,9 +13067,11 @@ function buildDoctorCommand() {
|
|
|
12872
13067
|
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
12873
13068
|
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
12874
13069
|
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
13070
|
+
`printf 'data_dir=%s\\n' "$data_dir"`,
|
|
12875
13071
|
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
12876
13072
|
`printf 'db_path=%s\\n' "$db_path"`,
|
|
12877
13073
|
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
13074
|
+
`printf 'data_dir_exists=%s\\n' "$(test -d "$data_dir" && printf yes || printf no)"`,
|
|
12878
13075
|
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
12879
13076
|
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
12880
13077
|
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
@@ -12882,31 +13079,139 @@ function buildDoctorCommand() {
|
|
|
12882
13079
|
`printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
|
|
12883
13080
|
`printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
|
|
12884
13081
|
`printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
|
|
12885
|
-
`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)\""
|
|
12886
13088
|
].join("; ");
|
|
12887
13089
|
}
|
|
12888
|
-
function
|
|
12889
|
-
|
|
13090
|
+
function fallbackAdapterCheck(domain) {
|
|
13091
|
+
return makeCheck2(`${domain}-adapter`, "ok", `Optional ${domain} adapter`, `No ${domain} adapter configured; skipped optional private integration check.`, {
|
|
13092
|
+
optional: true,
|
|
13093
|
+
source: "open-machines",
|
|
13094
|
+
data: { configured: false, fallback: true }
|
|
13095
|
+
});
|
|
13096
|
+
}
|
|
13097
|
+
function sanitizeAdapterCheck(check, domain, adapterId) {
|
|
13098
|
+
const safeAdapterId = redactIdentifier(adapterId);
|
|
13099
|
+
return makeCheck2(check.id.startsWith(`${domain}-`) || check.id.startsWith(`${domain}:`) ? check.id : `${domain}:${check.id}`, check.status, check.summary, String(redactSensitiveValue(check.detail)), {
|
|
13100
|
+
...check,
|
|
13101
|
+
optional: check.optional ?? true,
|
|
13102
|
+
source: check.source ? String(redactSensitiveValue(check.source)) : `adapter:${safeAdapterId}`,
|
|
13103
|
+
data: check.data ? redactSensitiveValue(check.data) : undefined
|
|
13104
|
+
});
|
|
13105
|
+
}
|
|
13106
|
+
function runOptionalAdapterChecks(context, adapters) {
|
|
13107
|
+
const checks = [];
|
|
13108
|
+
for (const domain of DOCTOR_OPTIONAL_ADAPTER_DOMAINS) {
|
|
13109
|
+
const adapter2 = adapters.find((candidate) => candidate.checks?.[domain]);
|
|
13110
|
+
const hook = adapter2?.checks?.[domain];
|
|
13111
|
+
if (!adapter2 || !hook) {
|
|
13112
|
+
checks.push(fallbackAdapterCheck(domain));
|
|
13113
|
+
continue;
|
|
13114
|
+
}
|
|
13115
|
+
try {
|
|
13116
|
+
const result = hook(context);
|
|
13117
|
+
const domainChecks = Array.isArray(result) ? result : result ? [result] : [fallbackAdapterCheck(domain)];
|
|
13118
|
+
checks.push(...domainChecks.map((check) => sanitizeAdapterCheck(check, domain, adapter2.id)));
|
|
13119
|
+
} catch {
|
|
13120
|
+
const safeAdapterId = redactIdentifier(adapter2.id);
|
|
13121
|
+
checks.push(makeCheck2(`${domain}-adapter`, "warn", `Optional ${domain} adapter failed`, "Adapter failed; details are intentionally hidden to avoid leaking private refs or credentials.", {
|
|
13122
|
+
optional: true,
|
|
13123
|
+
source: `adapter:${safeAdapterId}`,
|
|
13124
|
+
data: { adapter: safeAdapterId, fallback: true }
|
|
13125
|
+
}));
|
|
13126
|
+
}
|
|
13127
|
+
}
|
|
13128
|
+
return checks;
|
|
13129
|
+
}
|
|
13130
|
+
function runDoctor(machineId = getLocalMachineId(), options = {}) {
|
|
13131
|
+
const now = options.now ?? new Date;
|
|
13132
|
+
const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
|
|
12890
13133
|
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
12891
13134
|
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
12892
13135
|
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
13136
|
+
const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
|
|
13137
|
+
machineId,
|
|
13138
|
+
manifest,
|
|
13139
|
+
manifestSource,
|
|
13140
|
+
commandDetails: details,
|
|
13141
|
+
now
|
|
13142
|
+
}, options.adapters ?? []);
|
|
12893
13143
|
const checks = [
|
|
12894
|
-
makeCheck2("manifest-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
13144
|
+
makeCheck2("manifest-source", manifestSource.warnings.length > 0 ? "warn" : "ok", "Manifest source boundary", `${manifestSource.source.kind}:${manifestSource.source.ref} loaded from ${manifestSource.loadedFrom}`, {
|
|
13145
|
+
data: {
|
|
13146
|
+
source: manifestSource.source,
|
|
13147
|
+
loadedFrom: manifestSource.loadedFrom,
|
|
13148
|
+
fallbackSource: manifestSource.fallbackSource,
|
|
13149
|
+
warnings: manifestSource.warnings
|
|
13150
|
+
},
|
|
13151
|
+
remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
|
|
13152
|
+
}),
|
|
13153
|
+
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}`, {
|
|
13154
|
+
data: {
|
|
13155
|
+
declared: Boolean(machineInManifest),
|
|
13156
|
+
machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
|
|
13157
|
+
}
|
|
13158
|
+
}),
|
|
13159
|
+
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"}`, {
|
|
13160
|
+
data: {
|
|
13161
|
+
path: redactPath(details["data_dir"] || "unknown"),
|
|
13162
|
+
exists: details["data_dir_exists"] === "yes"
|
|
13163
|
+
}
|
|
13164
|
+
}),
|
|
13165
|
+
makeCheck2("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${redactPath(details["manifest_path"] || "unknown")} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13166
|
+
data: {
|
|
13167
|
+
path: redactPath(details["manifest_path"] || "unknown"),
|
|
13168
|
+
exists: details["manifest_exists"] === "yes"
|
|
13169
|
+
}
|
|
13170
|
+
}),
|
|
13171
|
+
makeCheck2("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${redactPath(details["db_path"] || "unknown")} ${details["db_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13172
|
+
data: {
|
|
13173
|
+
path: redactPath(details["db_path"] || "unknown"),
|
|
13174
|
+
exists: details["db_exists"] === "yes"
|
|
13175
|
+
}
|
|
13176
|
+
}),
|
|
13177
|
+
makeCheck2("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${redactPath(details["notifications_path"] || "unknown")} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13178
|
+
data: {
|
|
13179
|
+
path: redactPath(details["notifications_path"] || "unknown"),
|
|
13180
|
+
exists: details["notifications_exists"] === "yes"
|
|
13181
|
+
}
|
|
13182
|
+
}),
|
|
12898
13183
|
makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
12899
13184
|
makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
12900
13185
|
makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
12901
13186
|
makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
12902
|
-
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
13187
|
+
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
|
|
13188
|
+
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.", {
|
|
13189
|
+
data: { available: details["sudo_noninteractive"] === "ok" },
|
|
13190
|
+
remediation: details["sudo_noninteractive"] === "ok" ? undefined : ["Configure explicit sudo policy or run setup commands manually; do not store sudo passwords in public manifests."]
|
|
13191
|
+
}),
|
|
13192
|
+
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.", {
|
|
13193
|
+
data: { supported: details["ssh_cert_support"] === "ok" },
|
|
13194
|
+
remediation: details["ssh_cert_support"] === "ok" ? undefined : ["Install or update OpenSSH before adopting SSH certificate auth for this machine."]
|
|
13195
|
+
}),
|
|
13196
|
+
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.", {
|
|
13197
|
+
data: {
|
|
13198
|
+
gh_cli: details["gh_cli"] && details["gh_cli"] !== "missing",
|
|
13199
|
+
gh_auth: details["gh_auth"] === "ok",
|
|
13200
|
+
app_ref_configured: details["github_app_ref"] === "configured"
|
|
13201
|
+
},
|
|
13202
|
+
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."]
|
|
13203
|
+
}),
|
|
13204
|
+
...optionalAdapterChecks
|
|
12903
13205
|
];
|
|
12904
13206
|
return {
|
|
12905
13207
|
machineId,
|
|
12906
13208
|
source: commandChecks.source,
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
13209
|
+
schemaVersion: 1,
|
|
13210
|
+
generatedAt: now.toISOString(),
|
|
13211
|
+
manifestSource,
|
|
13212
|
+
manifestPath: details["manifest_path"] ? redactPath(details["manifest_path"]) : undefined,
|
|
13213
|
+
dbPath: details["db_path"] ? redactPath(details["db_path"]) : undefined,
|
|
13214
|
+
notificationsPath: details["notifications_path"] ? redactPath(details["notifications_path"]) : undefined,
|
|
12910
13215
|
checks
|
|
12911
13216
|
};
|
|
12912
13217
|
}
|
|
@@ -13949,6 +14254,13 @@ function buildBaseSteps(machine) {
|
|
|
13949
14254
|
manager: "apt",
|
|
13950
14255
|
privileged: true
|
|
13951
14256
|
});
|
|
14257
|
+
steps.push({
|
|
14258
|
+
id: "linux-update-downloads",
|
|
14259
|
+
title: "Enable Linux package list refresh and download-only upgrades",
|
|
14260
|
+
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`,
|
|
14261
|
+
manager: "apt",
|
|
14262
|
+
privileged: true
|
|
14263
|
+
});
|
|
13952
14264
|
} else if (machine.platform === "macos") {
|
|
13953
14265
|
steps.push({
|
|
13954
14266
|
id: "brew-base",
|
|
@@ -13962,7 +14274,26 @@ function buildBaseSteps(machine) {
|
|
|
13962
14274
|
command: "brew install git coreutils",
|
|
13963
14275
|
manager: "brew"
|
|
13964
14276
|
});
|
|
14277
|
+
steps.push({
|
|
14278
|
+
id: "macos-update-downloads",
|
|
14279
|
+
title: "Enable macOS update checks and downloads without automatic install",
|
|
14280
|
+
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",
|
|
14281
|
+
manager: "custom",
|
|
14282
|
+
privileged: true
|
|
14283
|
+
});
|
|
14284
|
+
steps.push({
|
|
14285
|
+
id: "macos-management-readiness",
|
|
14286
|
+
title: "Report Apple management readiness without enrolling devices",
|
|
14287
|
+
command: "profiles status -type enrollment 2>/dev/null || true",
|
|
14288
|
+
manager: "custom"
|
|
14289
|
+
});
|
|
13965
14290
|
}
|
|
14291
|
+
steps.push({
|
|
14292
|
+
id: "github-app-auth-readiness",
|
|
14293
|
+
title: "Check GitHub CLI/App auth readiness without printing credentials",
|
|
14294
|
+
command: "command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 || true",
|
|
14295
|
+
manager: "custom"
|
|
14296
|
+
});
|
|
13966
14297
|
return steps;
|
|
13967
14298
|
}
|
|
13968
14299
|
function buildPackageSteps(machine) {
|
|
@@ -14320,20 +14651,20 @@ function runSync(machineId, options = {}, runner = runMachineCommand) {
|
|
|
14320
14651
|
return summary;
|
|
14321
14652
|
}
|
|
14322
14653
|
// src/commands/workspace.ts
|
|
14323
|
-
function
|
|
14654
|
+
function isRecord3(value) {
|
|
14324
14655
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
14325
14656
|
}
|
|
14326
14657
|
function cloneMetadata(metadata) {
|
|
14327
|
-
return
|
|
14658
|
+
return isRecord3(metadata) ? { ...metadata } : {};
|
|
14328
14659
|
}
|
|
14329
14660
|
function mappedPath(metadata, field, key) {
|
|
14330
14661
|
const container = metadata[field];
|
|
14331
|
-
if (!
|
|
14662
|
+
if (!isRecord3(container))
|
|
14332
14663
|
return null;
|
|
14333
14664
|
const value = container[key];
|
|
14334
14665
|
if (typeof value === "string" && value.trim())
|
|
14335
14666
|
return value.trim();
|
|
14336
|
-
if (
|
|
14667
|
+
if (isRecord3(value)) {
|
|
14337
14668
|
const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
|
|
14338
14669
|
if (typeof nested === "string" && nested.trim())
|
|
14339
14670
|
return nested.trim();
|
|
@@ -14342,7 +14673,7 @@ function mappedPath(metadata, field, key) {
|
|
|
14342
14673
|
}
|
|
14343
14674
|
function writeMappedPath(metadata, field, key, path) {
|
|
14344
14675
|
const existing = metadata[field];
|
|
14345
|
-
const container =
|
|
14676
|
+
const container = isRecord3(existing) ? { ...existing } : {};
|
|
14346
14677
|
container[key] = path;
|
|
14347
14678
|
metadata[field] = container;
|
|
14348
14679
|
}
|
|
@@ -23696,10 +24027,18 @@ export {
|
|
|
23696
24027
|
renderDomainMapping,
|
|
23697
24028
|
renderDashboardHtml,
|
|
23698
24029
|
removeNotificationChannel,
|
|
24030
|
+
redactSensitiveValue,
|
|
24031
|
+
redactPrivateRef,
|
|
24032
|
+
redactPath,
|
|
24033
|
+
redactMetadata,
|
|
24034
|
+
redactManifestForDiagnostics,
|
|
24035
|
+
redactIdentifier,
|
|
23699
24036
|
recordSyncRun,
|
|
23700
24037
|
recordSetupRun,
|
|
23701
24038
|
readNotificationConfig,
|
|
24039
|
+
readManifestWithSource,
|
|
23702
24040
|
readManifest,
|
|
24041
|
+
publicMetadataKeys,
|
|
23703
24042
|
probeTmuxPane,
|
|
23704
24043
|
parseStorageTables,
|
|
23705
24044
|
parsePortOutput,
|
|
@@ -23717,6 +24056,7 @@ export {
|
|
|
23717
24056
|
listHeartbeats,
|
|
23718
24057
|
listDomainMappings,
|
|
23719
24058
|
listApps,
|
|
24059
|
+
isSensitiveKey,
|
|
23720
24060
|
getSyncMetaAll,
|
|
23721
24061
|
getStorageStatus,
|
|
23722
24062
|
getStoragePg,
|
|
@@ -23728,6 +24068,7 @@ export {
|
|
|
23728
24068
|
getServeInfo,
|
|
23729
24069
|
getPackageVersion,
|
|
23730
24070
|
getNotificationsPath,
|
|
24071
|
+
getManifestSourceRef,
|
|
23731
24072
|
getManifestPath,
|
|
23732
24073
|
getManifestMachine,
|
|
23733
24074
|
getMachinesConsumerCapabilities,
|
|
@@ -23779,6 +24120,9 @@ export {
|
|
|
23779
24120
|
STORAGE_MODE_ENV,
|
|
23780
24121
|
STORAGE_DATABASE_ENV,
|
|
23781
24122
|
SCREEN_SECRET_NAMESPACE_ENV,
|
|
24123
|
+
REDACTED_VALUE,
|
|
24124
|
+
PRIVATE_MANIFEST_REF_ENV,
|
|
24125
|
+
PRIVATE_MANIFEST_BACKEND_ENV,
|
|
23782
24126
|
MACHINE_MCP_TOOL_NAMES,
|
|
23783
24127
|
MACHINES_STORAGE_TABLES,
|
|
23784
24128
|
MACHINES_STORAGE_MODE_FALLBACK_ENV,
|
|
@@ -23797,6 +24141,7 @@ export {
|
|
|
23797
24141
|
MACHINES_BACKUP_PREFIX_ENV,
|
|
23798
24142
|
MACHINES_BACKUP_BUCKET_FALLBACK_ENV,
|
|
23799
24143
|
MACHINES_BACKUP_BUCKET_ENV,
|
|
24144
|
+
DOCTOR_OPTIONAL_ADAPTER_DOMAINS,
|
|
23800
24145
|
DEFAULT_SCREEN_SECRET_NAMESPACE,
|
|
23801
24146
|
DEFAULT_MACHINE_RESOLVER_TTL_MS,
|
|
23802
24147
|
DEFAULT_BACKUP_PREFIX,
|
package/dist/manifests.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { FleetManifest, MachineManifest } from "./types.js";
|
|
2
|
+
import type { FleetManifest, MachineManifest, ManifestLoadInfo, ManifestSourceRef } from "./types.js";
|
|
3
|
+
export declare const PRIVATE_MANIFEST_REF_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_REF";
|
|
4
|
+
export declare const PRIVATE_MANIFEST_BACKEND_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_BACKEND";
|
|
5
|
+
export interface ManifestSourceAdapter {
|
|
6
|
+
id: string;
|
|
7
|
+
readManifest(input: {
|
|
8
|
+
source: ManifestSourceRef;
|
|
9
|
+
rawRef: string;
|
|
10
|
+
}): FleetManifest | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface ReadManifestWithSourceOptions {
|
|
13
|
+
path?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
privateRef?: string;
|
|
16
|
+
privateBackend?: string;
|
|
17
|
+
adapter?: ManifestSourceAdapter | null;
|
|
18
|
+
}
|
|
3
19
|
export declare const machineSchema: z.ZodObject<{
|
|
4
20
|
id: z.ZodString;
|
|
5
21
|
hostname: z.ZodOptional<z.ZodString>;
|
|
@@ -270,8 +286,13 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
270
286
|
version: 1;
|
|
271
287
|
generatedAt?: string | undefined;
|
|
272
288
|
}>;
|
|
289
|
+
export declare function getManifestSourceRef(options?: ReadManifestWithSourceOptions): ManifestSourceRef;
|
|
273
290
|
export declare function getDefaultManifest(): FleetManifest;
|
|
274
291
|
export declare function readManifest(path?: string): FleetManifest;
|
|
292
|
+
export declare function readManifestWithSource(options?: ReadManifestWithSourceOptions): {
|
|
293
|
+
manifest: FleetManifest;
|
|
294
|
+
info: ManifestLoadInfo;
|
|
295
|
+
};
|
|
275
296
|
export declare function validateManifest(path?: string): FleetManifest;
|
|
276
297
|
export declare function writeManifest(manifest: FleetManifest, path?: string): string;
|
|
277
298
|
export declare function getManifestMachine(machineId: string, path?: string): MachineManifest | null;
|
package/dist/manifests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEtG,eAAO,MAAM,wBAAwB,wCAAwC,CAAC;AAC9E,eAAO,MAAM,4BAA4B,4CAA4C,CAAC;AAEtF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC;CACtG;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACxC;AAoBD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAItB,CAAC;AAsDH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,6BAAkC,GAAG,iBAAiB,CAOnG;AAED,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD;AAED,wBAAgB,YAAY,CAAC,IAAI,SAAoB,GAAG,aAAa,CAMpE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,6BAAkC,GAAG;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAiDvI;AAED,wBAAgB,gBAAgB,CAAC,IAAI,SAAoB,GAAG,aAAa,CAExE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,SAAoB,GAAG,MAAM,CAWvF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,eAAe,GAAG,IAAI,CAEtG;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAiB9D"}
|