@peerbit/server 5.9.5 → 5.10.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/dist/src/aws.d.ts.map +1 -1
- package/dist/src/aws.js +2 -1
- package/dist/src/aws.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +142 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +7 -1
- package/dist/src/client.js.map +1 -1
- package/dist/src/docker.d.ts.map +1 -1
- package/dist/src/docker.js +122 -32
- package/dist/src/docker.js.map +1 -1
- package/dist/src/hetzner.browser.d.ts +2 -0
- package/dist/src/hetzner.browser.d.ts.map +1 -0
- package/dist/src/hetzner.browser.js +3 -0
- package/dist/src/hetzner.browser.js.map +1 -0
- package/dist/src/hetzner.d.ts +26 -0
- package/dist/src/hetzner.d.ts.map +1 -0
- package/dist/src/hetzner.js +161 -0
- package/dist/src/hetzner.js.map +1 -0
- package/dist/src/remotes.d.ts +6 -1
- package/dist/src/remotes.d.ts.map +1 -1
- package/dist/src/remotes.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/assets/{index-BEgs1gyX.js → index-Dst3R_bZ.js} +6 -6
- package/dist/ui/index.html +1 -1
- package/package.json +6 -5
- package/src/aws.ts +2 -1
- package/src/cli.ts +164 -1
- package/src/client.ts +6 -1
- package/src/docker.ts +139 -31
- package/src/hetzner.browser.ts +1 -0
- package/src/hetzner.ts +242 -0
- package/src/remotes.ts +7 -1
package/dist/ui/index.html
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
Learn how to configure a non-root public URL by running `npm run build`.
|
|
24
24
|
-->
|
|
25
25
|
<title>Peerbit</title>
|
|
26
|
-
<script type="module" crossorigin src="/assets/index-
|
|
26
|
+
<script type="module" crossorigin src="/assets/index-Dst3R_bZ.js"></script>
|
|
27
27
|
<link rel="stylesheet" crossorigin href="/assets/index-CIfVvUo9.css">
|
|
28
28
|
</head>
|
|
29
29
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.0",
|
|
4
4
|
"author": "dao.xyz",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"./remotes.js": "./dist/src/remotes.browser.js",
|
|
27
27
|
"./dist/src/docker.js": "./dist/src/docker.browser.js",
|
|
28
28
|
"./docker.js": "./dist/src/docker.browser.js",
|
|
29
|
-
"./dist/src/aws.js": "./dist/src/aws.browser.js"
|
|
29
|
+
"./dist/src/aws.js": "./dist/src/aws.browser.js",
|
|
30
|
+
"./dist/src/hetzner.js": "./dist/src/hetzner.browser.js"
|
|
30
31
|
},
|
|
31
32
|
"files": [
|
|
32
33
|
"dist",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
67
68
|
"uuid": "^10.0.0",
|
|
68
69
|
"@peerbit/test-lib": "0.0.1",
|
|
69
|
-
"@peerbit/test-utils": "2.3.
|
|
70
|
+
"@peerbit/test-utils": "2.3.6"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
73
|
"axios": "^1.4.0",
|
|
@@ -88,10 +89,10 @@
|
|
|
88
89
|
"memory-level": "^3.1.0",
|
|
89
90
|
"multiformats": "^13.4.1",
|
|
90
91
|
"abstract-level": "^3.1.0",
|
|
91
|
-
"peerbit": "4.4.
|
|
92
|
+
"peerbit": "4.4.6",
|
|
92
93
|
"@peerbit/blocks": "3.1.3",
|
|
93
94
|
"@peerbit/crypto": "2.4.0",
|
|
94
|
-
"@peerbit/program": "5.4.
|
|
95
|
+
"@peerbit/program": "5.4.4",
|
|
95
96
|
"@peerbit/pubsub": "4.1.1",
|
|
96
97
|
"@peerbit/time": "2.3.0"
|
|
97
98
|
},
|
package/src/aws.ts
CHANGED
|
@@ -57,6 +57,7 @@ const setupUserData = (
|
|
|
57
57
|
serverVersion?: string,
|
|
58
58
|
) => {
|
|
59
59
|
const peerIdStrings = grantAccess.map((x) => x.toString());
|
|
60
|
+
const grantArgs = peerIdStrings.map((key) => `--ga ${key}`).join(" ");
|
|
60
61
|
|
|
61
62
|
// better-sqlite3 force use to install build-essentials for `make` command, TOOD dont bundle better-sqlite3 by default?
|
|
62
63
|
const versionSpec = serverVersion ? `@${serverVersion}` : "";
|
|
@@ -67,7 +68,7 @@ sudo apt-get install -y nodejs
|
|
|
67
68
|
sudo apt-get install -y build-essential
|
|
68
69
|
npm install -g @peerbit/server${versionSpec}
|
|
69
70
|
sudo peerbit domain test --email ${email}
|
|
70
|
-
peerbit start ${
|
|
71
|
+
peerbit start ${grantArgs} > log.txt 2>&1 &
|
|
71
72
|
`;
|
|
72
73
|
};
|
|
73
74
|
const PURPOSE_TAG_NAME = "Purpose";
|
package/src/cli.ts
CHANGED
|
@@ -30,6 +30,11 @@ import {
|
|
|
30
30
|
loadConfig,
|
|
31
31
|
startCertbot,
|
|
32
32
|
} from "./domain.js";
|
|
33
|
+
import {
|
|
34
|
+
HETZNER_SERVER_TYPES,
|
|
35
|
+
launchNodes as launchHetznerNodes,
|
|
36
|
+
terminateNode as terminateHetznerNode,
|
|
37
|
+
} from "./hetzner.js";
|
|
33
38
|
import { DEFAULT_REMOTE_GROUP, type RemoteObject, Remotes } from "./remotes.js";
|
|
34
39
|
import { LOCAL_API_PORT } from "./routes.js";
|
|
35
40
|
import { startServerWithNode } from "./server.js";
|
|
@@ -457,6 +462,152 @@ export const cli = async (args?: string[]) => {
|
|
|
457
462
|
}
|
|
458
463
|
},
|
|
459
464
|
})
|
|
465
|
+
.command({
|
|
466
|
+
command: "hetzner",
|
|
467
|
+
describe: "Spawn remote nodes on Hetzner Cloud",
|
|
468
|
+
builder: (hetznerArgs: Argv) => {
|
|
469
|
+
hetznerArgs.option("count", {
|
|
470
|
+
describe: "Amount of nodes to spawn",
|
|
471
|
+
defaultDescription: "One node",
|
|
472
|
+
type: "number",
|
|
473
|
+
alias: "c",
|
|
474
|
+
default: 1,
|
|
475
|
+
});
|
|
476
|
+
hetznerArgs.option("location", {
|
|
477
|
+
describe: "Location (e.g. fsn1, nbg1, hel1, ash, hil)",
|
|
478
|
+
type: "string",
|
|
479
|
+
alias: "l",
|
|
480
|
+
default: "fsn1",
|
|
481
|
+
});
|
|
482
|
+
hetznerArgs.option("group", {
|
|
483
|
+
describe: "Remote group to launch nodes in",
|
|
484
|
+
type: "string",
|
|
485
|
+
alias: "g",
|
|
486
|
+
default: DEFAULT_REMOTE_GROUP,
|
|
487
|
+
});
|
|
488
|
+
hetznerArgs.option("server-type", {
|
|
489
|
+
describe: "Server type",
|
|
490
|
+
type: "string",
|
|
491
|
+
alias: "t",
|
|
492
|
+
choices: [...HETZNER_SERVER_TYPES],
|
|
493
|
+
default: "cx11",
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
hetznerArgs.option("name", {
|
|
497
|
+
describe: "Name prefix for spawned nodes",
|
|
498
|
+
type: "string",
|
|
499
|
+
alias: "n",
|
|
500
|
+
default: "peerbit-node",
|
|
501
|
+
});
|
|
502
|
+
hetznerArgs.option("grant-access", {
|
|
503
|
+
describe: "Grant access to public keys on start",
|
|
504
|
+
defaultDescription:
|
|
505
|
+
"The publickey of this device located in 'directory'",
|
|
506
|
+
type: "string",
|
|
507
|
+
array: true,
|
|
508
|
+
alias: "ga",
|
|
509
|
+
});
|
|
510
|
+
hetznerArgs.option("directory", {
|
|
511
|
+
describe: "Peerbit directory",
|
|
512
|
+
defaultDescription: "~.peerbit",
|
|
513
|
+
type: "string",
|
|
514
|
+
alias: "d",
|
|
515
|
+
default: getHomeConfigDir(),
|
|
516
|
+
});
|
|
517
|
+
hetznerArgs.option("email", {
|
|
518
|
+
describe: "Email for Let's security messages",
|
|
519
|
+
type: "string",
|
|
520
|
+
alias: "e",
|
|
521
|
+
demandOption: true,
|
|
522
|
+
});
|
|
523
|
+
hetznerArgs.option("token", {
|
|
524
|
+
describe: "Hetzner Cloud API token (or set HCLOUD_TOKEN)",
|
|
525
|
+
type: "string",
|
|
526
|
+
alias: ["tok"],
|
|
527
|
+
});
|
|
528
|
+
hetznerArgs.option("image", {
|
|
529
|
+
describe: "Image to use (e.g. ubuntu-22.04)",
|
|
530
|
+
type: "string",
|
|
531
|
+
default: "ubuntu-22.04",
|
|
532
|
+
});
|
|
533
|
+
hetznerArgs.option("server-version", {
|
|
534
|
+
describe:
|
|
535
|
+
"@peerbit/server version or tag to install on the instance (e.g. 5.7.0-58d3d09)",
|
|
536
|
+
type: "string",
|
|
537
|
+
alias: ["sv"],
|
|
538
|
+
});
|
|
539
|
+
return hetznerArgs;
|
|
540
|
+
},
|
|
541
|
+
handler: async (args) => {
|
|
542
|
+
const self = (
|
|
543
|
+
await getKeypair(args.directory)
|
|
544
|
+
).publicKey.toPeerId();
|
|
545
|
+
const accessGrant: PeerId[] =
|
|
546
|
+
args["grant-access"]?.length > 0
|
|
547
|
+
? (args["grant-access"] as string[]).map((x) =>
|
|
548
|
+
peerIdFromString(x),
|
|
549
|
+
)
|
|
550
|
+
: [];
|
|
551
|
+
accessGrant.push(self);
|
|
552
|
+
const nodes = await launchHetznerNodes({
|
|
553
|
+
token: (args.token as string) || undefined,
|
|
554
|
+
email: args.email as string,
|
|
555
|
+
count: args.count,
|
|
556
|
+
namePrefix: args.name,
|
|
557
|
+
location: args.location,
|
|
558
|
+
serverType: args["server-type"],
|
|
559
|
+
grantAccess: accessGrant,
|
|
560
|
+
image: args.image,
|
|
561
|
+
serverVersion:
|
|
562
|
+
(args["server-version"] as string) || undefined,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
console.log(
|
|
566
|
+
`Waiting for ${args.count} ${
|
|
567
|
+
args.count > 1 ? "nodes" : "node"
|
|
568
|
+
} to spawn. This might take a few minutes. You can watch the progress in your Hetzner Cloud console.`,
|
|
569
|
+
);
|
|
570
|
+
const twirlTimer = (function () {
|
|
571
|
+
const P = ["\\", "|", "/", "-"];
|
|
572
|
+
let x = 0;
|
|
573
|
+
return setInterval(function () {
|
|
574
|
+
process.stdout.write(
|
|
575
|
+
"\r" + "Loading: " + chalk.hex(colors[x])(P[x++]),
|
|
576
|
+
);
|
|
577
|
+
x &= 3;
|
|
578
|
+
}, 250);
|
|
579
|
+
})();
|
|
580
|
+
for (const node of nodes) {
|
|
581
|
+
try {
|
|
582
|
+
const domain = await waitForDomain(node.publicIp);
|
|
583
|
+
const remotes = new Remotes(getRemotesPath(args.directory));
|
|
584
|
+
remotes.add({
|
|
585
|
+
name: node.name,
|
|
586
|
+
address: domain,
|
|
587
|
+
group: args.group,
|
|
588
|
+
origin: {
|
|
589
|
+
type: "hetzner",
|
|
590
|
+
serverId: node.serverId,
|
|
591
|
+
location: node.location,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
} catch (error: any) {
|
|
595
|
+
process.stdout.write("\r");
|
|
596
|
+
console.error(
|
|
597
|
+
`Error waiting for domain for ip: ${
|
|
598
|
+
node.publicIp
|
|
599
|
+
} to be available: ${error?.toString()}`,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
process.stdout.write("\r");
|
|
604
|
+
clearInterval(twirlTimer);
|
|
605
|
+
console.log(`New nodes available (${nodes.length}):`);
|
|
606
|
+
for (const node of nodes) {
|
|
607
|
+
console.log(chalk.green(node.name));
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
})
|
|
460
611
|
.strict()
|
|
461
612
|
.demandCommand();
|
|
462
613
|
})
|
|
@@ -469,6 +620,11 @@ export const cli = async (args?: string[]) => {
|
|
|
469
620
|
type: "boolean",
|
|
470
621
|
default: false,
|
|
471
622
|
});
|
|
623
|
+
killArgs.option("token", {
|
|
624
|
+
describe: "Used for Hetzner Cloud API (or set HCLOUD_TOKEN)",
|
|
625
|
+
type: "string",
|
|
626
|
+
alias: ["tok"],
|
|
627
|
+
});
|
|
472
628
|
killArgs.positional("name", {
|
|
473
629
|
type: "string",
|
|
474
630
|
describe: "Remote name",
|
|
@@ -495,6 +651,11 @@ export const cli = async (args?: string[]) => {
|
|
|
495
651
|
instanceId: remote.origin.instanceId,
|
|
496
652
|
region: remote.origin.region,
|
|
497
653
|
});
|
|
654
|
+
} else if (remote.origin?.type === "hetzner") {
|
|
655
|
+
await terminateHetznerNode({
|
|
656
|
+
serverId: remote.origin.serverId,
|
|
657
|
+
token: (args.token as string) || undefined,
|
|
658
|
+
});
|
|
498
659
|
}
|
|
499
660
|
}
|
|
500
661
|
}
|
|
@@ -537,7 +698,9 @@ export const cli = async (args?: string[]) => {
|
|
|
537
698
|
remote.group || "",
|
|
538
699
|
remote.origin?.type === "aws"
|
|
539
700
|
? `aws\n${remote.origin.region}\n${remote.origin.instanceId}`
|
|
540
|
-
: ""
|
|
701
|
+
: remote.origin?.type === "hetzner"
|
|
702
|
+
? `hetzner\n${remote.origin.location}\n${remote.origin.serverId}`
|
|
703
|
+
: "",
|
|
541
704
|
resolvedOrRejected[ix].status === "fulfilled"
|
|
542
705
|
? chalk.green("Y")
|
|
543
706
|
: chalk.red("N"),
|
package/src/client.ts
CHANGED
|
@@ -358,12 +358,17 @@ export const createClient = async (
|
|
|
358
358
|
},
|
|
359
359
|
|
|
360
360
|
terminate: async () => {
|
|
361
|
-
const { terminateNode } = await import("./aws.js");
|
|
362
361
|
if (remote.origin?.type === "aws") {
|
|
362
|
+
const { terminateNode } = await import("./aws.js");
|
|
363
363
|
await terminateNode({
|
|
364
364
|
instanceId: remote.origin.instanceId,
|
|
365
365
|
region: remote.origin.region,
|
|
366
366
|
});
|
|
367
|
+
} else if (remote.origin?.type === "hetzner") {
|
|
368
|
+
const { terminateNode } = await import("./hetzner.js");
|
|
369
|
+
await terminateNode({
|
|
370
|
+
serverId: remote.origin.serverId,
|
|
371
|
+
});
|
|
367
372
|
}
|
|
368
373
|
},
|
|
369
374
|
};
|
package/src/docker.ts
CHANGED
|
@@ -1,44 +1,152 @@
|
|
|
1
1
|
import { delay, waitFor } from "@peerbit/time";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type ExecResult = { stdout: string; stderr: string };
|
|
4
|
+
|
|
5
|
+
const execCommand = async (cmd: string): Promise<ExecResult> => {
|
|
4
6
|
const { exec } = await import("child_process");
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
exec(cmd, (error, stdout, stderr) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
(error as any).stdout = stdout;
|
|
11
|
+
(error as any).stderr = stderr;
|
|
12
|
+
reject(error);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
resolve({ stdout, stderr });
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
|
5
19
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const commandExists = async (command: string): Promise<boolean> => {
|
|
21
|
+
try {
|
|
22
|
+
await execCommand(`command -v ${command}`);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getSudoPrefix = async (): Promise<string> => {
|
|
30
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
31
|
+
if (isRoot) {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
if (await commandExists("sudo")) {
|
|
35
|
+
return "sudo ";
|
|
36
|
+
}
|
|
37
|
+
throw new Error("Docker installation requires elevated privileges (sudo not found)");
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const dockerCliExists = async (): Promise<boolean> => {
|
|
41
|
+
try {
|
|
42
|
+
await execCommand("docker --version");
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const dockerDaemonAccessible = async (): Promise<boolean> => {
|
|
50
|
+
try {
|
|
51
|
+
await execCommand("docker info");
|
|
52
|
+
return true;
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
const stderr: string = error?.stderr || "";
|
|
55
|
+
if (
|
|
56
|
+
stderr.includes("Got permission denied") ||
|
|
57
|
+
stderr.toLowerCase().includes("permission denied")
|
|
58
|
+
) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Docker is installed but the current user cannot access the Docker daemon. Add the user to the 'docker' group or run with elevated privileges.",
|
|
61
|
+
);
|
|
20
62
|
}
|
|
21
|
-
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
22
66
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
resolve(stdout);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
67
|
+
const startDockerDaemon = async (sudoPrefix: string) => {
|
|
68
|
+
if (await commandExists("snap")) {
|
|
69
|
+
try {
|
|
70
|
+
await execCommand(`${sudoPrefix}snap start docker`);
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
32
73
|
|
|
74
|
+
if (await commandExists("systemctl")) {
|
|
33
75
|
try {
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
await execCommand(`${sudoPrefix}systemctl enable --now docker`);
|
|
77
|
+
return;
|
|
78
|
+
} catch {}
|
|
79
|
+
try {
|
|
80
|
+
await execCommand(`${sudoPrefix}systemctl start docker`);
|
|
81
|
+
return;
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (await commandExists("service")) {
|
|
86
|
+
try {
|
|
87
|
+
await execCommand(`${sudoPrefix}service docker start`);
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const installDockerWithSnap = async (sudoPrefix: string) => {
|
|
93
|
+
await execCommand(`${sudoPrefix}snap install docker`);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const installDockerWithApt = async (sudoPrefix: string) => {
|
|
97
|
+
await execCommand(`${sudoPrefix}apt-get update`);
|
|
98
|
+
await execCommand(
|
|
99
|
+
`${sudoPrefix}DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io`,
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const installDocker = async () => {
|
|
104
|
+
const sudoPrefix = await getSudoPrefix();
|
|
105
|
+
|
|
106
|
+
if (!(await dockerCliExists())) {
|
|
107
|
+
let lastError: unknown;
|
|
108
|
+
|
|
109
|
+
if (await commandExists("snap")) {
|
|
110
|
+
try {
|
|
111
|
+
await installDockerWithSnap(sudoPrefix);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
lastError = error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!(await dockerCliExists()) && (await commandExists("apt-get"))) {
|
|
118
|
+
try {
|
|
119
|
+
await installDockerWithApt(sudoPrefix);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
lastError = error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!(await dockerCliExists())) {
|
|
126
|
+
const suffix =
|
|
127
|
+
lastError instanceof Error
|
|
128
|
+
? `: ${lastError.message}`
|
|
129
|
+
: lastError
|
|
130
|
+
? `: ${String(lastError)}`
|
|
131
|
+
: "";
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Failed to install docker (no supported installer succeeded)${suffix}`,
|
|
134
|
+
);
|
|
40
135
|
}
|
|
41
136
|
}
|
|
137
|
+
|
|
138
|
+
await startDockerDaemon(sudoPrefix);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await waitFor(async () => dockerDaemonAccessible(), {
|
|
142
|
+
timeout: 2 * 60 * 1000,
|
|
143
|
+
delayInterval: 2000,
|
|
144
|
+
});
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Docker is installed but not available: ${error?.message || "unknown error"}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
42
150
|
};
|
|
43
151
|
|
|
44
152
|
export const startContainer = async (cmd: string, errorMessage?: string) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Unsupported
|