joinweave 0.1.0 → 0.1.1
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/package.json +4 -2
- package/src/installer.js +29 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joinweave",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One command to join the Weave agent-native web.",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
15
|
"src",
|
|
16
|
-
"README.md"
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE-APACHE",
|
|
18
|
+
"LICENSE-MIT"
|
|
17
19
|
],
|
|
18
20
|
"dependencies": {
|
|
19
21
|
"@useweave/cli": "0.1.0"
|
package/src/installer.js
CHANGED
|
@@ -4,7 +4,10 @@ import { resolve, join, dirname, isAbsolute, parse } from 'node:path';
|
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
import { execFile } from 'node:child_process';
|
|
6
6
|
|
|
7
|
-
export const VERSION = '0.1.
|
|
7
|
+
export const VERSION = '0.1.1';
|
|
8
|
+
const NATIVE_PACKAGE_VERSION = '0.1.0';
|
|
9
|
+
// Published native0.1.0 treats this as its service-ownership protocol version.
|
|
10
|
+
const RECEIPT_VERSION = '0.1.0';
|
|
8
11
|
const PATH_BEGIN = '# >>> joinweave managed PATH >>>';
|
|
9
12
|
const PATH_END = '# <<< joinweave managed PATH <<<' ;
|
|
10
13
|
const MCP_BEGIN = '# >>> joinweave managed MCP >>>';
|
|
@@ -124,7 +127,7 @@ const systemdPath = (value) => value.replaceAll('%', '%%');
|
|
|
124
127
|
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
125
128
|
|
|
126
129
|
export class Installer {
|
|
127
|
-
constructor({ home = homedir(), weaveHome = process.env.WEAVE_HOME || join(home, '.weave'), platform = process.platform, uid = process.getuid?.(), env = process.env, run = runCommand, resolveNative, confirm = async () => false, progress = () => {}, sleep = (ms) => new Promise((done) => setTimeout(done, ms)) } = {}) {
|
|
130
|
+
constructor({ home = homedir(), weaveHome = process.env.WEAVE_HOME || join(home, '.weave'), platform = process.platform, uid = process.getuid?.(), env = process.env, run = runCommand, resolveNative, confirm = async () => false, progress = () => {}, sleep = (ms) => new Promise((done) => setTimeout(done, ms)), now = Date.now } = {}) {
|
|
128
131
|
this.home = resolve(home);
|
|
129
132
|
this.weaveHome = resolve(weaveHome);
|
|
130
133
|
if ([this.home, this.weaveHome].some((path) => /[\x00-\x1f\x7f]/.test(path))) fail('UNSAFE_PATH', 'Workspace and home paths cannot contain control characters.');
|
|
@@ -139,16 +142,17 @@ export class Installer {
|
|
|
139
142
|
this.confirm = confirm;
|
|
140
143
|
this.progress = progress;
|
|
141
144
|
this.sleep = sleep;
|
|
145
|
+
this.now = now;
|
|
142
146
|
this.binary = join(this.weaveHome, 'bin', 'weave');
|
|
143
147
|
this.manifestPath = join(this.weaveHome, '.joinweave-install.json');
|
|
144
148
|
this.lockPath = join(this.weaveHome, '.joinweave-install.lock');
|
|
145
|
-
this.manifest = { schema: 1, installer: 'joinweave', version: VERSION, binary: null, service: null, path: null, mcp: {} };
|
|
149
|
+
this.manifest = { schema: 1, installer: 'joinweave', version: RECEIPT_VERSION, installerVersion: VERSION, binary: null, service: null, path: null, mcp: {} };
|
|
146
150
|
this.warnings = [];
|
|
147
151
|
this.manualMcp = [];
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
validatePlatform() {
|
|
151
|
-
if (!['darwin', 'linux'].includes(this.platform)) fail('UNSUPPORTED_PLATFORM',
|
|
155
|
+
if (!['darwin', 'linux'].includes(this.platform)) fail('UNSUPPORTED_PLATFORM', `joinweave ${VERSION} supports macOS and Linux. Windows service installation is not supported yet.`);
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
loadManifest() {
|
|
@@ -161,7 +165,8 @@ export class Installer {
|
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
saveManifest() {
|
|
164
|
-
this.manifest.version =
|
|
168
|
+
this.manifest.version = RECEIPT_VERSION;
|
|
169
|
+
this.manifest.installerVersion = VERSION;
|
|
165
170
|
atomicWrite(this.manifestPath, Buffer.from(`${JSON.stringify(this.manifest, null, 2)}\n`), { previous: readFile(this.manifestPath) });
|
|
166
171
|
}
|
|
167
172
|
|
|
@@ -213,7 +218,7 @@ export class Installer {
|
|
|
213
218
|
if (old && (!this.manifest.binary || digest(old) !== this.manifest.binary.sha256)) fail('UNMANAGED_BINARY', `An unrelated or modified executable exists at ${this.binary}. It will not be replaced.`);
|
|
214
219
|
if (!old || digest(old) !== sha256) atomicWrite(this.binary, bytes, { previous: old, mode: 0o755 });
|
|
215
220
|
fs.chmodSync(this.binary, 0o755);
|
|
216
|
-
this.manifest.binary = { sha256, version:
|
|
221
|
+
this.manifest.binary = { sha256, version: NATIVE_PACKAGE_VERSION };
|
|
217
222
|
this.saveManifest();
|
|
218
223
|
this.progress('Installed Weave');
|
|
219
224
|
}
|
|
@@ -236,20 +241,31 @@ export class Installer {
|
|
|
236
241
|
return { path: join(this.home, '.config', 'systemd', 'user', 'weave-node.service'), bytes: Buffer.from(`[Unit]\nDescription=Weave peer node\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${systemdQuote(this.binary, true)} node run\n${entries}WorkingDirectory=${systemdPath(this.weaveHome)}\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:${systemdPath(logPath)}\nStandardError=append:${systemdPath(logPath)}\n\n[Install]\nWantedBy=default.target\n`) };
|
|
237
242
|
}
|
|
238
243
|
|
|
239
|
-
async serviceCommand(args, required = true) {
|
|
244
|
+
async serviceCommand(args, required = true, timeout = 30_000) {
|
|
240
245
|
const command = this.platform === 'darwin' ? 'launchctl' : 'systemctl';
|
|
241
|
-
const result = await this.run(command, args, { env: this.env, timeout
|
|
246
|
+
const result = await this.run(command, args, { env: this.env, timeout });
|
|
242
247
|
if (required && result.code !== 0) fail('SERVICE_COMMAND_FAILED', `${command} ${args.join(' ')} failed. Confirm a logged-in user service session is available; use --no-service for a detached node.`);
|
|
243
248
|
return result;
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
async stopService() {
|
|
247
252
|
if (this.platform === 'darwin') {
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
const deadline = this.now() + 10_000;
|
|
254
|
+
const timeoutMessage = 'Timed out waiting for launchd deregistration. Its binary and configuration were preserved.';
|
|
255
|
+
await this.serviceCommand(['bootout', `gui/${this.uid}/${serviceName}`], false, 10_000);
|
|
256
|
+
for (;;) {
|
|
257
|
+
const remaining = deadline - this.now();
|
|
258
|
+
if (remaining <= 0) fail('SERVICE_STOP_FAILED', timeoutMessage);
|
|
259
|
+
const status = await this.serviceCommand(['print', `gui/${this.uid}/${serviceName}`], false, remaining);
|
|
260
|
+
const checkedAt = this.now();
|
|
261
|
+
if (checkedAt >= deadline) fail('SERVICE_STOP_FAILED', timeoutMessage);
|
|
262
|
+
if (status.code !== 0) {
|
|
263
|
+
const missing = `Could not find service "${serviceName}" in domain for user gui: ${this.uid}`;
|
|
264
|
+
if (status.code !== 113 || !status.stderr.includes(missing)) fail('SERVICE_STOP_UNKNOWN', 'Could not verify launchd shutdown. Its binary and configuration were preserved.');
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
await this.sleep(Math.min(100, deadline - checkedAt));
|
|
268
|
+
}
|
|
253
269
|
} else {
|
|
254
270
|
await this.serviceCommand(['--user', 'disable', '--now', 'weave-node.service'], false);
|
|
255
271
|
const status = await this.serviceCommand(['--user', 'is-active', '--quiet', 'weave-node.service'], false);
|