@trim21/personal-pi-extensions 0.0.428 → 0.0.429
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/bwrap/holder.js +2 -15
- package/src/bwrap/holder.ts +4 -18
- package/src/bwrap/network-stack.ts +36 -25
package/package.json
CHANGED
package/src/bwrap/holder.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { open, readFile, writeFile } from "node:fs/promises";
|
|
3
2
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4
3
|
const args = process.argv.slice(2);
|
|
5
4
|
const configPath = args[0];
|
|
6
5
|
const singBoxPath = args[1];
|
|
7
|
-
|
|
8
|
-
if (!configPath || !singBoxPath || !readyFile) {
|
|
6
|
+
if (!configPath || !singBoxPath) {
|
|
9
7
|
process.exit(2);
|
|
10
8
|
}
|
|
11
9
|
function tap0Exists() {
|
|
@@ -24,11 +22,9 @@ async function waitFor(attempts, delayMs, predicate) {
|
|
|
24
22
|
}
|
|
25
23
|
async function main() {
|
|
26
24
|
await waitFor(100, 100, tap0Exists);
|
|
27
|
-
const logPath = `${configPath}.log`;
|
|
28
|
-
const logFd = await open(logPath, "w");
|
|
29
25
|
const singbox = spawn(singBoxPath, ["run", "-c", configPath], {
|
|
30
26
|
env: { ...process.env, ENABLE_DEPRECATED_MISSING_DOMAIN_RESOLVER: "true" },
|
|
31
|
-
stdio: ["ignore",
|
|
27
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
32
28
|
});
|
|
33
29
|
const stop = () => {
|
|
34
30
|
singbox.kill("SIGTERM");
|
|
@@ -41,15 +37,6 @@ async function main() {
|
|
|
41
37
|
stop();
|
|
42
38
|
process.exit(0);
|
|
43
39
|
});
|
|
44
|
-
await waitFor(200, 100, async () => {
|
|
45
|
-
try {
|
|
46
|
-
const log = await readFile(logPath, "utf8");
|
|
47
|
-
return log.includes("sing-box started");
|
|
48
|
-
} catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
await writeFile(readyFile, "");
|
|
53
40
|
await new Promise((resolve) => singbox.once("exit", () => resolve()));
|
|
54
41
|
}
|
|
55
42
|
try {
|
package/src/bwrap/holder.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-console, unicorn/no-process-exit -- 独立 CLI 脚本,由 unshare 直接执行 */
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { open, readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
4
|
|
|
6
|
-
// 用法:unshare -Urn node holder.ts <config> <singbox>
|
|
5
|
+
// 用法:unshare -Urn node holder.ts <config> <singbox>
|
|
7
6
|
const args = process.argv.slice(2);
|
|
8
7
|
const configPath = args[0];
|
|
9
8
|
const singBoxPath = args[1];
|
|
10
|
-
|
|
11
|
-
if (!configPath || !singBoxPath || !readyFile) {
|
|
9
|
+
if (!configPath || !singBoxPath) {
|
|
12
10
|
process.exit(2);
|
|
13
11
|
}
|
|
14
12
|
|
|
@@ -36,11 +34,10 @@ async function main(): Promise<void> {
|
|
|
36
34
|
// slirp4netns 由宿主侧 spawn,这里等它建好 tap0
|
|
37
35
|
await waitFor(100, 100, tap0Exists);
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
const logFd = await open(logPath, "w");
|
|
37
|
+
// sing-box 日志透传到 holder 的 stdout/stderr,宿主侧监听 "sing-box started" 判定就绪
|
|
41
38
|
const singbox = spawn(singBoxPath, ["run", "-c", configPath], {
|
|
42
39
|
env: { ...process.env, ENABLE_DEPRECATED_MISSING_DOMAIN_RESOLVER: "true" },
|
|
43
|
-
stdio: ["ignore",
|
|
40
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
44
41
|
});
|
|
45
42
|
|
|
46
43
|
const stop = (): void => {
|
|
@@ -55,17 +52,6 @@ async function main(): Promise<void> {
|
|
|
55
52
|
process.exit(0);
|
|
56
53
|
});
|
|
57
54
|
|
|
58
|
-
// 以 "sing-box started" 日志作为就绪标志(TUN 与 gvisor 数据面此时已可用)
|
|
59
|
-
await waitFor(200, 100, async () => {
|
|
60
|
-
try {
|
|
61
|
-
const log = await readFile(logPath, "utf8");
|
|
62
|
-
return log.includes("sing-box started");
|
|
63
|
-
} catch {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
await writeFile(readyFile, "");
|
|
68
|
-
|
|
69
55
|
await new Promise<void>((resolve) => singbox.once("exit", () => resolve()));
|
|
70
56
|
}
|
|
71
57
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
+
import { mkdtemp, readFile, readlink, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -72,17 +72,32 @@ async function waitForNewUserns(pid: number, timeoutMs = 5000): Promise<void> {
|
|
|
72
72
|
throw new Error("Timed out waiting for sandbox network namespace");
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
/** 监听 holder 的 stdout/stderr(sing-box 日志透传),以 "sing-box started" 作为就绪标志。 */
|
|
76
|
+
function waitForSingboxStarted(holder: ChildProcess, timeoutMs = 20000): Promise<void> {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
let settled = false;
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
if (settled) return;
|
|
81
|
+
settled = true;
|
|
82
|
+
reject(new Error("Timed out waiting for sing-box to start"));
|
|
83
|
+
}, timeoutMs);
|
|
84
|
+
const onData = (data: Buffer): void => {
|
|
85
|
+
if (settled) return;
|
|
86
|
+
if (data.toString().includes("sing-box started")) {
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
resolve();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
holder.stdout?.on("data", onData);
|
|
93
|
+
holder.stderr?.on("data", onData);
|
|
94
|
+
holder.once("exit", (code) => {
|
|
95
|
+
if (settled) return;
|
|
96
|
+
settled = true;
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
reject(new Error(`Sandbox holder exited before sing-box started (code ${code})`));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
function killChild(pid: number | undefined): void {
|
|
@@ -125,20 +140,16 @@ export async function startNetworkStack(options: NetworkStackOptions): Promise<N
|
|
|
125
140
|
const { allowlist, dnsServers, singBoxPath, slirp4netnsPath } = options;
|
|
126
141
|
const directory = await mkdtemp(join(tmpdir(), "pi-netns-"));
|
|
127
142
|
const configPath = join(directory, "singbox.json");
|
|
128
|
-
const readyFile = join(directory, "ready");
|
|
129
143
|
await writeFile(configPath, generateSingboxConfig({ allowlist, dnsServers }));
|
|
130
144
|
|
|
131
|
-
const holder = spawn(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
env: {
|
|
136
|
-
HOME: process.env.HOME ?? "",
|
|
137
|
-
PATH: `${process.env.PATH ?? ""}:${SBIN_PATH_SUFFIX}`,
|
|
138
|
-
},
|
|
139
|
-
stdio: "ignore",
|
|
145
|
+
const holder = spawn("unshare", ["-Urn", "--", "node", HOLDER_PATH, configPath, singBoxPath], {
|
|
146
|
+
env: {
|
|
147
|
+
HOME: process.env.HOME ?? "",
|
|
148
|
+
PATH: `${process.env.PATH ?? ""}:${SBIN_PATH_SUFFIX}`,
|
|
140
149
|
},
|
|
141
|
-
|
|
150
|
+
// sing-box 日志经 holder 透传到这里的 stdout/stderr,用于判定就绪
|
|
151
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
});
|
|
142
153
|
if (holder.pid === undefined) {
|
|
143
154
|
throw new Error("Failed to start network namespace holder");
|
|
144
155
|
}
|
|
@@ -157,7 +168,7 @@ export async function startNetworkStack(options: NetworkStackOptions): Promise<N
|
|
|
157
168
|
{ stdio: "ignore" },
|
|
158
169
|
);
|
|
159
170
|
const slirpPid = slirp.pid;
|
|
160
|
-
await
|
|
171
|
+
await waitForSingboxStarted(holder);
|
|
161
172
|
|
|
162
173
|
const state: NetworkStackState = { holderPid, slirpPid, directory };
|
|
163
174
|
const stack: NetworkStack = {
|