@ours.network/install 1.2.1-nightly.24 → 1.2.1-nightly.25
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 +1 -1
- package/assets/Dockerfile +1 -0
- package/assets/release-lock.json +4 -4
- package/assets/release.json +3 -3
- package/assets/scripts/runtime/client-setup.mjs +20 -2
- package/assets/scripts/runtime/diagnostics.mjs +72 -0
- package/assets/sources.json +4 -4
- package/install.mjs +2 -1
- package/lib/diagnostics.mjs +1 -0
- package/lib/docker-runtime-repair.mjs +14 -6
- package/lib/effects.mjs +15 -6
- package/lib/orchestrate.mjs +2 -6
- package/lib/server-onboarding.mjs +1 -1
- package/lib/setup.mjs +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -591,4 +591,4 @@ Existing installation records keep their ports and identities. A listener acquir
|
|
|
591
591
|
by another process after preflight still causes startup to fail safely; no foreign
|
|
592
592
|
process is stopped and no retained installation is silently moved.
|
|
593
593
|
|
|
594
|
-
Workspace setup pins Fleet `1.2.0-nightly.
|
|
594
|
+
Workspace setup pins Fleet `1.2.0-nightly.32` with its recorded registry integrity. This release includes the packaged `/fleet` browser entry, separate `ours-fleet setup-tunnel` and `ours-fleet link-device` commands, and persists the available Fleet loopback port used by the workspace tunnel. Existing installation identities and configured ports remain retained; a retained-port collision fails safely.
|
package/assets/Dockerfile
CHANGED
|
@@ -23,6 +23,7 @@ COPY --chmod=644 scripts/runtime/runtime-common.mjs /opt/ours/docker/runtime-com
|
|
|
23
23
|
COPY --chmod=644 scripts/runtime/check-client.mjs /opt/ours/docker/check-client.mjs
|
|
24
24
|
COPY --chmod=644 scripts/runtime/check-start.mjs /opt/ours/docker/check-start.mjs
|
|
25
25
|
COPY --chmod=644 scripts/runtime/client-setup.mjs /opt/ours/docker/client-setup.mjs
|
|
26
|
+
COPY --chmod=644 scripts/runtime/diagnostics.mjs /opt/ours/docker/diagnostics.mjs
|
|
26
27
|
COPY --chmod=644 scripts/runtime/legacy-import.mjs /opt/ours/docker/legacy-import.mjs
|
|
27
28
|
COPY --chmod=644 scripts/runtime/entrypoint.sh /opt/ours/docker/entrypoint.sh
|
|
28
29
|
COPY --chmod=644 scripts/runtime/healthcheck.mjs /opt/ours/docker/healthcheck.mjs
|
package/assets/release-lock.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@ours.network/codex": "1.2.0-nightly.10",
|
|
14
14
|
"@ours.network/cowork": "1.3.3-nightly.20261001.6d49d3a",
|
|
15
15
|
"@ours.network/daemon": "3.8.1-nightly.7",
|
|
16
|
-
"@ours.network/fleet": "1.2.0-nightly.
|
|
16
|
+
"@ours.network/fleet": "1.2.0-nightly.32",
|
|
17
17
|
"@ours.network/mcp": "1.2.0-nightly.10",
|
|
18
18
|
"@ours.network/messenger-server": "1.0.31-nightly.13",
|
|
19
19
|
"@ours.network/sdk": "3.8.1-nightly.15",
|
|
@@ -1085,9 +1085,9 @@
|
|
|
1085
1085
|
}
|
|
1086
1086
|
},
|
|
1087
1087
|
"node_modules/@ours.network/fleet": {
|
|
1088
|
-
"version": "1.2.0-nightly.
|
|
1089
|
-
"resolved": "https://registry.npmjs.org/@ours.network/fleet/-/fleet-1.2.0-nightly.
|
|
1090
|
-
"integrity": "sha512-
|
|
1088
|
+
"version": "1.2.0-nightly.32",
|
|
1089
|
+
"resolved": "https://registry.npmjs.org/@ours.network/fleet/-/fleet-1.2.0-nightly.32.tgz",
|
|
1090
|
+
"integrity": "sha512-FZRE+G25dc2qgDYbM3Dx76lw1Sws5OTF5KHPaMGTL5oun2SgYcNMhVKvwULZWSOlyrpggdMc3IFmp4eZSZTahg==",
|
|
1091
1091
|
"license": "FSL-1.1-Apache-2.0",
|
|
1092
1092
|
"dependencies": {
|
|
1093
1093
|
"@agentclientprotocol/sdk": "^1.3.0",
|
package/assets/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"channel": "nightly",
|
|
4
|
-
"installerVersion": "1.2.1-nightly.
|
|
4
|
+
"installerVersion": "1.2.1-nightly.25",
|
|
5
5
|
"packages": {
|
|
6
6
|
"@ours.network/sdk": {
|
|
7
7
|
"version": "3.8.1-nightly.15",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"integrity": "sha512-hDosXHgQ+vyzPdSxqTz6vCaOCn0LMTmOL4TJiWplkJhzyw2c586RckXJbqNJsTa0mmodvLxW0ewZyQ/xJVIeXA=="
|
|
25
25
|
},
|
|
26
26
|
"@ours.network/fleet": {
|
|
27
|
-
"version": "1.2.0-nightly.
|
|
28
|
-
"integrity": "sha512-
|
|
27
|
+
"version": "1.2.0-nightly.32",
|
|
28
|
+
"integrity": "sha512-FZRE+G25dc2qgDYbM3Dx76lw1Sws5OTF5KHPaMGTL5oun2SgYcNMhVKvwULZWSOlyrpggdMc3IFmp4eZSZTahg=="
|
|
29
29
|
},
|
|
30
30
|
"@ours.network/mcp": {
|
|
31
31
|
"version": "1.2.0-nightly.10",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBuildRecords, initializeBuildMarker } from '../maintenance/build-context.mjs';
|
|
2
|
+
import { redactDiagnostic } from './diagnostics.mjs';
|
|
2
3
|
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { randomBytes } from 'node:crypto';
|
|
4
5
|
import {
|
|
@@ -12,6 +13,7 @@ const DELIVERY_FILES = [
|
|
|
12
13
|
'/credentials/messenger/daemon-token',
|
|
13
14
|
];
|
|
14
15
|
const fail = (message) => { throw new Error(message); };
|
|
16
|
+
let diagnosticStage = 'validation';
|
|
15
17
|
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
16
18
|
|
|
17
19
|
function privatePath(path, directory, exact = directory ? 0o700 : 0o600) {
|
|
@@ -128,10 +130,13 @@ function prepare() {
|
|
|
128
130
|
|
|
129
131
|
function finishDaemonSetup() {
|
|
130
132
|
const state = '/var/lib/ours';
|
|
133
|
+
diagnosticStage = 'daemon-provenance';
|
|
131
134
|
initializeBuildMarker(`${state}/.ours-provenance`, readBuildRecords('/opt/ours'));
|
|
135
|
+
diagnosticStage = 'mcp-directory';
|
|
132
136
|
privatePath('/var/lib/ours-mcp', true);
|
|
133
137
|
const mcpProfile = { endpoint: 'http://127.0.0.1:3050', expectedInstanceId: process.env.OURS_DAEMON_ID, credentialPath: `${state}/daemon-token` };
|
|
134
138
|
const mcpPath = '/var/lib/ours-mcp/profile.json';
|
|
139
|
+
diagnosticStage = 'mcp-profile';
|
|
135
140
|
if (!existingPrivate(mcpPath, false)) atomicJson(mcpPath, mcpProfile);
|
|
136
141
|
else if (JSON.stringify(jsonObject(mcpPath)) !== JSON.stringify(mcpProfile)) fail('Existing MCP profile differs from the selected daemon');
|
|
137
142
|
}
|
|
@@ -155,10 +160,19 @@ async function telegramInput() {
|
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
function cliJson(args) {
|
|
163
|
+
diagnosticStage = 'official-cli';
|
|
158
164
|
const result = spawnSync(process.execPath, [existsSync('/opt/ours/node_modules/@ours.network/daemon/dist/cli.js') ? '/opt/ours/node_modules/@ours.network/daemon/dist/cli.js' : '/opt/ours/node_modules/@ours.network/cli/dist/cli.js', ...args], {
|
|
159
165
|
encoding: 'utf8', timeout: 60_000, maxBuffer: 1024 * 1024,
|
|
160
166
|
});
|
|
161
|
-
if (result.status !== 0)
|
|
167
|
+
if (result.error || result.status !== 0) {
|
|
168
|
+
// --json writes failures on stderr. Successful credential stdout is never
|
|
169
|
+
// forwarded, even if a later operation exits unsuccessfully.
|
|
170
|
+
let reason = 'CLI supplied no structured error reason';
|
|
171
|
+
for (const line of String(result.stderr ?? '').split('\n').reverse()) {
|
|
172
|
+
try { const value = JSON.parse(line); if (typeof value?.error?.message === 'string') { reason = value.error.message; break; } } catch {}
|
|
173
|
+
}
|
|
174
|
+
fail(`Official OURS CLI operation failed (${args[1]}; exit=${result.status ?? 'none'}; signal=${result.signal ?? 'none'}; system=${result.error?.code ?? 'none'}): ${reason}`);
|
|
175
|
+
}
|
|
162
176
|
try { return JSON.parse(result.stdout); }
|
|
163
177
|
catch { fail('Official OURS CLI returned malformed JSON'); }
|
|
164
178
|
}
|
|
@@ -192,6 +206,10 @@ try {
|
|
|
192
206
|
else if (['access-init', 'access-issue', 'access-replace'].includes(operation)) access(operation, process.argv[3]);
|
|
193
207
|
else fail('usage: client-setup.mjs prepare | telegram-input | access-init | access-issue [OUTPUT] | access-replace');
|
|
194
208
|
} catch (error) {
|
|
195
|
-
|
|
209
|
+
// Structured stderr retains the failing stage across sensitive Compose
|
|
210
|
+
// capture. The installer redacts secrets before exposing this envelope.
|
|
211
|
+
const message = redactDiagnostic(error.message);
|
|
212
|
+
console.error(JSON.stringify({ oursInstallerError: { stage: diagnosticStage, message } }));
|
|
213
|
+
console.error(`OURS client setup refused: ${message}`);
|
|
196
214
|
process.exit(1);
|
|
197
215
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Retain actionable failure details without emitting credential output. */
|
|
2
|
+
export function redactDiagnostic(value, env = process.env) {
|
|
3
|
+
let text = String(value ?? '').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '').replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
4
|
+
for (const [key, secret] of Object.entries(env)) {
|
|
5
|
+
if (/(?:token|password|secret|api.?key|private.?key|invitation|enrollment|authorization|credential|access.?token|refresh.?token)/i.test(key) && typeof secret === 'string' && secret)
|
|
6
|
+
text = text.split(secret).join('[redacted]');
|
|
7
|
+
}
|
|
8
|
+
return text
|
|
9
|
+
.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/g, '[redacted private key]')
|
|
10
|
+
.replace(/(Bearer\s+)[^\s"',;]+/gi, '$1[redacted]')
|
|
11
|
+
.replace(/((?:["']?(?:api[-_]?token|token|password|secret|api[-_]?key|private[-_]?key|invitation|enrollment|authorization|credential|access.?token|refresh.?token)["']?)\s*[:=]\s*)(?:"[^"\n]*"|'[^'\n]*'|[^\s,;}]+)/gi, '$1[redacted]')
|
|
12
|
+
.replace(/(--(?:setup-workspace|password|token|secret|api-key|private-key|invitation|enrollment)(?:=|\s+))\S+/gi, '$1[redacted]')
|
|
13
|
+
.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/g, '$1[redacted]@')
|
|
14
|
+
.replace(/\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[redacted credential]')
|
|
15
|
+
.replace(/[A-Za-z0-9_+/=-]{43,}/g, '[redacted opaque value]');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bounded = text => text.length <= 65536 ? text : text.slice(0, 65536) + '\n[Diagnostic output exceeds 64 KiB; remainder omitted.]';
|
|
19
|
+
function credentialFailure(stderr) {
|
|
20
|
+
// Credential stdout can be a successful issuance followed by failure: never
|
|
21
|
+
// include it. Only a structured error envelope or shipped helper refusal is
|
|
22
|
+
// accepted from stderr; arbitrary lines/warnings remain private.
|
|
23
|
+
const lines = String(stderr ?? '').split('\n').reverse();
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
try {
|
|
26
|
+
const value = JSON.parse(line);
|
|
27
|
+
if (typeof value?.oursInstallerError?.message === 'string') {
|
|
28
|
+
const detail = value.oursInstallerError;
|
|
29
|
+
const stage = /^[a-z][a-z-]{0,40}$/.test(detail.stage ?? '') ? detail.stage : 'credential-operation';
|
|
30
|
+
return `Stage: ${stage}\nReason: ${detail.message}`;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value?.error?.message === 'string') return `Reason: ${value.error.message}`;
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
const prefix = 'OURS client setup refused: ';
|
|
36
|
+
const legacy = lines.find(line => line.startsWith(prefix));
|
|
37
|
+
if (legacy) return `Reason: ${legacy.slice(prefix.length)}`;
|
|
38
|
+
return 'The credential operation provided no structured reason. Its raw output was withheld because it can contain credentials.';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function commandFailure(command, args, result, { sensitive = false, cwd, env = process.env } = {}) {
|
|
42
|
+
const visibleArgs = [...args];
|
|
43
|
+
if (sensitive) {
|
|
44
|
+
for (let i = 0; i < visibleArgs.length - 1; i++) if (['-e', '--eval'].includes(visibleArgs[i])) visibleArgs[i + 1] = '[captured installer helper]';
|
|
45
|
+
}
|
|
46
|
+
const details = [`Command: ${command} ${visibleArgs.join(' ')}${result.status != null ? ` exited ${result.status}` : ''}`];
|
|
47
|
+
if (cwd) details.push(`Working directory: ${cwd}`);
|
|
48
|
+
if (result.status !== null && result.status !== undefined) details.push(`Exit code: ${result.status}`);
|
|
49
|
+
if (result.signal) details.push(`Signal: ${result.signal}`);
|
|
50
|
+
if (result.error?.code) details.push(`System error: ${result.error.code}`);
|
|
51
|
+
if (result.error?.code === 'ETIMEDOUT') details.push('Reason: command timed out.');
|
|
52
|
+
if (sensitive) details.push(credentialFailure(result.stderr));
|
|
53
|
+
else {
|
|
54
|
+
const stderr = String(result.stderr ?? '').trim(), stdout = String(result.stdout ?? '').trim();
|
|
55
|
+
if (stderr) details.push(`Standard error:\n${stderr}`);
|
|
56
|
+
if (stdout) details.push(`Standard output:\n${stdout}`);
|
|
57
|
+
if (!stderr && !stdout) details.push('No captured command output. Streaming commands print their output above.');
|
|
58
|
+
}
|
|
59
|
+
details.push('Keep installation data intact. Include this error and the preceding installation stage when requesting help.');
|
|
60
|
+
return new Error(bounded(redactDiagnostic(details.join('\n'), env)), result.error ? { cause: result.error } : undefined);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function installerFailure(error, env = process.env) {
|
|
64
|
+
const details = []; const seen = new Set();
|
|
65
|
+
for (let cause = error; cause && !seen.has(cause) && seen.size < 8; cause = cause.cause) {
|
|
66
|
+
seen.add(cause);
|
|
67
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
68
|
+
if (!details.includes(message)) details.push(message);
|
|
69
|
+
if (typeof cause.code === 'string' && /^[A-Z_0-9]+$/.test(cause.code)) details.push(`System error: ${cause.code}`);
|
|
70
|
+
}
|
|
71
|
+
return bounded(redactDiagnostic(details.join('\nCaused by: '), env));
|
|
72
|
+
}
|
package/assets/sources.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"release": {
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"channel": "nightly",
|
|
5
|
-
"installerVersion": "1.2.1-nightly.
|
|
5
|
+
"installerVersion": "1.2.1-nightly.25",
|
|
6
6
|
"packages": {
|
|
7
7
|
"@ours.network/sdk": {
|
|
8
8
|
"version": "3.8.1-nightly.15",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"integrity": "sha512-hDosXHgQ+vyzPdSxqTz6vCaOCn0LMTmOL4TJiWplkJhzyw2c586RckXJbqNJsTa0mmodvLxW0ewZyQ/xJVIeXA=="
|
|
26
26
|
},
|
|
27
27
|
"@ours.network/fleet": {
|
|
28
|
-
"version": "1.2.0-nightly.
|
|
29
|
-
"integrity": "sha512-
|
|
28
|
+
"version": "1.2.0-nightly.32",
|
|
29
|
+
"integrity": "sha512-FZRE+G25dc2qgDYbM3Dx76lw1Sws5OTF5KHPaMGTL5oun2SgYcNMhVKvwULZWSOlyrpggdMc3IFmp4eZSZTahg=="
|
|
30
30
|
},
|
|
31
31
|
"@ours.network/mcp": {
|
|
32
32
|
"version": "1.2.0-nightly.10",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"@ours.network/fleet": {
|
|
81
81
|
"type": "npm",
|
|
82
|
-
"version": "1.2.0-nightly.
|
|
82
|
+
"version": "1.2.0-nightly.32"
|
|
83
83
|
},
|
|
84
84
|
"@ours.network/mcp": {
|
|
85
85
|
"type": "npm",
|
package/install.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
import { realEffects } from './lib/effects.mjs';
|
|
6
|
+
import { installerFailure } from './lib/diagnostics.mjs';
|
|
6
7
|
import { runWorkspaceSetup } from './lib/workspace-setup.mjs';
|
|
7
8
|
import { runSetup } from './lib/setup.mjs';
|
|
8
9
|
import { closeSync, makeWriter, openTty } from './lib/ui.mjs';
|
|
@@ -22,7 +23,7 @@ try {
|
|
|
22
23
|
}));
|
|
23
24
|
process.exitCode = code;
|
|
24
25
|
} catch (error) {
|
|
25
|
-
process.stderr.write(`ours-install: ${
|
|
26
|
+
process.stderr.write(`ours-install: ${installerFailure(error)}\n`);
|
|
26
27
|
process.exitCode = 1;
|
|
27
28
|
} finally {
|
|
28
29
|
if (ttyFd != null) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { redactDiagnostic, commandFailure, installerFailure } from '../assets/scripts/runtime/diagnostics.mjs';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runContainer } from './container-engine.mjs';
|
|
1
|
+
import { engineName, runContainer } from './container-engine.mjs';
|
|
2
2
|
/** Qualify cached images before reuse; repair only the known root-owned 0600 policy. */
|
|
3
3
|
import { existsSync, readdirSync, lstatSync, readFileSync, writeFileSync, mkdtempSync, rmSync, realpathSync, openSync, closeSync, fstatSync, fchmodSync, constants } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
@@ -112,16 +112,24 @@ export async function qualifyDockerRuntime(record, effects) {
|
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
114
|
const policy = readFileSync(record.sourcesPath), expectedHash = digest(policy);
|
|
115
|
-
const check = (report, mode) => {
|
|
116
|
-
|
|
115
|
+
const check = (report, mode, stage) => {
|
|
116
|
+
const expected = { mode, uid: 0, gid: 0, regular: true, links: 1, policyHash: expectedHash };
|
|
117
|
+
const mismatched = Object.keys(expected).filter(key => report?.[key] !== expected[key]);
|
|
118
|
+
if (mismatched.length) {
|
|
119
|
+
// Only fixed field names, a bounded numeric mode and a comparison boolean
|
|
120
|
+
// enter diagnostics. Never disclose policy content, hashes or probe output.
|
|
121
|
+
const observedMode = Number.isInteger(report?.mode) && report.mode >= 0 && report.mode <= 0o7777
|
|
122
|
+
? report.mode.toString(8).padStart(4, '0') : 'invalid';
|
|
123
|
+
fail(`image policy verification failed (stage=${stage}; engine=${engineName(record)}; mismatched=${mismatched.join(',')}; mode=${observedMode}; expectedMode=${mode.toString(8).padStart(4, '0')}; policyMatch=${report?.policyHash === expectedHash})`);
|
|
124
|
+
}
|
|
117
125
|
};
|
|
118
126
|
const original = await inspect(tag);
|
|
119
127
|
const ordinary = await probe(original.Id, `${uid}:${gid}`);
|
|
120
|
-
if (ordinary.code === 0) { check(ordinary.report, 0o644); return; }
|
|
128
|
+
if (ordinary.code === 0) { check(ordinary.report, 0o644, 'ordinary'); return; }
|
|
121
129
|
if (ordinary.code !== 74) fail('cached image is unusable; automatic repair only supports the known source-policy permission defect');
|
|
122
130
|
const privileged = await probe(original.Id, '0:0');
|
|
123
131
|
if (privileged.code !== 0) fail('original release graph could not be verified');
|
|
124
|
-
check(privileged.report, 0o600);
|
|
132
|
+
check(privileged.report, 0o600, 'privileged');
|
|
125
133
|
effects.out?.('Repairing cached Docker image permissions; keeping the selected packages and stored data.');
|
|
126
134
|
const directory = mkdtempSync(join(record.root, '.image-permissions-'));
|
|
127
135
|
const name = `ours-permission-repair-${randomUUID()}`, baseTag = `${name}:base`, candidateTag = `${name}:candidate`;
|
|
@@ -138,7 +146,7 @@ export async function qualifyDockerRuntime(record, effects) {
|
|
|
138
146
|
|| candidate.RootFS.Layers.length !== original.RootFS.Layers.length + 1) fail('repair changed image execution settings or ancestry');
|
|
139
147
|
const verified = await probe(candidate.Id, `${uid}:${gid}`);
|
|
140
148
|
if (verified.code !== 0) fail('repaired image did not pass runtime verification');
|
|
141
|
-
check(verified.report, 0o644);
|
|
149
|
+
check(verified.report, 0o644, 'repaired');
|
|
142
150
|
if (!isDeepStrictEqual(verified.report.records, privileged.report.records)) fail('repair changed build provenance');
|
|
143
151
|
if ((await inspect(tag)).Id !== original.Id) fail('selected image changed during repair; retry setup');
|
|
144
152
|
await runContainer(effects, record, ['image', 'tag', candidate.Id, tag]);
|
package/lib/effects.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { engineName, bindPodman, runContainer, nativeBuildCommands } from './con
|
|
|
15
15
|
// unit file or the service manager directly.
|
|
16
16
|
|
|
17
17
|
import { publishClientCli } from './client-cli.mjs';
|
|
18
|
+
import { commandFailure } from './diagnostics.mjs';
|
|
18
19
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
19
20
|
import { chmodSync, closeSync, constants, cpSync, existsSync, fstatSync, lstatSync, openSync, readFileSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
20
21
|
import { homedir, userInfo, platform as osPlatform, release as osRelease, arch as osArch } from 'node:os';
|
|
@@ -422,13 +423,12 @@ export function realEffects({ write, ttyFd, env = process.env, home = homedir(),
|
|
|
422
423
|
env: childEnv,
|
|
423
424
|
});
|
|
424
425
|
if (r.error) {
|
|
425
|
-
const error =
|
|
426
|
+
const error = commandFailure(executable, args, r, { sensitive, cwd, env: childEnv });
|
|
426
427
|
error.code = r.error.code;
|
|
427
428
|
throw error;
|
|
428
429
|
}
|
|
429
430
|
if (r.status !== 0 && !allowCodes.includes(r.status)) {
|
|
430
|
-
|
|
431
|
-
throw new Error(`${executable} ${args.join(' ')} exited ${r.status}${detail ? `: ${detail}` : ''}`);
|
|
431
|
+
throw commandFailure(executable, args, r, { sensitive, cwd, env: childEnv });
|
|
432
432
|
}
|
|
433
433
|
return { ok: true, code: r.status, stdout: r.stdout ?? '' };
|
|
434
434
|
},
|
|
@@ -442,7 +442,8 @@ export function realEffects({ write, ttyFd, env = process.env, home = homedir(),
|
|
|
442
442
|
delete childEnv.OURS_INSTALLER_LOCK_FD;
|
|
443
443
|
if (installationLockFd !== null) childEnv.OURS_INSTALLER_LOCK_FD = '3';
|
|
444
444
|
const r = spawnSync(cmd, args, { stdio: ['inherit', 'inherit', 'inherit', ...(installationLockFd === null ? [] : [installationLockFd])], env: childEnv });
|
|
445
|
-
|
|
445
|
+
if (r.error || r.status !== 0) throw commandFailure(cmd, args, r, { env: childEnv });
|
|
446
|
+
return { ok: true, code: r.status };
|
|
446
447
|
},
|
|
447
448
|
installedVersion: (pkg) => installedVersionOf(pkg, npmBin),
|
|
448
449
|
packageDependencies: (spec) => packageDependenciesOf(spec, npmBin),
|
|
@@ -1040,7 +1041,15 @@ export function networkEffects(effects) {
|
|
|
1040
1041
|
},
|
|
1041
1042
|
async serverAccess(record, operation, { output, migrate = false } = {}) {
|
|
1042
1043
|
if (record.mode === 'docker') {
|
|
1043
|
-
|
|
1044
|
+
// Retained images contain the old helper that discarded CLI errors.
|
|
1045
|
+
// Execute the shipped installer helper against the SAME selected image
|
|
1046
|
+
// and Compose volume/user/environment contract, without retag/rebuild.
|
|
1047
|
+
const diagnosticsUrl = `data:text/javascript;base64,${readFileSync(join(INSTALLER_ASSETS, 'scripts/runtime/diagnostics.mjs')).toString('base64')}`;
|
|
1048
|
+
const helper = `process.argv.splice(1, 0, '/opt/ours/docker/client-setup.mjs');\n` + readFileSync(join(INSTALLER_ASSETS, 'scripts/runtime/client-setup.mjs'), 'utf8')
|
|
1049
|
+
.replace("'../maintenance/build-context.mjs'", "'/opt/ours/maintenance/build-context.mjs'")
|
|
1050
|
+
.replace("'./diagnostics.mjs'", JSON.stringify(diagnosticsUrl));
|
|
1051
|
+
const accessArgs = (...args) => ['--entrypoint', 'node', 'access', '--input-type=module', '-e', helper, '--', ...args];
|
|
1052
|
+
if (!output) return compose(record, ['run', '--rm', '--no-deps', '-T', ...accessArgs(operation)], { sensitive: true, env: { OURS_ACCESS_MIGRATE: migrate ? '1' : '0' } });
|
|
1044
1053
|
privateDirectory(dirname(output));
|
|
1045
1054
|
if (existsSync(output)) throw new Error('Credential output already exists');
|
|
1046
1055
|
const name = `${record.project}-issue-${randomUUID()}`;
|
|
@@ -1048,7 +1057,7 @@ export function networkEffects(effects) {
|
|
|
1048
1057
|
const staging = join(dirname(output), `.ours-issued-${randomUUID()}`);
|
|
1049
1058
|
ensurePrivateDirectory(staging);
|
|
1050
1059
|
try {
|
|
1051
|
-
await compose(record, ['run', '--name', name, '--no-deps', '-T', 'access
|
|
1060
|
+
await compose(record, ['run', '--name', name, '--no-deps', '-T', ...accessArgs('access-issue', issuedPath)], { sensitive: true });
|
|
1052
1061
|
const file = join(staging, 'credential');
|
|
1053
1062
|
await container(record, ['cp', `${name}:${issuedPath}`, file], { sensitive: true });
|
|
1054
1063
|
assertPrivateRegularFile(file, 'issued credential');
|
package/lib/orchestrate.mjs
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
validateChannelVersion,
|
|
46
46
|
} from './marketplace.mjs';
|
|
47
47
|
import { USAGE } from './usage.mjs';
|
|
48
|
+
import { installerFailure } from './diagnostics.mjs';
|
|
48
49
|
import { ok, info, warn, heading, banner, box, c, progress } from './ui.mjs';
|
|
49
50
|
|
|
50
51
|
export const EXIT_OK = 0;
|
|
@@ -89,12 +90,7 @@ async function perform(effects, dryRun, label, thunk) {
|
|
|
89
90
|
const reason = (error) => (error instanceof Error ? error.message : String(error));
|
|
90
91
|
|
|
91
92
|
function clientDiagnostic(error) {
|
|
92
|
-
return
|
|
93
|
-
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
|
|
94
|
-
.replace(/\bBearer\s+[^\s,;]+/gi, 'Bearer [redacted]')
|
|
95
|
-
.replace(/((?:api[_-]?key|api[_-]?token|access[_-]?token|refresh[_-]?token|token|password|secret|authorization|credential)\s*["']?\s*[:=]\s*["']?)[^\s"',;]+/gi, '$1[redacted]')
|
|
96
|
-
.replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, '$1[redacted]@')
|
|
97
|
-
.replace(/[\x00-\x1f\x7f]/g, ' ').slice(0, 700);
|
|
93
|
+
return installerFailure(error);
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
function clientRetry(effects, imported, integrations) {
|
|
@@ -169,7 +169,7 @@ else { await request('identity/profile', selection.human); process.stdout.write(
|
|
|
169
169
|
if (!current) {
|
|
170
170
|
effects.out?.('Issuing a separate local client credential with the retained server authority.');
|
|
171
171
|
try { await effects.serverAccess(record, 'access-issue', { output: credential }); }
|
|
172
|
-
catch { throw new Error('Client credential issuance failed; existing profiles were retained'); }
|
|
172
|
+
catch (cause) { throw new Error('Client credential issuance failed; existing profiles were retained', { cause }); }
|
|
173
173
|
}
|
|
174
174
|
const retainedCredential = current?.credentialPath ?? credential;
|
|
175
175
|
const stat = privatePath(retainedCredential);
|
package/lib/setup.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { isCancel } from './prompt.mjs';
|
|
|
11
11
|
import { USAGE } from './usage.mjs';
|
|
12
12
|
import { validateIdentityName } from './server-onboarding.mjs';
|
|
13
13
|
import { validateFleetSettings } from './fleet-settings.mjs';
|
|
14
|
+
import { installerFailure } from './diagnostics.mjs';
|
|
14
15
|
|
|
15
16
|
const maintenance = new Set(['status', 'start', 'stop', 'restart', 'rebuild', 'gateway-enable', 'access-issue', 'access-replace', 'backup', 'restore', 'reset']);
|
|
16
17
|
|
|
@@ -165,7 +166,7 @@ export async function runSetup(argv, effects) {
|
|
|
165
166
|
return await executeSetupPlan(plan, effects);
|
|
166
167
|
} catch (error) {
|
|
167
168
|
if (isCancel(error)) { effects.out(warn('Installation cancelled.')); return 130; }
|
|
168
|
-
effects.out(warn(`ours-install: ${error.
|
|
169
|
+
effects.out(warn(`ours-install: ${installerFailure(error, effects.env)}`));
|
|
169
170
|
return 2;
|
|
170
171
|
}
|
|
171
172
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/install",
|
|
3
|
-
"version": "1.2.1-nightly.
|
|
3
|
+
"version": "1.2.1-nightly.25",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The all-in-one ours.network installer: one shared daemon, MCP, cowork, Telegram, Fleet initialization, harness plugins, Human identity, progress UI, and guided next steps.",
|
|
6
6
|
"type": "module",
|