@peerbit/server 1.0.3
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/LICENSE +202 -0
- package/lib/esm/api.d.ts +33 -0
- package/lib/esm/api.js +370 -0
- package/lib/esm/api.js.map +1 -0
- package/lib/esm/aws.d.ts +9 -0
- package/lib/esm/aws.js +39 -0
- package/lib/esm/aws.js.map +1 -0
- package/lib/esm/bin.d.ts +2 -0
- package/lib/esm/bin.js +9 -0
- package/lib/esm/bin.js.map +1 -0
- package/lib/esm/cli.d.ts +9 -0
- package/lib/esm/cli.js +255 -0
- package/lib/esm/cli.js.map +1 -0
- package/lib/esm/client.d.ts +2 -0
- package/lib/esm/client.js +20 -0
- package/lib/esm/client.js.map +1 -0
- package/lib/esm/config.d.ts +5 -0
- package/lib/esm/config.js +17 -0
- package/lib/esm/config.js.map +1 -0
- package/lib/esm/docker.d.ts +2 -0
- package/lib/esm/docker.js +63 -0
- package/lib/esm/docker.js.map +1 -0
- package/lib/esm/domain.d.ts +10 -0
- package/lib/esm/domain.js +132 -0
- package/lib/esm/domain.js.map +1 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/nginx-template.conf +148 -0
- package/lib/esm/package.json +3 -0
- package/lib/ui/assets/index-5265c558.css +1 -0
- package/lib/ui/assets/index-b451191e.js +80 -0
- package/lib/ui/assets/index-bd766cd9.js +3 -0
- package/lib/ui/assets/logo192-5b1fb15f.png +0 -0
- package/lib/ui/favicon-128.png +0 -0
- package/lib/ui/favicon-16.png +0 -0
- package/lib/ui/favicon-256.png +0 -0
- package/lib/ui/favicon-32.png +0 -0
- package/lib/ui/favicon-48.png +0 -0
- package/lib/ui/favicon-96.png +0 -0
- package/lib/ui/favicon.ico +0 -0
- package/lib/ui/index.html +36 -0
- package/lib/ui/logo192.png +0 -0
- package/lib/ui/logo512.png +0 -0
- package/lib/ui/manifest.json +25 -0
- package/lib/ui/robots.txt +3 -0
- package/package.json +61 -0
- package/src/api.ts +433 -0
- package/src/aws.ts +48 -0
- package/src/bin.ts +7 -0
- package/src/cli.ts +266 -0
- package/src/client.ts +20 -0
- package/src/config.ts +20 -0
- package/src/docker.ts +67 -0
- package/src/domain.ts +179 -0
- package/src/index.ts +1 -0
- package/src/nginx-template.conf +148 -0
package/src/aws.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getMyIp } from "./domain.js";
|
|
2
|
+
|
|
3
|
+
export const createRecord = async (options: {
|
|
4
|
+
domain: string;
|
|
5
|
+
region?: string;
|
|
6
|
+
hostedZoneId: string;
|
|
7
|
+
credentials?: { accessKeyId: string; secretAccessKey: string };
|
|
8
|
+
}): Promise<void> => {
|
|
9
|
+
const { Route53Client, ChangeResourceRecordSetsCommand } = await import(
|
|
10
|
+
"@aws-sdk/client-route-53"
|
|
11
|
+
);
|
|
12
|
+
const { isIPv4, isIPv6 } = await import("net");
|
|
13
|
+
|
|
14
|
+
const myIp = await getMyIp();
|
|
15
|
+
const v4 = isIPv4(myIp);
|
|
16
|
+
const v6 = isIPv6(myIp);
|
|
17
|
+
|
|
18
|
+
if (!v6 && !v4) {
|
|
19
|
+
throw new Error("Unknown ip type");
|
|
20
|
+
}
|
|
21
|
+
// TODO, make sure it works for ipv6 addresses with leading and trailing colon
|
|
22
|
+
const client = new Route53Client({
|
|
23
|
+
region: options.region,
|
|
24
|
+
credentials: options.credentials
|
|
25
|
+
? {
|
|
26
|
+
accessKeyId: options.credentials.accessKeyId,
|
|
27
|
+
secretAccessKey: options.credentials.secretAccessKey,
|
|
28
|
+
}
|
|
29
|
+
: undefined,
|
|
30
|
+
});
|
|
31
|
+
const cmd = new ChangeResourceRecordSetsCommand({
|
|
32
|
+
ChangeBatch: {
|
|
33
|
+
Changes: [
|
|
34
|
+
{
|
|
35
|
+
Action: "CREATE",
|
|
36
|
+
ResourceRecordSet: {
|
|
37
|
+
Name: options.domain,
|
|
38
|
+
Type: v4 ? "A" : "AAAA",
|
|
39
|
+
TTL: 60,
|
|
40
|
+
ResourceRecords: [{ Value: myIp }],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
HostedZoneId: options.hostedZoneId,
|
|
46
|
+
});
|
|
47
|
+
await client.send(cmd);
|
|
48
|
+
};
|
package/src/bin.ts
ADDED
package/src/cli.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createTestDomain, startCertbot } from "./domain.js";
|
|
2
|
+
import { serialize } from "@dao-xyz/borsh";
|
|
3
|
+
import { client, startServerWithNode } from "./api.js";
|
|
4
|
+
import { createRecord } from "./aws.js";
|
|
5
|
+
import { toBase64 } from "@peerbit/crypto";
|
|
6
|
+
import { getConfigDir } from "./config.js";
|
|
7
|
+
|
|
8
|
+
export const cli = async (args?: string[]) => {
|
|
9
|
+
const yargs = await import("yargs");
|
|
10
|
+
|
|
11
|
+
if (!args) {
|
|
12
|
+
const { hideBin } = await import("yargs/helpers");
|
|
13
|
+
args = hideBin(process.argv);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return yargs
|
|
17
|
+
.default(args)
|
|
18
|
+
.command({
|
|
19
|
+
command: "start",
|
|
20
|
+
describe: "Start node",
|
|
21
|
+
builder: {
|
|
22
|
+
directory: {
|
|
23
|
+
describe: "Directory for all data created by the node",
|
|
24
|
+
defaultDescription: "~.peerbit",
|
|
25
|
+
type: "string",
|
|
26
|
+
default: await getConfigDir(),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
handler: async (args) => {
|
|
30
|
+
await startServerWithNode(args.directory);
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
.command("domain", "Setup a domain and certificate", (yargs) => {
|
|
34
|
+
yargs
|
|
35
|
+
.command({
|
|
36
|
+
command: "test",
|
|
37
|
+
describe:
|
|
38
|
+
"Setup a testing domain with SSL (no guarantess on how long the domain will be available)",
|
|
39
|
+
builder: {
|
|
40
|
+
email: {
|
|
41
|
+
describe: "Email for Lets encrypt autorenewal messages",
|
|
42
|
+
type: "string",
|
|
43
|
+
demandOption: true,
|
|
44
|
+
},
|
|
45
|
+
outdir: {
|
|
46
|
+
describe: "Output path for Nginx config",
|
|
47
|
+
type: "string",
|
|
48
|
+
alias: "o",
|
|
49
|
+
},
|
|
50
|
+
wait: {
|
|
51
|
+
alias: "w",
|
|
52
|
+
describe: "Wait for setup to succeed (or fail)",
|
|
53
|
+
type: "boolean",
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
handler: async (args) => {
|
|
58
|
+
const domain = await createTestDomain();
|
|
59
|
+
await startCertbot(domain, args.email, args.outdir, args.wait);
|
|
60
|
+
const { exit } = await import("process");
|
|
61
|
+
exit();
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
.command({
|
|
65
|
+
command: "aws",
|
|
66
|
+
describe:
|
|
67
|
+
"Setup a domain with an AWS account. You either have to setup you AWS credentials in the .aws folder, or pass the credentials in the cli",
|
|
68
|
+
builder: {
|
|
69
|
+
domain: {
|
|
70
|
+
describe: "domain, e.g. abc.example.com, example.com",
|
|
71
|
+
alias: "d",
|
|
72
|
+
type: "string",
|
|
73
|
+
demandOption: true,
|
|
74
|
+
},
|
|
75
|
+
hostedZoneId: {
|
|
76
|
+
describe: 'The id of the hosted zone "HostedZoneId"',
|
|
77
|
+
alias: "hz",
|
|
78
|
+
type: "string",
|
|
79
|
+
require: true,
|
|
80
|
+
},
|
|
81
|
+
accessKeyId: {
|
|
82
|
+
describe: "Access key id of the AWS user",
|
|
83
|
+
alias: "ak",
|
|
84
|
+
type: "string",
|
|
85
|
+
},
|
|
86
|
+
region: {
|
|
87
|
+
describe: "AWS region",
|
|
88
|
+
alias: "r",
|
|
89
|
+
type: "string",
|
|
90
|
+
},
|
|
91
|
+
secretAccessKey: {
|
|
92
|
+
describe: "Secret key id of the AWS user",
|
|
93
|
+
alias: "sk",
|
|
94
|
+
type: "string",
|
|
95
|
+
},
|
|
96
|
+
email: {
|
|
97
|
+
describe: "Email for Lets encrypt autorenewal messages",
|
|
98
|
+
type: "string",
|
|
99
|
+
demandOption: true,
|
|
100
|
+
},
|
|
101
|
+
outdir: {
|
|
102
|
+
describe: "Output path for Nginx config",
|
|
103
|
+
type: "string",
|
|
104
|
+
alias: "o",
|
|
105
|
+
},
|
|
106
|
+
wait: {
|
|
107
|
+
alias: "w",
|
|
108
|
+
describe: "Wait for setup to succeed (or fail)",
|
|
109
|
+
type: "boolean",
|
|
110
|
+
default: false,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
handler: async (args) => {
|
|
114
|
+
if (
|
|
115
|
+
!!args.accessKeyId !== !!args.secretAccessKey ||
|
|
116
|
+
!!args.region !== !!args.secretAccessKey
|
|
117
|
+
) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"Expecting either all 'accessKeyId', 'region' and 'secretAccessKey' to be provided or none"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
await createRecord({
|
|
123
|
+
domain: args.domain,
|
|
124
|
+
hostedZoneId: args.hostedZoneId,
|
|
125
|
+
region: args.region,
|
|
126
|
+
credentials: args.accessKeyId
|
|
127
|
+
? {
|
|
128
|
+
accessKeyId: args.accessKeyId,
|
|
129
|
+
secretAccessKey: args.secretAccessKey,
|
|
130
|
+
}
|
|
131
|
+
: undefined,
|
|
132
|
+
});
|
|
133
|
+
await startCertbot(args.domain, args.email, args.outdir, args.wait);
|
|
134
|
+
const { exit } = await import("process");
|
|
135
|
+
exit();
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
.strict()
|
|
139
|
+
.demandCommand();
|
|
140
|
+
})
|
|
141
|
+
.command("topic", "Manage topics the node is listening to", (yargs) => {
|
|
142
|
+
yargs
|
|
143
|
+
.command({
|
|
144
|
+
command: "list",
|
|
145
|
+
aliases: "ls",
|
|
146
|
+
describe: "List all topics",
|
|
147
|
+
builder: (yargs: any) => {
|
|
148
|
+
yargs.option("replicate", {
|
|
149
|
+
type: "boolean",
|
|
150
|
+
describe: "Replicate data on this topic",
|
|
151
|
+
alias: "r",
|
|
152
|
+
default: false,
|
|
153
|
+
});
|
|
154
|
+
return yargs;
|
|
155
|
+
},
|
|
156
|
+
handler: async (args) => {
|
|
157
|
+
/* const c = await client();
|
|
158
|
+
const topics = await c.topics.get(args.replicate);
|
|
159
|
+
if (topics?.length > 0) {
|
|
160
|
+
console.log("Topic (" + topics.length + "):");
|
|
161
|
+
for (const t of topics) {
|
|
162
|
+
console.log(t);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.log("Not subscribed to any topics");
|
|
166
|
+
} */
|
|
167
|
+
console.error("Not implemented");
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
.strict()
|
|
171
|
+
.demandCommand();
|
|
172
|
+
return yargs;
|
|
173
|
+
})
|
|
174
|
+
.command("program", "Manage programs", (yargs) => {
|
|
175
|
+
yargs
|
|
176
|
+
.command({
|
|
177
|
+
command: "get <address>",
|
|
178
|
+
describe: "Get program manifest/serialized in base64",
|
|
179
|
+
builder: (yargs: any) => {
|
|
180
|
+
yargs.positional("address", {
|
|
181
|
+
type: "string",
|
|
182
|
+
describe: "Program address",
|
|
183
|
+
demandOption: true,
|
|
184
|
+
});
|
|
185
|
+
return yargs;
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
handler: async (args) => {
|
|
189
|
+
const c = await client();
|
|
190
|
+
const program = await c.program.get(args.address);
|
|
191
|
+
if (!program) {
|
|
192
|
+
console.log("Program does not exist");
|
|
193
|
+
} else {
|
|
194
|
+
console.log(toBase64(serialize(program)));
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
.command({
|
|
199
|
+
command: "add <program>",
|
|
200
|
+
describe: "Add program",
|
|
201
|
+
builder: (yargs: any) => {
|
|
202
|
+
yargs.positional("program", {
|
|
203
|
+
type: "string",
|
|
204
|
+
describe: "base64 serialized",
|
|
205
|
+
demandOption: true,
|
|
206
|
+
});
|
|
207
|
+
return yargs;
|
|
208
|
+
},
|
|
209
|
+
handler: async (args) => {
|
|
210
|
+
const c = await client();
|
|
211
|
+
const address = await c.program.put(args.program);
|
|
212
|
+
console.log(address.toString());
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
.command({
|
|
216
|
+
command: "import <library>",
|
|
217
|
+
describe: "import a library that contains programs",
|
|
218
|
+
builder: (yargs: any) => {
|
|
219
|
+
yargs.positional("library", {
|
|
220
|
+
type: "array",
|
|
221
|
+
describe:
|
|
222
|
+
"Library name (will be loaded with js import(...)). Onlu libraries that are globally installed and can be imported",
|
|
223
|
+
demandOption: true,
|
|
224
|
+
});
|
|
225
|
+
return yargs;
|
|
226
|
+
},
|
|
227
|
+
handler: async (args) => {
|
|
228
|
+
for (const lib of args.library) {
|
|
229
|
+
const importedLib = await import(
|
|
230
|
+
/* webpackIgnore: true */ /* @vite-ignore */ lib
|
|
231
|
+
);
|
|
232
|
+
console.log("imported lib:", importedLib);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
.strict()
|
|
237
|
+
.demandCommand();
|
|
238
|
+
return yargs;
|
|
239
|
+
})
|
|
240
|
+
.command("library", "Manage libraries", (yargs) => {
|
|
241
|
+
yargs
|
|
242
|
+
.command({
|
|
243
|
+
command: "add <library>",
|
|
244
|
+
describe: "add a library that contains programs",
|
|
245
|
+
builder: (yargs: any) => {
|
|
246
|
+
yargs.positional("library", {
|
|
247
|
+
type: "string",
|
|
248
|
+
describe:
|
|
249
|
+
"Library name (will be loaded with js import(...)). Onlu libraries that are globally installed and can be imported",
|
|
250
|
+
demandOption: true,
|
|
251
|
+
});
|
|
252
|
+
return yargs;
|
|
253
|
+
},
|
|
254
|
+
handler: async (args) => {
|
|
255
|
+
const c = await client();
|
|
256
|
+
await c.library.put(args.library);
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
.strict()
|
|
260
|
+
.demandCommand();
|
|
261
|
+
return yargs;
|
|
262
|
+
})
|
|
263
|
+
.help()
|
|
264
|
+
.strict()
|
|
265
|
+
.demandCommand().argv;
|
|
266
|
+
};
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DirectSub } from "@peerbit/pubsub";
|
|
2
|
+
import { Peerbit } from "peerbit";
|
|
3
|
+
|
|
4
|
+
export const create = (directory: string) => {
|
|
5
|
+
return Peerbit.create({
|
|
6
|
+
libp2p: {
|
|
7
|
+
addresses: {
|
|
8
|
+
listen: ["/ip4/127.0.0.1/tcp/8001", "/ip4/127.0.0.1/tcp/8002/ws"],
|
|
9
|
+
},
|
|
10
|
+
connectionManager: {
|
|
11
|
+
maxConnections: Infinity,
|
|
12
|
+
minConnections: 0,
|
|
13
|
+
},
|
|
14
|
+
services: {
|
|
15
|
+
pubsub: (c) => new DirectSub(c, { canRelayMessage: true }),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
directory,
|
|
19
|
+
});
|
|
20
|
+
};
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const getConfigDir = async (): Promise<string> => {
|
|
2
|
+
const path = await import("path");
|
|
3
|
+
const os = await import("os");
|
|
4
|
+
const configDir = path.join(os.homedir(), ".peerbit");
|
|
5
|
+
return configDir;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getCredentialsPath = async (
|
|
9
|
+
configDir: string
|
|
10
|
+
): Promise<string> => {
|
|
11
|
+
const path = await import("path");
|
|
12
|
+
return path.join(configDir, "credentials");
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getKeysPath = async (configDir: string): Promise<string> => {
|
|
16
|
+
const path = await import("path");
|
|
17
|
+
return path.join(configDir, "keys");
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class NotFoundError extends Error {}
|
package/src/docker.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { delay, waitForAsync } from "@peerbit/time";
|
|
2
|
+
|
|
3
|
+
export const installDocker = async () => {
|
|
4
|
+
const { exec } = await import("child_process");
|
|
5
|
+
|
|
6
|
+
// check if docker is installed
|
|
7
|
+
const dockerExist = async () => {
|
|
8
|
+
try {
|
|
9
|
+
const out = await new Promise((resolve, reject) => {
|
|
10
|
+
exec("docker --version", (error, stdout, stderr) => {
|
|
11
|
+
if (error || stderr) {
|
|
12
|
+
reject();
|
|
13
|
+
}
|
|
14
|
+
resolve(stdout);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
return true;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (!(await dockerExist())) {
|
|
24
|
+
await new Promise((resolve, reject) => {
|
|
25
|
+
exec("sudo snap install docker", (error, stdout, stderr) => {
|
|
26
|
+
if (error || stderr) {
|
|
27
|
+
reject();
|
|
28
|
+
}
|
|
29
|
+
resolve(stdout);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await waitForAsync(() => dockerExist(), {
|
|
35
|
+
timeout: 30 * 1000,
|
|
36
|
+
delayInterval: 1000,
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error("Failed to install docker");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const startContainer = async (cmd: string, errorMessage?: string) => {
|
|
45
|
+
const { exec } = await import("child_process");
|
|
46
|
+
const startContainer = () =>
|
|
47
|
+
new Promise((resolve, reject) => {
|
|
48
|
+
exec(cmd, (error, stdout, stderr) => {
|
|
49
|
+
if (error) {
|
|
50
|
+
reject(
|
|
51
|
+
(errorMessage || "Failed to start docker container: ") +
|
|
52
|
+
error.message
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
resolve(stdout);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await startContainer();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// try again no matter what?
|
|
62
|
+
// or
|
|
63
|
+
// typeof error === "string" && error.indexOf("Cannot connect to the Docker daemon") != -1
|
|
64
|
+
await delay(10000);
|
|
65
|
+
await startContainer();
|
|
66
|
+
}
|
|
67
|
+
};
|
package/src/domain.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { waitFor, waitForAsync } from "@peerbit/time";
|
|
2
|
+
import { client } from "./api.js";
|
|
3
|
+
import { installDocker, startContainer } from "./docker.js";
|
|
4
|
+
|
|
5
|
+
const isNode = typeof window === undefined || typeof window === "undefined";
|
|
6
|
+
|
|
7
|
+
const validateEmail = (email) => {
|
|
8
|
+
return String(email)
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.match(
|
|
11
|
+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createConfig = async (
|
|
16
|
+
outputPath: string,
|
|
17
|
+
domain: string
|
|
18
|
+
): Promise<{ domain: string }> => {
|
|
19
|
+
if (!isNode) {
|
|
20
|
+
throw new Error("Config can only be created with node");
|
|
21
|
+
}
|
|
22
|
+
const { AxiosError } = await import("axios");
|
|
23
|
+
|
|
24
|
+
const url = await import("url");
|
|
25
|
+
const __filename = url.fileURLToPath(import.meta.url);
|
|
26
|
+
const fs = await import("fs");
|
|
27
|
+
const path = await import("path");
|
|
28
|
+
let file = fs.readFileSync(
|
|
29
|
+
path.join(__filename, "../nginx-template.conf"),
|
|
30
|
+
"utf-8"
|
|
31
|
+
);
|
|
32
|
+
let ipfsId: string;
|
|
33
|
+
try {
|
|
34
|
+
ipfsId = await (await client()).peer.id.get();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof AxiosError) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Failed to connect to Peerbit, have you started the node? e.g. 'peerbit start'"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
file = file.replaceAll("%IPFS_ID%", ipfsId);
|
|
44
|
+
file = file.replaceAll("%DOMAIN%", domain);
|
|
45
|
+
|
|
46
|
+
fs.mkdir(outputPath, { recursive: true }, (err) => {
|
|
47
|
+
if (err) throw err;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await waitFor(() => fs.existsSync(outputPath));
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(path.join(outputPath, "default.conf"), file);
|
|
53
|
+
return { domain };
|
|
54
|
+
};
|
|
55
|
+
const getUIPath = async (): Promise<string> => {
|
|
56
|
+
const url = await import("url");
|
|
57
|
+
const path = await import("path");
|
|
58
|
+
const __filename = url.fileURLToPath(import.meta.url);
|
|
59
|
+
const p1 = path.join(__filename, "../../", "ui");
|
|
60
|
+
|
|
61
|
+
const fs = await import("fs");
|
|
62
|
+
|
|
63
|
+
if (fs.existsSync(p1) && fs.lstatSync(p1).isDirectory()) {
|
|
64
|
+
return p1; // build
|
|
65
|
+
} else {
|
|
66
|
+
const p2 = path.join(__filename, "../../", "lib/ui");
|
|
67
|
+
if (fs.existsSync(p2) && fs.lstatSync(p2).isDirectory()) {
|
|
68
|
+
return p2;
|
|
69
|
+
}
|
|
70
|
+
throw new Error("Failed to find UI path");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
export const getMyIp = async (): Promise<string> => {
|
|
74
|
+
const { exec } = await import("child_process");
|
|
75
|
+
const ipv4: string = await new Promise((resolve, reject) => {
|
|
76
|
+
exec(
|
|
77
|
+
"dig @resolver4.opendns.com myip.opendns.com +short",
|
|
78
|
+
(error, stdout, stderr) => {
|
|
79
|
+
if (error || stderr) {
|
|
80
|
+
reject("DNS lookup failed");
|
|
81
|
+
}
|
|
82
|
+
resolve(stdout.trimEnd());
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
return ipv4;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const createTestDomain = async () => {
|
|
90
|
+
const { default: axios } = await import("axios");
|
|
91
|
+
const domain: string = (
|
|
92
|
+
await axios.post(
|
|
93
|
+
"https://bfbbnhwpfj2ptcmurz6lit4xlu0vjajw.lambda-url.us-east-1.on.aws",
|
|
94
|
+
await getMyIp(),
|
|
95
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
96
|
+
)
|
|
97
|
+
).data.domain;
|
|
98
|
+
return domain;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param email
|
|
104
|
+
* @param nginxConfigPath
|
|
105
|
+
* @param dockerProcessName
|
|
106
|
+
* @returns domain
|
|
107
|
+
*/
|
|
108
|
+
export const startCertbot = async (
|
|
109
|
+
domain: string,
|
|
110
|
+
email: string,
|
|
111
|
+
nginxConfigPath?: string,
|
|
112
|
+
waitForUp = false,
|
|
113
|
+
dockerProcessName = "nginx-certbot"
|
|
114
|
+
): Promise<void> => {
|
|
115
|
+
if (!validateEmail(email)) {
|
|
116
|
+
throw new Error("Email for SSL renenewal is invalid");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { exec } = await import("child_process");
|
|
120
|
+
const pwd: string = await new Promise((resolve, reject) => {
|
|
121
|
+
exec("pwd", (error, stdout, stderr) => {
|
|
122
|
+
if (error || stderr) {
|
|
123
|
+
reject("Failed to get current directory");
|
|
124
|
+
}
|
|
125
|
+
resolve(stdout.trimEnd());
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
const path = await import("path");
|
|
129
|
+
nginxConfigPath = path.join(nginxConfigPath || pwd, "nginx");
|
|
130
|
+
|
|
131
|
+
await createConfig(nginxConfigPath, domain);
|
|
132
|
+
|
|
133
|
+
await installDocker();
|
|
134
|
+
|
|
135
|
+
// run
|
|
136
|
+
const isTest = process.env.JEST_WORKER_ID !== undefined;
|
|
137
|
+
|
|
138
|
+
const uiPath = await getUIPath();
|
|
139
|
+
|
|
140
|
+
// copy ui from node_modules to home for permission reasons (volume will not work otherwise)
|
|
141
|
+
const certbotDockerCommand = `cp -r ${uiPath} $(pwd)/ui && docker pull jonasal/nginx-certbot:latest && docker run -d --net=host \
|
|
142
|
+
--env CERTBOT_EMAIL=${email} ${isTest ? "--env STAGING=1" : ""}\
|
|
143
|
+
-v $(pwd)/nginx_secrets:/etc/letsencrypt \
|
|
144
|
+
-v ${nginxConfigPath}:/etc/nginx/user_conf.d:ro \
|
|
145
|
+
-v $(pwd)/ui:/usr/share/nginx/html:ro \
|
|
146
|
+
--name ${dockerProcessName} jonasal/nginx-certbot:latest`;
|
|
147
|
+
|
|
148
|
+
console.log("Starting Certbot");
|
|
149
|
+
// try two times with some delay, because sometimes the docker daemon is not available immidatel
|
|
150
|
+
await startContainer(
|
|
151
|
+
certbotDockerCommand,
|
|
152
|
+
"Failed to start certbot container"
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log("Certbot started succesfully!");
|
|
156
|
+
console.log("You domain is: ");
|
|
157
|
+
console.log(domain);
|
|
158
|
+
if (waitForUp) {
|
|
159
|
+
const { default: axios } = await import("axios");
|
|
160
|
+
|
|
161
|
+
console.log("Waiting for domain to be ready ...");
|
|
162
|
+
await waitForAsync(
|
|
163
|
+
async () => {
|
|
164
|
+
try {
|
|
165
|
+
const status = (await axios.get("https://" + domain)).status;
|
|
166
|
+
return status >= 200 && status < 400;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{ timeout: 5 * 60 * 10000, delayInterval: 5000 }
|
|
172
|
+
);
|
|
173
|
+
console.log("Domain is ready");
|
|
174
|
+
} else {
|
|
175
|
+
console.log(
|
|
176
|
+
"The domain is not available immediately as it takes some time to request SSL certificate."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api.js";
|