@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 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.31` with its recorded registry integrity. This release includes the packaged `/fleet` browser entry 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.
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
@@ -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.31",
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.31",
1089
- "resolved": "https://registry.npmjs.org/@ours.network/fleet/-/fleet-1.2.0-nightly.31.tgz",
1090
- "integrity": "sha512-45oIV09WCsK5yEZF8IKU8LEJjC4Hk26XG/OeA+BqxSFvQWEXoHhMhLeabP5Jc9QcDcEmtH5AeYKXRvCXTeb+YA==",
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",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "channel": "nightly",
4
- "installerVersion": "1.2.1-nightly.24",
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.31",
28
- "integrity": "sha512-45oIV09WCsK5yEZF8IKU8LEJjC4Hk26XG/OeA+BqxSFvQWEXoHhMhLeabP5Jc9QcDcEmtH5AeYKXRvCXTeb+YA=="
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) fail('Official OURS CLI operation failed');
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
- console.error(`OURS client setup refused: ${error.message}`);
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
+ }
@@ -2,7 +2,7 @@
2
2
  "release": {
3
3
  "schema": 1,
4
4
  "channel": "nightly",
5
- "installerVersion": "1.2.1-nightly.24",
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.31",
29
- "integrity": "sha512-45oIV09WCsK5yEZF8IKU8LEJjC4Hk26XG/OeA+BqxSFvQWEXoHhMhLeabP5Jc9QcDcEmtH5AeYKXRvCXTeb+YA=="
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.31"
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: ${error instanceof Error ? error.message : String(error)}\n`);
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
- if (!report || report.mode !== mode || report.uid !== 0 || report.gid !== 0 || !report.regular || report.links !== 1 || report.policyHash !== expectedHash) fail('image policy differs from retained selection or expected permissions');
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 = new Error(`${executable} could not start (${r.error.code ?? 'launch error'})`, { cause: r.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
- const detail = sensitive ? '' : (r.stderr || r.stdout || '').trim().split('\n').slice(-3).join('; ');
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
- return { ok: !r.error && r.status === 0, code: r.status ?? -1 };
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
- if (!output) return compose(record, ['run', '--rm', '--no-deps', '-T', 'access', operation], { sensitive: true, env: { OURS_ACCESS_MIGRATE: migrate ? '1' : '0' } });
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', 'access-issue', issuedPath], { sensitive: true });
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');
@@ -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 reason(error)
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.message}`));
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.24",
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",