@studiometa/trafic-agent 0.1.25 → 0.1.27
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 +6 -0
- package/dist/cli.js +23 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-VlzPosuk.js → server-zVWzPuj2.js} +29 -6
- package/dist/server-zVWzPuj2.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/setup/agent.d.ts +11 -5
- package/dist/setup/agent.d.ts.map +1 -1
- package/dist/setup/ddev.d.ts +8 -7
- package/dist/setup/ddev.d.ts.map +1 -1
- package/dist/setup/docker.d.ts +4 -3
- package/dist/setup/docker.d.ts.map +1 -1
- package/dist/setup/hardening.d.ts +17 -7
- package/dist/setup/hardening.d.ts.map +1 -1
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/io.d.ts +31 -0
- package/dist/setup/io.d.ts.map +1 -0
- package/dist/setup/types.d.ts +5 -0
- package/dist/setup/types.d.ts.map +1 -1
- package/dist/setup/user.d.ts +4 -3
- package/dist/setup/user.d.ts.map +1 -1
- package/dist/{setup-D61uhYiJ.js → setup-BcY2feHr.js} +221 -153
- package/dist/setup-BcY2feHr.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/auth.d.ts +16 -0
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/server-VlzPosuk.js.map +0 -1
- package/dist/setup-D61uhYiJ.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as getProjectInfo, i as shouldNeverStop, l as getIdleProjects, n as getIdleTimeoutMs, p as setProjectStatus, r as loadProjectConfig, x as stopProject, y as loadProjectList } from "./server-
|
|
1
|
+
import { _ as getProjectInfo, i as shouldNeverStop, l as getIdleProjects, n as getIdleTimeoutMs, p as setProjectStatus, r as loadProjectConfig, x as stopProject, y as loadProjectList } from "./server-zVWzPuj2.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { parse } from "smol-toml";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
@@ -21,7 +21,8 @@ var DEFAULT_CONFIG = {
|
|
|
21
21
|
allowedIps: [],
|
|
22
22
|
tokens: [],
|
|
23
23
|
basicAuth: [],
|
|
24
|
-
rules: []
|
|
24
|
+
rules: [],
|
|
25
|
+
trustedProxyHops: 1
|
|
25
26
|
}
|
|
26
27
|
};
|
|
27
28
|
/**
|
|
@@ -87,10 +88,23 @@ function mergeAuthConfig(raw) {
|
|
|
87
88
|
policy: r.policy,
|
|
88
89
|
tokens: r.tokens,
|
|
89
90
|
allowedIps: r.allowed_ips
|
|
90
|
-
}))
|
|
91
|
+
})),
|
|
92
|
+
trustedProxyHops: parseTrustedProxyHops(raw.trusted_proxy_hops)
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
96
|
+
* Parse trusted_proxy_hops, ignoring values that cannot be a hop count.
|
|
97
|
+
*
|
|
98
|
+
* A bad value falls back to the default rather than 0, because 0 would read
|
|
99
|
+
* the socket peer — always the proxy — and quietly stop the IP allowlist and
|
|
100
|
+
* per-hostname IP rules from ever matching.
|
|
101
|
+
*/
|
|
102
|
+
function parseTrustedProxyHops(raw) {
|
|
103
|
+
const hops = typeof raw === "number" ? raw : Number(raw);
|
|
104
|
+
if (!Number.isInteger(hops) || hops < 1) return DEFAULT_CONFIG.auth.trustedProxyHops;
|
|
105
|
+
return hops;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
94
108
|
* Validate configuration
|
|
95
109
|
*/
|
|
96
110
|
function validateConfig(config) {
|
|
@@ -237,39 +251,68 @@ function commandExists(command) {
|
|
|
237
251
|
function isRoot() {
|
|
238
252
|
return process.getuid?.() === 0;
|
|
239
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Write a file (or show in dry-run mode)
|
|
256
|
+
*/
|
|
257
|
+
function writeFile(path, content) {
|
|
258
|
+
if (dryRun) {
|
|
259
|
+
console.log(` \x1b[90mWrite ${path}:\x1b[0m`);
|
|
260
|
+
const lines = content.split("\n").slice(0, 5);
|
|
261
|
+
for (const line of lines) console.log(` \x1b[90m ${line}\x1b[0m`);
|
|
262
|
+
if (content.split("\n").length > 5) console.log(` \x1b[90m ...\x1b[0m`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
writeFileSync(path, content);
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/setup/io.ts
|
|
269
|
+
/**
|
|
270
|
+
* The real implementation, used when no io is injected.
|
|
271
|
+
*
|
|
272
|
+
* `writeFile` comes from steps.js rather than `node:fs` so writes are
|
|
273
|
+
* skipped in dry-run mode. Reads stay direct: they change nothing.
|
|
274
|
+
*/
|
|
275
|
+
var nodeIo = {
|
|
276
|
+
exec,
|
|
277
|
+
commandExists,
|
|
278
|
+
writeFile,
|
|
279
|
+
fileExists: existsSync,
|
|
280
|
+
readFile: (path) => readFileSync(path, "utf-8"),
|
|
281
|
+
env: process.env
|
|
282
|
+
};
|
|
240
283
|
//#endregion
|
|
241
284
|
//#region src/setup/user.ts
|
|
242
285
|
/**
|
|
243
286
|
* Write /etc/sudoers.d/trafic-ddev allowing the ddev user to run
|
|
244
287
|
* ddev-hostname without a password. Idempotent — safe to call multiple times.
|
|
245
288
|
*/
|
|
246
|
-
function configureDdevSudoers() {
|
|
247
|
-
|
|
289
|
+
function configureDdevSudoers(io = nodeIo) {
|
|
290
|
+
io.writeFile("/etc/sudoers.d/trafic-ddev", `# Trafic: allow ddev user to run ddev-hostname without a password.
|
|
248
291
|
# ddev-hostname manages /etc/hosts and always calls sudo internally.
|
|
249
292
|
ddev ALL=(ALL) NOPASSWD: /usr/bin/ddev-hostname
|
|
250
293
|
`);
|
|
251
|
-
exec("chmod 440 /etc/sudoers.d/trafic-ddev", { silent: true });
|
|
294
|
+
io.exec("chmod 440 /etc/sudoers.d/trafic-ddev", { silent: true });
|
|
252
295
|
success("Configured passwordless sudo for ddev-hostname");
|
|
253
296
|
}
|
|
254
297
|
/**
|
|
255
298
|
* Create the ddev user with proper permissions
|
|
256
299
|
*/
|
|
257
|
-
function createDdevUser() {
|
|
300
|
+
function createDdevUser(io = nodeIo) {
|
|
258
301
|
step("Create ddev user");
|
|
259
|
-
if (
|
|
302
|
+
if (io.exec("id -u ddev 2>/dev/null", { silent: true })) info("User 'ddev' already exists");
|
|
260
303
|
else {
|
|
261
|
-
exec("useradd -m -s /bin/bash ddev");
|
|
304
|
+
io.exec("useradd -m -s /bin/bash ddev");
|
|
262
305
|
success("Created user 'ddev'");
|
|
263
306
|
}
|
|
264
|
-
exec("usermod -aG docker ddev 2>/dev/null || true", { silent: true });
|
|
265
|
-
exec("mkdir -p /home/ddev/www");
|
|
266
|
-
exec("chown ddev:ddev /home/ddev/www");
|
|
267
|
-
exec("chmod 750 /home/ddev/www");
|
|
307
|
+
io.exec("usermod -aG docker ddev 2>/dev/null || true", { silent: true });
|
|
308
|
+
io.exec("mkdir -p /home/ddev/www");
|
|
309
|
+
io.exec("chown ddev:ddev /home/ddev/www");
|
|
310
|
+
io.exec("chmod 750 /home/ddev/www");
|
|
268
311
|
success("Created /home/ddev/www");
|
|
269
|
-
exec("mkdir -p /home/ddev/.ssh");
|
|
270
|
-
exec("chmod 700 /home/ddev/.ssh");
|
|
271
|
-
exec("chown ddev:ddev /home/ddev/.ssh");
|
|
272
|
-
configureDdevSudoers();
|
|
312
|
+
io.exec("mkdir -p /home/ddev/.ssh");
|
|
313
|
+
io.exec("chmod 700 /home/ddev/.ssh");
|
|
314
|
+
io.exec("chown ddev:ddev /home/ddev/.ssh");
|
|
315
|
+
configureDdevSudoers(io);
|
|
273
316
|
success("User ddev configured");
|
|
274
317
|
}
|
|
275
318
|
//#endregion
|
|
@@ -277,26 +320,26 @@ function createDdevUser() {
|
|
|
277
320
|
/**
|
|
278
321
|
* Install Docker using the official script
|
|
279
322
|
*/
|
|
280
|
-
function installDocker() {
|
|
323
|
+
function installDocker(io = nodeIo) {
|
|
281
324
|
step("Install Docker");
|
|
282
|
-
if (commandExists("docker")) {
|
|
283
|
-
info(`Docker already installed: ${exec("docker --version", { silent: true })?.trim() ?? "unknown version"}`);
|
|
325
|
+
if (io.commandExists("docker")) {
|
|
326
|
+
info(`Docker already installed: ${io.exec("docker --version", { silent: true })?.trim() ?? "unknown version"}`);
|
|
284
327
|
return;
|
|
285
328
|
}
|
|
286
329
|
info("Downloading and running Docker install script...");
|
|
287
|
-
exec("curl -fsSL https://get.docker.com -o /tmp/get-docker.sh");
|
|
288
|
-
exec("sh /tmp/get-docker.sh", { silent: true });
|
|
289
|
-
exec("rm /tmp/get-docker.sh");
|
|
290
|
-
exec("systemctl enable docker", { silent: true });
|
|
291
|
-
exec("systemctl start docker", { silent: true });
|
|
330
|
+
io.exec("curl -fsSL https://get.docker.com -o /tmp/get-docker.sh");
|
|
331
|
+
io.exec("sh /tmp/get-docker.sh", { silent: true });
|
|
332
|
+
io.exec("rm /tmp/get-docker.sh");
|
|
333
|
+
io.exec("systemctl enable docker", { silent: true });
|
|
334
|
+
io.exec("systemctl start docker", { silent: true });
|
|
292
335
|
success("Docker installed and started");
|
|
293
|
-
exec("usermod -aG docker ddev", { silent: true });
|
|
336
|
+
io.exec("usermod -aG docker ddev", { silent: true });
|
|
294
337
|
success("Added ddev user to docker group");
|
|
295
338
|
}
|
|
296
339
|
/**
|
|
297
340
|
* Configure Docker for production use
|
|
298
341
|
*/
|
|
299
|
-
function configureDocker() {
|
|
342
|
+
function configureDocker(io = nodeIo) {
|
|
300
343
|
step("Configure Docker");
|
|
301
344
|
const daemonConfig = {
|
|
302
345
|
"log-driver": "json-file",
|
|
@@ -307,32 +350,32 @@ function configureDocker() {
|
|
|
307
350
|
"storage-driver": "overlay2",
|
|
308
351
|
"live-restore": true
|
|
309
352
|
};
|
|
310
|
-
exec("mkdir -p /etc/docker");
|
|
353
|
+
io.exec("mkdir -p /etc/docker");
|
|
311
354
|
const configPath = "/etc/docker/daemon.json";
|
|
312
|
-
if (
|
|
355
|
+
if (io.fileExists(configPath)) try {
|
|
313
356
|
const merged = {
|
|
314
|
-
...JSON.parse(
|
|
357
|
+
...JSON.parse(io.readFile(configPath)),
|
|
315
358
|
...daemonConfig
|
|
316
359
|
};
|
|
317
|
-
|
|
360
|
+
io.writeFile(configPath, JSON.stringify(merged, null, 2));
|
|
318
361
|
info("Merged with existing Docker config");
|
|
319
362
|
} catch {
|
|
320
363
|
warn("Could not parse existing Docker config, skipping");
|
|
321
364
|
return;
|
|
322
365
|
}
|
|
323
|
-
else
|
|
324
|
-
exec("systemctl reload docker || systemctl restart docker");
|
|
366
|
+
else io.writeFile(configPath, JSON.stringify(daemonConfig, null, 2));
|
|
367
|
+
io.exec("systemctl reload docker || systemctl restart docker");
|
|
325
368
|
success("Docker configured with production settings");
|
|
326
369
|
}
|
|
327
370
|
/**
|
|
328
371
|
* Setup Docker system prune cron job
|
|
329
372
|
*/
|
|
330
|
-
function setupDockerPrune() {
|
|
373
|
+
function setupDockerPrune(io = nodeIo) {
|
|
331
374
|
step("Setup Docker cleanup cron");
|
|
332
|
-
|
|
375
|
+
io.writeFile("/etc/cron.d/trafic-docker-prune", `# Trafic: Clean up Docker resources weekly
|
|
333
376
|
0 3 * * 0 root docker system prune -af --volumes 2>&1 | logger -t docker-prune
|
|
334
377
|
`);
|
|
335
|
-
exec("chmod 644 /etc/cron.d/trafic-docker-prune");
|
|
378
|
+
io.exec("chmod 644 /etc/cron.d/trafic-docker-prune");
|
|
336
379
|
success("Docker prune scheduled weekly at 3am on Sundays");
|
|
337
380
|
}
|
|
338
381
|
//#endregion
|
|
@@ -340,39 +383,39 @@ function setupDockerPrune() {
|
|
|
340
383
|
/**
|
|
341
384
|
* Install system dependencies required by Trafic
|
|
342
385
|
*/
|
|
343
|
-
function installSystemDeps() {
|
|
386
|
+
function installSystemDeps(io = nodeIo) {
|
|
344
387
|
step("Install system dependencies");
|
|
345
|
-
exec("apt-get update -qq", { silent: true });
|
|
346
|
-
exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y jq curl rsync gnupg ca-certificates", { silent: true });
|
|
388
|
+
io.exec("apt-get update -qq", { silent: true });
|
|
389
|
+
io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y jq curl rsync gnupg ca-certificates", { silent: true });
|
|
347
390
|
success("System dependencies installed: jq, curl, rsync, gnupg, ca-certificates");
|
|
348
391
|
}
|
|
349
392
|
/**
|
|
350
393
|
* Add the official DDEV apt repository and GPG key.
|
|
351
394
|
* Idempotent: safe to call even if the repository is already configured.
|
|
352
395
|
*/
|
|
353
|
-
function addDdevAptRepo() {
|
|
354
|
-
exec("install -m 0755 -d /etc/apt/keyrings", { silent: true });
|
|
355
|
-
exec("curl -fsSL https://pkg.ddev.com/apt/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/ddev.gpg > /dev/null", { silent: true });
|
|
356
|
-
exec("chmod a+r /etc/apt/keyrings/ddev.gpg", { silent: true });
|
|
357
|
-
exec("echo \"deb [signed-by=/etc/apt/keyrings/ddev.gpg] https://pkg.ddev.com/apt/ * *\" | tee /etc/apt/sources.list.d/ddev.list > /dev/null", { silent: true });
|
|
396
|
+
function addDdevAptRepo(io = nodeIo) {
|
|
397
|
+
io.exec("install -m 0755 -d /etc/apt/keyrings", { silent: true });
|
|
398
|
+
io.exec("curl -fsSL https://pkg.ddev.com/apt/gpg.key | gpg --dearmor | tee /etc/apt/keyrings/ddev.gpg > /dev/null", { silent: true });
|
|
399
|
+
io.exec("chmod a+r /etc/apt/keyrings/ddev.gpg", { silent: true });
|
|
400
|
+
io.exec("echo \"deb [signed-by=/etc/apt/keyrings/ddev.gpg] https://pkg.ddev.com/apt/ * *\" | tee /etc/apt/sources.list.d/ddev.list > /dev/null", { silent: true });
|
|
358
401
|
}
|
|
359
402
|
/**
|
|
360
403
|
* Install DDEV
|
|
361
404
|
*/
|
|
362
|
-
function installDdev() {
|
|
405
|
+
function installDdev(io = nodeIo) {
|
|
363
406
|
step("Install DDEV");
|
|
364
|
-
if (commandExists("ddev")) {
|
|
365
|
-
info(`DDEV already installed: ${exec("ddev --version 2>/dev/null | head -1", { silent: true })?.trim() ?? "unknown version"}`);
|
|
407
|
+
if (io.commandExists("ddev")) {
|
|
408
|
+
info(`DDEV already installed: ${io.exec("ddev --version 2>/dev/null | head -1", { silent: true })?.trim() ?? "unknown version"}`);
|
|
366
409
|
return;
|
|
367
410
|
}
|
|
368
411
|
info("Adding DDEV apt repository...");
|
|
369
|
-
addDdevAptRepo();
|
|
412
|
+
addDdevAptRepo(io);
|
|
370
413
|
info("Installing DDEV...");
|
|
371
|
-
exec("apt-get update -qq", { silent: true });
|
|
372
|
-
exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y ddev", { silent: true });
|
|
373
|
-
exec("HOME=/home/ddev mkcert -install", { silent: true });
|
|
374
|
-
exec("chown -R ddev:ddev /home/ddev/.local/share/mkcert", { silent: true });
|
|
375
|
-
success(`DDEV installed: ${exec("ddev --version 2>/dev/null | head -1", { silent: true })?.trim() ?? "unknown version"}`);
|
|
414
|
+
io.exec("apt-get update -qq", { silent: true });
|
|
415
|
+
io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y ddev", { silent: true });
|
|
416
|
+
io.exec("HOME=/home/ddev mkcert -install", { silent: true });
|
|
417
|
+
io.exec("chown -R ddev:ddev /home/ddev/.local/share/mkcert", { silent: true });
|
|
418
|
+
success(`DDEV installed: ${io.exec("ddev --version 2>/dev/null | head -1", { silent: true })?.trim() ?? "unknown version"}`);
|
|
376
419
|
}
|
|
377
420
|
/**
|
|
378
421
|
* Configure DDEV global settings for production
|
|
@@ -385,10 +428,10 @@ function installDdev() {
|
|
|
385
428
|
*
|
|
386
429
|
* With proper DNS, DDEV won't need sudo for hostname management.
|
|
387
430
|
*/
|
|
388
|
-
function configureDdev(tld, email) {
|
|
431
|
+
function configureDdev(tld, email, io = nodeIo) {
|
|
389
432
|
step("Configure DDEV");
|
|
390
433
|
const ddevCmd = (cmd) => `su - ddev -c '${cmd}'`;
|
|
391
|
-
exec(ddevCmd(`ddev config global --project-tld=${tld} --router-http-port=80 --router-https-port=443 --router-bind-all-interfaces=true --use-letsencrypt=${email ? "true" : "false"} ${email ? `--letsencrypt-email=${email}` : ""} --instrumentation-opt-in=false`), { silent: true });
|
|
434
|
+
io.exec(ddevCmd(`ddev config global --project-tld=${tld} --router-http-port=80 --router-https-port=443 --router-bind-all-interfaces=true --use-letsencrypt=${email ? "true" : "false"} ${email ? `--letsencrypt-email=${email}` : ""} --instrumentation-opt-in=false`), { silent: true });
|
|
392
435
|
success(`DDEV configured with TLD: ${tld}`);
|
|
393
436
|
info(`Ensure DNS is configured: *.${tld} → server IP`);
|
|
394
437
|
if (email) success(`Let's Encrypt enabled with email: ${email}`);
|
|
@@ -400,19 +443,19 @@ function configureDdev(tld, email) {
|
|
|
400
443
|
* Falls back to the default `bridge` network, then to 172.17.0.1.
|
|
401
444
|
* `host.docker.internal` is not available on Linux without extra configuration.
|
|
402
445
|
*/
|
|
403
|
-
function getDockerGatewayIp() {
|
|
404
|
-
const ddevIp = exec("docker network inspect ddev_default --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null", { silent: true })?.trim();
|
|
446
|
+
function getDockerGatewayIp(io = nodeIo) {
|
|
447
|
+
const ddevIp = io.exec("docker network inspect ddev_default --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null", { silent: true })?.trim();
|
|
405
448
|
if (ddevIp) return ddevIp;
|
|
406
|
-
return exec("docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null", { silent: true })?.trim() || "172.17.0.1";
|
|
449
|
+
return io.exec("docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}' 2>/dev/null", { silent: true })?.trim() || "172.17.0.1";
|
|
407
450
|
}
|
|
408
451
|
/**
|
|
409
452
|
* Configure Traefik for forward auth
|
|
410
453
|
*/
|
|
411
|
-
function configureTraefik() {
|
|
454
|
+
function configureTraefik(io = nodeIo) {
|
|
412
455
|
step("Configure Traefik for forward auth");
|
|
413
|
-
exec("mkdir -p /home/ddev/.ddev/traefik/custom-global-config", { silent: true });
|
|
414
|
-
exec("chown -R ddev:ddev /home/ddev/.ddev", { silent: true });
|
|
415
|
-
const gatewayIp = getDockerGatewayIp();
|
|
456
|
+
io.exec("mkdir -p /home/ddev/.ddev/traefik/custom-global-config", { silent: true });
|
|
457
|
+
io.exec("chown -R ddev:ddev /home/ddev/.ddev", { silent: true });
|
|
458
|
+
const gatewayIp = getDockerGatewayIp(io);
|
|
416
459
|
const dynamicConfig = `# Trafic: Forward auth middleware definition
|
|
417
460
|
http:
|
|
418
461
|
middlewares:
|
|
@@ -436,11 +479,11 @@ http:
|
|
|
436
479
|
servers:
|
|
437
480
|
- url: "http://${gatewayIp}:9876"
|
|
438
481
|
`;
|
|
439
|
-
|
|
440
|
-
exec("chown ddev:ddev /home/ddev/.ddev/traefik/trafic.yaml", { silent: true });
|
|
441
|
-
|
|
442
|
-
exec("chown ddev:ddev /home/ddev/.ddev/traefik/custom-global-config/trafic.yaml", { silent: true });
|
|
443
|
-
|
|
482
|
+
io.writeFile("/home/ddev/.ddev/traefik/trafic.yaml", dynamicConfig);
|
|
483
|
+
io.exec("chown ddev:ddev /home/ddev/.ddev/traefik/trafic.yaml", { silent: true });
|
|
484
|
+
io.writeFile("/home/ddev/.ddev/traefik/custom-global-config/trafic.yaml", dynamicConfig);
|
|
485
|
+
io.exec("chown ddev:ddev /home/ddev/.ddev/traefik/custom-global-config/trafic.yaml", { silent: true });
|
|
486
|
+
io.writeFile("/home/ddev/.ddev/traefik/static_config.trafic.yaml", `entryPoints:
|
|
444
487
|
http-80:
|
|
445
488
|
http:
|
|
446
489
|
middlewares:
|
|
@@ -452,7 +495,7 @@ http:
|
|
|
452
495
|
- trafic-auth@file
|
|
453
496
|
- trafic-errors@file
|
|
454
497
|
`);
|
|
455
|
-
exec("chown ddev:ddev /home/ddev/.ddev/traefik/static_config.trafic.yaml", { silent: true });
|
|
498
|
+
io.exec("chown ddev:ddev /home/ddev/.ddev/traefik/static_config.trafic.yaml", { silent: true });
|
|
456
499
|
success("Traefik configured with Trafic middleware");
|
|
457
500
|
}
|
|
458
501
|
//#endregion
|
|
@@ -468,13 +511,13 @@ var NODESOURCE_KEYRING = "/etc/apt/keyrings/nodesource.gpg";
|
|
|
468
511
|
* nodejs.org points at NodeSource for apt installs. The Ubuntu 24.04
|
|
469
512
|
* `nodejs` package is 18.x, far below what the agent needs.
|
|
470
513
|
*/
|
|
471
|
-
function addNodeSourceAptRepo() {
|
|
472
|
-
exec("install -m 0755 -d /etc/apt/keyrings", { silent: true });
|
|
473
|
-
exec("curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /tmp/trafic-nodesource.key", { silent: true });
|
|
474
|
-
exec("gpg --batch --yes --dearmor -o /tmp/trafic-nodesource.gpg /tmp/trafic-nodesource.key", { silent: true });
|
|
475
|
-
exec(`install -m 0644 /tmp/trafic-nodesource.gpg ${NODESOURCE_KEYRING}`, { silent: true });
|
|
476
|
-
exec("rm -f /tmp/trafic-nodesource.key /tmp/trafic-nodesource.gpg", { silent: true });
|
|
477
|
-
exec(`echo "deb [signed-by=${NODESOURCE_KEYRING}] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null`, { silent: true });
|
|
514
|
+
function addNodeSourceAptRepo(io = nodeIo) {
|
|
515
|
+
io.exec("install -m 0755 -d /etc/apt/keyrings", { silent: true });
|
|
516
|
+
io.exec("curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /tmp/trafic-nodesource.key", { silent: true });
|
|
517
|
+
io.exec("gpg --batch --yes --dearmor -o /tmp/trafic-nodesource.gpg /tmp/trafic-nodesource.key", { silent: true });
|
|
518
|
+
io.exec(`install -m 0644 /tmp/trafic-nodesource.gpg ${NODESOURCE_KEYRING}`, { silent: true });
|
|
519
|
+
io.exec("rm -f /tmp/trafic-nodesource.key /tmp/trafic-nodesource.gpg", { silent: true });
|
|
520
|
+
io.exec(`echo "deb [signed-by=${NODESOURCE_KEYRING}] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null`, { silent: true });
|
|
478
521
|
}
|
|
479
522
|
/**
|
|
480
523
|
* Install Node.js from the NodeSource apt repository.
|
|
@@ -483,37 +526,38 @@ function addNodeSourceAptRepo() {
|
|
|
483
526
|
* trafic-agent` resolves for the systemd unit — and unattended-upgrades
|
|
484
527
|
* patches them, which a version manager install never gets.
|
|
485
528
|
*/
|
|
486
|
-
function installNode() {
|
|
529
|
+
function installNode(io = nodeIo) {
|
|
487
530
|
step("Install Node.js");
|
|
488
|
-
if (commandExists("node")) {
|
|
489
|
-
info(`Node.js already installed: ${exec("node --version", { silent: true })?.trim()}`);
|
|
531
|
+
if (io.commandExists("node")) {
|
|
532
|
+
info(`Node.js already installed: ${io.exec("node --version", { silent: true })?.trim()}`);
|
|
490
533
|
return;
|
|
491
534
|
}
|
|
492
535
|
info("Adding NodeSource apt repository...");
|
|
493
|
-
addNodeSourceAptRepo();
|
|
536
|
+
addNodeSourceAptRepo(io);
|
|
494
537
|
info("Installing Node.js...");
|
|
495
|
-
exec("apt-get update -qq", { silent: true });
|
|
496
|
-
exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y nodejs", { silent: true });
|
|
538
|
+
io.exec("apt-get update -qq", { silent: true });
|
|
539
|
+
io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y nodejs", { silent: true });
|
|
497
540
|
success(`Node.js ${NODE_MAJOR} installed from apt`);
|
|
498
541
|
}
|
|
499
542
|
/**
|
|
500
543
|
* Install the Trafic agent
|
|
501
544
|
*/
|
|
502
|
-
function installAgent() {
|
|
545
|
+
function installAgent(io = nodeIo) {
|
|
503
546
|
step("Install Trafic agent");
|
|
504
|
-
exec("npm install -g @studiometa/trafic-agent");
|
|
547
|
+
io.exec("npm install -g @studiometa/trafic-agent");
|
|
505
548
|
success("Trafic agent installed");
|
|
506
549
|
}
|
|
507
550
|
/**
|
|
508
551
|
* Create the agent configuration
|
|
509
552
|
*/
|
|
510
|
-
function createAgentConfig(
|
|
553
|
+
function createAgentConfig(options, io = nodeIo) {
|
|
554
|
+
const { tld, trustedProxyHops = 1 } = options;
|
|
511
555
|
step("Create agent configuration");
|
|
512
|
-
exec("mkdir -p /etc/trafic");
|
|
513
|
-
exec("mkdir -p /var/lib/trafic");
|
|
514
|
-
exec("chown ddev:ddev /var/lib/trafic");
|
|
556
|
+
io.exec("mkdir -p /etc/trafic");
|
|
557
|
+
io.exec("mkdir -p /var/lib/trafic");
|
|
558
|
+
io.exec("chown ddev:ddev /var/lib/trafic");
|
|
515
559
|
const configPath = "/etc/trafic/config.toml";
|
|
516
|
-
if (
|
|
560
|
+
if (io.fileExists(configPath)) {
|
|
517
561
|
info("Configuration already exists at /etc/trafic/config.toml — skipping");
|
|
518
562
|
info("Edit this file to configure authentication");
|
|
519
563
|
return;
|
|
@@ -543,17 +587,24 @@ allowed_ips = []
|
|
|
543
587
|
tokens = []
|
|
544
588
|
basic_auth = []
|
|
545
589
|
rules = []
|
|
590
|
+
|
|
591
|
+
# Proxies in front of the agent, counted so the client address can be read
|
|
592
|
+
# from X-Forwarded-For. 1 is ddev-router/Traefik alone. Raise it by one for
|
|
593
|
+
# every extra proxy — a CDN or load balancer ahead of Traefik makes it 2.
|
|
594
|
+
# Too low and allowed_ips matches a proxy instead of the client; too high and
|
|
595
|
+
# it can be spoofed by a client-supplied header.
|
|
596
|
+
trusted_proxy_hops = ${trustedProxyHops}
|
|
546
597
|
`;
|
|
547
|
-
|
|
548
|
-
exec("chmod 640 /etc/trafic/config.toml");
|
|
549
|
-
exec("chown root:ddev /etc/trafic/config.toml");
|
|
598
|
+
io.writeFile(configPath, config);
|
|
599
|
+
io.exec("chmod 640 /etc/trafic/config.toml");
|
|
600
|
+
io.exec("chown root:ddev /etc/trafic/config.toml");
|
|
550
601
|
success("Configuration created at /etc/trafic/config.toml");
|
|
551
602
|
info("Edit this file to configure authentication");
|
|
552
603
|
}
|
|
553
604
|
/**
|
|
554
605
|
* Create systemd service for the agent
|
|
555
606
|
*/
|
|
556
|
-
function createSystemdService() {
|
|
607
|
+
function createSystemdService(io = nodeIo) {
|
|
557
608
|
step("Create systemd service");
|
|
558
609
|
const serviceContent = `[Unit]
|
|
559
610
|
Description=Trafic Agent - DDEV preview environment manager
|
|
@@ -564,7 +615,7 @@ Requires=docker.service
|
|
|
564
615
|
Type=simple
|
|
565
616
|
User=ddev
|
|
566
617
|
Group=ddev
|
|
567
|
-
ExecStart=${exec("which trafic-agent", { silent: true })?.trim() || "/usr/bin/trafic-agent"} start --config /etc/trafic/config.toml
|
|
618
|
+
ExecStart=${io.exec("which trafic-agent", { silent: true })?.trim() || "/usr/bin/trafic-agent"} start --config /etc/trafic/config.toml
|
|
568
619
|
Restart=always
|
|
569
620
|
RestartSec=5
|
|
570
621
|
StandardOutput=journal
|
|
@@ -588,21 +639,35 @@ PrivateTmp=true
|
|
|
588
639
|
[Install]
|
|
589
640
|
WantedBy=multi-user.target
|
|
590
641
|
`;
|
|
591
|
-
|
|
592
|
-
exec("chmod 644 /etc/systemd/system/trafic-agent.service");
|
|
593
|
-
exec("systemctl daemon-reload");
|
|
594
|
-
exec("systemctl enable trafic-agent");
|
|
595
|
-
exec("systemctl start trafic-agent");
|
|
642
|
+
io.writeFile("/etc/systemd/system/trafic-agent.service", serviceContent);
|
|
643
|
+
io.exec("chmod 644 /etc/systemd/system/trafic-agent.service");
|
|
644
|
+
io.exec("systemctl daemon-reload");
|
|
645
|
+
io.exec("systemctl enable trafic-agent");
|
|
646
|
+
io.exec("systemctl start trafic-agent");
|
|
596
647
|
success("Trafic agent service started");
|
|
597
648
|
info("View logs: journalctl -u trafic-agent -f");
|
|
598
649
|
}
|
|
599
650
|
//#endregion
|
|
600
651
|
//#region src/setup/hardening.ts
|
|
601
652
|
/**
|
|
653
|
+
* Resolve the users listed in AllowUsers.
|
|
654
|
+
*
|
|
655
|
+
* The user who invoked sudo is always included. Without them, the
|
|
656
|
+
* AllowUsers directive locks them out on their next connection, and
|
|
657
|
+
* reloading sshd keeps the current session alive so nothing looks wrong
|
|
658
|
+
* until it is too late. root needs no entry — it is always listed.
|
|
659
|
+
*/
|
|
660
|
+
function resolveAllowedUsers(allowedUsers, sudoUser) {
|
|
661
|
+
const users = allowedUsers.map((user) => user.trim()).filter(Boolean);
|
|
662
|
+
if (sudoUser && sudoUser !== "root" && !users.includes(sudoUser)) users.push(sudoUser);
|
|
663
|
+
return users;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
602
666
|
* Configure SSH hardening
|
|
603
667
|
*/
|
|
604
|
-
function hardenSsh(allowedUsers = ["ddev"]) {
|
|
668
|
+
function hardenSsh(allowedUsers = ["ddev"], io = nodeIo) {
|
|
605
669
|
step("Harden SSH configuration");
|
|
670
|
+
const users = resolveAllowedUsers(allowedUsers, io.env.SUDO_USER);
|
|
606
671
|
const sshConfig = `# Trafic SSH hardening
|
|
607
672
|
# Generated by trafic setup
|
|
608
673
|
|
|
@@ -621,7 +686,7 @@ ClientAliveInterval 300
|
|
|
621
686
|
ClientAliveCountMax 2
|
|
622
687
|
|
|
623
688
|
# Allowed users (root kept for emergency access)
|
|
624
|
-
AllowUsers root ${
|
|
689
|
+
AllowUsers root ${users.join(" ")}
|
|
625
690
|
|
|
626
691
|
# Disable X11 forwarding
|
|
627
692
|
X11Forwarding no
|
|
@@ -634,40 +699,40 @@ KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
|
|
|
634
699
|
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
|
|
635
700
|
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
|
|
636
701
|
`;
|
|
637
|
-
|
|
638
|
-
exec("chmod 644 /etc/ssh/sshd_config.d/trafic.conf");
|
|
639
|
-
if (exec("sshd -t 2>&1 || echo 'error'", { silent: true })?.includes("error")) {
|
|
702
|
+
io.writeFile("/etc/ssh/sshd_config.d/trafic.conf", sshConfig);
|
|
703
|
+
io.exec("chmod 644 /etc/ssh/sshd_config.d/trafic.conf");
|
|
704
|
+
if (io.exec("sshd -t 2>&1 || echo 'error'", { silent: true })?.includes("error")) {
|
|
640
705
|
warn("SSH config test failed, reverting...");
|
|
641
|
-
exec("rm /etc/ssh/sshd_config.d/trafic.conf");
|
|
706
|
+
io.exec("rm /etc/ssh/sshd_config.d/trafic.conf");
|
|
642
707
|
return;
|
|
643
708
|
}
|
|
644
|
-
exec("systemctl reload ssh || systemctl reload sshd");
|
|
709
|
+
io.exec("systemctl reload ssh || systemctl reload sshd");
|
|
645
710
|
success("SSH hardened: password auth disabled, root key-only login retained");
|
|
646
|
-
info(`Allowed users: root, ${
|
|
711
|
+
info(`Allowed users: root, ${users.join(", ")}`);
|
|
647
712
|
}
|
|
648
713
|
/**
|
|
649
714
|
* Configure UFW firewall
|
|
650
715
|
*/
|
|
651
|
-
function configureFirewall() {
|
|
716
|
+
function configureFirewall(io = nodeIo) {
|
|
652
717
|
step("Configure firewall (UFW)");
|
|
653
|
-
if (!commandExists("ufw")) exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y ufw", { silent: true });
|
|
654
|
-
exec("ufw --force reset", { silent: true });
|
|
655
|
-
exec("ufw default deny incoming");
|
|
656
|
-
exec("ufw default allow outgoing");
|
|
657
|
-
exec("ufw allow 22/tcp comment 'SSH'");
|
|
658
|
-
exec("ufw allow 80/tcp comment 'HTTP'");
|
|
659
|
-
exec("ufw allow 443/tcp comment 'HTTPS'");
|
|
660
|
-
exec("ufw allow from 172.16.0.0/12 to any port 9876 comment 'trafic-agent from Docker'");
|
|
661
|
-
exec("ufw --force enable");
|
|
718
|
+
if (!io.commandExists("ufw")) io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y ufw", { silent: true });
|
|
719
|
+
io.exec("ufw --force reset", { silent: true });
|
|
720
|
+
io.exec("ufw default deny incoming");
|
|
721
|
+
io.exec("ufw default allow outgoing");
|
|
722
|
+
io.exec("ufw allow 22/tcp comment 'SSH'");
|
|
723
|
+
io.exec("ufw allow 80/tcp comment 'HTTP'");
|
|
724
|
+
io.exec("ufw allow 443/tcp comment 'HTTPS'");
|
|
725
|
+
io.exec("ufw allow from 172.16.0.0/12 to any port 9876 comment 'trafic-agent from Docker'");
|
|
726
|
+
io.exec("ufw --force enable");
|
|
662
727
|
success("Firewall enabled: SSH (22), HTTP (80), HTTPS (443), trafic-agent (9876 from Docker) allowed");
|
|
663
728
|
}
|
|
664
729
|
/**
|
|
665
730
|
* Configure automatic security updates
|
|
666
731
|
*/
|
|
667
|
-
function configureUnattendedUpgrades() {
|
|
732
|
+
function configureUnattendedUpgrades(io = nodeIo) {
|
|
668
733
|
step("Configure automatic security updates");
|
|
669
|
-
exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y unattended-upgrades apt-listchanges", { silent: true });
|
|
670
|
-
|
|
734
|
+
io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y unattended-upgrades apt-listchanges", { silent: true });
|
|
735
|
+
io.writeFile("/etc/apt/apt.conf.d/50unattended-upgrades", `// Trafic: Automatic security updates
|
|
671
736
|
Unattended-Upgrade::Allowed-Origins {
|
|
672
737
|
"\${distro_id}:\${distro_codename}";
|
|
673
738
|
"\${distro_id}:\${distro_codename}-security";
|
|
@@ -684,22 +749,22 @@ Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
|
684
749
|
// Email notifications (optional)
|
|
685
750
|
// Unattended-Upgrade::Mail "root";
|
|
686
751
|
`);
|
|
687
|
-
|
|
752
|
+
io.writeFile("/etc/apt/apt.conf.d/20auto-upgrades", `APT::Periodic::Update-Package-Lists "1";
|
|
688
753
|
APT::Periodic::Download-Upgradeable-Packages "1";
|
|
689
754
|
APT::Periodic::AutocleanInterval "7";
|
|
690
755
|
APT::Periodic::Unattended-Upgrade "1";
|
|
691
756
|
`);
|
|
692
|
-
exec("systemctl enable unattended-upgrades");
|
|
693
|
-
exec("systemctl start unattended-upgrades");
|
|
757
|
+
io.exec("systemctl enable unattended-upgrades");
|
|
758
|
+
io.exec("systemctl start unattended-upgrades");
|
|
694
759
|
success("Automatic security updates enabled");
|
|
695
760
|
}
|
|
696
761
|
/**
|
|
697
762
|
* Configure Fail2ban
|
|
698
763
|
*/
|
|
699
|
-
function configureFail2ban() {
|
|
764
|
+
function configureFail2ban(io = nodeIo) {
|
|
700
765
|
step("Configure Fail2ban");
|
|
701
|
-
if (!commandExists("fail2ban-client")) exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y fail2ban", { silent: true });
|
|
702
|
-
|
|
766
|
+
if (!io.commandExists("fail2ban-client")) io.exec("DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y fail2ban", { silent: true });
|
|
767
|
+
io.writeFile("/etc/fail2ban/jail.local", `# Trafic: Fail2ban configuration
|
|
703
768
|
[DEFAULT]
|
|
704
769
|
bantime = 3600
|
|
705
770
|
findtime = 600
|
|
@@ -714,55 +779,55 @@ logpath = /var/log/auth.log
|
|
|
714
779
|
maxretry = 3
|
|
715
780
|
bantime = 7200
|
|
716
781
|
`);
|
|
717
|
-
exec("chmod 644 /etc/fail2ban/jail.local");
|
|
718
|
-
exec("systemctl enable fail2ban");
|
|
719
|
-
exec("systemctl restart fail2ban");
|
|
782
|
+
io.exec("chmod 644 /etc/fail2ban/jail.local");
|
|
783
|
+
io.exec("systemctl enable fail2ban");
|
|
784
|
+
io.exec("systemctl restart fail2ban");
|
|
720
785
|
success("Fail2ban enabled: SSH brute force protection active");
|
|
721
786
|
}
|
|
722
787
|
/**
|
|
723
788
|
* Configure system limits
|
|
724
789
|
*/
|
|
725
|
-
function configureSystemLimits() {
|
|
790
|
+
function configureSystemLimits(io = nodeIo) {
|
|
726
791
|
step("Configure system limits");
|
|
727
|
-
|
|
792
|
+
io.writeFile("/etc/security/limits.d/trafic.conf", `# Trafic: System limits for ddev user
|
|
728
793
|
ddev soft nofile 65536
|
|
729
794
|
ddev hard nofile 65536
|
|
730
795
|
ddev soft nproc 4096
|
|
731
796
|
ddev hard nproc 4096
|
|
732
797
|
`);
|
|
733
|
-
exec("chmod 644 /etc/security/limits.d/trafic.conf");
|
|
798
|
+
io.exec("chmod 644 /etc/security/limits.d/trafic.conf");
|
|
734
799
|
success("System limits configured for ddev user");
|
|
735
800
|
}
|
|
736
801
|
/**
|
|
737
802
|
* Set proper file permissions
|
|
738
803
|
*/
|
|
739
|
-
function configureFilePermissions() {
|
|
804
|
+
function configureFilePermissions(io = nodeIo) {
|
|
740
805
|
step("Configure file permissions");
|
|
741
|
-
exec("mkdir -p /etc/trafic");
|
|
742
|
-
exec("mkdir -p /var/lib/trafic");
|
|
743
|
-
exec("mkdir -p /var/backups/trafic");
|
|
744
|
-
exec("chmod 755 /etc/trafic");
|
|
745
|
-
exec("chmod 700 /var/lib/trafic");
|
|
746
|
-
exec("chmod 700 /var/backups/trafic");
|
|
747
|
-
exec("chown root:ddev /etc/trafic");
|
|
748
|
-
exec("chown ddev:ddev /var/lib/trafic");
|
|
749
|
-
exec("chown ddev:ddev /var/backups/trafic");
|
|
750
|
-
if (
|
|
751
|
-
exec("chmod 640 /etc/trafic/config.toml");
|
|
752
|
-
exec("chown root:ddev /etc/trafic/config.toml");
|
|
806
|
+
io.exec("mkdir -p /etc/trafic");
|
|
807
|
+
io.exec("mkdir -p /var/lib/trafic");
|
|
808
|
+
io.exec("mkdir -p /var/backups/trafic");
|
|
809
|
+
io.exec("chmod 755 /etc/trafic");
|
|
810
|
+
io.exec("chmod 700 /var/lib/trafic");
|
|
811
|
+
io.exec("chmod 700 /var/backups/trafic");
|
|
812
|
+
io.exec("chown root:ddev /etc/trafic");
|
|
813
|
+
io.exec("chown ddev:ddev /var/lib/trafic");
|
|
814
|
+
io.exec("chown ddev:ddev /var/backups/trafic");
|
|
815
|
+
if (io.fileExists("/etc/trafic/config.toml")) {
|
|
816
|
+
io.exec("chmod 640 /etc/trafic/config.toml");
|
|
817
|
+
io.exec("chown root:ddev /etc/trafic/config.toml");
|
|
753
818
|
}
|
|
754
819
|
success("File permissions configured");
|
|
755
820
|
}
|
|
756
821
|
/**
|
|
757
822
|
* Run all hardening steps
|
|
758
823
|
*/
|
|
759
|
-
function hardenServer(sshUsers = ["ddev"]) {
|
|
760
|
-
hardenSsh(sshUsers);
|
|
761
|
-
configureFirewall();
|
|
762
|
-
configureUnattendedUpgrades();
|
|
763
|
-
configureFail2ban();
|
|
764
|
-
configureSystemLimits();
|
|
765
|
-
configureFilePermissions();
|
|
824
|
+
function hardenServer(sshUsers = ["ddev"], io = nodeIo) {
|
|
825
|
+
hardenSsh(sshUsers, io);
|
|
826
|
+
configureFirewall(io);
|
|
827
|
+
configureUnattendedUpgrades(io);
|
|
828
|
+
configureFail2ban(io);
|
|
829
|
+
configureSystemLimits(io);
|
|
830
|
+
configureFilePermissions(io);
|
|
766
831
|
}
|
|
767
832
|
//#endregion
|
|
768
833
|
//#region src/setup/audit.ts
|
|
@@ -1309,7 +1374,10 @@ async function setup(options) {
|
|
|
1309
1374
|
}
|
|
1310
1375
|
installNode();
|
|
1311
1376
|
installAgent();
|
|
1312
|
-
createAgentConfig(
|
|
1377
|
+
createAgentConfig({
|
|
1378
|
+
tld: options.tld,
|
|
1379
|
+
trustedProxyHops: options.trustedProxyHops
|
|
1380
|
+
});
|
|
1313
1381
|
createSystemdService();
|
|
1314
1382
|
if (!options.noHardening) hardenServer(options.sshUsers ?? ["ddev"]);
|
|
1315
1383
|
else {
|
|
@@ -1340,4 +1408,4 @@ async function audit() {
|
|
|
1340
1408
|
//#endregion
|
|
1341
1409
|
export { validateConfig as _, printAuditResults as a, info as c, success as d, warn as f, parseDuration as g, loadConfig as h, runPendingMigrations as i, isRoot as l, stopIdleProjects as m, setup as n, runAudit as o, startIdleScheduler as p, listMigrations as r, exec as s, audit as t, step as u };
|
|
1342
1410
|
|
|
1343
|
-
//# sourceMappingURL=setup-
|
|
1411
|
+
//# sourceMappingURL=setup-BcY2feHr.js.map
|