pinomad 0.11.0 → 0.12.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.
- package/README.md +1 -1
- package/dist/{chunk-b457q0tm.js → chunk-41q0saz0.js} +11 -8
- package/dist/chunk-42nqtx5r.js +22 -0
- package/dist/{chunk-wb10efyq.js → chunk-445cevpm.js} +1 -1
- package/dist/{chunk-xqycs43g.js → chunk-47ff1d1t.js} +35 -7
- package/dist/{chunk-n1m1tjrk.js → chunk-5qf1grkk.js} +19 -13
- package/dist/{chunk-ncbqa8jk.js → chunk-7mn38n4f.js} +1 -1
- package/dist/{chunk-sd5fkxjj.js → chunk-7wj0jh3t.js} +1 -1
- package/dist/{chunk-4dcs8sbj.js → chunk-agq4tn9r.js} +43 -21
- package/dist/{chunk-as6pyf00.js → chunk-aqj9wfmb.js} +1 -1
- package/dist/{chunk-y127fe1a.js → chunk-g3ahp7b9.js} +11 -0
- package/dist/{chunk-gtdpjqa3.js → chunk-gg2dcqbg.js} +186 -45
- package/dist/{chunk-6d16c2fv.js → chunk-jeqsdcj1.js} +13 -6
- package/dist/{chunk-67gq282t.js → chunk-ksfxqd8s.js} +491 -34
- package/dist/{chunk-9h5vawnm.js → chunk-mnmqyq7d.js} +237 -27
- package/dist/{chunk-jt34g8st.js → chunk-np4jd070.js} +154 -2
- package/dist/{chunk-44pnts98.js → chunk-nppgxh6c.js} +1 -1
- package/dist/chunk-nq57fdnq.js +106 -0
- package/dist/{chunk-vvj9b1gc.js → chunk-t5abqsva.js} +174 -52
- package/dist/{chunk-tj57aqav.js → chunk-vqkb70d7.js} +2 -2
- package/dist/{chunk-h5q2y57m.js → chunk-vx7dy07y.js} +10 -4
- package/dist/pinomad.js +15 -14
- package/package.json +2 -1
- package/web/assets/astro-DOly5024.js +1 -0
- package/web/assets/c-BIGW1oBm.js +1 -0
- package/web/assets/core-DnpzcQzs.js +4 -0
- package/web/assets/cpp-DddhVWhK.js +1 -0
- package/web/assets/csharp-B_TYUkTI.js +1 -0
- package/web/assets/css-Z72CF80r.js +1 -0
- package/web/assets/dart-CtKP3W-j.js +1 -0
- package/web/assets/diff-D97Zzqfu.js +1 -0
- package/web/assets/dockerfile-BcOcwvcX.js +1 -0
- package/web/assets/elixir-DkJLSxtU.js +1 -0
- package/web/assets/engine-javascript-BHPwbUfN.js +141 -0
- package/web/assets/engine-oniguruma-CQjQPVHy.js +1 -0
- package/web/assets/fish-BvzEVeQv.js +1 -0
- package/web/assets/go-C27-OAKa.js +1 -0
- package/web/assets/graphql-ChdNCCLP.js +1 -0
- package/web/assets/haskell-Df6bDoY_.js +1 -0
- package/web/assets/hcl-BWvSN4gD.js +1 -0
- package/web/assets/highlighted-diff-Dn5ifsvK.js +175 -0
- package/web/assets/highlights-DnHv6NBM.js +4 -0
- package/web/assets/highlights-themes-CUdrlZk3.js +1 -0
- package/web/assets/html-BlS82ilI.js +1 -0
- package/web/assets/{index-DoyHMPwf.css → index-BV0ecolo.css} +1 -1
- package/web/assets/index-DNsTTgPE.js +396 -0
- package/web/assets/ini-BEwlwnbL.js +1 -0
- package/web/assets/java-CylS5w8V.js +1 -0
- package/web/assets/javascript-wDzz0qaB.js +1 -0
- package/web/assets/json-Cp-IABpG.js +1 -0
- package/web/assets/jsonc-Des-eS-w.js +1 -0
- package/web/assets/jsx-g9-lgVsj.js +1 -0
- package/web/assets/kotlin-BdnUsdx6.js +1 -0
- package/web/assets/less-B1dDrJ26.js +1 -0
- package/web/assets/lua-BaeVxFsk.js +1 -0
- package/web/assets/make-CHLpvVh8.js +1 -0
- package/web/assets/markdown-Cvjx9yec.js +1 -0
- package/web/assets/nix-CwoSXNpI.js +1 -0
- package/web/assets/objective-c-DXmwc3jG.js +1 -0
- package/web/assets/php-DoruYMF0.js +1 -0
- package/web/assets/pierre-dark-CyvmCCZW.js +1 -0
- package/web/assets/pierre-dark-VKo95ZgA.js +1 -0
- package/web/assets/pierre-light-480U9XYS.js +1 -0
- package/web/assets/pierre-light-BVSW-Xev.js +1 -0
- package/web/assets/postcss-DnzrXTVv.js +1 -0
- package/web/assets/powershell-Dd0kTxKP.js +1 -0
- package/web/assets/prisma-Vru482bI.js +1 -0
- package/web/assets/proto-C7zT0LnQ.js +1 -0
- package/web/assets/python-B6aJPvgy.js +1 -0
- package/web/assets/ruby-B43yHlf9.js +1 -0
- package/web/assets/rust-DPvrY3IQ.js +1 -0
- package/web/assets/scala-CqE71os6.js +1 -0
- package/web/assets/scss-Cw6XMfVs.js +1 -0
- package/web/assets/shellscript-Yzrsuije.js +1 -0
- package/web/assets/shiki-BuPP5HCg.js +2 -0
- package/web/assets/shiki-langs-FJk0v0Ga.js +2 -0
- package/web/assets/shiki-wasm-CgFRq91U.js +1 -0
- package/web/assets/sql-DkvoVjww.js +1 -0
- package/web/assets/stream-tokenizer-Bl6bisIa.js +4 -0
- package/web/assets/svelte-DoIXUgJc.js +1 -0
- package/web/assets/swift-C2oV4EkX.js +1 -0
- package/web/assets/terraform-BETggiCN.js +1 -0
- package/web/assets/theming-themes-BL_CC-Mm.js +1 -0
- package/web/assets/toml-vGWfd6FD.js +1 -0
- package/web/assets/tsx-COt5Ahok.js +1 -0
- package/web/assets/typescript-BPQ3VLAy.js +1 -0
- package/web/assets/vue-CwSeJimo.js +1 -0
- package/web/assets/xml-sdJ4AIDG.js +1 -0
- package/web/assets/xterm-view-6GBZ9nXN.css +32 -0
- package/web/assets/xterm-view-ZsuzJb2C.js +38 -0
- package/web/assets/yaml-Buea-lGh.js +1 -0
- package/web/assets/zig-VOosw3JB.js +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-etf7ef97.js +0 -55
- package/dist/chunk-greb3dp6.js +0 -22
- package/web/assets/index-BhHrKpoo.js +0 -395
|
@@ -3,28 +3,28 @@ import {
|
|
|
3
3
|
isSecureServerOrigin,
|
|
4
4
|
probeServerAccounts,
|
|
5
5
|
runServerOperation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7mn38n4f.js";
|
|
7
7
|
import {
|
|
8
8
|
constantValue,
|
|
9
9
|
openHostSettings
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-nppgxh6c.js";
|
|
11
11
|
import {
|
|
12
12
|
loadHostKey,
|
|
13
13
|
loadRelayKey
|
|
14
14
|
} from "./chunk-t91zt5f0.js";
|
|
15
15
|
import {
|
|
16
16
|
writeServerConfig
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-nq57fdnq.js";
|
|
18
18
|
import {
|
|
19
19
|
relayHostId
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-g3ahp7b9.js";
|
|
21
21
|
import {
|
|
22
22
|
argValue,
|
|
23
23
|
installSpec,
|
|
24
24
|
restartService,
|
|
25
25
|
serviceManager,
|
|
26
26
|
withoutRelayArgs
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-t5abqsva.js";
|
|
28
28
|
import {
|
|
29
29
|
cleanHostName,
|
|
30
30
|
normalizeHostName
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import {
|
|
36
36
|
commandHint,
|
|
37
37
|
hostEntry
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-7wj0jh3t.js";
|
|
39
39
|
|
|
40
40
|
// apps/host/src/cli/join.ts
|
|
41
41
|
import { mkdir } from "node:fs/promises";
|
|
@@ -45,7 +45,10 @@ import { parseArgs } from "node:util";
|
|
|
45
45
|
|
|
46
46
|
// apps/host/src/cli/host-link.ts
|
|
47
47
|
import { spawn } from "node:child_process";
|
|
48
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
49
|
+
import { createServer } from "node:http";
|
|
48
50
|
import { arch, platform } from "node:os";
|
|
51
|
+
var CODE_EXPIRED = "the code expired before anyone confirmed it — run the command again for a new one";
|
|
49
52
|
var SLOW_DOWN_STEP_MS = 5000;
|
|
50
53
|
var REQUEST_TIMEOUT_MS = 15000;
|
|
51
54
|
var EXPIRY_GRACE_MS = 2 * 60000;
|
|
@@ -58,18 +61,28 @@ function browserCommand(os, env) {
|
|
|
58
61
|
return "xdg-open";
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
var OPENER_SETTLE_MS = 2000;
|
|
65
|
+
function openInBrowser(url, command = browserCommand(process.platform, process.env), settleMs = OPENER_SETTLE_MS) {
|
|
63
66
|
if (command === undefined)
|
|
64
|
-
return false;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
return Promise.resolve(false);
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
let timer;
|
|
70
|
+
const settle = (opened) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
resolve(opened);
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const child = spawn(command, [url], { detached: true, stdio: "ignore" });
|
|
76
|
+
child.once("spawn", () => {
|
|
77
|
+
child.unref();
|
|
78
|
+
timer = setTimeout(() => settle(true), settleMs);
|
|
79
|
+
});
|
|
80
|
+
child.once("error", () => settle(false));
|
|
81
|
+
child.once("exit", (code) => settle(code === 0));
|
|
82
|
+
} catch {
|
|
83
|
+
settle(false);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
73
86
|
}
|
|
74
87
|
function hostOsLabel() {
|
|
75
88
|
const os = platform();
|
|
@@ -78,6 +91,29 @@ function hostOsLabel() {
|
|
|
78
91
|
}
|
|
79
92
|
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
80
93
|
var errorCode = (body) => isRecord(body) && typeof body.error === "string" ? body.error : undefined;
|
|
94
|
+
var escapeHtml = (text) => text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
95
|
+
function answerPage(response, status, text) {
|
|
96
|
+
response.writeHead(status, {
|
|
97
|
+
"content-type": "text/html; charset=utf-8",
|
|
98
|
+
"cache-control": "no-store",
|
|
99
|
+
"referrer-policy": "no-referrer",
|
|
100
|
+
"content-security-policy": "default-src 'none'",
|
|
101
|
+
connection: "close"
|
|
102
|
+
});
|
|
103
|
+
response.end(`<!doctype html><meta charset="utf-8"><title>PiNomad</title><p>${escapeHtml(text)}</p>
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
function pkcePair() {
|
|
107
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
108
|
+
return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
|
|
109
|
+
}
|
|
110
|
+
var codeStart = (body) => isRecord(body) && typeof body.deviceCode === "string" && typeof body.userCode === "string" && typeof body.verifyUrl === "string" && typeof body.interval === "number" && typeof body.expiresAt === "number" && isSecureServerOrigin(body.verifyUrl) ? {
|
|
111
|
+
deviceCode: body.deviceCode,
|
|
112
|
+
userCode: body.userCode,
|
|
113
|
+
verifyUrl: body.verifyUrl,
|
|
114
|
+
expiresAt: body.expiresAt,
|
|
115
|
+
interval: body.interval
|
|
116
|
+
} : undefined;
|
|
81
117
|
async function requestJoinToken(args, deps) {
|
|
82
118
|
const fetchImpl = deps?.fetch ?? fetch;
|
|
83
119
|
const sleep = deps?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -103,13 +139,13 @@ async function requestJoinToken(args, deps) {
|
|
|
103
139
|
signal: controller.signal
|
|
104
140
|
});
|
|
105
141
|
const text = await response.text();
|
|
106
|
-
let
|
|
142
|
+
let body;
|
|
107
143
|
try {
|
|
108
|
-
|
|
144
|
+
body = JSON.parse(text);
|
|
109
145
|
} catch {
|
|
110
|
-
|
|
146
|
+
body = undefined;
|
|
111
147
|
}
|
|
112
|
-
return { status: response.status, body
|
|
148
|
+
return { status: response.status, body };
|
|
113
149
|
})().catch(() => {
|
|
114
150
|
return;
|
|
115
151
|
});
|
|
@@ -119,41 +155,144 @@ async function requestJoinToken(args, deps) {
|
|
|
119
155
|
clearTimeout(timer);
|
|
120
156
|
}
|
|
121
157
|
};
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
158
|
+
const startBody = { hostId: args.hostId, name: args.name, os: args.os ?? hostOsLabel() };
|
|
159
|
+
const openBrowser = async (url) => Promise.resolve((deps?.openBrowser ?? openInBrowser)(url)).catch(() => false);
|
|
160
|
+
const start = async (payload) => {
|
|
161
|
+
const started2 = await post("/api/host-link/start", payload);
|
|
162
|
+
if (started2 === undefined)
|
|
163
|
+
throw new Error(`cannot reach ${origin} — check the address and that the server is up`);
|
|
164
|
+
if (started2.status === 200)
|
|
165
|
+
return started2.body;
|
|
166
|
+
const code = errorCode(started2.body);
|
|
167
|
+
if (started2.status === 404) {
|
|
129
168
|
throw new Error(`${origin} can't confirm hosts in the browser yet — create a join token under "Add a host" and run the join command with it`);
|
|
130
169
|
}
|
|
131
170
|
if (code === "rate_limited")
|
|
132
171
|
throw new Error("too many join requests from this address — wait a few minutes and try again");
|
|
133
172
|
if (code === "busy")
|
|
134
173
|
throw new Error(`${origin} has too many hosts waiting to join right now — try again in a few minutes`);
|
|
135
|
-
throw new Error(`${origin} refused to start the join: ${code ?? `HTTP ${
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
174
|
+
throw new Error(`${origin} refused to start the join: ${code ?? `HTTP ${started2.status}`}`);
|
|
175
|
+
};
|
|
176
|
+
const unexpected = () => new Error(`${origin} answered the join request with something unexpected`);
|
|
177
|
+
const viaLoopback = async () => {
|
|
178
|
+
const { verifier, challenge } = pkcePair();
|
|
179
|
+
const state = randomBytes(16).toString("base64url");
|
|
180
|
+
let settle;
|
|
181
|
+
const callback = new Promise((resolve) => {
|
|
182
|
+
settle = resolve;
|
|
183
|
+
});
|
|
184
|
+
const listener = createServer((request, response) => {
|
|
185
|
+
const url = URL.parse(request.url ?? "/", "http://127.0.0.1");
|
|
186
|
+
if (url === null) {
|
|
187
|
+
answerPage(response, 400, "Bad request.");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (request.method !== "GET" || url.pathname !== "/callback" || url.searchParams.get("state") !== state) {
|
|
191
|
+
answerPage(response, 404, "Not found.");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (settle === undefined) {
|
|
195
|
+
answerPage(response, 409, "This join was already answered. You can close this page.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const deliver = settle;
|
|
199
|
+
settle = undefined;
|
|
200
|
+
const closed = new Promise((resolve) => response.once("close", () => resolve()));
|
|
201
|
+
deliver({
|
|
202
|
+
params: url.searchParams,
|
|
203
|
+
reply: async (status, text) => {
|
|
204
|
+
answerPage(response, status, text);
|
|
205
|
+
await closed;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
await new Promise((resolve, reject) => {
|
|
210
|
+
listener.once("error", reject);
|
|
211
|
+
listener.listen(0, "127.0.0.1", () => resolve());
|
|
212
|
+
});
|
|
213
|
+
try {
|
|
214
|
+
const { port } = listener.address();
|
|
215
|
+
const body = await start({
|
|
216
|
+
...startBody,
|
|
217
|
+
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
218
|
+
codeChallenge: challenge,
|
|
219
|
+
state
|
|
220
|
+
});
|
|
221
|
+
if (!isRecord(body) || typeof body.requestId !== "string") {
|
|
222
|
+
const fallback = codeStart(body);
|
|
223
|
+
if (fallback === undefined)
|
|
224
|
+
throw unexpected();
|
|
225
|
+
return { fallback };
|
|
226
|
+
}
|
|
227
|
+
if (typeof body.verifyUrl !== "string" || typeof body.expiresAt !== "number" || !isSecureServerOrigin(body.verifyUrl)) {
|
|
228
|
+
throw unexpected();
|
|
229
|
+
}
|
|
230
|
+
const { verifyUrl: verifyUrl2, expiresAt: expiresAt2 } = body;
|
|
231
|
+
if (!await openBrowser(verifyUrl2)) {
|
|
232
|
+
browserOpens = false;
|
|
233
|
+
return { fallback: undefined };
|
|
234
|
+
}
|
|
235
|
+
const minutes2 = Math.max(1, Math.round((expiresAt2 - now()) / 60000));
|
|
236
|
+
log(`Opened ${verifyUrl2} in your browser to add this host to your account on ${origin}.`);
|
|
237
|
+
log(`Sign in there and confirm it on this computer — only if you started this on ${args.name} (host id ${args.hostId}).`);
|
|
238
|
+
log(`To confirm from another device instead, press Ctrl-C and run the command again with --code.`);
|
|
239
|
+
log(`Waiting for confirmation (the request expires in ${minutes2} min)…`);
|
|
240
|
+
const wait = deps?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms).unref()));
|
|
241
|
+
const answer = await Promise.race([
|
|
242
|
+
callback,
|
|
243
|
+
wait(Math.max(0, expiresAt2 + EXPIRY_GRACE_MS - now())).then(() => {
|
|
244
|
+
return;
|
|
245
|
+
})
|
|
246
|
+
]);
|
|
247
|
+
if (answer === undefined)
|
|
248
|
+
throw new Error("the request expired before anyone confirmed it — run the command again");
|
|
249
|
+
const { params, reply } = answer;
|
|
250
|
+
if (params.get("error") === "access_denied") {
|
|
251
|
+
await reply(200, `Denied: ${args.name} was not added. You can close this page.`);
|
|
252
|
+
throw new Error(`the join was denied on ${origin}`);
|
|
253
|
+
}
|
|
254
|
+
const code = params.get("code");
|
|
255
|
+
const exchanged = code === null ? undefined : await post("/api/host-link/token", { code, codeVerifier: verifier });
|
|
256
|
+
const token = exchanged?.status === 200 && isRecord(exchanged.body) ? exchanged.body.token : undefined;
|
|
257
|
+
if (typeof token !== "string") {
|
|
258
|
+
await reply(502, "The join did not finish. Go back to the terminal and run the command again.");
|
|
259
|
+
throw new Error(`${origin} did not hand over the join token: ${errorCode(exchanged?.body) ?? (exchanged === undefined ? "no answer" : `HTTP ${exchanged.status}`)} — run the command again`);
|
|
260
|
+
}
|
|
261
|
+
await reply(200, `Confirmed: ${args.name} is joining your account. You can close this page.`);
|
|
262
|
+
log("Confirmed.");
|
|
263
|
+
return { token };
|
|
264
|
+
} finally {
|
|
265
|
+
listener.close();
|
|
266
|
+
listener.closeAllConnections();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
let browserOpens = args.code !== true;
|
|
270
|
+
let started;
|
|
271
|
+
if (args.code !== true && (deps?.loopback ?? browserCommand(process.platform, process.env) !== undefined)) {
|
|
272
|
+
const loopback = await viaLoopback();
|
|
273
|
+
if ("token" in loopback)
|
|
274
|
+
return loopback.token;
|
|
275
|
+
started = loopback.fallback;
|
|
139
276
|
}
|
|
140
|
-
|
|
141
|
-
|
|
277
|
+
started ??= codeStart(await start(startBody));
|
|
278
|
+
if (started === undefined)
|
|
279
|
+
throw unexpected();
|
|
280
|
+
const { deviceCode, userCode, verifyUrl, expiresAt } = started;
|
|
281
|
+
let intervalMs = Math.max(1, started.interval) * 1000;
|
|
142
282
|
const minutes = Math.max(1, Math.round((expiresAt - now()) / 60000));
|
|
143
283
|
log(`To add this host to your account on ${origin}:`);
|
|
144
284
|
log(` 1. Open ${verifyUrl} and sign in.`);
|
|
145
285
|
log(` 2. Enter this code: ${userCode}`);
|
|
146
286
|
log("");
|
|
147
287
|
log(`Only confirm it if you started this on ${args.name} (host id ${args.hostId}).`);
|
|
148
|
-
const opened = await
|
|
288
|
+
const opened = browserOpens && await openBrowser(verifyUrl);
|
|
149
289
|
log(`${opened ? "Opened the page in your browser. " : ""}Waiting for confirmation (the code expires in ${minutes} min)…`);
|
|
150
290
|
for (;; ) {
|
|
151
291
|
await sleep(intervalMs);
|
|
152
292
|
const response = await post("/api/host-link/poll", { deviceCode });
|
|
153
293
|
if (response === undefined || response.status >= 500) {
|
|
154
|
-
if (now() > expiresAt + EXPIRY_GRACE_MS)
|
|
155
|
-
throw new Error(
|
|
156
|
-
}
|
|
294
|
+
if (now() > expiresAt + EXPIRY_GRACE_MS)
|
|
295
|
+
throw new Error(CODE_EXPIRED);
|
|
157
296
|
continue;
|
|
158
297
|
}
|
|
159
298
|
const answer = response.body;
|
|
@@ -165,7 +304,7 @@ async function requestJoinToken(args, deps) {
|
|
|
165
304
|
if (code === "access_denied")
|
|
166
305
|
throw new Error(`the join was denied on ${origin}`);
|
|
167
306
|
if (code === "expired_token")
|
|
168
|
-
throw new Error(
|
|
307
|
+
throw new Error(CODE_EXPIRED);
|
|
169
308
|
if (code === "slow_down") {
|
|
170
309
|
const asked = isRecord(answer) && typeof answer.interval === "number" ? answer.interval * 1000 : 0;
|
|
171
310
|
intervalMs = Math.max(intervalMs + SLOW_DOWN_STEP_MS, asked);
|
|
@@ -179,7 +318,7 @@ async function requestJoinToken(args, deps) {
|
|
|
179
318
|
import { homedir } from "node:os";
|
|
180
319
|
import { resolve } from "node:path";
|
|
181
320
|
async function openServiceSlot(dataDir, manager) {
|
|
182
|
-
const state = await manager.state().catch(() => ({ installed: false, running: false, detail: "unknown" }));
|
|
321
|
+
const state = await manager.state().catch(() => ({ installed: false, stopped: false, running: false, detail: "unknown" }));
|
|
183
322
|
const previous = state.installed ? await manager.read() : undefined;
|
|
184
323
|
if (previous !== undefined && resolve(previous.dataDir) !== dataDir) {
|
|
185
324
|
throw new Error(`the installed service uses a different data dir (${previous.dataDir}); ` + `run this again with --data-dir ${previous.dataDir} or uninstall the service first`);
|
|
@@ -249,7 +388,7 @@ async function joinServer(args, deps) {
|
|
|
249
388
|
const current = settings.value;
|
|
250
389
|
const storedName = current.custom.includes("hostName") ? current.hostName : undefined;
|
|
251
390
|
const name = normalizeHostName(args.name ?? storedName ?? deps?.hostname ?? hostname());
|
|
252
|
-
const joinToken = args.token ?? await requestJoinToken({ origin, hostId, name }, deps);
|
|
391
|
+
const joinToken = args.token ?? await requestJoinToken({ origin, hostId, name, code: args.code }, deps);
|
|
253
392
|
const reply = await runServerOperation({
|
|
254
393
|
origin,
|
|
255
394
|
signingKey,
|
|
@@ -273,7 +412,7 @@ async function joinServer(args, deps) {
|
|
|
273
412
|
service = await writeService(slot, (hostArgs) => edit(withoutRelayArgs(hostArgs)), { changed: true });
|
|
274
413
|
} catch (error) {
|
|
275
414
|
throw new Error(`joined ${username} at ${origin}, but local setup failed: ${message(error)}
|
|
276
|
-
` + (args.token === undefined ? `run the command again and confirm
|
|
415
|
+
` + (args.token === undefined ? `run the command again and confirm it again (re-joining the same host is safe)` : `generate a fresh join token and run the command again (re-joining the same host is safe)`));
|
|
277
416
|
}
|
|
278
417
|
return { hostId, username, origin, service };
|
|
279
418
|
}
|
|
@@ -281,18 +420,20 @@ async function main(argv, deps) {
|
|
|
281
420
|
const { values, positionals } = parseArgs({
|
|
282
421
|
args: [...argv],
|
|
283
422
|
allowPositionals: true,
|
|
284
|
-
options: { "data-dir": { type: "string" }, name: { type: "string" } }
|
|
423
|
+
options: { "data-dir": { type: "string" }, name: { type: "string" }, code: { type: "boolean" } }
|
|
285
424
|
});
|
|
286
425
|
const [origin, token] = positionals;
|
|
287
426
|
if (origin === undefined || positionals.length > 2) {
|
|
288
|
-
console.error(`usage: ${commandHint("join")} <server-origin> [join-token] [--data-dir DIR] [--name NAME]`);
|
|
289
|
-
console.error(" without a join token, confirm
|
|
427
|
+
console.error(`usage: ${commandHint("join")} <server-origin> [join-token] [--code] [--data-dir DIR] [--name NAME]`);
|
|
428
|
+
console.error(" without a join token, confirm this host in the browser where you're signed in;");
|
|
429
|
+
console.error(" --code prints a code to confirm instead, from this or another device");
|
|
290
430
|
process.exit(1);
|
|
291
431
|
}
|
|
292
432
|
try {
|
|
293
433
|
const outcome = await joinServer({
|
|
294
434
|
origin,
|
|
295
435
|
...token === undefined ? {} : { token },
|
|
436
|
+
...values.code === true ? { code: true } : {},
|
|
296
437
|
dataDir: values["data-dir"] ?? DEFAULT_DATA_DIR,
|
|
297
438
|
...values.name === undefined ? {} : { name: values.name }
|
|
298
439
|
}, deps);
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
import {
|
|
3
3
|
restartService,
|
|
4
4
|
serviceManager
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import"./chunk-
|
|
5
|
+
} from "./chunk-t5abqsva.js";
|
|
6
|
+
import"./chunk-np4jd070.js";
|
|
7
7
|
import"./chunk-kzbzacz9.js";
|
|
8
|
-
import"./chunk-
|
|
8
|
+
import"./chunk-47ff1d1t.js";
|
|
9
9
|
import"./chunk-pqe7q1s7.js";
|
|
10
10
|
import {
|
|
11
11
|
hostAddressOptions
|
|
12
12
|
} from "./chunk-ngf1zatm.js";
|
|
13
13
|
import {
|
|
14
|
+
commandHint,
|
|
14
15
|
globalPrefixOf,
|
|
15
16
|
packageDir,
|
|
16
17
|
packagedVersion
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-7wj0jh3t.js";
|
|
18
19
|
import"./chunk-df32avw6.js";
|
|
19
20
|
|
|
20
21
|
// apps/host/src/cli/upgrade.ts
|
|
@@ -34,7 +35,7 @@ function npmBeside(execPath) {
|
|
|
34
35
|
const cli = join(binDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
35
36
|
return existsSync(cli) ? { command: execPath, args: [cli], binDir } : { command: "npm", args: [] };
|
|
36
37
|
}
|
|
37
|
-
var
|
|
38
|
+
var serviceState = () => Promise.resolve().then(() => serviceManager().state()).catch(() => ({ installed: false, stopped: false }));
|
|
38
39
|
async function main(argv, deps = {}) {
|
|
39
40
|
const { values } = parseArgs({
|
|
40
41
|
args: [...argv],
|
|
@@ -75,11 +76,17 @@ async function main(argv, deps = {}) {
|
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
+
const service = await (deps.serviceState ?? serviceState)();
|
|
80
|
+
if (!service.installed) {
|
|
79
81
|
console.log("service not installed; upgrade done, nothing restarted");
|
|
80
82
|
process.exitCode = 0;
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
85
|
+
if (service.stopped) {
|
|
86
|
+
console.log(`service stopped; upgrade done, it stays off until \`${commandHint("service")} start\``);
|
|
87
|
+
process.exitCode = 0;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
83
90
|
(deps.exit ?? process.exit)(await restart({ force: values.force, argv: argv.filter((arg) => arg !== "--force") }));
|
|
84
91
|
}
|
|
85
92
|
if (false) {}
|