pinomad 0.3.0 → 0.5.0

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.
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  main,
4
4
  restartService
5
- } from "./chunk-t9y2wfk5.js";
6
- import"./chunk-3tf23vjq.js";
7
- import"./chunk-bggesvnj.js";
8
- import"./chunk-c155p0t0.js";
5
+ } from "./chunk-aqj4wg33.js";
6
+ import"./chunk-1d9sppj0.js";
7
+ import"./chunk-m4q9wafw.js";
8
+ import"./chunk-hsp12cjt.js";
9
9
  import"./chunk-cjfmfyjb.js";
10
10
  import"./chunk-df32avw6.js";
11
11
  export {
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertSecureServerOrigin,
4
+ probeServerAccounts,
5
+ runServerOperation
6
+ } from "./chunk-26d04z02.js";
7
+ import {
8
+ loadHostKey,
9
+ loadRelayKey,
10
+ writeServerConfig
11
+ } from "./chunk-ebfaf3k2.js";
12
+ import {
13
+ relayHostId
14
+ } from "./chunk-kmbh44dt.js";
15
+ import {
16
+ argValue,
17
+ installSpec,
18
+ restartService,
19
+ serviceManager,
20
+ withoutRelayArgs
21
+ } from "./chunk-aqj4wg33.js";
22
+ import {
23
+ commandHint,
24
+ hostEntry
25
+ } from "./chunk-1d9sppj0.js";
26
+ import"./chunk-m4q9wafw.js";
27
+ import"./chunk-hsp12cjt.js";
28
+ import {
29
+ DEFAULT_DATA_DIR
30
+ } from "./chunk-cjfmfyjb.js";
31
+ import"./chunk-df32avw6.js";
32
+
33
+ // apps/host/src/cli/join.ts
34
+ import { mkdir } from "node:fs/promises";
35
+ import { homedir, hostname } from "node:os";
36
+ import { resolve } from "node:path";
37
+ import { parseArgs } from "node:util";
38
+ var message = (error) => error instanceof Error ? error.message : String(error);
39
+ async function joinServer(args, deps) {
40
+ const origin = args.origin.replace(/\/+$/, "");
41
+ const dataDir = resolve(args.dataDir);
42
+ assertSecureServerOrigin(origin);
43
+ await mkdir(dataDir, { recursive: true });
44
+ const signingKey = await loadRelayKey(dataDir);
45
+ const hostKey = await loadHostKey(dataDir);
46
+ const hostId = relayHostId(signingKey.publicKey);
47
+ const manager = deps?.manager ?? serviceManager();
48
+ const installed = await manager.state().then((state) => state.installed, () => false);
49
+ const previous = installed ? await manager.read() : undefined;
50
+ if (previous !== undefined && resolve(previous.dataDir) !== dataDir) {
51
+ throw new Error(`the installed service uses a different data dir (${previous.dataDir}); ` + `join it with --data-dir ${previous.dataDir} or uninstall the service first`);
52
+ }
53
+ const probe = await probeServerAccounts(origin, deps?.fetch);
54
+ if (probe === "unreachable")
55
+ throw new Error(`cannot reach ${origin} — check the address and that the server is up`);
56
+ if (probe === "disabled") {
57
+ throw new Error(`${origin} does not run server accounts — the server is too old or was started without a data dir`);
58
+ }
59
+ const reply = await runServerOperation({
60
+ origin,
61
+ signingKey,
62
+ noisePublicKey: hostKey.publicKey,
63
+ op: "join",
64
+ joinToken: args.token,
65
+ name: args.name ?? deps?.hostname ?? hostname()
66
+ });
67
+ if (reply.t === "refused") {
68
+ throw new Error(reply.reason === "expired_token" ? "the join token expired — generate a fresh one" : reply.reason === "invalid_token" ? "the join token is invalid or already spent — generate a fresh one" : `the server refused the join: ${reply.reason}`);
69
+ }
70
+ if (reply.t !== "joined")
71
+ throw new Error(`unexpected answer to join: ${reply.t}`);
72
+ const username = reply.username;
73
+ let service;
74
+ try {
75
+ await writeServerConfig(dataDir, { origin });
76
+ const freshSpec = installSpec(["--data-dir", dataDir], {
77
+ execPath: process.execPath,
78
+ env: process.env,
79
+ home: homedir(),
80
+ cwd: process.cwd(),
81
+ mainPath: hostEntry.script,
82
+ entryArgs: hostEntry.args
83
+ });
84
+ if (!installed) {
85
+ await manager.install(freshSpec);
86
+ service = "installed";
87
+ } else {
88
+ const spec = previous === undefined ? freshSpec : { ...previous, hostArgs: withoutRelayArgs(previous.hostArgs) };
89
+ const port = argValue(spec.hostArgs, "--port");
90
+ const code = await restartService({
91
+ force: false,
92
+ argv: ["--data-dir", dataDir, ...port === undefined ? [] : ["--url", `ws://127.0.0.1:${port}`]],
93
+ manager,
94
+ restart: async () => {
95
+ await manager.install(spec);
96
+ }
97
+ });
98
+ service = code === 0 ? "updated" : "failed";
99
+ }
100
+ } catch (error) {
101
+ throw new Error(`joined ${username} at ${origin}, but local setup failed: ${message(error)}
102
+ ` + `generate a fresh join token and run the command again (re-joining the same host is safe)`);
103
+ }
104
+ return { hostId, username, origin, service };
105
+ }
106
+ async function main(argv) {
107
+ const { values, positionals } = parseArgs({
108
+ args: [...argv],
109
+ allowPositionals: true,
110
+ options: { "data-dir": { type: "string" }, name: { type: "string" } }
111
+ });
112
+ const [origin, token] = positionals;
113
+ if (origin === undefined || token === undefined) {
114
+ console.error(`usage: ${commandHint("join")} <server-origin> <join-token> [--data-dir DIR] [--name NAME]`);
115
+ process.exit(1);
116
+ }
117
+ try {
118
+ const outcome = await joinServer({
119
+ origin,
120
+ token,
121
+ dataDir: values["data-dir"] ?? DEFAULT_DATA_DIR,
122
+ ...values.name === undefined ? {} : { name: values.name }
123
+ });
124
+ console.log(`host id: ${outcome.hostId}`);
125
+ console.log(`joined ${outcome.username} at ${outcome.origin}`);
126
+ if (outcome.service === "installed")
127
+ console.log("service installed and started");
128
+ else if (outcome.service === "updated")
129
+ console.log("service definition updated; host restarted when idle");
130
+ else {
131
+ console.error("joined, but the service restart did not finish — check `service status`");
132
+ process.exitCode = 1;
133
+ }
134
+ } catch (error) {
135
+ console.error(message(error));
136
+ process.exitCode = 1;
137
+ }
138
+ }
139
+ if (false)
140
+ ;
141
+ export {
142
+ main,
143
+ joinServer
144
+ };
@@ -2,10 +2,10 @@ import {
2
2
  commandHint,
3
3
  hostEntry,
4
4
  packagedVersion
5
- } from "./chunk-3tf23vjq.js";
5
+ } from "./chunk-1d9sppj0.js";
6
6
  import {
7
7
  connectRemoteDurable
8
- } from "./chunk-bggesvnj.js";
8
+ } from "./chunk-m4q9wafw.js";
9
9
  import {
10
10
  DEFAULT_DATA_DIR,
11
11
  hostAddress
@@ -66,6 +66,86 @@ function installSpec(argv, inputs) {
66
66
  }
67
67
  var launchdPlistPath = (home) => join(home, "Library", "LaunchAgents", "pinomad.host.plist");
68
68
  var systemdUnitPath = (home) => join(home, ".config", "systemd", "user", "pinomad.service");
69
+ function argValue(args, flag) {
70
+ let value;
71
+ for (let i = 0;i < args.length; i += 1) {
72
+ const arg = args[i];
73
+ if (arg === flag)
74
+ value = args[i + 1];
75
+ else if (arg.startsWith(`${flag}=`))
76
+ value = arg.slice(flag.length + 1);
77
+ }
78
+ return value;
79
+ }
80
+ function withoutRelayArgs(args) {
81
+ const out = [];
82
+ for (let i = 0;i < args.length; i += 1) {
83
+ const arg = args[i];
84
+ if (arg === "--relay") {
85
+ i += 1;
86
+ continue;
87
+ }
88
+ if (arg.startsWith("--relay="))
89
+ continue;
90
+ out.push(arg);
91
+ }
92
+ return out;
93
+ }
94
+ var unescapeExec = (value) => value.replace(/\\(.)/g, "$1");
95
+ var unspecify = (value) => value.replace(/%%/g, "%").replace(/\$\$/g, "$");
96
+ var xmlUnescape = (value) => value.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
97
+ function specFromProgram(program, env, home) {
98
+ const at = program.indexOf("--data-dir");
99
+ if (program.length < 4 || at < 2 || program[at + 1] === undefined) {
100
+ throw new Error("service definition has no --data-dir argument boundary");
101
+ }
102
+ return {
103
+ nodePath: program[0],
104
+ mainPath: program[1],
105
+ entryArgs: program.slice(2, at),
106
+ dataDir: program[at + 1],
107
+ hostArgs: program.slice(at + 2),
108
+ env: {
109
+ PATH: env.PATH ?? "",
110
+ ...env.LANG === undefined ? {} : { LANG: env.LANG },
111
+ ...env.LC_ALL === undefined ? {} : { LC_ALL: env.LC_ALL }
112
+ },
113
+ home
114
+ };
115
+ }
116
+ function specFromSystemdUnit(text) {
117
+ const exec = /^ExecStart=(.*)$/m.exec(text)?.[1];
118
+ if (exec === undefined)
119
+ throw new Error("service unit has no ExecStart");
120
+ const program = [...exec.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => unspecify(unescapeExec(match[1])));
121
+ const env = {};
122
+ const envLine = /^Environment=(.*)$/m.exec(text)?.[1] ?? "";
123
+ for (const match of envLine.matchAll(/"([^"]*)"/g)) {
124
+ const pair = unspecify(match[1]);
125
+ const eq = pair.indexOf("=");
126
+ if (eq > 0)
127
+ env[pair.slice(0, eq)] = pair.slice(eq + 1);
128
+ }
129
+ const workdir = /^WorkingDirectory=(.*)$/m.exec(text)?.[1];
130
+ if (workdir === undefined)
131
+ throw new Error("service unit has no WorkingDirectory");
132
+ return specFromProgram(program, env, unspecify(workdir));
133
+ }
134
+ function specFromLaunchdPlist(text) {
135
+ const array = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(text)?.[1];
136
+ if (array === undefined)
137
+ throw new Error("plist has no ProgramArguments");
138
+ const program = [...array.matchAll(/<string>([\s\S]*?)<\/string>/g)].map((match) => xmlUnescape(match[1]));
139
+ const env = {};
140
+ const dict = /<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/.exec(text)?.[1] ?? "";
141
+ for (const match of dict.matchAll(/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/g)) {
142
+ env[xmlUnescape(match[1])] = xmlUnescape(match[2]);
143
+ }
144
+ const workdir = /<key>WorkingDirectory<\/key>\s*<string>([\s\S]*?)<\/string>/.exec(text)?.[1];
145
+ if (workdir === undefined)
146
+ throw new Error("plist has no WorkingDirectory");
147
+ return specFromProgram(program, env, xmlUnescape(workdir));
148
+ }
69
149
  var xmlEscape = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
