moshcode 0.89.0 → 0.90.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/package.json +1 -1
- package/src/dns-service.mjs +167 -10
- package/src/dns.mjs +120 -13
package/package.json
CHANGED
package/src/dns-service.mjs
CHANGED
|
@@ -29,9 +29,8 @@
|
|
|
29
29
|
// it, and the entry is the script this very command was invoked from. Nothing
|
|
30
30
|
// is guessed and nothing depends on PATH.
|
|
31
31
|
import { spawn } from "node:child_process";
|
|
32
|
-
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
33
|
-
import { existsSync } from "node:fs";
|
|
34
|
-
import { homedir } from "node:os";
|
|
32
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
33
|
+
import { existsSync, statSync } from "node:fs";
|
|
35
34
|
import { dirname, join } from "node:path";
|
|
36
35
|
import { operatorHome } from "./trust.mjs";
|
|
37
36
|
|
|
@@ -48,10 +47,49 @@ export const UNIT_NAME = "moshcode-dns.service";
|
|
|
48
47
|
* /run/user/<uid>/moshpit-dns.pid for the daemon and for the person asking
|
|
49
48
|
* after it. Under a system unit those are two different paths.
|
|
50
49
|
*/
|
|
51
|
-
export function servicePaths({ system = false, home =
|
|
50
|
+
export function servicePaths({ system = false, home = operatorHome(), env = process.env } = {}) {
|
|
52
51
|
return system
|
|
53
52
|
? { path: join("/etc/systemd/system", UNIT_NAME), systemctl: ["systemctl"], scope: "system" }
|
|
54
|
-
: { path: join(home, ".config/systemd/user", UNIT_NAME), systemctl:
|
|
53
|
+
: { path: join(home, ".config/systemd/user", UNIT_NAME), systemctl: userSystemctl(env), scope: "user" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* How to reach the operator's own systemd from wherever this is running.
|
|
58
|
+
*
|
|
59
|
+
* `systemctl --user` talks to the session of whoever is running it. `dns enable`
|
|
60
|
+
* escalates, so from there it is root's session — which has no bridge in it, has
|
|
61
|
+
* never had one, and reports every query about one as "not loaded". Meanwhile
|
|
62
|
+
* the operator's bridge keeps running with whatever it started with.
|
|
63
|
+
*
|
|
64
|
+
* That is why enabling proxy mode could be detected, written, and still not take
|
|
65
|
+
* effect: the unit that had to change belongs to a session the escalated half of
|
|
66
|
+
* the command cannot see.
|
|
67
|
+
*
|
|
68
|
+
* So an escalated run drops back to the invoking user, and hands them the runtime
|
|
69
|
+
* directory their session bus lives in — deriving it rather than inheriting it,
|
|
70
|
+
* because sudo does not carry XDG_RUNTIME_DIR across and the default under sudo
|
|
71
|
+
* points at root's.
|
|
72
|
+
*/
|
|
73
|
+
export function userSystemctl(env = process.env, { home = operatorHome({ env }), owner = ownerOf } = {}) {
|
|
74
|
+
const user = env.SUDO_USER || env.DOAS_USER;
|
|
75
|
+
// Not escalated, or escalated from root itself: the session in reach is the
|
|
76
|
+
// right one.
|
|
77
|
+
if (!user || user === "root") return ["systemctl", "--user"];
|
|
78
|
+
// sudo publishes the uid; doas publishes only the name. Falling back to the
|
|
79
|
+
// owner of the operator's home covers that, and covers an escalator that
|
|
80
|
+
// publishes neither — without it, a doas machine would quietly address root's
|
|
81
|
+
// session, which has no bridge in it and never will.
|
|
82
|
+
const uid = env.SUDO_UID || env.DOAS_UID || owner(home);
|
|
83
|
+
if (uid === null || uid === undefined) return ["systemctl", "--user"];
|
|
84
|
+
return ["sudo", "-u", user, "env", `XDG_RUNTIME_DIR=/run/user/${uid}`, "systemctl", "--user"];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ownerOf(path) {
|
|
88
|
+
try {
|
|
89
|
+
return statSync(path).uid;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
55
93
|
}
|
|
56
94
|
|
|
57
95
|
/**
|
|
@@ -127,6 +165,8 @@ export function serviceUnit({
|
|
|
127
165
|
return lines.join("\n");
|
|
128
166
|
}
|
|
129
167
|
|
|
168
|
+
const defaultRead = async (path) => readFile(path, "utf8").catch(() => "");
|
|
169
|
+
|
|
130
170
|
function run(command, args) {
|
|
131
171
|
return new Promise((resolve) => {
|
|
132
172
|
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -140,8 +180,8 @@ function run(command, args) {
|
|
|
140
180
|
}
|
|
141
181
|
|
|
142
182
|
/** Write the unit and start it. Returns the steps taken, in order, for printing. */
|
|
143
|
-
export async function installService(unit, { system = false, home =
|
|
144
|
-
const { path, systemctl, scope } = servicePaths({ system, home });
|
|
183
|
+
export async function installService(unit, { system = false, home = operatorHome(), exec = run, env = process.env } = {}) {
|
|
184
|
+
const { path, systemctl, scope } = servicePaths({ system, home, env });
|
|
145
185
|
const steps = [];
|
|
146
186
|
try {
|
|
147
187
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -152,7 +192,14 @@ export async function installService(unit, { system = false, home = homedir(), e
|
|
|
152
192
|
}
|
|
153
193
|
|
|
154
194
|
const [cmd, ...flags] = systemctl;
|
|
155
|
-
|
|
195
|
+
// Enable *and* restart. `enable --now` starts a stopped unit and does nothing
|
|
196
|
+
// to a running one, so rewriting the unit to add `--proxy` would leave the old
|
|
197
|
+
// bridge running without it — the change on disk, no change in behaviour.
|
|
198
|
+
for (const args of [
|
|
199
|
+
[...flags, "daemon-reload"],
|
|
200
|
+
[...flags, "enable", UNIT_NAME],
|
|
201
|
+
[...flags, "restart", UNIT_NAME],
|
|
202
|
+
]) {
|
|
156
203
|
const result = await exec(cmd, args);
|
|
157
204
|
steps.push({ step: `${cmd} ${args.join(" ")}`, ok: result.ok, error: result.error });
|
|
158
205
|
if (!result.ok) return { ok: false, path, scope, steps };
|
|
@@ -160,9 +207,119 @@ export async function installService(unit, { system = false, home = homedir(), e
|
|
|
160
207
|
return { ok: true, path, scope, steps };
|
|
161
208
|
}
|
|
162
209
|
|
|
210
|
+
/**
|
|
211
|
+
* The `--upstream` servers an installed unit already forwards to.
|
|
212
|
+
*
|
|
213
|
+
* Read back rather than recomputed. A supervised bridge needs upstreams to hand
|
|
214
|
+
* the clearnet to, and the machine may already be routing every lookup at that
|
|
215
|
+
* bridge — so asking the system resolver what its upstreams are can answer
|
|
216
|
+
* "this bridge", and a bridge whose upstream is itself resolves nothing at all.
|
|
217
|
+
* Whatever the unit was working with is the safe answer to keep.
|
|
218
|
+
*/
|
|
219
|
+
export function unitUpstreams(text) {
|
|
220
|
+
const line = String(text ?? "").split("\n").find((l) => l.startsWith("ExecStart="));
|
|
221
|
+
if (!line) return [];
|
|
222
|
+
const parts = line.trim().split(/\s+/);
|
|
223
|
+
const found = [];
|
|
224
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
225
|
+
if (parts[i] !== "--upstream") continue;
|
|
226
|
+
const value = parts[i + 1];
|
|
227
|
+
if (!value || value.startsWith("--")) continue;
|
|
228
|
+
if (!found.includes(value)) found.push(value);
|
|
229
|
+
}
|
|
230
|
+
return found;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Re-describe the bridge unit to match the run happening now, and restart it so
|
|
235
|
+
* the description becomes the truth.
|
|
236
|
+
*
|
|
237
|
+
* This is the step that made proxy mode arrive one reboot late. A supervised
|
|
238
|
+
* bridge is not started by `enable`: it is already up under `Restart=always`,
|
|
239
|
+
* so `startDaemon` finds a live pidfile and reports "already running" — true,
|
|
240
|
+
* and useless, because what a resolver answers with is fixed when it spawns. A
|
|
241
|
+
* bridge that came up before the proxy existed goes on answering origins
|
|
242
|
+
* forever, and stopping it by hand does not help, since systemd brings the same
|
|
243
|
+
* ExecStart straight back.
|
|
244
|
+
*
|
|
245
|
+
* The only thing that changes a supervised bridge's mind is rewriting its unit
|
|
246
|
+
* and restarting it. That is all this is.
|
|
247
|
+
*
|
|
248
|
+
* With no unit installed it does nothing and says so. An unsupervised machine
|
|
249
|
+
* is `startDaemon`'s business, and writing a unit here would be `enable`
|
|
250
|
+
* quietly making the bridge outlive a reboot on a machine that never asked for
|
|
251
|
+
* that — a different decision, and one `dns service --write` exists to make.
|
|
252
|
+
*/
|
|
253
|
+
export async function refreshService({
|
|
254
|
+
entry,
|
|
255
|
+
port,
|
|
256
|
+
registryBase = null,
|
|
257
|
+
proxy = null,
|
|
258
|
+
system = false,
|
|
259
|
+
home = operatorHome(),
|
|
260
|
+
env = process.env,
|
|
261
|
+
exec = run,
|
|
262
|
+
exists = existsSync,
|
|
263
|
+
read = defaultRead,
|
|
264
|
+
} = {}) {
|
|
265
|
+
const { path, scope } = servicePaths({ system, home, env });
|
|
266
|
+
if (!exists(path)) return { refreshed: false, reason: "no unit installed", path, scope, upstreams: [], steps: [] };
|
|
267
|
+
|
|
268
|
+
const current = await read(path);
|
|
269
|
+
const upstreams = unitUpstreams(current);
|
|
270
|
+
const unit = serviceUnit({ system, entry, port, registryBase, upstreams, proxy });
|
|
271
|
+
|
|
272
|
+
// Deliberately not short-circuited on `current === unit`. Matching text says
|
|
273
|
+
// the unit describes the right bridge, not that the bridge is running it: a
|
|
274
|
+
// unit can be installed and stopped, installed and never enabled, or running
|
|
275
|
+
// what it was spawned with before the file last changed. Since the whole
|
|
276
|
+
// point here is to make what is running match what is written, the enable and
|
|
277
|
+
// restart happen either way, and cost a moment of no resolver during a
|
|
278
|
+
// command that is already rewriting the machine's routing.
|
|
279
|
+
|
|
280
|
+
const result = await installService(unit, { system, home, env, exec });
|
|
281
|
+
return {
|
|
282
|
+
refreshed: result.ok,
|
|
283
|
+
reason: result.ok ? null : "systemctl refused the unit",
|
|
284
|
+
path,
|
|
285
|
+
scope,
|
|
286
|
+
upstreams,
|
|
287
|
+
steps: result.steps,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Stop the supervised bridge and leave the unit where it is.
|
|
293
|
+
*
|
|
294
|
+
* `stopDaemon` cannot do this. It reads the pidfile and signals that process,
|
|
295
|
+
* which is right for a bridge started by hand and useless for one systemd owns:
|
|
296
|
+
* the unit is `Restart=always`, so the pid dies and the same ExecStart is back
|
|
297
|
+
* within the second. `disable` printed "bridge stopped" and left a bridge
|
|
298
|
+
* running — on a machine whose routing had just been put back, so the bridge
|
|
299
|
+
* was still up, still answering, and no longer on anybody's path.
|
|
300
|
+
*
|
|
301
|
+
* The unit file stays. Removing it is a different decision than turning
|
|
302
|
+
* resolution off for an afternoon, and `enable` re-enables what it finds — so
|
|
303
|
+
* leaving it costs nothing and deleting it would quietly take away a unit the
|
|
304
|
+
* operator may have written themselves.
|
|
305
|
+
*/
|
|
306
|
+
export async function stopService({ system = false, home = operatorHome(), env = process.env, exec = run, exists = existsSync } = {}) {
|
|
307
|
+
const { path, systemctl, scope } = servicePaths({ system, home, env });
|
|
308
|
+
if (!exists(path)) return { stopped: false, reason: "no unit installed", path, scope, steps: [] };
|
|
309
|
+
const [cmd, ...flags] = systemctl;
|
|
310
|
+
const result = await exec(cmd, [...flags, "disable", "--now", UNIT_NAME]);
|
|
311
|
+
return {
|
|
312
|
+
stopped: result.ok,
|
|
313
|
+
reason: result.ok ? null : (result.error || "systemctl refused"),
|
|
314
|
+
path,
|
|
315
|
+
scope,
|
|
316
|
+
steps: [{ step: `${cmd} ${flags.join(" ")} disable --now ${UNIT_NAME}`, ok: result.ok, error: result.error }],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
163
320
|
/** Stop it and take the unit away. Missing is not a failure — removal is idempotent. */
|
|
164
|
-
export async function removeService({ system = false, home =
|
|
165
|
-
const { path, systemctl, scope } = servicePaths({ system, home });
|
|
321
|
+
export async function removeService({ system = false, home = operatorHome(), exec = run, env = process.env } = {}) {
|
|
322
|
+
const { path, systemctl, scope } = servicePaths({ system, home, env });
|
|
166
323
|
const [cmd, ...flags] = systemctl;
|
|
167
324
|
const steps = [];
|
|
168
325
|
for (const args of [[...flags, "disable", "--now", UNIT_NAME]]) {
|
package/src/dns.mjs
CHANGED
|
@@ -2148,6 +2148,59 @@ export async function verifyResolution({
|
|
|
2148
2148
|
return { ok: checks.every((c) => c.ok), checks };
|
|
2149
2149
|
}
|
|
2150
2150
|
|
|
2151
|
+
/**
|
|
2152
|
+
* Wait for a bridge that systemd has just restarted to start answering.
|
|
2153
|
+
*
|
|
2154
|
+
* `startDaemon` cannot be asked this. It spawns, watches its own child, and
|
|
2155
|
+
* decides from a pidfile — none of which describes a unit that systemd owns and
|
|
2156
|
+
* has just cycled. Calling it here would either report the pre-restart pid as
|
|
2157
|
+
* "already running" or, on a pidfile not yet rewritten, spawn a second bridge
|
|
2158
|
+
* against the one systemd is bringing up.
|
|
2159
|
+
*
|
|
2160
|
+
* `Type=simple` reports active the moment the process forks, so systemd saying
|
|
2161
|
+
* the restart worked is not yet a resolver that answers. Hence the probe.
|
|
2162
|
+
*
|
|
2163
|
+
* A timeout is reported as started-but-unverified rather than as a failure, the
|
|
2164
|
+
* same way `startDaemon` treats a live process that has not answered yet: the
|
|
2165
|
+
* unit is active, and refusing this machine its DNS over a slow first registry
|
|
2166
|
+
* fetch would be the worse mistake.
|
|
2167
|
+
*/
|
|
2168
|
+
export async function supervisedReady({
|
|
2169
|
+
host = DEFAULT_HOST,
|
|
2170
|
+
port,
|
|
2171
|
+
probe = probeResolver,
|
|
2172
|
+
status = daemonStatus,
|
|
2173
|
+
timeoutMs = READY_TIMEOUT_MS,
|
|
2174
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
2175
|
+
} = {}) {
|
|
2176
|
+
const deadline = Date.now() + timeoutMs;
|
|
2177
|
+
let answered = false;
|
|
2178
|
+
while (Date.now() < deadline) {
|
|
2179
|
+
if (await probe({ host, port }).catch(() => false)) {
|
|
2180
|
+
answered = true;
|
|
2181
|
+
break;
|
|
2182
|
+
}
|
|
2183
|
+
await sleep(150);
|
|
2184
|
+
}
|
|
2185
|
+
const current = await Promise.resolve(status()).catch(() => null);
|
|
2186
|
+
if (!answered) {
|
|
2187
|
+
// Reported as a failure, unlike `startDaemon`'s slow-but-alive case, and
|
|
2188
|
+
// for a reason that does not apply there: that one has watched its own
|
|
2189
|
+
// child and knows it is running. Nothing here has. `systemctl restart`
|
|
2190
|
+
// returns as soon as a Type=simple unit forks, so it returns 0 for a bridge
|
|
2191
|
+
// that forked and died — and the next thing this run does is point every
|
|
2192
|
+
// lookup on the machine at that port. Refusing is the safe direction.
|
|
2193
|
+
return {
|
|
2194
|
+
started: false,
|
|
2195
|
+
alreadyRunning: false,
|
|
2196
|
+
pid: current?.pid ?? null,
|
|
2197
|
+
supervised: true,
|
|
2198
|
+
error: `${UNIT_NAME} restarted but the bridge did not answer on ${host}:${port}`,
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
return { started: true, pid: current?.pid ?? null, alreadyRunning: false, supervised: true, verified: true };
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2151
2204
|
const defaultReadMaybe = async (path) => {
|
|
2152
2205
|
const { readFile: rf } = await import("node:fs/promises");
|
|
2153
2206
|
return rf(path, "utf8").catch(() => null);
|
|
@@ -2407,10 +2460,10 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
2407
2460
|
import { existsSync } from "node:fs";
|
|
2408
2461
|
import { fileURLToPath } from "node:url";
|
|
2409
2462
|
import { isRealTld } from "./iana-tlds.mjs";
|
|
2410
|
-
import { installService, removeService, serviceUnit, servicePaths, UNIT_NAME, ensureProxyService, removeProxyService, proxyServicePaths, proxyWrapperPath } from "./dns-service.mjs";
|
|
2463
|
+
import { installService, refreshService, removeService, serviceUnit, servicePaths, stopService, UNIT_NAME, ensureProxyService, removeProxyService, proxyServicePaths, proxyWrapperPath } from "./dns-service.mjs";
|
|
2411
2464
|
import {
|
|
2412
2465
|
applyPlan, daemonStatus, describePlan, detectPlatform, disablePlan, enablePlan,
|
|
2413
|
-
probeResolver, requiredPort, startDaemon, stopDaemon,
|
|
2466
|
+
probeResolver, READY_TIMEOUT_MS, requiredPort, startDaemon, stopDaemon,
|
|
2414
2467
|
} from "./dns-system.mjs";
|
|
2415
2468
|
import { escalateSelf } from "./escalate.mjs";
|
|
2416
2469
|
|
|
@@ -2508,10 +2561,13 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
2508
2561
|
presenceImpl = bridgePresence,
|
|
2509
2562
|
exists = existsSync,
|
|
2510
2563
|
startBridge = startDaemon,
|
|
2564
|
+
refreshBridge = refreshService,
|
|
2565
|
+
bridgeReady = supervisedReady,
|
|
2511
2566
|
proxyReachableImpl = proxyReachable,
|
|
2512
2567
|
findLocalProxyImpl = findLocalProxy,
|
|
2513
2568
|
autoTrustImpl = createAutoTrust,
|
|
2514
2569
|
stopBridge = stopDaemon,
|
|
2570
|
+
stopSupervised = stopService,
|
|
2515
2571
|
// The two proxy-service calls, injected for the same reason as every
|
|
2516
2572
|
// other system call here: a test must be able to exercise the branch
|
|
2517
2573
|
// without shelling out to systemctl or writing to /etc.
|
|
@@ -3224,6 +3280,19 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
3224
3280
|
return 1;
|
|
3225
3281
|
}
|
|
3226
3282
|
|
|
3283
|
+
// The supervised bridge first, and by asking systemd rather than by
|
|
3284
|
+
// signalling a pid. `stopDaemon` kills what the pidfile names, and the
|
|
3285
|
+
// unit is `Restart=always` — so the process died, systemd replaced it
|
|
3286
|
+
// within the second, and this command reported "bridge stopped" on a
|
|
3287
|
+
// machine where the bridge was still up and answering, just no longer on
|
|
3288
|
+
// anything's path.
|
|
3289
|
+
const unsupervised = await stopSupervised();
|
|
3290
|
+
if (unsupervised.reason !== "no unit installed") {
|
|
3291
|
+
out(unsupervised.stopped
|
|
3292
|
+
? ` ok ${UNIT_NAME} stopped and disabled`
|
|
3293
|
+
: ` -- could not stop ${UNIT_NAME} (${unsupervised.reason}) — it will restart itself`);
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3227
3296
|
const stopped = await stopBridge();
|
|
3228
3297
|
out(stopped.stopped ? " ok bridge stopped" : ` ok bridge was not running${stopped.reason ? ` (${stopped.reason})` : ""}`);
|
|
3229
3298
|
|
|
@@ -3412,17 +3481,42 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
3412
3481
|
}
|
|
3413
3482
|
}
|
|
3414
3483
|
|
|
3484
|
+
// A bridge under systemd is re-described, not started — and this is the step
|
|
3485
|
+
// whose absence left the last of this to be done by hand.
|
|
3486
|
+
//
|
|
3487
|
+
// `startBridge` below reports "already running" for a supervised bridge and
|
|
3488
|
+
// leaves it alone. That is correct, and it is also why proxy mode arrived a
|
|
3489
|
+
// reboot late: what a resolver answers with is fixed when it spawns, so a
|
|
3490
|
+
// bridge that came up before the proxy existed goes on answering origins no
|
|
3491
|
+
// matter what this run decides. Stopping it does not help either, since
|
|
3492
|
+
// `Restart=always` brings the same ExecStart back.
|
|
3493
|
+
//
|
|
3494
|
+
// So the unit is rewritten to match the run happening now, and restarted.
|
|
3495
|
+
// v4 by preference: `dns start --proxy` takes one address and probes both
|
|
3496
|
+
// families itself, so handing it the v4 loopback lets it find ::1 too
|
|
3497
|
+
// rather than pinning the answer to one family.
|
|
3498
|
+
const proxyArg = proxyAddress ? (proxyAddress.v4 || proxyAddress.v6) : null;
|
|
3499
|
+
|
|
3500
|
+
let refreshed = null;
|
|
3501
|
+
if (!reusing && platform === "linux") {
|
|
3502
|
+
refreshed = await refreshBridge({ entry: cliEntry(), port: wanted, registryBase, proxy: proxyArg });
|
|
3503
|
+
for (const step of refreshed.steps || []) {
|
|
3504
|
+
out(` ${step.ok ? "ok " : "-- "} ${step.step}${step.error ? ` — ${step.error}` : ""}`);
|
|
3505
|
+
}
|
|
3506
|
+
if (refreshed.refreshed) {
|
|
3507
|
+
const forwarding = refreshed.upstreams?.length ? `, forwarding the clearnet to ${refreshed.upstreams.join(", ")}` : "";
|
|
3508
|
+
out(` ok ${UNIT_NAME} restarted with proxy mode ${proxyArg ? "on" : "off"}${forwarding}`);
|
|
3509
|
+
} else if (refreshed.reason !== "no unit installed") {
|
|
3510
|
+
out(` -- could not update ${UNIT_NAME} (${refreshed.reason})`);
|
|
3511
|
+
out(" the bridge already running keeps the mode it started with");
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3415
3515
|
const started = reusing
|
|
3416
3516
|
? { started: false, pid: reusing.pid, alreadyRunning: true, reused: true }
|
|
3417
|
-
:
|
|
3418
|
-
port: wanted
|
|
3419
|
-
registryBase,
|
|
3420
|
-
entry: cliEntry(),
|
|
3421
|
-
// v4 by preference: `dns start --proxy` takes one address and probes
|
|
3422
|
-
// both families itself, so handing it the v4 loopback lets it find ::1
|
|
3423
|
-
// too rather than pinning the answer to one family.
|
|
3424
|
-
proxy: proxyAddress ? (proxyAddress.v4 || proxyAddress.v6) : null,
|
|
3425
|
-
});
|
|
3517
|
+
: refreshed?.refreshed
|
|
3518
|
+
? await bridgeReady({ host: DEFAULT_HOST, port: wanted })
|
|
3519
|
+
: await startBridge({ port: wanted, registryBase, entry: cliEntry(), proxy: proxyArg });
|
|
3426
3520
|
// The routing this is about to install is catch-all — every lookup on the
|
|
3427
3521
|
// machine, not just Moshpit ones — so a bridge that did not come up is not
|
|
3428
3522
|
// a degraded feature, it is the machine's resolver pointed at nothing.
|
|
@@ -3504,7 +3598,13 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
3504
3598
|
out(`Moshpit names now resolve on this machine. Try: moshcode dns resolve ${moshpitProbe || "<name>"}`);
|
|
3505
3599
|
out(`Routing covers the ${tlds.length} TLDs claimed right now. New ones do not route`);
|
|
3506
3600
|
out("until you re-run this — there is no common suffix to match, so every TLD is listed.");
|
|
3507
|
-
|
|
3601
|
+
// Only where it is still true. On a supervised machine the unit was just
|
|
3602
|
+
// enabled and restarted, so the bridge does come back — and telling
|
|
3603
|
+
// someone to re-run a command they do not need is how advice stops being
|
|
3604
|
+
// read at all.
|
|
3605
|
+
out(started.supervised
|
|
3606
|
+
? `Note: ${UNIT_NAME} brings the bridge back after a reboot.`
|
|
3607
|
+
: "Note: the bridge does not yet survive a reboot. Re-run `moshcode dns enable` after one.");
|
|
3508
3608
|
return 0;
|
|
3509
3609
|
}
|
|
3510
3610
|
|
|
@@ -3512,7 +3612,14 @@ export async function dnsCommand(args = [], out = console.log, deps = {}) {
|
|
|
3512
3612
|
report(outcome.rolledBack.results);
|
|
3513
3613
|
// Started by this run and no longer routed to, so leaving it would be a
|
|
3514
3614
|
// process holding 5354 that the next enable's preflight refuses to run past.
|
|
3515
|
-
|
|
3615
|
+
//
|
|
3616
|
+
// A supervised bridge is exempt: this run did not start it, only restarted
|
|
3617
|
+
// it, so it was holding that port before the run and is meant to go on
|
|
3618
|
+
// holding it. Signalling its pid would not stop it anyway — `Restart=always`
|
|
3619
|
+
// replaces it within the second — so the only thing the old line achieved
|
|
3620
|
+
// there was printing "remove bridge started by this run" about a bridge
|
|
3621
|
+
// that was neither started by this run nor removed.
|
|
3622
|
+
if (started.started && !started.supervised) {
|
|
3516
3623
|
const stopped = await stopBridge();
|
|
3517
3624
|
if (stopped.stopped) out(" ok remove bridge started by this run");
|
|
3518
3625
|
}
|