blitzwing 0.1.10 → 0.2.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 +14 -5
- package/package.json +2 -1
- package/runtime/app.py +398 -0
- package/runtime/http_chain.py +189 -0
- package/runtime/inference.py +13 -0
- package/runtime/local_runner.py +148 -0
- package/runtime/tensor_codec.py +26 -0
- package/src/heartbeat.js +4 -1
- package/src/install.js +51 -180
- package/src/tunnel.js +231 -0
- package/src/wizard.js +68 -105
package/src/tunnel.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pipeline } from "node:stream/promises";
|
|
5
|
+
import { createWriteStream } from "node:fs";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
import { HOME_DIR } from "./config.js";
|
|
8
|
+
import { which } from "./net.js";
|
|
9
|
+
|
|
10
|
+
const BIN_DIR = path.join(HOME_DIR, "bin");
|
|
11
|
+
const URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
12
|
+
|
|
13
|
+
function platformAsset() {
|
|
14
|
+
const plat = process.platform;
|
|
15
|
+
const arch = process.arch;
|
|
16
|
+
if (plat === "linux" && arch === "x64") {
|
|
17
|
+
return { name: "cloudflared-linux-amd64", archive: false };
|
|
18
|
+
}
|
|
19
|
+
if (plat === "linux" && (arch === "arm64" || arch === "aarch64")) {
|
|
20
|
+
return { name: "cloudflared-linux-arm64", archive: false };
|
|
21
|
+
}
|
|
22
|
+
if (plat === "darwin" && arch === "arm64") {
|
|
23
|
+
return { name: "cloudflared-darwin-arm64.tgz", archive: true };
|
|
24
|
+
}
|
|
25
|
+
if (plat === "darwin" && arch === "x64") {
|
|
26
|
+
return { name: "cloudflared-darwin-amd64.tgz", archive: true };
|
|
27
|
+
}
|
|
28
|
+
if (plat === "win32" && arch === "x64") {
|
|
29
|
+
return { name: "cloudflared-windows-amd64.exe", archive: false, exe: true };
|
|
30
|
+
}
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Unsupported platform for cloudflared auto-install: ${plat}/${arch}. Install cloudflared manually and retry.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function localBinaryPath() {
|
|
37
|
+
const exe = process.platform === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
38
|
+
return path.join(BIN_DIR, exe);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve cloudflared binary: PATH first, then ~/.blitzwing/bin (download if missing).
|
|
43
|
+
* @param {{ onLog?: (msg: string) => void }} [opts]
|
|
44
|
+
*/
|
|
45
|
+
export async function ensureCloudflared({ onLog } = {}) {
|
|
46
|
+
const onPath = which("cloudflared");
|
|
47
|
+
if (onPath) return onPath;
|
|
48
|
+
|
|
49
|
+
const dest = localBinaryPath();
|
|
50
|
+
if (fs.existsSync(dest)) {
|
|
51
|
+
try {
|
|
52
|
+
fs.chmodSync(dest, 0o755);
|
|
53
|
+
} catch {
|
|
54
|
+
/* ignore */
|
|
55
|
+
}
|
|
56
|
+
return dest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const asset = platformAsset();
|
|
60
|
+
const url = `https://github.com/cloudflare/cloudflared/releases/latest/download/${asset.name}`;
|
|
61
|
+
onLog?.(`Downloading cloudflared (${asset.name})…`);
|
|
62
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
63
|
+
|
|
64
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
65
|
+
if (!res.ok || !res.body) {
|
|
66
|
+
throw new Error(`Failed to download cloudflared: HTTP ${res.status} from ${url}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (asset.archive) {
|
|
70
|
+
const tgzPath = path.join(BIN_DIR, asset.name);
|
|
71
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(tgzPath));
|
|
72
|
+
execFileSync("tar", ["-xzf", tgzPath, "-C", BIN_DIR], { stdio: "ignore" });
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(tgzPath);
|
|
75
|
+
} catch {
|
|
76
|
+
/* ignore */
|
|
77
|
+
}
|
|
78
|
+
// tgz extracts a binary named cloudflared
|
|
79
|
+
const extracted = path.join(BIN_DIR, "cloudflared");
|
|
80
|
+
if (!fs.existsSync(extracted) && fs.existsSync(dest)) {
|
|
81
|
+
/* already named correctly */
|
|
82
|
+
} else if (fs.existsSync(extracted) && extracted !== dest) {
|
|
83
|
+
fs.renameSync(extracted, dest);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
fs.chmodSync(dest, 0o755);
|
|
91
|
+
} catch {
|
|
92
|
+
/* windows */
|
|
93
|
+
}
|
|
94
|
+
if (!fs.existsSync(dest)) {
|
|
95
|
+
throw new Error(`cloudflared download finished but binary missing at ${dest}`);
|
|
96
|
+
}
|
|
97
|
+
onLog?.(`cloudflared ready at ${dest}`);
|
|
98
|
+
return dest;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Kill a process (and its group on Unix).
|
|
103
|
+
* @param {number} pid
|
|
104
|
+
*/
|
|
105
|
+
export function stopTunnelProcess(pid) {
|
|
106
|
+
if (!pid || !Number.isFinite(pid)) return;
|
|
107
|
+
try {
|
|
108
|
+
if (process.platform === "win32") {
|
|
109
|
+
execFileSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
110
|
+
} else {
|
|
111
|
+
try {
|
|
112
|
+
process.kill(-pid, "SIGTERM");
|
|
113
|
+
} catch {
|
|
114
|
+
process.kill(pid, "SIGTERM");
|
|
115
|
+
}
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
try {
|
|
118
|
+
process.kill(-pid, "SIGKILL");
|
|
119
|
+
} catch {
|
|
120
|
+
try {
|
|
121
|
+
process.kill(pid, "SIGKILL");
|
|
122
|
+
} catch {
|
|
123
|
+
/* already gone */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, 2000).unref?.();
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
/* already gone */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Start a Cloudflare Quick Tunnel to http://127.0.0.1:port (no account/token).
|
|
135
|
+
* @param {{ port: number, binary?: string, logPath?: string, timeoutMs?: number }} opts
|
|
136
|
+
* @returns {Promise<{ url: string, pid: number, stop: () => void }>}
|
|
137
|
+
*/
|
|
138
|
+
export async function startQuickTunnel({
|
|
139
|
+
port,
|
|
140
|
+
binary,
|
|
141
|
+
logPath = path.join(HOME_DIR, "cloudflared.log"),
|
|
142
|
+
timeoutMs = 90_000,
|
|
143
|
+
}) {
|
|
144
|
+
const bin = binary || (await ensureCloudflared());
|
|
145
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
146
|
+
const logFd = fs.openSync(logPath, "a");
|
|
147
|
+
|
|
148
|
+
const child = spawn(
|
|
149
|
+
bin,
|
|
150
|
+
["tunnel", "--url", `http://127.0.0.1:${port}`, "--no-autoupdate"],
|
|
151
|
+
{
|
|
152
|
+
detached: process.platform !== "win32",
|
|
153
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
154
|
+
windowsHide: true,
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
let settled = false;
|
|
159
|
+
let buffer = "";
|
|
160
|
+
|
|
161
|
+
const appendLog = (chunk) => {
|
|
162
|
+
const text = chunk.toString();
|
|
163
|
+
buffer += text;
|
|
164
|
+
try {
|
|
165
|
+
fs.writeSync(logFd, text);
|
|
166
|
+
} catch {
|
|
167
|
+
/* ignore */
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
child.stdout?.on("data", appendLog);
|
|
172
|
+
child.stderr?.on("data", appendLog);
|
|
173
|
+
|
|
174
|
+
const stop = () => {
|
|
175
|
+
stopTunnelProcess(child.pid);
|
|
176
|
+
try {
|
|
177
|
+
fs.closeSync(logFd);
|
|
178
|
+
} catch {
|
|
179
|
+
/* ignore */
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
const timer = setTimeout(() => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
stop();
|
|
188
|
+
reject(
|
|
189
|
+
new Error(
|
|
190
|
+
`Timed out waiting for Cloudflare Quick Tunnel URL. See ${logPath}`
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
}, timeoutMs);
|
|
194
|
+
|
|
195
|
+
const tryMatch = () => {
|
|
196
|
+
const m = buffer.match(URL_RE);
|
|
197
|
+
if (!m || settled) return;
|
|
198
|
+
settled = true;
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
if (process.platform !== "win32") {
|
|
201
|
+
child.unref();
|
|
202
|
+
}
|
|
203
|
+
resolve({
|
|
204
|
+
url: m[0].replace(/\/$/, ""),
|
|
205
|
+
pid: child.pid,
|
|
206
|
+
stop,
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
child.stdout?.on("data", tryMatch);
|
|
211
|
+
child.stderr?.on("data", tryMatch);
|
|
212
|
+
|
|
213
|
+
child.on("error", (err) => {
|
|
214
|
+
if (settled) return;
|
|
215
|
+
settled = true;
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
reject(err);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
child.on("exit", (code) => {
|
|
221
|
+
if (settled) return;
|
|
222
|
+
settled = true;
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
reject(
|
|
225
|
+
new Error(
|
|
226
|
+
`cloudflared exited early (code ${code}). See ${logPath}`
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
package/src/wizard.js
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import color from "picocolors";
|
|
3
|
-
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
6
4
|
import { DEFAULT_DISCOVERY_URL, HOME_DIR, SHARD_PORT, PETALS_PORT } from "./config.js";
|
|
7
5
|
import { listMothers, motherHosts, joinHost, readyHost, leaveHost } from "./api.js";
|
|
8
|
-
import {
|
|
9
|
-
detectPublicIp,
|
|
10
|
-
detectLocalIp,
|
|
11
|
-
discoverNgrokTcpAnnounce,
|
|
12
|
-
extractPeerMultiaddrFromLog,
|
|
13
|
-
} from "./net.js";
|
|
6
|
+
import { detectPublicIp, detectLocalIp } from "./net.js";
|
|
14
7
|
import {
|
|
15
8
|
ensureVenvAndPetals,
|
|
16
9
|
startShardManagerProcess,
|
|
17
10
|
waitForShardRunning,
|
|
11
|
+
syncRuntimeFiles,
|
|
18
12
|
venvPython,
|
|
19
13
|
} from "./install.js";
|
|
14
|
+
import { ensureCloudflared, startQuickTunnel, stopTunnelProcess } from "./tunnel.js";
|
|
20
15
|
import { saveState, loadState, clearState, ensureHome } from "./state.js";
|
|
21
16
|
import { startContributorHeartbeatDaemon } from "./heartbeat.js";
|
|
22
17
|
|
|
@@ -60,10 +55,9 @@ ${color.bold("blitzwing")} — join a Blitzwing mother swarm as a compute node
|
|
|
60
55
|
blitzwing leave Leave the swarm and reclaim your layers
|
|
61
56
|
|
|
62
57
|
Env:
|
|
63
|
-
BLITZWING_DISCOVERY_URL
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
PETALS_USE_AUTO_RELAY=0 Disable libp2p auto-relay (use with public IP / port-forward)
|
|
58
|
+
BLITZWING_DISCOVERY_URL Override discovery service (default ${DEFAULT_DISCOVERY_URL})
|
|
59
|
+
BLITZWING_HEDERA_ACCOUNT_ID Prefill Hedera payout account (0.0.N)
|
|
60
|
+
BLITZWING_SHARD_PORT Local shard HTTP port (default ${SHARD_PORT})
|
|
67
61
|
`);
|
|
68
62
|
}
|
|
69
63
|
|
|
@@ -71,6 +65,7 @@ async function wizard(args) {
|
|
|
71
65
|
p.intro(color.bgCyan(color.black(" blitzwing ")));
|
|
72
66
|
ensureHome();
|
|
73
67
|
|
|
68
|
+
const existing = loadState();
|
|
74
69
|
const spin = p.spinner();
|
|
75
70
|
spin.start("Loading network from Discovery Service…");
|
|
76
71
|
let mothers;
|
|
@@ -138,74 +133,14 @@ async function wizard(args) {
|
|
|
138
133
|
}
|
|
139
134
|
const layersN = Number(layers);
|
|
140
135
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
value: "relay",
|
|
147
|
-
label: "Auto (recommended for home / NAT)",
|
|
148
|
-
hint: "Uses Petals libp2p relay — no port forwarding",
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
value: "public",
|
|
152
|
-
label: "Public IP / cloud VM",
|
|
153
|
-
hint: "TCP 31337 (and 8001) open on the internet",
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
value: "ngrok",
|
|
157
|
-
label: "Dev tunnel (ngrok TCP on :4040)",
|
|
158
|
-
hint: "Only if ngrok is already running",
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
initialValue: "relay",
|
|
162
|
-
});
|
|
163
|
-
if (p.isCancel(networkMode)) {
|
|
164
|
-
p.cancel("Setup cancelled");
|
|
165
|
-
process.exit(0);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
let publicIp = "";
|
|
169
|
-
let announceMaddrs = "";
|
|
170
|
-
let useAutoRelay = "1";
|
|
171
|
-
|
|
172
|
-
if (networkMode === "public") {
|
|
173
|
-
publicIp = await p.text({
|
|
174
|
-
message: "Public IPv4 (must be reachable on TCP " + PETALS_PORT + ")",
|
|
175
|
-
initialValue: detected || "",
|
|
176
|
-
validate(v) {
|
|
177
|
-
if (!v || !/^\d+\.\d+\.\d+\.\d+$/.test(String(v).trim())) {
|
|
178
|
-
return "Enter a valid public IPv4 address";
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
if (p.isCancel(publicIp)) {
|
|
183
|
-
p.cancel("Setup cancelled");
|
|
184
|
-
process.exit(0);
|
|
185
|
-
}
|
|
186
|
-
publicIp = String(publicIp).trim();
|
|
187
|
-
announceMaddrs = `/ip4/${publicIp}/tcp/${PETALS_PORT}`;
|
|
188
|
-
useAutoRelay = "0";
|
|
189
|
-
} else if (networkMode === "ngrok") {
|
|
190
|
-
const ngrok = await discoverNgrokTcpAnnounce(PETALS_PORT);
|
|
191
|
-
if (!ngrok) {
|
|
192
|
-
p.cancel("No ngrok TCP tunnel on http://127.0.0.1:4040. Start: ngrok tcp " + PETALS_PORT);
|
|
193
|
-
process.exit(1);
|
|
194
|
-
}
|
|
195
|
-
announceMaddrs = ngrok;
|
|
196
|
-
publicIp = ngrok.match(/dns4\/([^/]+)/)?.[1] || detected || "relay";
|
|
197
|
-
useAutoRelay = "0";
|
|
198
|
-
p.log.info(`Using ngrok announce ${announceMaddrs}`);
|
|
199
|
-
} else {
|
|
200
|
-
publicIp = detected || "relay";
|
|
201
|
-
p.log.info("Using Petals libp2p auto-relay (no inbound port forward required).");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const localIp = await detectLocalIp();
|
|
205
|
-
|
|
136
|
+
const hederaPrefill =
|
|
137
|
+
process.env.BLITZWING_HEDERA_ACCOUNT_ID ||
|
|
138
|
+
process.env.HEDERA_ACCOUNT_ID ||
|
|
139
|
+
existing?.hedera_account_id ||
|
|
140
|
+
"";
|
|
206
141
|
const hederaAccount = await p.text({
|
|
207
142
|
message: "Hedera account ID for layer payouts (e.g. 0.0.123456)",
|
|
208
|
-
initialValue:
|
|
143
|
+
initialValue: hederaPrefill,
|
|
209
144
|
validate(v) {
|
|
210
145
|
const s = String(v || "").trim();
|
|
211
146
|
if (!/^0\.0\.\d+$/.test(s)) return "Enter a Hedera account like 0.0.123456";
|
|
@@ -218,7 +153,7 @@ async function wizard(args) {
|
|
|
218
153
|
const hederaAccountId = String(hederaAccount).trim();
|
|
219
154
|
|
|
220
155
|
const confirm = await p.confirm({
|
|
221
|
-
message: `Join ${selected.model} hosting ${layersN} layers
|
|
156
|
+
message: `Join ${selected.model} hosting ${layersN} layers, payouts to ${hederaAccountId}?`,
|
|
222
157
|
initialValue: true,
|
|
223
158
|
});
|
|
224
159
|
if (p.isCancel(confirm) || !confirm) {
|
|
@@ -233,6 +168,7 @@ async function wizard(args) {
|
|
|
233
168
|
spin.message(msg);
|
|
234
169
|
},
|
|
235
170
|
});
|
|
171
|
+
syncRuntimeFiles();
|
|
236
172
|
} catch (err) {
|
|
237
173
|
spin.stop("Install failed");
|
|
238
174
|
p.cancel(err.message);
|
|
@@ -241,8 +177,28 @@ async function wizard(args) {
|
|
|
241
177
|
const python = venvPython();
|
|
242
178
|
spin.stop("Environment ready");
|
|
243
179
|
|
|
244
|
-
|
|
245
|
-
|
|
180
|
+
spin.start("Setting up Cloudflare Quick Tunnel…");
|
|
181
|
+
let tunnel;
|
|
182
|
+
try {
|
|
183
|
+
const cfBin = await ensureCloudflared({
|
|
184
|
+
onLog: (msg) => {
|
|
185
|
+
spin.message(msg);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
tunnel = await startQuickTunnel({
|
|
189
|
+
port: SHARD_PORT,
|
|
190
|
+
binary: cfBin,
|
|
191
|
+
logPath: path.join(HOME_DIR, "cloudflared.log"),
|
|
192
|
+
});
|
|
193
|
+
} catch (err) {
|
|
194
|
+
spin.stop("Tunnel failed");
|
|
195
|
+
p.cancel(err.message);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
spin.stop(`Public URL ${tunnel.url}`);
|
|
199
|
+
|
|
200
|
+
const localIp = await detectLocalIp();
|
|
201
|
+
const publicIp = (await detectPublicIp()) || localIp || "tunnel";
|
|
246
202
|
|
|
247
203
|
spin.start("Requesting layer assignment from mother…");
|
|
248
204
|
let assignment;
|
|
@@ -251,10 +207,11 @@ async function wizard(args) {
|
|
|
251
207
|
model: selected.model,
|
|
252
208
|
layers: layersN,
|
|
253
209
|
public_ip: String(publicIp).trim(),
|
|
254
|
-
shard_manager_url:
|
|
210
|
+
shard_manager_url: tunnel.url,
|
|
255
211
|
hedera_account_id: hederaAccountId,
|
|
256
212
|
});
|
|
257
213
|
} catch (err) {
|
|
214
|
+
tunnel.stop();
|
|
258
215
|
spin.stop("Join rejected");
|
|
259
216
|
const max = err.body?.detail?.max_layers;
|
|
260
217
|
p.cancel(`${err.message}${max != null ? ` (max available: ${max})` : ""}`);
|
|
@@ -265,19 +222,21 @@ async function wizard(args) {
|
|
|
265
222
|
const logPath = path.join(HOME_DIR, "shard_manager.log");
|
|
266
223
|
spin.start("Starting local Petals server…");
|
|
267
224
|
const shardEnv = {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
225
|
+
MODEL_NAME: selected.model,
|
|
226
|
+
PUBLIC_IP: String(publicIp).trim(),
|
|
227
|
+
BLOCK_INDICES: assignment.block_indices,
|
|
228
|
+
INITIAL_PEERS: "",
|
|
229
|
+
NEW_SWARM: "1",
|
|
230
|
+
BLITZWING_HTTP_ONLY: "1",
|
|
231
|
+
PETALS_PORT: String(PETALS_PORT),
|
|
232
|
+
IDENTITY_PATH: path.join(HOME_DIR, "petals-identity"),
|
|
233
|
+
SHARD_AUTO_START: "1",
|
|
234
|
+
PETALS_USE_AUTO_RELAY: "0",
|
|
235
|
+
PETALS_SKIP_REACHABILITY_CHECK: "1",
|
|
236
|
+
BLITZWING_MOTHER_URL: selected.mother_url,
|
|
237
|
+
MOTHER_PUBLIC_SHARD_URL: selected.mother_url,
|
|
238
|
+
BLITZWING_HOST_ID: assignment.host_id,
|
|
239
|
+
};
|
|
281
240
|
const { pid } = startShardManagerProcess({
|
|
282
241
|
python,
|
|
283
242
|
logPath,
|
|
@@ -287,6 +246,7 @@ async function wizard(args) {
|
|
|
287
246
|
try {
|
|
288
247
|
await waitForShardRunning({ timeoutMs: 600000, statusHost: "127.0.0.1" });
|
|
289
248
|
} catch (err) {
|
|
249
|
+
tunnel.stop();
|
|
290
250
|
spin.stop("Petals did not become ready");
|
|
291
251
|
p.cancel(`${err.message}. See ${logPath}`);
|
|
292
252
|
process.exit(1);
|
|
@@ -294,13 +254,12 @@ async function wizard(args) {
|
|
|
294
254
|
spin.stop("Petals is serving your layers");
|
|
295
255
|
|
|
296
256
|
spin.start("Finalizing handoff with mother…");
|
|
297
|
-
const peerMultiaddr = extractPeerMultiaddrFromLog(petalsLogPath, fs);
|
|
298
257
|
try {
|
|
299
258
|
await readyHost(selected.mother_url, {
|
|
300
259
|
host_id: assignment.host_id,
|
|
301
|
-
peer_multiaddr: peerMultiaddr || undefined,
|
|
302
260
|
});
|
|
303
261
|
} catch (err) {
|
|
262
|
+
tunnel.stop();
|
|
304
263
|
spin.stop("Handoff failed");
|
|
305
264
|
p.cancel(err.message);
|
|
306
265
|
process.exit(1);
|
|
@@ -314,9 +273,10 @@ async function wizard(args) {
|
|
|
314
273
|
block_indices: assignment.block_indices,
|
|
315
274
|
layers_hosted: assignment.layers_hosted,
|
|
316
275
|
public_ip: String(publicIp).trim(),
|
|
317
|
-
network_mode:
|
|
318
|
-
|
|
319
|
-
|
|
276
|
+
network_mode: "cloudflare",
|
|
277
|
+
shard_manager_url: tunnel.url,
|
|
278
|
+
tunnel_url: tunnel.url,
|
|
279
|
+
tunnel_pid: tunnel.pid,
|
|
320
280
|
shard_pid: pid,
|
|
321
281
|
discovery_url: args.discoveryUrl,
|
|
322
282
|
hedera_account_id: hederaAccountId,
|
|
@@ -330,6 +290,7 @@ async function wizard(args) {
|
|
|
330
290
|
|
|
331
291
|
p.outro(
|
|
332
292
|
`${color.green("You are online.")} Hosting ${color.cyan(String(state.layers_hosted))} layers of ${color.cyan(state.model)} at ${state.block_indices}\n` +
|
|
293
|
+
`Tunnel: ${color.cyan(tunnel.url)}\n` +
|
|
333
294
|
`Run ${color.bold("blitzwing status")} anytime, or ${color.bold("blitzwing leave")} to exit.`
|
|
334
295
|
);
|
|
335
296
|
}
|
|
@@ -346,10 +307,10 @@ async function showStatus() {
|
|
|
346
307
|
console.log(` layers_hosted: ${state.layers_hosted}`);
|
|
347
308
|
console.log(` block_indices: ${state.block_indices}`);
|
|
348
309
|
console.log(` mother: ${state.mother_url}`);
|
|
349
|
-
console.log(`
|
|
310
|
+
console.log(` tunnel: ${state.tunnel_url || state.shard_manager_url || "—"}`);
|
|
311
|
+
console.log(` hedera: ${state.hedera_account_id || "—"}`);
|
|
350
312
|
try {
|
|
351
|
-
const
|
|
352
|
-
const res = await fetch(`http://${statusHost}:${SHARD_PORT}/status`);
|
|
313
|
+
const res = await fetch(`http://127.0.0.1:${SHARD_PORT}/status`);
|
|
353
314
|
if (res.ok) {
|
|
354
315
|
const st = await res.json();
|
|
355
316
|
console.log(` petals_running: ${st.running}`);
|
|
@@ -376,11 +337,13 @@ async function doLeave() {
|
|
|
376
337
|
p.log.warn(`Mother leave call failed: ${err.message}`);
|
|
377
338
|
}
|
|
378
339
|
try {
|
|
379
|
-
|
|
380
|
-
await fetch(`http://${statusHost}:${SHARD_PORT}/stop`, { method: "POST" });
|
|
340
|
+
await fetch(`http://127.0.0.1:${SHARD_PORT}/stop`, { method: "POST" });
|
|
381
341
|
} catch {
|
|
382
342
|
/* ignore */
|
|
383
343
|
}
|
|
344
|
+
if (state.tunnel_pid) {
|
|
345
|
+
stopTunnelProcess(state.tunnel_pid);
|
|
346
|
+
}
|
|
384
347
|
clearState();
|
|
385
348
|
spin.stop("Left swarm");
|
|
386
349
|
p.outro("Your layers were reclaimed by the mother (when reachable).");
|