70
150
  function launchdPlist(spec) {
71
151
  const program = [spec.nodePath, spec.mainPath, ...spec.entryArgs, "--data-dir", spec.dataDir, ...spec.hostArgs];
@@ -214,6 +294,10 @@ var launchd = {
214
294
  process.stdout.write(`${all.slice(-lines).join(`
215
295
  `)}
216
296
  `);
297
+ },
298
+ async read() {
299
+ const text = await readFile(launchdPlistPath(homedir()), "utf8").then((value) => value, (error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
300
+ return text === undefined ? undefined : specFromLaunchdPlist(text);
217
301
  }
218
302
  };
219
303
  var systemd = {
@@ -273,6 +357,10 @@ var systemd = {
273
357
  stdio: "inherit"
274
358
  });
275
359
  await new Promise((resolve2) => journal.once("exit", () => resolve2()));
360
+ },
361
+ async read() {
362
+ const text = await readFile(systemdUnitPath(homedir()), "utf8").then((value) => value, (error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
363
+ return text === undefined ? undefined : specFromSystemdUnit(text);
276
364
  }
277
365
  };
278
366
  function serviceManager(platform = process.platform) {
@@ -339,8 +427,12 @@ async function status(argv) {
339
427
  console.log(`host: unreachable (${message(error)})`);
340
428
  }
341
429
  }
342
- async function restartService({ force, argv }) {
343
- const manager = serviceManager();
430
+ async function restartService({
431
+ force,
432
+ argv,
433
+ manager = serviceManager(),
434
+ restart
435
+ }) {
344
436
  if (!(await manager.state()).installed) {
345
437
  console.log(`service not installed (${commandHint("service")} install)`);
346
438
  return 1;
@@ -360,7 +452,7 @@ async function restartService({ force, argv }) {
360
452
  remote.close();
361
453
  } catch {}
362
454
  }
363
- await manager.restart();
455
+ await (restart?.() ?? manager.restart());
364
456
  if (address === undefined) {
365
457
  console.log("restart requested (host address unknown; not polling)");
366
458
  return 0;
@@ -427,4 +519,4 @@ async function main(argv) {
427
519
  if (false)
428
520
  ;
429
521
 
430
- export { serviceManager, restartService, main };
522
+ export { installSpec, argValue, withoutRelayArgs, serviceManager, restartService, main };
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  restartService,
4
4
  serviceManager
5
- } from "./chunk-t9y2wfk5.js";
5
+ } from "./chunk-aqj4wg33.js";
6
6
  import {
7
7
  globalPrefixOf,
8
8
  packageDir,
9
9
  packagedVersion
10
- } from "./chunk-3tf23vjq.js";
11
- import"./chunk-bggesvnj.js";
12
- import"./chunk-c155p0t0.js";
10
+ } from "./chunk-1d9sppj0.js";
11
+ import"./chunk-m4q9wafw.js";
12
+ import"./chunk-hsp12cjt.js";
13
13
  import"./chunk-cjfmfyjb.js";
14
14
  import"./chunk-df32avw6.js";
15
15