@bitcall/webrtc-sip-gateway 0.2.5 → 0.2.7
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 +20 -6
- package/lib/constants.js +1 -1
- package/lib/firewall.js +357 -0
- package/lib/system.js +29 -12
- package/package.json +2 -2
- package/src/index.js +701 -209
- package/templates/.env.template +7 -2
- package/templates/docker-compose.yml.template +0 -3
package/README.md
CHANGED
|
@@ -5,25 +5,36 @@ Linux-only CLI to install and operate the Bitcall WebRTC-to-SIP gateway.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
sudo npm i -g @bitcall/webrtc-sip-gateway@0.2.
|
|
8
|
+
sudo npm i -g @bitcall/webrtc-sip-gateway@0.2.7
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Main workflow
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
sudo bitcall-gateway init
|
|
14
|
+
sudo bitcall-gateway init --dev
|
|
15
15
|
sudo bitcall-gateway status
|
|
16
16
|
sudo bitcall-gateway logs -f
|
|
17
|
+
sudo bitcall-gateway media status
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
Default media policy is IPv4-only
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Default media policy is IPv4-only via IPv6 media firewall drops on RTP/TURN
|
|
21
|
+
ports only. Host IPv6 remains enabled for signaling and non-media traffic.
|
|
22
|
+
Backend selection prefers nftables on non-UFW hosts and uses ip6tables when UFW
|
|
23
|
+
is active.
|
|
24
|
+
|
|
25
|
+
Use `sudo bitcall-gateway init --production` for strict input validation and
|
|
26
|
+
hardening checks.
|
|
27
|
+
Use `--verbose` to stream apt/docker output during install. Default mode keeps
|
|
28
|
+
console output concise and writes command details to
|
|
29
|
+
`/var/log/bitcall-gateway-install.log`.
|
|
23
30
|
|
|
24
31
|
## Commands
|
|
25
32
|
|
|
26
33
|
- `sudo bitcall-gateway init`
|
|
34
|
+
- `sudo bitcall-gateway init --dev`
|
|
35
|
+
- `sudo bitcall-gateway init --production`
|
|
36
|
+
- `sudo bitcall-gateway init --advanced`
|
|
37
|
+
- `sudo bitcall-gateway init --verbose`
|
|
27
38
|
- `sudo bitcall-gateway up`
|
|
28
39
|
- `sudo bitcall-gateway down`
|
|
29
40
|
- `sudo bitcall-gateway restart`
|
|
@@ -33,6 +44,9 @@ IPv6 candidates.
|
|
|
33
44
|
- `sudo bitcall-gateway cert renew`
|
|
34
45
|
- `sudo bitcall-gateway cert install --cert /path/cert.pem --key /path/key.pem`
|
|
35
46
|
- `sudo bitcall-gateway update`
|
|
47
|
+
- `sudo bitcall-gateway media status`
|
|
48
|
+
- `sudo bitcall-gateway media ipv4-only on`
|
|
49
|
+
- `sudo bitcall-gateway media ipv4-only off`
|
|
36
50
|
- `sudo bitcall-gateway uninstall`
|
|
37
51
|
|
|
38
52
|
## Files created by init
|
package/lib/constants.js
CHANGED
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
SSL_DIR: path.join(GATEWAY_DIR, "ssl"),
|
|
15
15
|
ENV_PATH: path.join(GATEWAY_DIR, ".env"),
|
|
16
16
|
COMPOSE_PATH: path.join(GATEWAY_DIR, "docker-compose.yml"),
|
|
17
|
-
DEFAULT_GATEWAY_IMAGE: "ghcr.io/bitcallio/webrtc-sip-gateway:0.2.
|
|
17
|
+
DEFAULT_GATEWAY_IMAGE: "ghcr.io/bitcallio/webrtc-sip-gateway:0.2.7",
|
|
18
18
|
DEFAULT_PROVIDER_HOST: "sip.example.com",
|
|
19
19
|
DEFAULT_WEBPHONE_ORIGIN: "*",
|
|
20
20
|
RENEW_HOOK_PATH: "/etc/letsencrypt/renewal-hooks/deploy/bitcall-gateway.sh",
|
package/lib/firewall.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { run, runShell, output, commandExists } = require("./shell");
|
|
7
|
+
|
|
8
|
+
const MARKER = "bitcall-gateway media ipv6 block";
|
|
9
|
+
const NFT_TABLE = "bitcall_gateway_media_ipv6";
|
|
10
|
+
const NFT_RULE_FILE = "/etc/nftables.d/bitcall-gateway.nft";
|
|
11
|
+
const NFT_MAIN_CONF = "/etc/nftables.conf";
|
|
12
|
+
const NFT_INCLUDE_LINE = `include "${NFT_RULE_FILE}"`;
|
|
13
|
+
|
|
14
|
+
function withDeps(deps = {}) {
|
|
15
|
+
return {
|
|
16
|
+
fs,
|
|
17
|
+
run,
|
|
18
|
+
runShell,
|
|
19
|
+
output,
|
|
20
|
+
commandExists,
|
|
21
|
+
...deps,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toInt(value, fallback) {
|
|
26
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
27
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeOptions(options = {}) {
|
|
31
|
+
const rtpMin = toInt(options.rtpMin, 10000);
|
|
32
|
+
const rtpMax = toInt(options.rtpMax, 20000);
|
|
33
|
+
const turnEnabled = Boolean(options.turnEnabled);
|
|
34
|
+
const turnUdpPort = toInt(options.turnUdpPort, 3478);
|
|
35
|
+
const turnsTcpPort = toInt(options.turnsTcpPort, 5349);
|
|
36
|
+
const turnRelayMin = toInt(options.turnRelayMin, 49152);
|
|
37
|
+
const turnRelayMax = toInt(options.turnRelayMax, 49252);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
rtpMin,
|
|
41
|
+
rtpMax,
|
|
42
|
+
turnEnabled,
|
|
43
|
+
turnUdpPort,
|
|
44
|
+
turnsTcpPort,
|
|
45
|
+
turnRelayMin,
|
|
46
|
+
turnRelayMax,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detectFirewallBackend(deps = {}) {
|
|
51
|
+
const d = withDeps(deps);
|
|
52
|
+
let ufwActive = false;
|
|
53
|
+
if (d.commandExists("ufw")) {
|
|
54
|
+
const ufwStatus = d.run("ufw", ["status"], { check: false, stdio: "pipe" });
|
|
55
|
+
ufwActive = ufwStatus.status === 0 && ((ufwStatus.stdout || "").toLowerCase().includes("status: active"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (ufwActive && d.commandExists("ip6tables")) {
|
|
59
|
+
return "ip6tables";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (d.commandExists("nft")) {
|
|
63
|
+
return "nft";
|
|
64
|
+
}
|
|
65
|
+
if (d.commandExists("ip6tables")) {
|
|
66
|
+
return "ip6tables";
|
|
67
|
+
}
|
|
68
|
+
throw new Error(
|
|
69
|
+
"No IPv6 firewall backend found. Install nftables or ip6tables/netfilter-persistent."
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildIp6tablesRules(options = {}) {
|
|
74
|
+
const cfg = normalizeOptions(options);
|
|
75
|
+
const rules = [
|
|
76
|
+
{ proto: "udp", dport: `${cfg.rtpMin}:${cfg.rtpMax}` },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
if (cfg.turnEnabled) {
|
|
80
|
+
rules.push({ proto: "udp", dport: String(cfg.turnUdpPort) });
|
|
81
|
+
rules.push({ proto: "tcp", dport: String(cfg.turnsTcpPort) });
|
|
82
|
+
rules.push({ proto: "udp", dport: `${cfg.turnRelayMin}:${cfg.turnRelayMax}` });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return rules;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildNftRuleset(options = {}) {
|
|
89
|
+
const rules = buildIp6tablesRules(options);
|
|
90
|
+
const lines = [
|
|
91
|
+
`table inet ${NFT_TABLE} {`,
|
|
92
|
+
" chain input {",
|
|
93
|
+
" type filter hook input priority 0; policy accept;",
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const rule of rules) {
|
|
97
|
+
lines.push(
|
|
98
|
+
` meta nfproto ipv6 ${rule.proto} dport ${rule.dport} comment \"${MARKER}\" drop`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
lines.push(" }");
|
|
103
|
+
lines.push("}");
|
|
104
|
+
lines.push("");
|
|
105
|
+
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function persistIp6tables(d) {
|
|
110
|
+
if (!d.commandExists("netfilter-persistent")) {
|
|
111
|
+
d.run("apt-get", ["update"], { stdio: "inherit" });
|
|
112
|
+
d.run("apt-get", ["install", "-y", "netfilter-persistent", "iptables-persistent"], {
|
|
113
|
+
check: false,
|
|
114
|
+
stdio: "inherit",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!d.commandExists("netfilter-persistent")) {
|
|
119
|
+
throw new Error("Unable to persist ip6tables rules: netfilter-persistent not available.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const saved = d.run("netfilter-persistent", ["save"], {
|
|
123
|
+
check: false,
|
|
124
|
+
stdio: "inherit",
|
|
125
|
+
});
|
|
126
|
+
if (saved.status !== 0) {
|
|
127
|
+
throw new Error("Failed to persist ip6tables rules with netfilter-persistent save.");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function ensureNftInclude(d) {
|
|
132
|
+
let content = "";
|
|
133
|
+
if (d.fs.existsSync(NFT_MAIN_CONF)) {
|
|
134
|
+
content = d.fs.readFileSync(NFT_MAIN_CONF, "utf8");
|
|
135
|
+
} else {
|
|
136
|
+
content = "#!/usr/sbin/nft -f\n\n";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!content.includes(NFT_INCLUDE_LINE)) {
|
|
140
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
141
|
+
content = `${content}${suffix}${NFT_INCLUDE_LINE}\n`;
|
|
142
|
+
d.fs.writeFileSync(NFT_MAIN_CONF, content, { mode: 0o644 });
|
|
143
|
+
d.fs.chmodSync(NFT_MAIN_CONF, 0o644);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function removeNftInclude(d) {
|
|
148
|
+
if (!d.fs.existsSync(NFT_MAIN_CONF)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const content = d.fs.readFileSync(NFT_MAIN_CONF, "utf8");
|
|
153
|
+
const lines = content.split("\n").filter((line) => line.trim() !== NFT_INCLUDE_LINE);
|
|
154
|
+
const next = `${lines.join("\n").replace(/\n+$/g, "")}\n`;
|
|
155
|
+
d.fs.writeFileSync(NFT_MAIN_CONF, next, { mode: 0o644 });
|
|
156
|
+
d.fs.chmodSync(NFT_MAIN_CONF, 0o644);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function applyNftRules(options, d) {
|
|
160
|
+
d.fs.mkdirSync(path.dirname(NFT_RULE_FILE), { recursive: true, mode: 0o755 });
|
|
161
|
+
d.fs.writeFileSync(NFT_RULE_FILE, buildNftRuleset(options), { mode: 0o644 });
|
|
162
|
+
d.fs.chmodSync(NFT_RULE_FILE, 0o644);
|
|
163
|
+
|
|
164
|
+
d.run("nft", ["delete", "table", "inet", NFT_TABLE], {
|
|
165
|
+
check: false,
|
|
166
|
+
stdio: "ignore",
|
|
167
|
+
});
|
|
168
|
+
d.run("nft", ["-f", NFT_RULE_FILE], { stdio: "inherit" });
|
|
169
|
+
|
|
170
|
+
ensureNftInclude(d);
|
|
171
|
+
|
|
172
|
+
const enabled = d.run("systemctl", ["enable", "--now", "nftables"], {
|
|
173
|
+
check: false,
|
|
174
|
+
stdio: "inherit",
|
|
175
|
+
});
|
|
176
|
+
if (enabled.status !== 0) {
|
|
177
|
+
throw new Error("Failed to enable nftables service for persistent IPv6 media block rules.");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function removeNftRules(d) {
|
|
182
|
+
d.run("nft", ["delete", "table", "inet", NFT_TABLE], {
|
|
183
|
+
check: false,
|
|
184
|
+
stdio: "ignore",
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (d.fs.existsSync(NFT_RULE_FILE)) {
|
|
188
|
+
d.fs.rmSync(NFT_RULE_FILE, { force: true });
|
|
189
|
+
}
|
|
190
|
+
removeNftInclude(d);
|
|
191
|
+
|
|
192
|
+
d.run("systemctl", ["reload", "nftables"], {
|
|
193
|
+
check: false,
|
|
194
|
+
stdio: "ignore",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function ip6tableRuleArgs(rule) {
|
|
199
|
+
return [
|
|
200
|
+
"-p",
|
|
201
|
+
rule.proto,
|
|
202
|
+
"--dport",
|
|
203
|
+
rule.dport,
|
|
204
|
+
"-m",
|
|
205
|
+
"comment",
|
|
206
|
+
"--comment",
|
|
207
|
+
MARKER,
|
|
208
|
+
"-j",
|
|
209
|
+
"DROP",
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function applyIp6tablesRules(options, d) {
|
|
214
|
+
const rules = buildIp6tablesRules(options);
|
|
215
|
+
|
|
216
|
+
for (const rule of rules) {
|
|
217
|
+
const args = ip6tableRuleArgs(rule);
|
|
218
|
+
const exists = d.run("ip6tables", ["-C", "INPUT", ...args], {
|
|
219
|
+
check: false,
|
|
220
|
+
stdio: "ignore",
|
|
221
|
+
}).status === 0;
|
|
222
|
+
|
|
223
|
+
if (!exists) {
|
|
224
|
+
d.run("ip6tables", ["-I", "INPUT", "1", ...args], {
|
|
225
|
+
stdio: "inherit",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
persistIp6tables(d);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function removeIp6tablesRules(options, d) {
|
|
234
|
+
const rules = buildIp6tablesRules(options);
|
|
235
|
+
|
|
236
|
+
for (const rule of rules) {
|
|
237
|
+
const args = ip6tableRuleArgs(rule);
|
|
238
|
+
for (;;) {
|
|
239
|
+
const exists = d.run("ip6tables", ["-C", "INPUT", ...args], {
|
|
240
|
+
check: false,
|
|
241
|
+
stdio: "ignore",
|
|
242
|
+
}).status === 0;
|
|
243
|
+
|
|
244
|
+
if (!exists) {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
d.run("ip6tables", ["-D", "INPUT", ...args], {
|
|
249
|
+
stdio: "inherit",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
persistIp6tables(d);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isNftPresent(d) {
|
|
258
|
+
const res = d.run("nft", ["list", "table", "inet", NFT_TABLE], {
|
|
259
|
+
check: false,
|
|
260
|
+
stdio: "pipe",
|
|
261
|
+
});
|
|
262
|
+
if (res.status !== 0) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return (res.stdout || "").includes(MARKER);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isIp6tablesPresent(d) {
|
|
269
|
+
const rules = d.output("sh", ["-lc", "ip6tables-save 2>/dev/null || true"]);
|
|
270
|
+
return rules.includes(MARKER);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function applyMediaIpv4OnlyRules(options = {}, runtime = {}) {
|
|
274
|
+
const d = withDeps(runtime.deps);
|
|
275
|
+
const backend = runtime.backend || detectFirewallBackend(d);
|
|
276
|
+
|
|
277
|
+
if (backend === "nft") {
|
|
278
|
+
applyNftRules(options, d);
|
|
279
|
+
return { backend };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (backend === "ip6tables") {
|
|
283
|
+
applyIp6tablesRules(options, d);
|
|
284
|
+
return { backend };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
throw new Error(`Unsupported firewall backend: ${backend}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function removeMediaIpv4OnlyRules(options = {}, runtime = {}) {
|
|
291
|
+
const d = withDeps(runtime.deps);
|
|
292
|
+
const backend = runtime.backend || detectFirewallBackend(d);
|
|
293
|
+
|
|
294
|
+
if (backend === "nft") {
|
|
295
|
+
removeNftRules(d);
|
|
296
|
+
return { backend };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (backend === "ip6tables") {
|
|
300
|
+
removeIp6tablesRules(options, d);
|
|
301
|
+
return { backend };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
throw new Error(`Unsupported firewall backend: ${backend}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isMediaIpv4OnlyRulesPresent(runtime = {}) {
|
|
308
|
+
const d = withDeps(runtime.deps);
|
|
309
|
+
let backend = runtime.backend;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
backend = backend || detectFirewallBackend(d);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
enabled: false,
|
|
316
|
+
backend: null,
|
|
317
|
+
error: error.message,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (backend === "nft") {
|
|
322
|
+
return {
|
|
323
|
+
enabled: isNftPresent(d),
|
|
324
|
+
backend,
|
|
325
|
+
marker: MARKER,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (backend === "ip6tables") {
|
|
330
|
+
return {
|
|
331
|
+
enabled: isIp6tablesPresent(d),
|
|
332
|
+
backend,
|
|
333
|
+
marker: MARKER,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
enabled: false,
|
|
339
|
+
backend,
|
|
340
|
+
marker: MARKER,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
MARKER,
|
|
346
|
+
NFT_TABLE,
|
|
347
|
+
NFT_RULE_FILE,
|
|
348
|
+
NFT_MAIN_CONF,
|
|
349
|
+
NFT_INCLUDE_LINE,
|
|
350
|
+
normalizeOptions,
|
|
351
|
+
detectFirewallBackend,
|
|
352
|
+
buildIp6tablesRules,
|
|
353
|
+
buildNftRuleset,
|
|
354
|
+
applyMediaIpv4OnlyRules,
|
|
355
|
+
removeMediaIpv4OnlyRules,
|
|
356
|
+
isMediaIpv4OnlyRulesPresent,
|
|
357
|
+
};
|
package/lib/system.js
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const { output, run, runShell, commandExists } = require("./shell");
|
|
5
5
|
|
|
6
|
+
function pickExec(options = {}) {
|
|
7
|
+
if (typeof options.exec === "function") {
|
|
8
|
+
return options.exec;
|
|
9
|
+
}
|
|
10
|
+
return (command, args = [], execOptions = {}) => run(command, args, execOptions);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function pickShell(options = {}) {
|
|
14
|
+
if (typeof options.shell === "function") {
|
|
15
|
+
return options.shell;
|
|
16
|
+
}
|
|
17
|
+
return (script, execOptions = {}) => runShell(script, execOptions);
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
function parseOsRelease() {
|
|
7
21
|
const info = {};
|
|
8
22
|
const content = fs.readFileSync("/etc/os-release", "utf8");
|
|
@@ -112,28 +126,31 @@ function portInUse(port) {
|
|
|
112
126
|
};
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
function ensureDockerInstalled() {
|
|
129
|
+
function ensureDockerInstalled(options = {}) {
|
|
130
|
+
const exec = pickExec(options);
|
|
131
|
+
const shell = pickShell(options);
|
|
132
|
+
|
|
116
133
|
if (commandExists("docker") && run("docker", ["info"], { check: false }).status === 0) {
|
|
117
134
|
return;
|
|
118
135
|
}
|
|
119
136
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
run("systemctl", ["enable", "containerd.service"], { stdio: "inherit" });
|
|
127
|
-
run("systemctl", ["start", "docker"], { stdio: "inherit" });
|
|
137
|
+
exec("apt-get", ["update"]);
|
|
138
|
+
exec("apt-get", ["install", "-y", "curl", "ca-certificates", "gnupg"]);
|
|
139
|
+
shell("curl -fsSL https://get.docker.com | sh");
|
|
140
|
+
exec("systemctl", ["enable", "docker.service"]);
|
|
141
|
+
exec("systemctl", ["enable", "containerd.service"]);
|
|
142
|
+
exec("systemctl", ["start", "docker"]);
|
|
128
143
|
}
|
|
129
144
|
|
|
130
|
-
function ensureComposePlugin() {
|
|
145
|
+
function ensureComposePlugin(options = {}) {
|
|
146
|
+
const exec = pickExec(options);
|
|
147
|
+
|
|
131
148
|
if (run("docker", ["compose", "version"], { check: false }).status === 0) {
|
|
132
149
|
return;
|
|
133
150
|
}
|
|
134
151
|
|
|
135
|
-
|
|
136
|
-
|
|
152
|
+
exec("apt-get", ["update"]);
|
|
153
|
+
exec("apt-get", ["install", "-y", "docker-compose-plugin"]);
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitcall/webrtc-sip-gateway",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Linux CLI for bootstrapping and managing the Bitcall WebRTC-to-SIP Gateway",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"lint": "node --check src/index.js && node --check bin/bitcall-gateway.js && for f in lib/*.js; do node --check \"$f\"; done",
|
|
25
|
-
"test": "node test/smoke.test.js",
|
|
25
|
+
"test": "node test/smoke.test.js && node test/firewall.test.js && node test/init-config.test.js",
|
|
26
26
|
"pack:dry": "npm pack --dry-run"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|