@ours.network/install 1.2.0-nightly.2 → 1.2.1-nightly.2
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 +61 -132
- package/assets/Dockerfile +2 -2
- package/assets/release-lock.json +7152 -0
- package/assets/release.json +43 -0
- package/assets/scripts/build/record-build.mjs +2 -0
- package/assets/scripts/maintenance/release-graph.mjs +94 -0
- package/assets/scripts/runtime/runtime-common.mjs +2 -0
- package/assets/sources.json +78 -16
- package/install.mjs +2 -2
- package/install.sh +1 -1
- package/lib/build-transition.mjs +13 -7
- package/lib/docker-conversion-runtime.mjs +1 -0
- package/lib/effects.mjs +108 -28
- package/lib/fleet-settings.mjs +43 -0
- package/lib/orchestrate.mjs +64 -23
- package/lib/plan.mjs +3 -1
- package/lib/server-onboarding.mjs +113 -0
- package/lib/setup-options.mjs +157 -0
- package/lib/setup.mjs +145 -0
- package/lib/usage.mjs +47 -44
- package/package.json +5 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": 1,
|
|
3
|
+
"channel": "nightly",
|
|
4
|
+
"installerVersion": "1.2.1-nightly.2",
|
|
5
|
+
"packages": {
|
|
6
|
+
"@ours.network/sdk": {
|
|
7
|
+
"version": "3.8.1-nightly.3",
|
|
8
|
+
"integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
|
|
9
|
+
},
|
|
10
|
+
"@ours.network/cli": {
|
|
11
|
+
"version": "2.8.1-nightly.1",
|
|
12
|
+
"integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
|
|
13
|
+
},
|
|
14
|
+
"@ours.network/tg-connector": {
|
|
15
|
+
"version": "1.0.1-nightly.1",
|
|
16
|
+
"integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
|
|
17
|
+
},
|
|
18
|
+
"@ours.network/cowork": {
|
|
19
|
+
"version": "1.3.2-nightly.20260918.4d9242e",
|
|
20
|
+
"integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
|
|
21
|
+
},
|
|
22
|
+
"@ours.network/messenger-server": {
|
|
23
|
+
"version": "1.0.31-nightly.1",
|
|
24
|
+
"integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
|
|
25
|
+
},
|
|
26
|
+
"@ours.network/fleet": {
|
|
27
|
+
"version": "1.2.0-nightly.1",
|
|
28
|
+
"integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
|
|
29
|
+
},
|
|
30
|
+
"@ours.network/mcp": {
|
|
31
|
+
"version": "1.2.0-nightly.3",
|
|
32
|
+
"integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
|
|
33
|
+
},
|
|
34
|
+
"@ours.network/codex": {
|
|
35
|
+
"version": "1.2.0-nightly.3",
|
|
36
|
+
"integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
|
|
37
|
+
},
|
|
38
|
+
"@ours.network/claude-code": {
|
|
39
|
+
"version": "1.2.0-nightly.3",
|
|
40
|
+
"integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -4,6 +4,7 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import { createBuildContext, CONTEXT } from '../maintenance/build-context.mjs';
|
|
7
|
+
import { verifyRuntimeRelease } from '../maintenance/release-graph.mjs';
|
|
7
8
|
export function finalizeBuild(root) {
|
|
8
9
|
if (fs.realpathSync(root) !== root) throw new Error('Build root must be canonical');
|
|
9
10
|
try { fs.lstatSync(join(root, CONTEXT)); throw new Error('Existing context cannot be regenerated'); }
|
|
@@ -20,6 +21,7 @@ export function finalizeBuild(root) {
|
|
|
20
21
|
if (typeof spec !== 'string' || !/^file:docker\/vendor\/ours\.network-[a-z-]+\.tgz$/.test(spec)) throw new Error('Unexpected installer vendor reference');
|
|
21
22
|
protect(join(root, spec.slice(5)));
|
|
22
23
|
}
|
|
24
|
+
verifyRuntimeRelease(root);
|
|
23
25
|
const tree = execFileSync('npm', ['ls', '--omit=dev', '--all', '--json'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
24
26
|
JSON.parse(tree);
|
|
25
27
|
const path = join(root, 'dependency-tree.json');
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Enforce the selected ours artifacts; this is not third-party lock replay. */
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { join, resolve, relative, isAbsolute } from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const names = ['sdk', 'cli', 'tg-connector', 'cowork', 'messenger-server', 'fleet', 'mcp', 'codex', 'claude-code'].map(name => '@ours.network/' + name);
|
|
7
|
+
const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
8
|
+
const nightly = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-nightly\.(0|[1-9]\d*)(?:\.[0-9a-f]{7,40})?$/;
|
|
9
|
+
const fail = message => { throw new Error(`Release graph refused: ${message}`); };
|
|
10
|
+
function file(path) {
|
|
11
|
+
if (!lstatSync(path).isFile() || realpathSync(path) !== path) fail(`non-regular or linked file: ${path}`);
|
|
12
|
+
return readFileSync(path);
|
|
13
|
+
}
|
|
14
|
+
const json = path => JSON.parse(file(path));
|
|
15
|
+
|
|
16
|
+
export function releaseBinding(policy) {
|
|
17
|
+
// Only an absent binding denotes explicit development/retained legacy inputs.
|
|
18
|
+
if (!Object.hasOwn(policy ?? {}, 'release')) return null;
|
|
19
|
+
const release = policy.release;
|
|
20
|
+
if (release?.schema !== 1 || !['stable', 'nightly'].includes(release.channel)) fail('invalid release binding');
|
|
21
|
+
const pattern = release.channel === 'nightly' ? nightly : stable;
|
|
22
|
+
if (typeof release.installerVersion !== 'string' || !pattern.test(release.installerVersion)) fail('invalid installer version/channel');
|
|
23
|
+
if (!release.packages || JSON.stringify(Object.keys(release.packages).sort()) !== JSON.stringify([...names].sort())) fail('release must select exactly nine ours packages');
|
|
24
|
+
for (const [name, entry] of Object.entries(release.packages)) {
|
|
25
|
+
if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid release artifact: ${name}`);
|
|
26
|
+
}
|
|
27
|
+
if (!policy.packages || !Object.keys(policy.packages).length) fail('missing release source selection');
|
|
28
|
+
for (const [name, entry] of Object.entries(policy.packages)) {
|
|
29
|
+
if (entry?.type !== 'npm' || entry.version !== release.packages[name]?.version || Object.keys(entry).sort().join(',') !== 'type,version') fail(`source selection differs from release: ${name}`);
|
|
30
|
+
}
|
|
31
|
+
return release;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function verifyReleaseGraph(root, policy, { requiredPackages = Object.keys(policy?.packages ?? {}) } = {}) {
|
|
35
|
+
const release = releaseBinding(policy);
|
|
36
|
+
if (!release) return { verified: false, reason: 'development or retained legacy source selection' };
|
|
37
|
+
root = resolve(root);
|
|
38
|
+
if (realpathSync(root) !== root) fail('build root is not canonical');
|
|
39
|
+
const manifest = json(join(root, 'package.json'));
|
|
40
|
+
if (manifest.name?.startsWith('@ours.network/') && manifest.version !== release.packages[manifest.name]?.version) fail(`root package differs from release: ${manifest.name}`);
|
|
41
|
+
const locks = ['package-lock.json', 'node_modules/.package-lock.json'].map(path => {
|
|
42
|
+
const lock = json(join(root, path));
|
|
43
|
+
if (lock.lockfileVersion !== 3 || !lock.packages || Array.isArray(lock.packages)) fail(`invalid lock: ${path}`);
|
|
44
|
+
const entries = new Map();
|
|
45
|
+
for (const [location, entry] of Object.entries(lock.packages)) {
|
|
46
|
+
const match = location.match(/(?:^|\/)node_modules\/(@ours\.network\/[^/]+)$/);
|
|
47
|
+
if (!match) continue;
|
|
48
|
+
const name = match[1], expected = release.packages[name];
|
|
49
|
+
if (!expected || entry?.link || entry.version !== expected.version || entry.integrity !== expected.integrity) fail(`version/integrity mismatch: ${path}:${location}`);
|
|
50
|
+
const installed = resolve(root, location), rel = relative(root, installed);
|
|
51
|
+
if (!rel || rel.startsWith('..') || isAbsolute(rel)) fail(`escaping lock location: ${location}`);
|
|
52
|
+
if (typeof entry.resolved !== 'string') fail(`missing artifact location: ${location}`);
|
|
53
|
+
if (entry.resolved.startsWith('file:')) {
|
|
54
|
+
const archive = resolve(root, entry.resolved.slice(5));
|
|
55
|
+
const archiveRel = relative(root, archive);
|
|
56
|
+
if (archiveRel.startsWith('..') || isAbsolute(archiveRel) || !archive.endsWith('.tgz')) fail(`unbound local artifact: ${location}`);
|
|
57
|
+
const integrity = 'sha512-' + createHash('sha512').update(file(archive)).digest('base64');
|
|
58
|
+
if (integrity !== expected.integrity) fail(`vendor bytes differ from release: ${location}`);
|
|
59
|
+
} else if (!entry.resolved.startsWith(`https://registry.npmjs.org/${name}/-/`)) fail(`non-official artifact: ${location}`);
|
|
60
|
+
entries.set(location, { name, entry, installed });
|
|
61
|
+
}
|
|
62
|
+
return entries;
|
|
63
|
+
});
|
|
64
|
+
const [declared, installed] = locks;
|
|
65
|
+
const direct = Object.entries(manifest.dependencies ?? {}).filter(([name]) => name.startsWith('@ours.network/'));
|
|
66
|
+
if (!manifest.name?.startsWith('@ours.network/') && JSON.stringify(direct.map(([name]) => name).sort()) !== JSON.stringify([...requiredPackages].sort())) fail('direct package set differs from release source selection');
|
|
67
|
+
for (const [name, spec] of direct) {
|
|
68
|
+
const expected = release.packages[name], locked = declared.get(`node_modules/${name}`)?.entry;
|
|
69
|
+
if (!expected || (spec !== expected.version && !(typeof spec === 'string' && spec.startsWith('file:') && spec === locked?.resolved))) fail(`direct package spec differs from release: ${name}`);
|
|
70
|
+
}
|
|
71
|
+
for (const name of Object.keys(manifest.dependencies ?? {}).filter(name => name.startsWith('@ours.network/'))) {
|
|
72
|
+
if (!declared.has(`node_modules/${name}`) || !installed.has(`node_modules/${name}`)) fail(`missing direct release package: ${name}`);
|
|
73
|
+
}
|
|
74
|
+
for (const [location, value] of declared) {
|
|
75
|
+
// npm's root lock retains dev-only entries omitted from a production install.
|
|
76
|
+
if (!installed.has(location) && !value.entry.dev && !value.entry.optional) fail(`installed release entry missing: ${location}`);
|
|
77
|
+
}
|
|
78
|
+
for (const [location, value] of installed) {
|
|
79
|
+
const expected = declared.get(location);
|
|
80
|
+
if (!expected || expected.entry.version !== value.entry.version || expected.entry.integrity !== value.entry.integrity || expected.entry.resolved !== value.entry.resolved) fail(`installed/root lock mismatch: ${location}`);
|
|
81
|
+
if (!lstatSync(value.installed).isDirectory() || realpathSync(value.installed) !== value.installed) fail(`linked installed package: ${location}`);
|
|
82
|
+
const actual = json(join(value.installed, 'package.json'));
|
|
83
|
+
if (actual.name !== value.name || actual.version !== value.entry.version) fail(`installed package differs from release: ${location}`);
|
|
84
|
+
}
|
|
85
|
+
return { verified: true, packages: installed.size };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function verifyRuntimeRelease(root) {
|
|
89
|
+
const path = join(resolve(root), 'sources.json');
|
|
90
|
+
// Historic installations predate sources/release bindings; their existing
|
|
91
|
+
// provenance and conversion checks remain authoritative until explicit update.
|
|
92
|
+
if (!existsSync(path)) return { verified: false, reason: 'legacy runtime without sources' };
|
|
93
|
+
return verifyReleaseGraph(root, json(path));
|
|
94
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBuildRecords, initializeBuildMarker } from '../maintenance/build-context.mjs';
|
|
2
|
+
import { verifyRuntimeRelease } from '../maintenance/release-graph.mjs';
|
|
2
3
|
import { accessSync, constants, lstatSync, readFileSync } from 'node:fs';
|
|
3
4
|
|
|
4
5
|
export function privatePath(path, directory = false, writable = false) {
|
|
@@ -43,5 +44,6 @@ export function checkCredential(path) {
|
|
|
43
44
|
// Called with the startup state-directory lock held. These are build records,
|
|
44
45
|
// not a storage schema or a declaration that arbitrary upgrades are compatible.
|
|
45
46
|
export function recordBuild(state) {
|
|
47
|
+
verifyRuntimeRelease('/opt/ours');
|
|
46
48
|
initializeBuildMarker(`${state}/.ours-provenance`, readBuildRecords('/opt/ours'));
|
|
47
49
|
}
|
package/assets/sources.json
CHANGED
|
@@ -1,21 +1,83 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
"release": {
|
|
3
|
+
"schema": 1,
|
|
4
|
+
"channel": "nightly",
|
|
5
|
+
"installerVersion": "1.2.1-nightly.2",
|
|
6
|
+
"packages": {
|
|
7
|
+
"@ours.network/sdk": {
|
|
8
|
+
"version": "3.8.1-nightly.3",
|
|
9
|
+
"integrity": "sha512-iUVCs+jryoeR2Ip/uHzpkJwbpoJeLDASUaE3lC+eYDj8dbI1MBwTpeOXFemUK052O6lA4ZWrhyRgE9pJU3a+Kw=="
|
|
10
|
+
},
|
|
11
|
+
"@ours.network/cli": {
|
|
12
|
+
"version": "2.8.1-nightly.1",
|
|
13
|
+
"integrity": "sha512-1Rmbqlr790FhqQW/wQ//YEL7b0Q2KVSCA2TlpjaLRkj9Xh55rOoazBJjFuW/fUpTz3Z1B3MUPK9LWiYd+A0kNA=="
|
|
14
|
+
},
|
|
15
|
+
"@ours.network/tg-connector": {
|
|
16
|
+
"version": "1.0.1-nightly.1",
|
|
17
|
+
"integrity": "sha512-/Do7EfINknDks3yYndOi9aLc1alxB50qk+WKv8DRuZpDB89DDMQ+DoSGQerdvsMp32nQ3LICL8TPjcdFMZfx8Q=="
|
|
18
|
+
},
|
|
19
|
+
"@ours.network/cowork": {
|
|
20
|
+
"version": "1.3.2-nightly.20260918.4d9242e",
|
|
21
|
+
"integrity": "sha512-04D2lJ97SrTrkVRRbdTZ0y+KvQdG/bpnmFFjsyUqQTij+5zEkNizTG/xhcCL8m7Mb0XvVFL8WzJ62Xgh5ahlRw=="
|
|
22
|
+
},
|
|
23
|
+
"@ours.network/messenger-server": {
|
|
24
|
+
"version": "1.0.31-nightly.1",
|
|
25
|
+
"integrity": "sha512-XB6N+Ppd4txmI0+2ZVFMWJxXi4DtMIDlFSHOzszNM5hopFebpw1Df+KKCK4Cgd/LZUkoOOYWGEYpib9m3RbHWw=="
|
|
26
|
+
},
|
|
27
|
+
"@ours.network/fleet": {
|
|
28
|
+
"version": "1.2.0-nightly.1",
|
|
29
|
+
"integrity": "sha512-vioCobVbosmp9BgYo2XccjpPoVQd1wwV+Mt64Bm41/Vxtro2j85PUdy4AWcsY9jEFnJmj/UQfc5E5AxvlpPnrw=="
|
|
30
|
+
},
|
|
31
|
+
"@ours.network/mcp": {
|
|
32
|
+
"version": "1.2.0-nightly.3",
|
|
33
|
+
"integrity": "sha512-z0N5jn1jj9iwGRqU1d3AM2RX9mCr4T/WhASEKr+Vh52yNDMzhVjFlx0U0v8aS+GTfOQaPZTfL3TGMc17ETjh3g=="
|
|
34
|
+
},
|
|
35
|
+
"@ours.network/codex": {
|
|
36
|
+
"version": "1.2.0-nightly.3",
|
|
37
|
+
"integrity": "sha512-dIWDqbo6hJzujegHRO6x1SsBnh5p1c6gRGj/WkPha/hcN7s/27P4jqBnJ7czQyVGiNVFZ1aAxj4c9+CDLzHENw=="
|
|
38
|
+
},
|
|
39
|
+
"@ours.network/claude-code": {
|
|
40
|
+
"version": "1.2.0-nightly.3",
|
|
41
|
+
"integrity": "sha512-7ta6KnvoHB2i/yoaykhXvzKFQTIOPtDHeTzG1PRwHjdTJ4vcGEh4jWYMd2c1jjj73lC3q9qW3LYSRutIJovtQg=="
|
|
42
|
+
}
|
|
43
|
+
}
|
|
9
44
|
},
|
|
10
45
|
"packages": {
|
|
11
|
-
"@ours.network/sdk": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"@ours.network/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"@ours.network/
|
|
46
|
+
"@ours.network/sdk": {
|
|
47
|
+
"type": "npm",
|
|
48
|
+
"version": "3.8.1-nightly.3"
|
|
49
|
+
},
|
|
50
|
+
"@ours.network/cli": {
|
|
51
|
+
"type": "npm",
|
|
52
|
+
"version": "2.8.1-nightly.1"
|
|
53
|
+
},
|
|
54
|
+
"@ours.network/tg-connector": {
|
|
55
|
+
"type": "npm",
|
|
56
|
+
"version": "1.0.1-nightly.1"
|
|
57
|
+
},
|
|
58
|
+
"@ours.network/cowork": {
|
|
59
|
+
"type": "npm",
|
|
60
|
+
"version": "1.3.2-nightly.20260918.4d9242e"
|
|
61
|
+
},
|
|
62
|
+
"@ours.network/messenger-server": {
|
|
63
|
+
"type": "npm",
|
|
64
|
+
"version": "1.0.31-nightly.1"
|
|
65
|
+
},
|
|
66
|
+
"@ours.network/fleet": {
|
|
67
|
+
"type": "npm",
|
|
68
|
+
"version": "1.2.0-nightly.1"
|
|
69
|
+
},
|
|
70
|
+
"@ours.network/mcp": {
|
|
71
|
+
"type": "npm",
|
|
72
|
+
"version": "1.2.0-nightly.3"
|
|
73
|
+
},
|
|
74
|
+
"@ours.network/codex": {
|
|
75
|
+
"type": "npm",
|
|
76
|
+
"version": "1.2.0-nightly.3"
|
|
77
|
+
},
|
|
78
|
+
"@ours.network/claude-code": {
|
|
79
|
+
"type": "npm",
|
|
80
|
+
"version": "1.2.0-nightly.3"
|
|
81
|
+
}
|
|
20
82
|
}
|
|
21
83
|
}
|
package/install.mjs
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
import { realEffects } from './lib/effects.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { runSetup } from './lib/setup.mjs';
|
|
7
7
|
import { closeSync, makeWriter, openTty } from './lib/ui.mjs';
|
|
8
8
|
|
|
9
9
|
const version = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version;
|
|
10
10
|
const ttyFd = openTty();
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
const code = await
|
|
13
|
+
const code = await runSetup(process.argv.slice(2), realEffects({
|
|
14
14
|
write: makeWriter(ttyFd),
|
|
15
15
|
ttyFd,
|
|
16
16
|
env: process.env,
|
package/install.sh
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# pipes a script into your shell). It just gets Node.js/npm sorted, then does the same
|
|
10
10
|
# `npm i -g @ours.network/install` and runs `ours-install`. Meant to be run as:
|
|
11
11
|
#
|
|
12
|
-
# curl -fsSL https://raw.githubusercontent.com/adapt-toolkit/ours-
|
|
12
|
+
# curl -fsSL https://raw.githubusercontent.com/adapt-toolkit/ours-network/main/install.sh | bash
|
|
13
13
|
#
|
|
14
14
|
# This file is a THIN bootstrap: it checks that Node.js + npm are present (and prints friendly,
|
|
15
15
|
# per-OS guidance if not), then runs the real experience — the Node installer (install.mjs) that
|
package/lib/build-transition.mjs
CHANGED
|
@@ -2,10 +2,16 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { validateInstallation } from './plan.mjs';
|
|
5
|
+
import { info, ok, warn } from './ui.mjs';
|
|
5
6
|
|
|
6
7
|
/** The caller holds the installation lock; installation.json owns retry state. */
|
|
7
8
|
export async function serverBuildTransition(record, args, effects) {
|
|
8
9
|
validateInstallation(record, record.root);
|
|
10
|
+
const stage = async (label, action) => {
|
|
11
|
+
effects.out?.(info(label));
|
|
12
|
+
try { const result = await action(); effects.out?.(ok(`${label} complete`)); return result; }
|
|
13
|
+
catch (error) { effects.out?.(warn(`Server ${args.operation} stopped during ${label.toLowerCase()}.`)); throw error; }
|
|
14
|
+
};
|
|
9
15
|
const path = join(record.root, 'installation.json');
|
|
10
16
|
const save = transition => {
|
|
11
17
|
record = { ...record, buildTransition: transition };
|
|
@@ -14,9 +20,9 @@ export async function serverBuildTransition(record, args, effects) {
|
|
|
14
20
|
};
|
|
15
21
|
let transition = record.buildTransition;
|
|
16
22
|
if (!transition) {
|
|
17
|
-
const candidate = await effects.prepareServerBuild(record, args);
|
|
23
|
+
const candidate = await stage('Prepare the updated runtime', () => effects.prepareServerBuild(record, args));
|
|
18
24
|
try {
|
|
19
|
-
await effects.checkServerBuild(record, candidate, !!args.compatible, args.operation);
|
|
25
|
+
await stage('Verify package and stored-state compatibility', () => effects.checkServerBuild(record, candidate, !!args.compatible, args.operation));
|
|
20
26
|
transition = { operation: args.operation, candidate, compatible: !!args.compatible,
|
|
21
27
|
...(args.sources ? { sourcePolicyHash: createHash('sha256').update(readFileSync(args.sources)).digest('hex') } : {}),
|
|
22
28
|
runningServices: await effects.serverLifecycle(record, 'status'), phase: 'prepared' };
|
|
@@ -34,19 +40,19 @@ export async function serverBuildTransition(record, args, effects) {
|
|
|
34
40
|
const { candidate, runningServices } = transition;
|
|
35
41
|
// A failed readiness check can leave some new services running. Every retry
|
|
36
42
|
// excludes those writers again and retains the original requested running set.
|
|
37
|
-
await effects.retireServerBuildRuntime(record);
|
|
43
|
+
await stage('Stop services before updating stored state', () => effects.retireServerBuildRuntime(record));
|
|
38
44
|
if (transition.phase === 'prepared') {
|
|
39
|
-
await effects.updateServerBuildState(record, candidate, transition.compatible, transition.operation);
|
|
45
|
+
await stage('Update stored state while retaining identities and credentials', () => effects.updateServerBuildState(record, candidate, transition.compatible, transition.operation));
|
|
40
46
|
transition = { ...transition, phase: 'state-updated' };
|
|
41
47
|
save(transition);
|
|
42
48
|
}
|
|
43
49
|
if (transition.phase === 'state-updated') {
|
|
44
|
-
await effects.publishServerBuild(record, candidate);
|
|
50
|
+
await stage('Activate the prepared runtime', () => effects.publishServerBuild(record, candidate));
|
|
45
51
|
transition = { ...transition, phase: 'runtime-activated' };
|
|
46
52
|
save(transition);
|
|
47
53
|
}
|
|
48
|
-
await effects.validateServerBuildState(record);
|
|
49
|
-
await effects.serverLifecycle(record, 'start', runningServices);
|
|
54
|
+
await stage('Verify retained state and identities', () => effects.validateServerBuildState(record));
|
|
55
|
+
await stage('Restore previously running services and check readiness', () => effects.serverLifecycle(record, 'start', runningServices));
|
|
50
56
|
const completed = { ...record, sourcePolicyHash: candidate.sourcePolicyHash };
|
|
51
57
|
delete completed.buildTransition;
|
|
52
58
|
effects.writeJson(path, JSON.stringify(completed, null, 2) + '\n');
|
|
@@ -22,6 +22,7 @@ export async function prepareDockerConversionRuntime(record, effects, assets) {
|
|
|
22
22
|
fs.writeFileSync(join(context, 'Dockerfile'), `# syntax=docker/dockerfile:1
|
|
23
23
|
FROM ${record.project}:runtime AS runtime
|
|
24
24
|
COPY --chmod=644 runtime/ /opt/ours/docker/
|
|
25
|
+
COPY --chmod=644 maintenance/release-graph.mjs /opt/ours/maintenance/release-graph.mjs
|
|
25
26
|
FROM runtime AS maintenance
|
|
26
27
|
USER 0:0
|
|
27
28
|
COPY --chmod=644 maintenance/ /opt/ours/docker/
|